sqlite-hub 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/changelog.md +14 -0
- package/{js → frontend/js}/api.js +99 -5
- package/{js → frontend/js}/app.js +162 -11
- package/{js → frontend/js}/components/connectionCard.js +8 -9
- package/frontend/js/components/connectionLogo.js +33 -0
- package/{js → frontend/js}/components/dataGrid.js +3 -3
- package/{js → frontend/js}/components/emptyState.js +25 -11
- package/{js → frontend/js}/components/modal.js +57 -0
- package/{js → frontend/js}/components/queryEditor.js +33 -55
- package/frontend/js/components/queryHistoryDetail.js +263 -0
- package/frontend/js/components/queryHistoryPanel.js +228 -0
- package/{js → frontend/js}/components/queryResults.js +32 -46
- package/{js → frontend/js}/components/rowEditorPanel.js +73 -14
- package/{js → frontend/js}/components/sidebar.js +8 -3
- package/{js → frontend/js}/store.js +577 -21
- package/{js → frontend/js}/utils/format.js +23 -0
- package/{js → frontend/js}/views/data.js +136 -10
- package/{js → frontend/js}/views/editor.js +34 -10
- package/{js → frontend/js}/views/overview.js +15 -0
- package/{js → frontend/js}/views/structure.js +10 -12
- package/{styles → frontend/styles}/components.css +106 -0
- package/{styles → frontend/styles}/structure-graph.css +5 -10
- package/package.json +2 -2
- package/{publish_brew.sh → scripts/publish_brew.sh} +2 -2
- package/{publish_npm.sh → scripts/publish_npm.sh} +2 -2
- package/server/data/db_logos/.gitkeep +0 -0
- package/server/routes/connections.js +2 -0
- package/server/routes/data.js +2 -0
- package/server/routes/export.js +4 -1
- package/server/routes/overview.js +12 -0
- package/server/routes/sql.js +163 -5
- package/server/server.js +8 -6
- package/server/services/sqlite/connectionManager.js +68 -33
- package/server/services/sqlite/dataBrowserService.js +4 -16
- package/server/services/sqlite/exportService.js +4 -16
- package/server/services/sqlite/overviewService.js +34 -0
- package/server/services/sqlite/sqlExecutor.js +83 -63
- package/server/services/sqlite/tableSort.js +63 -0
- package/server/services/storage/appStateStore.js +832 -20
- package/server/services/storage/queryHistoryUtils.js +169 -0
- package/server/utils/appPaths.js +42 -18
- package/{assets → frontend/assets}/images/logo.webp +0 -0
- package/{assets → frontend/assets}/images/logo_extrasmall.webp +0 -0
- package/{assets → frontend/assets}/images/logo_raw.png +0 -0
- package/{assets → frontend/assets}/images/logo_small.webp +0 -0
- package/{assets → frontend/assets}/mockups/connections.png +0 -0
- package/{assets → frontend/assets}/mockups/data.png +0 -0
- package/{assets → frontend/assets}/mockups/data_edit.png +0 -0
- package/{assets → frontend/assets}/mockups/graph_visualize.png +0 -0
- package/{assets → frontend/assets}/mockups/home.png +0 -0
- package/{assets → frontend/assets}/mockups/overview.png +0 -0
- package/{assets → frontend/assets}/mockups/sql_editor.png +0 -0
- package/{assets → frontend/assets}/mockups/structure.png +0 -0
- package/{index.html → frontend/index.html} +0 -0
- package/{js → frontend/js}/components/actionBar.js +0 -0
- package/{js → frontend/js}/components/appShell.js +0 -0
- package/{js → frontend/js}/components/badges.js +0 -0
- package/{js → frontend/js}/components/bottomTabs.js +1 -1
- /package/{js → frontend/js}/components/metricCard.js +0 -0
- /package/{js → frontend/js}/components/pageHeader.js +0 -0
- /package/{js → frontend/js}/components/statusBar.js +0 -0
- /package/{js → frontend/js}/components/structureGraph.js +0 -0
- /package/{js → frontend/js}/components/toast.js +0 -0
- /package/{js → frontend/js}/components/topNav.js +0 -0
- /package/{js → frontend/js}/lib/cytoscapeRuntime.js +0 -0
- /package/{js → frontend/js}/router.js +0 -0
- /package/{js → frontend/js}/views/connections.js +0 -0
- /package/{js → frontend/js}/views/landing.js +0 -0
- /package/{js → frontend/js}/views/settings.js +0 -0
- /package/{styles → frontend/styles}/base.css +0 -0
- /package/{styles → frontend/styles}/layout.css +0 -0
- /package/{styles → frontend/styles}/tokens.css +0 -0
- /package/{styles → frontend/styles}/views.css +0 -0
- /package/{data → server/data}/.gitkeep +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# sqlite-hub ⚡️
|
|
2
2
|
|
|
3
|
-

|
|
3
|
+

|
|
4
4
|
|
|
5
5
|
A focused local-first app for browsing, editing, and querying SQLite databases.
|
|
6
6
|
|
|
@@ -24,25 +24,25 @@ SQLite Hub keeps that workflow sharp:
|
|
|
24
24
|
|
|
25
25
|
### Structure view
|
|
26
26
|
|
|
27
|
-

|
|
27
|
+

|
|
28
28
|
|
|
29
29
|
Inspect tables, columns, types, and schema details without losing pace. Visualized in a graph.
|
|
30
30
|
|
|
31
31
|
### Data browser
|
|
32
32
|
|
|
33
|
-

|
|
33
|
+

|
|
34
34
|
|
|
35
35
|
Scan rows, sort fast, move through local data quickly, and export full tables as CSV.
|
|
36
36
|
|
|
37
37
|
### Row editing
|
|
38
38
|
|
|
39
|
-

|
|
39
|
+

|
|
40
40
|
|
|
41
41
|
Open one record, edit it in place, commit, continue.
|
|
42
42
|
|
|
43
43
|
### SQL editor
|
|
44
44
|
|
|
45
|
-

|
|
45
|
+

|
|
46
46
|
|
|
47
47
|
Write queries in a syntax-highlighted editor, inspect results in the same workflow, and export result sets as CSV.
|
|
48
48
|
|
package/changelog.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# v0.5.0
|
|
2
|
+
|
|
3
|
+
- sql query history
|
|
4
|
+
- open in finder in overview
|
|
5
|
+
- sort columns in data & sql_editor
|
|
6
|
+
- json view in row editor
|
|
7
|
+
|
|
8
|
+
# v0.4.0
|
|
9
|
+
|
|
10
|
+
- search in data
|
|
11
|
+
- shift + enter
|
|
12
|
+
- logos for databases
|
|
13
|
+
- clean up some chaotic code things :)
|
|
14
|
+
|
|
1
15
|
# v0.3.2
|
|
2
16
|
|
|
3
17
|
- delete rows
|
|
@@ -123,6 +123,12 @@ export function getOverview() {
|
|
|
123
123
|
return request("/api/db/overview");
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
export function openOverviewInFinder() {
|
|
127
|
+
return request("/api/db/overview/open-in-finder", {
|
|
128
|
+
method: "POST",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
126
132
|
export function getDbStatus() {
|
|
127
133
|
return request("/api/db/status");
|
|
128
134
|
}
|
|
@@ -134,16 +140,92 @@ export function executeSql(sql) {
|
|
|
134
140
|
});
|
|
135
141
|
}
|
|
136
142
|
|
|
137
|
-
export function
|
|
138
|
-
|
|
143
|
+
export function getQueryHistory(options = {}) {
|
|
144
|
+
const params = new URLSearchParams();
|
|
145
|
+
|
|
146
|
+
if (options.tab) {
|
|
147
|
+
params.set("tab", String(options.tab));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (options.limit !== undefined) {
|
|
151
|
+
params.set("limit", String(options.limit));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (options.offset !== undefined) {
|
|
155
|
+
params.set("offset", String(options.offset));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (options.search) {
|
|
159
|
+
params.set("search", String(options.search));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (options.queryType) {
|
|
163
|
+
params.set("queryType", String(options.queryType));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (options.onlySaved) {
|
|
167
|
+
params.set("onlySaved", "true");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (options.onlyFavorites) {
|
|
171
|
+
params.set("onlyFavorites", "true");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const query = params.toString();
|
|
175
|
+
return request(`/api/sql/history${query ? `?${query}` : ""}`);
|
|
139
176
|
}
|
|
140
177
|
|
|
141
|
-
export function
|
|
178
|
+
export function clearQueryHistory() {
|
|
142
179
|
return request("/api/sql/history", {
|
|
143
180
|
method: "DELETE",
|
|
144
181
|
});
|
|
145
182
|
}
|
|
146
183
|
|
|
184
|
+
export function getQueryHistoryItem(historyId) {
|
|
185
|
+
return request(`/api/sql/history/${encodeURIComponent(historyId)}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function getQueryHistoryRuns(historyId, options = {}) {
|
|
189
|
+
const params = new URLSearchParams();
|
|
190
|
+
|
|
191
|
+
if (options.limit !== undefined) {
|
|
192
|
+
params.set("limit", String(options.limit));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const query = params.toString();
|
|
196
|
+
|
|
197
|
+
return request(
|
|
198
|
+
`/api/sql/history/${encodeURIComponent(historyId)}/runs${query ? `?${query}` : ""}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function toggleQueryHistorySaved(historyId, value) {
|
|
203
|
+
return request(`/api/sql/history/${encodeURIComponent(historyId)}/saved`, {
|
|
204
|
+
method: "PATCH",
|
|
205
|
+
body: { value },
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function renameQueryHistoryItem(historyId, title) {
|
|
210
|
+
return request(`/api/sql/history/${encodeURIComponent(historyId)}/title`, {
|
|
211
|
+
method: "PATCH",
|
|
212
|
+
body: { title },
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function updateQueryHistoryNotes(historyId, notes) {
|
|
217
|
+
return request(`/api/sql/history/${encodeURIComponent(historyId)}/notes`, {
|
|
218
|
+
method: "PATCH",
|
|
219
|
+
body: { notes },
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function deleteQueryHistoryItem(historyId) {
|
|
224
|
+
return request(`/api/sql/history/${encodeURIComponent(historyId)}`, {
|
|
225
|
+
method: "DELETE",
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
147
229
|
export function getStructureOverview() {
|
|
148
230
|
return request("/api/structure");
|
|
149
231
|
}
|
|
@@ -167,6 +249,14 @@ export function getDataTable(tableName, options = {}) {
|
|
|
167
249
|
params.set("offset", String(options.offset));
|
|
168
250
|
}
|
|
169
251
|
|
|
252
|
+
if (options.sortColumn) {
|
|
253
|
+
params.set("sortColumn", String(options.sortColumn));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (options.sortDirection) {
|
|
257
|
+
params.set("sortDirection", String(options.sortDirection));
|
|
258
|
+
}
|
|
259
|
+
|
|
170
260
|
const query = params.toString();
|
|
171
261
|
|
|
172
262
|
return request(`/api/data/${encodeURIComponent(tableName)}${query ? `?${query}` : ""}`);
|
|
@@ -205,10 +295,14 @@ export function downloadQueryCsv(sql) {
|
|
|
205
295
|
});
|
|
206
296
|
}
|
|
207
297
|
|
|
208
|
-
export function downloadTableCsv(tableName) {
|
|
298
|
+
export function downloadTableCsv(tableName, options = {}) {
|
|
209
299
|
return download("/api/export/table.csv", {
|
|
210
300
|
method: "POST",
|
|
211
|
-
body: {
|
|
301
|
+
body: {
|
|
302
|
+
tableName,
|
|
303
|
+
sortColumn: options.sortColumn,
|
|
304
|
+
sortDirection: options.sortDirection,
|
|
305
|
+
},
|
|
212
306
|
fallbackFilename: `${tableName || "table"}.csv`,
|
|
213
307
|
});
|
|
214
308
|
}
|
|
@@ -16,30 +16,43 @@ import {
|
|
|
16
16
|
clearDataRowSelection,
|
|
17
17
|
clearEditorRowSelection,
|
|
18
18
|
clearEditorResults,
|
|
19
|
-
|
|
19
|
+
clearQueryHistorySelection,
|
|
20
20
|
closeModal,
|
|
21
|
+
deleteQueryHistoryStateItem,
|
|
21
22
|
dismissToast,
|
|
22
23
|
executeCurrentQuery,
|
|
23
24
|
exportCurrentDataTableCsv,
|
|
24
25
|
exportCurrentQueryCsv,
|
|
25
26
|
getState,
|
|
26
27
|
initializeApp,
|
|
27
|
-
|
|
28
|
+
loadMoreQueryHistory,
|
|
28
29
|
openModal,
|
|
30
|
+
openOverviewInFinder,
|
|
31
|
+
openQueryHistoryInEditor,
|
|
29
32
|
openDeleteDataRowModal,
|
|
30
33
|
openDeleteEditorRowModal,
|
|
31
34
|
openEditConnectionModal,
|
|
32
35
|
refreshCurrentRoute,
|
|
33
36
|
removeConnection,
|
|
37
|
+
runQueryHistoryItem,
|
|
34
38
|
selectDataRow,
|
|
35
39
|
selectEditorRow,
|
|
36
40
|
selectConnection,
|
|
41
|
+
selectQueryHistoryItem,
|
|
37
42
|
selectStructureEntry,
|
|
38
43
|
setDataPage,
|
|
39
44
|
setDataPageSize,
|
|
45
|
+
setDataSearchColumn,
|
|
46
|
+
setDataSearchQuery,
|
|
40
47
|
setCurrentQuery,
|
|
41
48
|
setEditorTab,
|
|
49
|
+
sortDataTableByColumn,
|
|
50
|
+
sortEditorResultsByColumn,
|
|
51
|
+
setQueryHistorySearchInput,
|
|
52
|
+
setQueryHistoryTab,
|
|
42
53
|
setRoute,
|
|
54
|
+
saveQueryHistoryNotes,
|
|
55
|
+
saveQueryHistoryTitle,
|
|
43
56
|
submitCreateConnection,
|
|
44
57
|
submitDeleteRowConfirmation,
|
|
45
58
|
submitDataRowUpdate,
|
|
@@ -48,6 +61,7 @@ import {
|
|
|
48
61
|
submitImportSql,
|
|
49
62
|
submitOpenConnection,
|
|
50
63
|
subscribe,
|
|
64
|
+
toggleQueryHistorySavedState,
|
|
51
65
|
} from "./store.js";
|
|
52
66
|
import { renderConnectionsView } from "./views/connections.js";
|
|
53
67
|
import { renderDataView } from "./views/data.js";
|
|
@@ -115,6 +129,35 @@ function syncQueryEditorScroll(textarea) {
|
|
|
115
129
|
highlightNode.style.transform = `translate(${-textarea.scrollLeft}px, ${-textarea.scrollTop}px)`;
|
|
116
130
|
}
|
|
117
131
|
|
|
132
|
+
function readFileAsBase64(file) {
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const reader = new FileReader();
|
|
135
|
+
|
|
136
|
+
reader.onload = () => {
|
|
137
|
+
const result = String(reader.result ?? "");
|
|
138
|
+
const markerIndex = result.indexOf(",");
|
|
139
|
+
resolve(markerIndex >= 0 ? result.slice(markerIndex + 1) : result);
|
|
140
|
+
};
|
|
141
|
+
reader.onerror = () => {
|
|
142
|
+
reject(reader.error ?? new Error("The selected logo could not be read."));
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
reader.readAsDataURL(file);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function buildConnectionLogoUpload(file) {
|
|
150
|
+
if (!(file instanceof File) || !file.size) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
fileName: file.name,
|
|
156
|
+
mimeType: file.type,
|
|
157
|
+
base64: await readFileAsBase64(file),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
118
161
|
function renderNotFoundView() {
|
|
119
162
|
return {
|
|
120
163
|
main: `
|
|
@@ -224,7 +267,9 @@ function restoreFocusedInputState(snapshot) {
|
|
|
224
267
|
function renderApp(state) {
|
|
225
268
|
const focusedInput = captureFocusedInputState();
|
|
226
269
|
const { main, panel } = resolveView(state);
|
|
227
|
-
const isLockedRoute = ["editor", "editorResults", "data"].includes(
|
|
270
|
+
const isLockedRoute = ["editor", "editorResults", "data", "structure"].includes(
|
|
271
|
+
state.route.name
|
|
272
|
+
);
|
|
228
273
|
|
|
229
274
|
teardownStructureGraph();
|
|
230
275
|
shellRefs.topNav.innerHTML = renderTopNav(state);
|
|
@@ -249,6 +294,11 @@ const router = createRouter((route) => {
|
|
|
249
294
|
setRoute(route);
|
|
250
295
|
});
|
|
251
296
|
|
|
297
|
+
async function executeEditorQueryAndNavigate() {
|
|
298
|
+
const success = await executeCurrentQuery();
|
|
299
|
+
router.navigate(success ? "/editor/results" : "/editor");
|
|
300
|
+
}
|
|
301
|
+
|
|
252
302
|
async function handleAction(actionNode) {
|
|
253
303
|
const { action } = actionNode.dataset;
|
|
254
304
|
|
|
@@ -300,9 +350,11 @@ async function handleAction(actionNode) {
|
|
|
300
350
|
case "create-backup":
|
|
301
351
|
await createActiveConnectionBackup();
|
|
302
352
|
return;
|
|
353
|
+
case "open-overview-in-finder":
|
|
354
|
+
await openOverviewInFinder();
|
|
355
|
+
return;
|
|
303
356
|
case "execute-query": {
|
|
304
|
-
|
|
305
|
-
router.navigate(success ? "/editor/results" : "/editor");
|
|
357
|
+
await executeEditorQueryAndNavigate();
|
|
306
358
|
return;
|
|
307
359
|
}
|
|
308
360
|
case "delete-data-row":
|
|
@@ -313,11 +365,57 @@ async function handleAction(actionNode) {
|
|
|
313
365
|
return;
|
|
314
366
|
case "clear-query":
|
|
315
367
|
clearCurrentQuery();
|
|
368
|
+
if (getState().route.name === "editorResults") {
|
|
369
|
+
router.navigate("/editor");
|
|
370
|
+
}
|
|
316
371
|
return;
|
|
317
372
|
case "clear-results":
|
|
318
373
|
clearEditorResults();
|
|
319
374
|
router.navigate("/editor");
|
|
320
375
|
return;
|
|
376
|
+
case "select-query-history-item":
|
|
377
|
+
if (actionNode.dataset.historyId) {
|
|
378
|
+
await selectQueryHistoryItem(actionNode.dataset.historyId);
|
|
379
|
+
}
|
|
380
|
+
return;
|
|
381
|
+
case "clear-query-history-selection":
|
|
382
|
+
clearQueryHistorySelection();
|
|
383
|
+
return;
|
|
384
|
+
case "set-query-history-tab":
|
|
385
|
+
if (actionNode.dataset.tab) {
|
|
386
|
+
await setQueryHistoryTab(actionNode.dataset.tab);
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
case "load-more-query-history":
|
|
390
|
+
await loadMoreQueryHistory();
|
|
391
|
+
return;
|
|
392
|
+
case "open-query-history":
|
|
393
|
+
if (actionNode.dataset.historyId && openQueryHistoryInEditor(actionNode.dataset.historyId)) {
|
|
394
|
+
router.navigate("/editor");
|
|
395
|
+
}
|
|
396
|
+
return;
|
|
397
|
+
case "run-query-history":
|
|
398
|
+
if (actionNode.dataset.historyId) {
|
|
399
|
+
const success = await runQueryHistoryItem(actionNode.dataset.historyId);
|
|
400
|
+
router.navigate(success ? "/editor/results" : "/editor");
|
|
401
|
+
}
|
|
402
|
+
return;
|
|
403
|
+
case "toggle-query-history-saved":
|
|
404
|
+
if (actionNode.dataset.historyId) {
|
|
405
|
+
await toggleQueryHistorySavedState(
|
|
406
|
+
actionNode.dataset.historyId,
|
|
407
|
+
actionNode.dataset.nextValue === "true"
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
return;
|
|
411
|
+
case "delete-query-history":
|
|
412
|
+
if (
|
|
413
|
+
actionNode.dataset.historyId &&
|
|
414
|
+
window.confirm("Delete this query history item and all recorded runs?")
|
|
415
|
+
) {
|
|
416
|
+
await deleteQueryHistoryStateItem(actionNode.dataset.historyId);
|
|
417
|
+
}
|
|
418
|
+
return;
|
|
321
419
|
case "set-editor-tab": {
|
|
322
420
|
const tab = actionNode.dataset.tab;
|
|
323
421
|
if (!tab) {
|
|
@@ -327,9 +425,6 @@ async function handleAction(actionNode) {
|
|
|
327
425
|
router.navigate(tab === "results" ? "/editor/results" : "/editor");
|
|
328
426
|
return;
|
|
329
427
|
}
|
|
330
|
-
case "clear-sql-history":
|
|
331
|
-
await clearSqlHistoryStateAndData();
|
|
332
|
-
return;
|
|
333
428
|
case "export-query-csv":
|
|
334
429
|
await exportCurrentQueryCsv();
|
|
335
430
|
return;
|
|
@@ -362,6 +457,16 @@ async function handleAction(actionNode) {
|
|
|
362
457
|
await setDataPage(actionNode.dataset.page);
|
|
363
458
|
}
|
|
364
459
|
return;
|
|
460
|
+
case "sort-data-column":
|
|
461
|
+
if (actionNode.dataset.columnName) {
|
|
462
|
+
await sortDataTableByColumn(actionNode.dataset.columnName);
|
|
463
|
+
}
|
|
464
|
+
return;
|
|
465
|
+
case "sort-editor-results-column":
|
|
466
|
+
if (actionNode.dataset.columnName) {
|
|
467
|
+
sortEditorResultsByColumn(actionNode.dataset.columnName);
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
365
470
|
case "set-data-page-size":
|
|
366
471
|
if (actionNode.dataset.pageSize) {
|
|
367
472
|
await setDataPageSize(actionNode.dataset.pageSize);
|
|
@@ -385,6 +490,27 @@ document.addEventListener("click", (event) => {
|
|
|
385
490
|
});
|
|
386
491
|
|
|
387
492
|
document.addEventListener("keydown", (event) => {
|
|
493
|
+
const target = event.target;
|
|
494
|
+
|
|
495
|
+
if (
|
|
496
|
+
event.key === "Enter" &&
|
|
497
|
+
event.shiftKey &&
|
|
498
|
+
!event.altKey &&
|
|
499
|
+
!event.ctrlKey &&
|
|
500
|
+
!event.metaKey &&
|
|
501
|
+
!event.defaultPrevented &&
|
|
502
|
+
target instanceof HTMLTextAreaElement &&
|
|
503
|
+
target.dataset.bind === "current-query"
|
|
504
|
+
) {
|
|
505
|
+
event.preventDefault();
|
|
506
|
+
|
|
507
|
+
if (!getState().editor.executing) {
|
|
508
|
+
void executeEditorQueryAndNavigate();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
388
514
|
if (event.key !== "Escape" || event.defaultPrevented) {
|
|
389
515
|
return;
|
|
390
516
|
}
|
|
@@ -420,6 +546,16 @@ document.addEventListener("input", (event) => {
|
|
|
420
546
|
syncQueryEditorHighlight(bindNode);
|
|
421
547
|
syncQueryEditorScroll(bindNode);
|
|
422
548
|
setCurrentQuery(bindNode.value);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (bindNode.dataset.bind === "data-search-query") {
|
|
553
|
+
setDataSearchQuery(bindNode.value);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (bindNode.dataset.bind === "query-history-search") {
|
|
558
|
+
setQueryHistorySearchInput(bindNode.value);
|
|
423
559
|
}
|
|
424
560
|
});
|
|
425
561
|
|
|
@@ -444,9 +580,8 @@ document.addEventListener("change", (event) => {
|
|
|
444
580
|
return;
|
|
445
581
|
}
|
|
446
582
|
|
|
447
|
-
if (bindNode.dataset.bind === "
|
|
448
|
-
|
|
449
|
-
bindNode.value = "";
|
|
583
|
+
if (bindNode.dataset.bind === "data-search-column") {
|
|
584
|
+
setDataSearchColumn(bindNode.value);
|
|
450
585
|
}
|
|
451
586
|
});
|
|
452
587
|
|
|
@@ -512,6 +647,8 @@ document.addEventListener("submit", async (event) => {
|
|
|
512
647
|
case "edit-connection": {
|
|
513
648
|
const connectionId = String(formData.get("connectionId") ?? "");
|
|
514
649
|
const isActiveConnection = getState().connections.active?.id === connectionId;
|
|
650
|
+
const logoFile = formData.get("logoFile");
|
|
651
|
+
const logoUpload = await buildConnectionLogoUpload(logoFile);
|
|
515
652
|
|
|
516
653
|
if (isActiveConnection) {
|
|
517
654
|
resetStructureGraphForDatabaseChange();
|
|
@@ -521,6 +658,8 @@ document.addEventListener("submit", async (event) => {
|
|
|
521
658
|
path: String(formData.get("path") ?? ""),
|
|
522
659
|
label: String(formData.get("label") ?? ""),
|
|
523
660
|
readOnly: formData.get("readOnly") === "on",
|
|
661
|
+
clearLogo: formData.get("clearLogo") === "on" && !logoUpload,
|
|
662
|
+
logoUpload,
|
|
524
663
|
});
|
|
525
664
|
|
|
526
665
|
return;
|
|
@@ -562,6 +701,18 @@ document.addEventListener("submit", async (event) => {
|
|
|
562
701
|
);
|
|
563
702
|
return;
|
|
564
703
|
}
|
|
704
|
+
case "save-query-history-title":
|
|
705
|
+
await saveQueryHistoryTitle(
|
|
706
|
+
String(formData.get("historyId") ?? ""),
|
|
707
|
+
String(formData.get("title") ?? "")
|
|
708
|
+
);
|
|
709
|
+
return;
|
|
710
|
+
case "save-query-history-notes":
|
|
711
|
+
await saveQueryHistoryNotes(
|
|
712
|
+
String(formData.get("historyId") ?? ""),
|
|
713
|
+
String(formData.get("notes") ?? "")
|
|
714
|
+
);
|
|
715
|
+
return;
|
|
565
716
|
default:
|
|
566
717
|
}
|
|
567
718
|
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
truncateMiddle,
|
|
6
6
|
} from "../utils/format.js";
|
|
7
7
|
import { renderStatusBadge } from "./badges.js";
|
|
8
|
+
import { renderConnectionLogo } from "./connectionLogo.js";
|
|
8
9
|
|
|
9
10
|
export function renderConnectionCard(connection, activeConnectionId) {
|
|
10
11
|
const isActive = activeConnectionId
|
|
@@ -20,16 +21,14 @@ export function renderConnectionCard(connection, activeConnectionId) {
|
|
|
20
21
|
>
|
|
21
22
|
<div class="flex-1 p-6">
|
|
22
23
|
<div class="mb-6 flex items-start justify-between">
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
${renderConnectionLogo(connection, {
|
|
25
|
+
containerClass: `clipped-corner flex h-10 w-10 items-center justify-center overflow-hidden transition-colors ${
|
|
25
26
|
isActive ? "bg-primary-container" : "bg-surface-container-highest"
|
|
26
|
-
}
|
|
27
|
-
style="--clip-path: ${clipPath};"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}">database</span>
|
|
32
|
-
</div>
|
|
27
|
+
}`,
|
|
28
|
+
containerAttributes: `style="--clip-path: ${clipPath};"`,
|
|
29
|
+
imageClassName: "h-full w-full object-cover",
|
|
30
|
+
iconClassName: isActive ? "text-on-primary" : "text-outline-variant",
|
|
31
|
+
})}
|
|
33
32
|
<div class="flex items-center gap-2">
|
|
34
33
|
${renderStatusBadge(isActive ? "ACTIVE" : "RECENT", isActive ? "primary" : "muted")}
|
|
35
34
|
${connection.readOnly ? renderStatusBadge("READ_ONLY", "alert") : ""}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { escapeHtml } from "../utils/format.js";
|
|
2
|
+
|
|
3
|
+
export function renderConnectionLogo(
|
|
4
|
+
connection,
|
|
5
|
+
{
|
|
6
|
+
containerClass = "",
|
|
7
|
+
containerAttributes = "",
|
|
8
|
+
imageClassName = "h-full w-full object-cover",
|
|
9
|
+
iconClassName = "",
|
|
10
|
+
icon = "database",
|
|
11
|
+
} = {}
|
|
12
|
+
) {
|
|
13
|
+
const logoUrl = connection?.logoUrl ?? null;
|
|
14
|
+
const label = connection?.label ?? "Database";
|
|
15
|
+
|
|
16
|
+
if (logoUrl) {
|
|
17
|
+
return `
|
|
18
|
+
<div ${containerAttributes} class="${containerClass}">
|
|
19
|
+
<img
|
|
20
|
+
alt="${escapeHtml(`${label} logo`)}"
|
|
21
|
+
class="${imageClassName}"
|
|
22
|
+
src="${escapeHtml(logoUrl)}"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `
|
|
29
|
+
<div ${containerAttributes} class="${containerClass}">
|
|
30
|
+
<span class="material-symbols-outlined ${iconClassName}">${escapeHtml(icon)}</span>
|
|
31
|
+
</div>
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
@@ -14,9 +14,9 @@ export function renderDataGrid({
|
|
|
14
14
|
<tr class="${headerRowClass}">
|
|
15
15
|
${columns
|
|
16
16
|
.map(
|
|
17
|
-
|
|
18
|
-
<th class="${column.headerClassName ?? ""}">
|
|
19
|
-
${column.label}
|
|
17
|
+
(column) => `
|
|
18
|
+
<th class="${column.headerClassName ?? ""}" ${column.headerAttrs ?? ""}>
|
|
19
|
+
${column.renderHeader ? column.renderHeader() : column.label}
|
|
20
20
|
</th>
|
|
21
21
|
`
|
|
22
22
|
)
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
formatDateTime,
|
|
5
5
|
truncateMiddle,
|
|
6
6
|
} from "../utils/format.js";
|
|
7
|
+
import { renderConnectionLogo } from "./connectionLogo.js";
|
|
7
8
|
|
|
8
9
|
function renderRecentConnections(recentConnections = []) {
|
|
9
10
|
if (!recentConnections.length) {
|
|
@@ -26,7 +27,12 @@ function renderRecentConnections(recentConnections = []) {
|
|
|
26
27
|
data-connection-id="${escapeHtml(connection.id)}"
|
|
27
28
|
type="button"
|
|
28
29
|
>
|
|
29
|
-
|
|
30
|
+
${renderConnectionLogo(connection, {
|
|
31
|
+
containerClass:
|
|
32
|
+
"flex h-8 w-8 items-center justify-center overflow-hidden bg-surface-container-highest",
|
|
33
|
+
imageClassName: "h-full w-full object-cover",
|
|
34
|
+
iconClassName: "text-sm text-primary-container",
|
|
35
|
+
})}
|
|
30
36
|
<span class="min-w-0">
|
|
31
37
|
<span class="block truncate font-mono text-xs">${escapeHtml(connection.label)}</span>
|
|
32
38
|
<span class="block truncate text-[10px] text-on-surface-variant/45">${escapeHtml(
|
|
@@ -53,16 +59,24 @@ function renderActiveConnection(activeConnection) {
|
|
|
53
59
|
return `
|
|
54
60
|
<div class="mx-auto max-w-2xl border border-outline-variant/15 bg-surface-container-low px-6 py-5 text-left">
|
|
55
61
|
<div class="flex flex-wrap items-start justify-between gap-4">
|
|
56
|
-
<div>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
<div class="flex items-start gap-4">
|
|
63
|
+
${renderConnectionLogo(activeConnection, {
|
|
64
|
+
containerClass:
|
|
65
|
+
"flex h-14 w-14 items-center justify-center overflow-hidden border border-outline-variant/15 bg-surface-container-highest",
|
|
66
|
+
imageClassName: "h-full w-full object-cover",
|
|
67
|
+
iconClassName: "text-2xl text-primary-container",
|
|
68
|
+
})}
|
|
69
|
+
<div>
|
|
70
|
+
<p class="text-[10px] font-mono uppercase tracking-[0.24em] text-primary-container/70">
|
|
71
|
+
ACTIVE_DATABASE
|
|
72
|
+
</p>
|
|
73
|
+
<h2 class="mt-2 font-headline text-2xl font-black uppercase tracking-tight text-primary-container">
|
|
74
|
+
${escapeHtml(activeConnection.label)}
|
|
75
|
+
</h2>
|
|
76
|
+
<p class="mt-2 font-mono text-[10px] text-on-surface-variant/55">${escapeHtml(
|
|
77
|
+
truncateMiddle(activeConnection.path, 72)
|
|
78
|
+
)}</p>
|
|
79
|
+
</div>
|
|
66
80
|
</div>
|
|
67
81
|
<div class="text-right text-xs text-on-surface-variant/65">
|
|
68
82
|
<div>${escapeHtml(formatBytes(activeConnection.sizeBytes))}</div>
|