sqlite-hub 0.4.0 → 0.6.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 +2 -2
- package/changelog.md +15 -0
- package/frontend/assets/mockups/connections.png +0 -0
- package/frontend/assets/mockups/data.png +0 -0
- package/frontend/assets/mockups/data_row_editor.png +0 -0
- package/frontend/assets/mockups/home.png +0 -0
- package/frontend/assets/mockups/sql_editor.png +0 -0
- package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
- package/frontend/assets/mockups/structure.png +0 -0
- package/frontend/assets/mockups/structure_inspector.png +0 -0
- package/frontend/js/api.js +114 -5
- package/frontend/js/app.js +368 -18
- package/frontend/js/components/bottomTabs.js +1 -1
- package/frontend/js/components/dataGrid.js +3 -3
- package/frontend/js/components/queryEditor.js +33 -55
- package/frontend/js/components/queryHistoryDetail.js +263 -0
- package/frontend/js/components/queryHistoryPanel.js +228 -0
- package/frontend/js/components/queryResults.js +32 -46
- package/frontend/js/components/rowEditorPanel.js +73 -14
- package/frontend/js/components/sidebar.js +1 -0
- package/frontend/js/components/tableDesignerEditor.js +356 -0
- package/frontend/js/components/tableDesignerSidebar.js +126 -0
- package/frontend/js/components/tableDesignerSqlPreview.js +40 -0
- package/frontend/js/router.js +10 -0
- package/frontend/js/store.js +841 -22
- package/frontend/js/utils/format.js +23 -0
- package/frontend/js/utils/tableDesigner.js +1192 -0
- package/frontend/js/views/data.js +273 -250
- package/frontend/js/views/editor.js +34 -10
- package/frontend/js/views/overview.js +15 -0
- package/frontend/js/views/tableDesigner.js +37 -0
- package/frontend/styles/base.css +87 -73
- package/frontend/styles/components.css +841 -188
- package/frontend/styles/views.css +40 -0
- package/package.json +1 -1
- 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/routes/tableDesigner.js +60 -0
- package/server/server.js +5 -1
- 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/tableDesigner/changeAnalysis.js +295 -0
- package/server/services/sqlite/tableDesigner/schemaMapping.js +233 -0
- package/server/services/sqlite/tableDesigner/sql.js +63 -0
- package/server/services/sqlite/tableDesigner/validation.js +245 -0
- package/server/services/sqlite/tableDesignerService.js +181 -0
- package/server/services/sqlite/tableSort.js +63 -0
- package/server/services/storage/appStateStore.js +674 -1
- package/server/services/storage/queryHistoryUtils.js +169 -0
- package/frontend/assets/mockups/data_edit.png +0 -0
- package/frontend/assets/mockups/graph_visualize.png +0 -0
- package/frontend/assets/mockups/overview.png +0 -0
package/frontend/js/app.js
CHANGED
|
@@ -16,32 +16,47 @@ 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,
|
|
43
|
+
setTableDesignerSearchQuery,
|
|
38
44
|
setDataPage,
|
|
39
45
|
setDataPageSize,
|
|
40
46
|
setDataSearchColumn,
|
|
41
47
|
setDataSearchQuery,
|
|
42
48
|
setCurrentQuery,
|
|
43
49
|
setEditorTab,
|
|
50
|
+
sortDataTableByColumn,
|
|
51
|
+
sortEditorResultsByColumn,
|
|
52
|
+
setQueryHistorySearchInput,
|
|
53
|
+
setQueryHistoryTab,
|
|
44
54
|
setRoute,
|
|
55
|
+
saveQueryHistoryNotes,
|
|
56
|
+
saveQueryHistoryTitle,
|
|
57
|
+
saveCurrentTableDesignerDraft,
|
|
58
|
+
queueTableDesignerCsvImport,
|
|
59
|
+
showToast,
|
|
45
60
|
submitCreateConnection,
|
|
46
61
|
submitDeleteRowConfirmation,
|
|
47
62
|
submitDataRowUpdate,
|
|
@@ -50,6 +65,11 @@ import {
|
|
|
50
65
|
submitImportSql,
|
|
51
66
|
submitOpenConnection,
|
|
52
67
|
subscribe,
|
|
68
|
+
toggleQueryHistorySavedState,
|
|
69
|
+
updateCurrentTableDesignerColumnField,
|
|
70
|
+
updateCurrentTableDesignerField,
|
|
71
|
+
addCurrentTableDesignerColumn,
|
|
72
|
+
removeCurrentTableDesignerColumn,
|
|
53
73
|
} from "./store.js";
|
|
54
74
|
import { renderConnectionsView } from "./views/connections.js";
|
|
55
75
|
import { renderDataView } from "./views/data.js";
|
|
@@ -58,6 +78,7 @@ import { renderLandingView } from "./views/landing.js";
|
|
|
58
78
|
import { renderOverviewView } from "./views/overview.js";
|
|
59
79
|
import { renderSettingsView } from "./views/settings.js";
|
|
60
80
|
import { renderStructureView } from "./views/structure.js";
|
|
81
|
+
import { renderTableDesignerView } from "./views/tableDesigner.js";
|
|
61
82
|
import { highlightSql } from "./utils/format.js";
|
|
62
83
|
|
|
63
84
|
const appRoot = document.querySelector("#app");
|
|
@@ -74,6 +95,8 @@ const shellRefs = {
|
|
|
74
95
|
modal: document.querySelector("#modal-root"),
|
|
75
96
|
toast: document.querySelector("#toast-root"),
|
|
76
97
|
};
|
|
98
|
+
let lastRenderedRoutePath = null;
|
|
99
|
+
let pendingNewTableDesignerAutofocus = false;
|
|
77
100
|
|
|
78
101
|
function resetStructureGraphForDatabaseChange() {
|
|
79
102
|
resetPersistedStructureGraphState();
|
|
@@ -134,6 +157,21 @@ function readFileAsBase64(file) {
|
|
|
134
157
|
});
|
|
135
158
|
}
|
|
136
159
|
|
|
160
|
+
function readFileAsText(file) {
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const reader = new FileReader();
|
|
163
|
+
|
|
164
|
+
reader.onload = () => {
|
|
165
|
+
resolve(String(reader.result ?? ""));
|
|
166
|
+
};
|
|
167
|
+
reader.onerror = () => {
|
|
168
|
+
reject(reader.error ?? new Error("The selected CSV file could not be read."));
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
reader.readAsText(file);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
137
175
|
async function buildConnectionLogoUpload(file) {
|
|
138
176
|
if (!(file instanceof File) || !file.size) {
|
|
139
177
|
return null;
|
|
@@ -188,6 +226,8 @@ function resolveView(state) {
|
|
|
188
226
|
return renderEditorView(state, { isResultsRoute: true });
|
|
189
227
|
case "structure":
|
|
190
228
|
return renderStructureView(state);
|
|
229
|
+
case "tableDesigner":
|
|
230
|
+
return renderTableDesignerView(state);
|
|
191
231
|
case "settings":
|
|
192
232
|
return renderSettingsView(state);
|
|
193
233
|
default:
|
|
@@ -200,7 +240,11 @@ function captureFocusedInputState() {
|
|
|
200
240
|
|
|
201
241
|
if (
|
|
202
242
|
!activeElement ||
|
|
203
|
-
!(
|
|
243
|
+
!(
|
|
244
|
+
activeElement instanceof HTMLInputElement ||
|
|
245
|
+
activeElement instanceof HTMLTextAreaElement ||
|
|
246
|
+
activeElement instanceof HTMLSelectElement
|
|
247
|
+
)
|
|
204
248
|
) {
|
|
205
249
|
return null;
|
|
206
250
|
}
|
|
@@ -212,6 +256,8 @@ function captureFocusedInputState() {
|
|
|
212
256
|
|
|
213
257
|
return {
|
|
214
258
|
bind,
|
|
259
|
+
field: activeElement.dataset.field ?? null,
|
|
260
|
+
columnId: activeElement.dataset.columnId ?? null,
|
|
215
261
|
selectionStart: activeElement.selectionStart,
|
|
216
262
|
selectionEnd: activeElement.selectionEnd,
|
|
217
263
|
selectionDirection: activeElement.selectionDirection,
|
|
@@ -220,24 +266,61 @@ function captureFocusedInputState() {
|
|
|
220
266
|
};
|
|
221
267
|
}
|
|
222
268
|
|
|
269
|
+
function buildFocusedInputSelectors(snapshot) {
|
|
270
|
+
if (!snapshot?.bind) {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const selectors = [];
|
|
275
|
+
const bindSelector = `[data-bind="${CSS.escape(snapshot.bind)}"]`;
|
|
276
|
+
|
|
277
|
+
if (snapshot.columnId && snapshot.field) {
|
|
278
|
+
selectors.push(
|
|
279
|
+
`${bindSelector}[data-column-id="${CSS.escape(snapshot.columnId)}"][data-field="${CSS.escape(snapshot.field)}"]`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (snapshot.field) {
|
|
284
|
+
selectors.push(`${bindSelector}[data-field="${CSS.escape(snapshot.field)}"]`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (snapshot.columnId) {
|
|
288
|
+
selectors.push(`${bindSelector}[data-column-id="${CSS.escape(snapshot.columnId)}"]`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
selectors.push(bindSelector);
|
|
292
|
+
return selectors;
|
|
293
|
+
}
|
|
294
|
+
|
|
223
295
|
function restoreFocusedInputState(snapshot) {
|
|
224
296
|
if (!snapshot) {
|
|
225
|
-
return;
|
|
297
|
+
return false;
|
|
226
298
|
}
|
|
227
299
|
|
|
228
|
-
const
|
|
229
|
-
|
|
300
|
+
const nextElement = buildFocusedInputSelectors(snapshot)
|
|
301
|
+
.map((selector) => document.querySelector(selector))
|
|
302
|
+
.find(
|
|
303
|
+
(candidate) =>
|
|
304
|
+
candidate instanceof HTMLInputElement ||
|
|
305
|
+
candidate instanceof HTMLTextAreaElement ||
|
|
306
|
+
candidate instanceof HTMLSelectElement
|
|
307
|
+
);
|
|
230
308
|
|
|
231
309
|
if (
|
|
232
310
|
!nextElement ||
|
|
233
|
-
!(
|
|
311
|
+
!(
|
|
312
|
+
nextElement instanceof HTMLInputElement ||
|
|
313
|
+
nextElement instanceof HTMLTextAreaElement ||
|
|
314
|
+
nextElement instanceof HTMLSelectElement
|
|
315
|
+
)
|
|
234
316
|
) {
|
|
235
|
-
return;
|
|
317
|
+
return false;
|
|
236
318
|
}
|
|
237
319
|
|
|
238
320
|
nextElement.focus({ preventScroll: true });
|
|
239
321
|
|
|
240
322
|
if (
|
|
323
|
+
(nextElement instanceof HTMLInputElement || nextElement instanceof HTMLTextAreaElement) &&
|
|
241
324
|
typeof snapshot.selectionStart === "number" &&
|
|
242
325
|
typeof snapshot.selectionEnd === "number"
|
|
243
326
|
) {
|
|
@@ -250,14 +333,97 @@ function restoreFocusedInputState(snapshot) {
|
|
|
250
333
|
|
|
251
334
|
nextElement.scrollTop = snapshot.scrollTop;
|
|
252
335
|
nextElement.scrollLeft = snapshot.scrollLeft;
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function focusNewTableDesignerNameField() {
|
|
340
|
+
const input = document.querySelector(
|
|
341
|
+
'[data-bind="table-designer-field"][data-field="tableName"]'
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
input.focus({ preventScroll: true });
|
|
349
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function focusTableDesignerColumnNameField(columnId) {
|
|
354
|
+
if (!columnId) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const input = document.querySelector(
|
|
359
|
+
`[data-bind="table-designer-column-field"][data-column-id="${CSS.escape(columnId)}"][data-field="name"]`
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
input.focus({ preventScroll: true });
|
|
367
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function handleTableDesignerCsvImport(fileInput) {
|
|
372
|
+
if (!(fileInput instanceof HTMLInputElement)) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const file = fileInput.files?.[0];
|
|
377
|
+
|
|
378
|
+
if (!(file instanceof File)) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!file.size) {
|
|
383
|
+
showToast("The selected CSV file is empty.", "alert");
|
|
384
|
+
fileInput.value = "";
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const csvText = await readFileAsText(file);
|
|
390
|
+
const imported = queueTableDesignerCsvImport(file.name, csvText);
|
|
391
|
+
|
|
392
|
+
if (!imported) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
router.navigate("/table-designer/new");
|
|
397
|
+
showToast(
|
|
398
|
+
`Imported ${imported.columnCount} columns and ${imported.importedRowCount} row${
|
|
399
|
+
imported.importedRowCount === 1 ? "" : "s"
|
|
400
|
+
} from ${file.name}.`,
|
|
401
|
+
"success"
|
|
402
|
+
);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
showToast(error?.message || "CSV import failed.", "alert");
|
|
405
|
+
} finally {
|
|
406
|
+
fileInput.value = "";
|
|
407
|
+
}
|
|
253
408
|
}
|
|
254
409
|
|
|
255
410
|
function renderApp(state) {
|
|
256
411
|
const focusedInput = captureFocusedInputState();
|
|
412
|
+
const previousRoutePath = lastRenderedRoutePath;
|
|
257
413
|
const { main, panel } = resolveView(state);
|
|
258
|
-
const isLockedRoute = ["editor", "editorResults", "data", "structure"].includes(
|
|
414
|
+
const isLockedRoute = ["editor", "editorResults", "data", "structure", "tableDesigner"].includes(
|
|
259
415
|
state.route.name
|
|
260
416
|
);
|
|
417
|
+
const isEnteringNewTableDesignerRoute =
|
|
418
|
+
state.route.name === "tableDesigner" &&
|
|
419
|
+
state.route.params?.isNew &&
|
|
420
|
+
previousRoutePath !== state.route.path;
|
|
421
|
+
|
|
422
|
+
if (isEnteringNewTableDesignerRoute) {
|
|
423
|
+
pendingNewTableDesignerAutofocus = true;
|
|
424
|
+
} else if (state.route.name !== "tableDesigner" || !state.route.params?.isNew) {
|
|
425
|
+
pendingNewTableDesignerAutofocus = false;
|
|
426
|
+
}
|
|
261
427
|
|
|
262
428
|
teardownStructureGraph();
|
|
263
429
|
shellRefs.topNav.innerHTML = renderTopNav(state);
|
|
@@ -269,7 +435,21 @@ function renderApp(state) {
|
|
|
269
435
|
shellRefs.modal.innerHTML = renderModal(state);
|
|
270
436
|
shellRefs.toast.innerHTML = renderToasts(state.toasts);
|
|
271
437
|
shellRefs.shell.classList.toggle("panel-open", Boolean(panel));
|
|
272
|
-
|
|
438
|
+
|
|
439
|
+
if (
|
|
440
|
+
pendingNewTableDesignerAutofocus &&
|
|
441
|
+
state.route.name === "tableDesigner" &&
|
|
442
|
+
state.route.params?.isNew &&
|
|
443
|
+
state.tableDesigner.draft?.mode === "create"
|
|
444
|
+
) {
|
|
445
|
+
if (focusNewTableDesignerNameField()) {
|
|
446
|
+
pendingNewTableDesignerAutofocus = false;
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
restoreFocusedInputState(focusedInput);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
lastRenderedRoutePath = state.route.path;
|
|
273
453
|
|
|
274
454
|
if (state.route.name === "structure") {
|
|
275
455
|
mountStructureGraph(state).catch((error) => {
|
|
@@ -338,6 +518,9 @@ async function handleAction(actionNode) {
|
|
|
338
518
|
case "create-backup":
|
|
339
519
|
await createActiveConnectionBackup();
|
|
340
520
|
return;
|
|
521
|
+
case "open-overview-in-finder":
|
|
522
|
+
await openOverviewInFinder();
|
|
523
|
+
return;
|
|
341
524
|
case "execute-query": {
|
|
342
525
|
await executeEditorQueryAndNavigate();
|
|
343
526
|
return;
|
|
@@ -350,11 +533,57 @@ async function handleAction(actionNode) {
|
|
|
350
533
|
return;
|
|
351
534
|
case "clear-query":
|
|
352
535
|
clearCurrentQuery();
|
|
536
|
+
if (getState().route.name === "editorResults") {
|
|
537
|
+
router.navigate("/editor");
|
|
538
|
+
}
|
|
353
539
|
return;
|
|
354
540
|
case "clear-results":
|
|
355
541
|
clearEditorResults();
|
|
356
542
|
router.navigate("/editor");
|
|
357
543
|
return;
|
|
544
|
+
case "select-query-history-item":
|
|
545
|
+
if (actionNode.dataset.historyId) {
|
|
546
|
+
await selectQueryHistoryItem(actionNode.dataset.historyId);
|
|
547
|
+
}
|
|
548
|
+
return;
|
|
549
|
+
case "clear-query-history-selection":
|
|
550
|
+
clearQueryHistorySelection();
|
|
551
|
+
return;
|
|
552
|
+
case "set-query-history-tab":
|
|
553
|
+
if (actionNode.dataset.tab) {
|
|
554
|
+
await setQueryHistoryTab(actionNode.dataset.tab);
|
|
555
|
+
}
|
|
556
|
+
return;
|
|
557
|
+
case "load-more-query-history":
|
|
558
|
+
await loadMoreQueryHistory();
|
|
559
|
+
return;
|
|
560
|
+
case "open-query-history":
|
|
561
|
+
if (actionNode.dataset.historyId && openQueryHistoryInEditor(actionNode.dataset.historyId)) {
|
|
562
|
+
router.navigate("/editor");
|
|
563
|
+
}
|
|
564
|
+
return;
|
|
565
|
+
case "run-query-history":
|
|
566
|
+
if (actionNode.dataset.historyId) {
|
|
567
|
+
const success = await runQueryHistoryItem(actionNode.dataset.historyId);
|
|
568
|
+
router.navigate(success ? "/editor/results" : "/editor");
|
|
569
|
+
}
|
|
570
|
+
return;
|
|
571
|
+
case "toggle-query-history-saved":
|
|
572
|
+
if (actionNode.dataset.historyId) {
|
|
573
|
+
await toggleQueryHistorySavedState(
|
|
574
|
+
actionNode.dataset.historyId,
|
|
575
|
+
actionNode.dataset.nextValue === "true"
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
return;
|
|
579
|
+
case "delete-query-history":
|
|
580
|
+
if (
|
|
581
|
+
actionNode.dataset.historyId &&
|
|
582
|
+
window.confirm("Delete this query history item and all recorded runs?")
|
|
583
|
+
) {
|
|
584
|
+
await deleteQueryHistoryStateItem(actionNode.dataset.historyId);
|
|
585
|
+
}
|
|
586
|
+
return;
|
|
358
587
|
case "set-editor-tab": {
|
|
359
588
|
const tab = actionNode.dataset.tab;
|
|
360
589
|
if (!tab) {
|
|
@@ -364,9 +593,6 @@ async function handleAction(actionNode) {
|
|
|
364
593
|
router.navigate(tab === "results" ? "/editor/results" : "/editor");
|
|
365
594
|
return;
|
|
366
595
|
}
|
|
367
|
-
case "clear-sql-history":
|
|
368
|
-
await clearSqlHistoryStateAndData();
|
|
369
|
-
return;
|
|
370
596
|
case "export-query-csv":
|
|
371
597
|
await exportCurrentQueryCsv();
|
|
372
598
|
return;
|
|
@@ -378,6 +604,53 @@ async function handleAction(actionNode) {
|
|
|
378
604
|
await selectStructureEntry(actionNode.dataset.entryName);
|
|
379
605
|
}
|
|
380
606
|
return;
|
|
607
|
+
case "add-table-designer-column":
|
|
608
|
+
{
|
|
609
|
+
const columnId = addCurrentTableDesignerColumn();
|
|
610
|
+
if (columnId) {
|
|
611
|
+
window.requestAnimationFrame(() => {
|
|
612
|
+
focusTableDesignerColumnNameField(columnId);
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return;
|
|
617
|
+
case "remove-table-designer-column":
|
|
618
|
+
if (actionNode.dataset.columnId) {
|
|
619
|
+
removeCurrentTableDesignerColumn(actionNode.dataset.columnId);
|
|
620
|
+
}
|
|
621
|
+
return;
|
|
622
|
+
case "save-table-designer": {
|
|
623
|
+
const savedTableName = await saveCurrentTableDesignerDraft();
|
|
624
|
+
if (savedTableName) {
|
|
625
|
+
router.navigate(`/table-designer/${encodeURIComponent(savedTableName)}`);
|
|
626
|
+
}
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
case "copy-table-designer-sql": {
|
|
630
|
+
const sqlPreview = getState().tableDesigner.draft?.sqlPreview ?? "";
|
|
631
|
+
if (!sqlPreview.trim()) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
await navigator.clipboard.writeText(sqlPreview);
|
|
637
|
+
showToast("SQL preview copied.", "success");
|
|
638
|
+
} catch (error) {
|
|
639
|
+
showToast("Clipboard access failed.", "alert");
|
|
640
|
+
}
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
case "import-table-designer-csv": {
|
|
644
|
+
const fileInput = document.querySelector('[data-bind="table-designer-import-file"]');
|
|
645
|
+
|
|
646
|
+
if (!(fileInput instanceof HTMLInputElement)) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
fileInput.value = "";
|
|
651
|
+
fileInput.click();
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
381
654
|
case "select-data-row":
|
|
382
655
|
if (actionNode.dataset.rowIndex) {
|
|
383
656
|
selectDataRow(actionNode.dataset.rowIndex);
|
|
@@ -399,6 +672,16 @@ async function handleAction(actionNode) {
|
|
|
399
672
|
await setDataPage(actionNode.dataset.page);
|
|
400
673
|
}
|
|
401
674
|
return;
|
|
675
|
+
case "sort-data-column":
|
|
676
|
+
if (actionNode.dataset.columnName) {
|
|
677
|
+
await sortDataTableByColumn(actionNode.dataset.columnName);
|
|
678
|
+
}
|
|
679
|
+
return;
|
|
680
|
+
case "sort-editor-results-column":
|
|
681
|
+
if (actionNode.dataset.columnName) {
|
|
682
|
+
sortEditorResultsByColumn(actionNode.dataset.columnName);
|
|
683
|
+
}
|
|
684
|
+
return;
|
|
402
685
|
case "set-data-page-size":
|
|
403
686
|
if (actionNode.dataset.pageSize) {
|
|
404
687
|
await setDataPageSize(actionNode.dataset.pageSize);
|
|
@@ -483,6 +766,34 @@ document.addEventListener("input", (event) => {
|
|
|
483
766
|
|
|
484
767
|
if (bindNode.dataset.bind === "data-search-query") {
|
|
485
768
|
setDataSearchQuery(bindNode.value);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (bindNode.dataset.bind === "table-designer-search") {
|
|
773
|
+
setTableDesignerSearchQuery(bindNode.value);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (bindNode.dataset.bind === "table-designer-field") {
|
|
778
|
+
if (bindNode instanceof HTMLInputElement && bindNode.type === "checkbox") {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.value);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (bindNode.dataset.bind === "table-designer-column-field") {
|
|
787
|
+
updateCurrentTableDesignerColumnField(
|
|
788
|
+
bindNode.dataset.columnId,
|
|
789
|
+
bindNode.dataset.field,
|
|
790
|
+
bindNode.value
|
|
791
|
+
);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (bindNode.dataset.bind === "query-history-search") {
|
|
796
|
+
setQueryHistorySearchInput(bindNode.value);
|
|
486
797
|
}
|
|
487
798
|
});
|
|
488
799
|
|
|
@@ -507,14 +818,41 @@ document.addEventListener("change", (event) => {
|
|
|
507
818
|
return;
|
|
508
819
|
}
|
|
509
820
|
|
|
510
|
-
if (bindNode.dataset.bind === "
|
|
511
|
-
|
|
512
|
-
bindNode.value = "";
|
|
821
|
+
if (bindNode.dataset.bind === "data-search-column") {
|
|
822
|
+
setDataSearchColumn(bindNode.value);
|
|
513
823
|
return;
|
|
514
824
|
}
|
|
515
825
|
|
|
516
|
-
if (bindNode.dataset.bind === "
|
|
517
|
-
|
|
826
|
+
if (bindNode.dataset.bind === "table-designer-import-file") {
|
|
827
|
+
void handleTableDesignerCsvImport(bindNode);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (bindNode.dataset.bind === "table-designer-field") {
|
|
832
|
+
if (bindNode instanceof HTMLInputElement && bindNode.type === "checkbox") {
|
|
833
|
+
updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.checked);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.value);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (bindNode.dataset.bind === "table-designer-column-field") {
|
|
842
|
+
updateCurrentTableDesignerColumnField(
|
|
843
|
+
bindNode.dataset.columnId,
|
|
844
|
+
bindNode.dataset.field,
|
|
845
|
+
bindNode.value
|
|
846
|
+
);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (bindNode.dataset.bind === "table-designer-column-flag") {
|
|
851
|
+
updateCurrentTableDesignerColumnField(
|
|
852
|
+
bindNode.dataset.columnId,
|
|
853
|
+
bindNode.dataset.field,
|
|
854
|
+
bindNode.checked
|
|
855
|
+
);
|
|
518
856
|
}
|
|
519
857
|
});
|
|
520
858
|
|
|
@@ -634,6 +972,18 @@ document.addEventListener("submit", async (event) => {
|
|
|
634
972
|
);
|
|
635
973
|
return;
|
|
636
974
|
}
|
|
975
|
+
case "save-query-history-title":
|
|
976
|
+
await saveQueryHistoryTitle(
|
|
977
|
+
String(formData.get("historyId") ?? ""),
|
|
978
|
+
String(formData.get("title") ?? "")
|
|
979
|
+
);
|
|
980
|
+
return;
|
|
981
|
+
case "save-query-history-notes":
|
|
982
|
+
await saveQueryHistoryNotes(
|
|
983
|
+
String(formData.get("historyId") ?? ""),
|
|
984
|
+
String(formData.get("notes") ?? "")
|
|
985
|
+
);
|
|
986
|
+
return;
|
|
637
987
|
default:
|
|
638
988
|
}
|
|
639
989
|
});
|
|
@@ -3,8 +3,8 @@ import { escapeHtml } from "../utils/format.js";
|
|
|
3
3
|
export function renderBottomTabs(activeTab, counts = {}) {
|
|
4
4
|
const tabs = [
|
|
5
5
|
{ key: "results", label: "results", meta: counts.resultRows ?? 0 },
|
|
6
|
-
{ key: "messages", label: "messages", meta: counts.messages ?? 0 },
|
|
7
6
|
{ key: "performance", label: "performance", meta: counts.statementCount ?? 0 },
|
|
7
|
+
{ key: "messages", label: "messages", meta: counts.messages ?? 0 },
|
|
8
8
|
];
|
|
9
9
|
|
|
10
10
|
return `
|
|
@@ -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
|
)
|