sqlite-hub 0.5.0 → 0.7.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 +109 -2
- package/bin/sqlite-hub.js +285 -4
- 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_croped.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/index.html +1 -0
- package/frontend/js/api.js +49 -0
- package/frontend/js/app.js +377 -9
- package/frontend/js/components/modal.js +314 -1
- package/frontend/js/components/queryChartRenderer.js +125 -0
- package/frontend/js/components/queryEditor.js +37 -8
- package/frontend/js/components/queryHistoryPanel.js +16 -5
- package/frontend/js/components/sidebar.js +2 -0
- package/frontend/js/components/tableDesignerEditor.js +356 -0
- package/frontend/js/components/tableDesignerSidebar.js +129 -0
- package/frontend/js/components/tableDesignerSqlPreview.js +40 -0
- package/frontend/js/lib/queryChartOptions.js +283 -0
- package/frontend/js/lib/queryCharts.js +560 -0
- package/frontend/js/router.js +18 -0
- package/frontend/js/store.js +914 -1
- package/frontend/js/utils/tableDesigner.js +1192 -0
- package/frontend/js/views/charts.js +471 -0
- package/frontend/js/views/data.js +281 -277
- package/frontend/js/views/editor.js +19 -13
- package/frontend/js/views/structure.js +81 -39
- package/frontend/js/views/tableDesigner.js +37 -0
- package/frontend/styles/base.css +87 -73
- package/frontend/styles/components.css +790 -251
- package/frontend/styles/views.css +316 -46
- package/package.json +2 -1
- package/server/routes/charts.js +153 -0
- package/server/routes/tableDesigner.js +60 -0
- package/server/server.js +10 -0
- 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/storage/appStateStore.js +400 -1
- package/server/services/storage/queryHistoryChartUtils.js +145 -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/store.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import * as api from "./api.js";
|
|
2
2
|
import { formatCellValue, inferStatusTone, truncateMiddle } from "./utils/format.js";
|
|
3
|
+
import {
|
|
4
|
+
addTableDesignerColumn,
|
|
5
|
+
createTableDesignerDraftFromCsvImport,
|
|
6
|
+
createNewTableDesignerDraft,
|
|
7
|
+
hydrateTableDesignerDraft,
|
|
8
|
+
removeTableDesignerColumn,
|
|
9
|
+
updateTableDesignerColumnField,
|
|
10
|
+
updateTableDesignerDraftField,
|
|
11
|
+
} from "./utils/tableDesigner.js";
|
|
12
|
+
import {
|
|
13
|
+
analyzeQueryChartResult,
|
|
14
|
+
buildDefaultQueryChartName,
|
|
15
|
+
buildQueryChartResultColumns,
|
|
16
|
+
buildSuggestedChartConfig,
|
|
17
|
+
resolveUniqueQueryChartName,
|
|
18
|
+
suggestQueryChartType,
|
|
19
|
+
validateQueryChartConfig,
|
|
20
|
+
} from "./lib/queryCharts.js";
|
|
3
21
|
|
|
4
22
|
const listeners = new Set();
|
|
5
23
|
const DEFAULT_SETTINGS = {
|
|
@@ -10,6 +28,7 @@ const DEFAULT_SETTINGS = {
|
|
|
10
28
|
const DATA_PAGE_SIZES = [25, 50, 100];
|
|
11
29
|
const QUERY_HISTORY_PAGE_SIZE = 30;
|
|
12
30
|
const QUERY_HISTORY_RUN_LIMIT = 8;
|
|
31
|
+
const CHART_HEIGHT_PRESETS = new Set(["small", "medium", "large"]);
|
|
13
32
|
const MISSING_DATABASE_ERROR = {
|
|
14
33
|
code: "ACTIVE_DATABASE_REQUIRED",
|
|
15
34
|
message: "No active SQLite database selected.",
|
|
@@ -19,6 +38,8 @@ let routeLoadVersion = 0;
|
|
|
19
38
|
let queryHistoryLoadVersion = 0;
|
|
20
39
|
let queryHistoryDetailLoadVersion = 0;
|
|
21
40
|
let queryHistorySearchTimer = null;
|
|
41
|
+
let chartsLoadVersion = 0;
|
|
42
|
+
let chartsDetailLoadVersion = 0;
|
|
22
43
|
|
|
23
44
|
const state = {
|
|
24
45
|
ready: false,
|
|
@@ -46,6 +67,7 @@ const state = {
|
|
|
46
67
|
dataBrowser: {
|
|
47
68
|
tables: [],
|
|
48
69
|
selectedTable: null,
|
|
70
|
+
tablesVisible: true,
|
|
49
71
|
table: null,
|
|
50
72
|
loading: false,
|
|
51
73
|
tableLoading: false,
|
|
@@ -64,7 +86,9 @@ const state = {
|
|
|
64
86
|
},
|
|
65
87
|
editor: {
|
|
66
88
|
sqlText: "",
|
|
89
|
+
editorPanelVisible: true,
|
|
67
90
|
history: [],
|
|
91
|
+
historyPanelVisible: true,
|
|
68
92
|
historyLoading: false,
|
|
69
93
|
historyLoadingMore: false,
|
|
70
94
|
historyError: null,
|
|
@@ -92,6 +116,34 @@ const state = {
|
|
|
92
116
|
deleting: false,
|
|
93
117
|
saveError: null,
|
|
94
118
|
},
|
|
119
|
+
charts: {
|
|
120
|
+
queries: [],
|
|
121
|
+
loading: false,
|
|
122
|
+
error: null,
|
|
123
|
+
selectedHistoryId: null,
|
|
124
|
+
chartHeightPreset: "medium",
|
|
125
|
+
sqlExpanded: false,
|
|
126
|
+
resultsVisible: true,
|
|
127
|
+
detail: null,
|
|
128
|
+
detailLoading: false,
|
|
129
|
+
detailError: null,
|
|
130
|
+
result: null,
|
|
131
|
+
resultLoading: false,
|
|
132
|
+
resultError: null,
|
|
133
|
+
},
|
|
134
|
+
tableDesigner: {
|
|
135
|
+
tables: [],
|
|
136
|
+
selectedTableName: null,
|
|
137
|
+
draft: null,
|
|
138
|
+
pendingImportedDraft: null,
|
|
139
|
+
loading: false,
|
|
140
|
+
detailLoading: false,
|
|
141
|
+
saving: false,
|
|
142
|
+
searchQuery: "",
|
|
143
|
+
supportedTypes: [],
|
|
144
|
+
error: null,
|
|
145
|
+
saveError: null,
|
|
146
|
+
},
|
|
95
147
|
structure: {
|
|
96
148
|
data: null,
|
|
97
149
|
selectedName: null,
|
|
@@ -145,7 +197,15 @@ function clearQueryHistoryDetailState() {
|
|
|
145
197
|
}
|
|
146
198
|
|
|
147
199
|
function requiresActiveDatabase(routeName) {
|
|
148
|
-
return [
|
|
200
|
+
return [
|
|
201
|
+
"overview",
|
|
202
|
+
"data",
|
|
203
|
+
"editor",
|
|
204
|
+
"editorResults",
|
|
205
|
+
"charts",
|
|
206
|
+
"structure",
|
|
207
|
+
"tableDesigner",
|
|
208
|
+
].includes(routeName);
|
|
149
209
|
}
|
|
150
210
|
|
|
151
211
|
function normalizeDataPageSize(value, fallback = 50) {
|
|
@@ -270,6 +330,22 @@ function getCurrentStructureEntry(snapshot = state) {
|
|
|
270
330
|
return entries.find((entry) => entry.name === snapshot.structure.selectedName) ?? null;
|
|
271
331
|
}
|
|
272
332
|
|
|
333
|
+
function getTableDesignerContext(snapshot = state) {
|
|
334
|
+
return {
|
|
335
|
+
catalogTables: snapshot.tableDesigner.tables ?? [],
|
|
336
|
+
supportedTypes: snapshot.tableDesigner.supportedTypes ?? [],
|
|
337
|
+
readOnly: Boolean(snapshot.connections.active?.readOnly),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function decorateTableDesignerDraft(draft, snapshot = state) {
|
|
342
|
+
if (!draft) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return hydrateTableDesignerDraft(draft, getTableDesignerContext(snapshot));
|
|
347
|
+
}
|
|
348
|
+
|
|
273
349
|
function buildDeleteRowPreview(fields = []) {
|
|
274
350
|
return fields
|
|
275
351
|
.filter((field) => field && field.label)
|
|
@@ -330,6 +406,52 @@ function resetQueryHistoryState({ preserveSearch = true } = {}) {
|
|
|
330
406
|
}
|
|
331
407
|
}
|
|
332
408
|
|
|
409
|
+
function resetChartsState() {
|
|
410
|
+
chartsLoadVersion += 1;
|
|
411
|
+
chartsDetailLoadVersion += 1;
|
|
412
|
+
state.charts.queries = [];
|
|
413
|
+
state.charts.loading = false;
|
|
414
|
+
state.charts.error = null;
|
|
415
|
+
state.charts.selectedHistoryId = null;
|
|
416
|
+
state.charts.chartHeightPreset = "medium";
|
|
417
|
+
state.charts.sqlExpanded = false;
|
|
418
|
+
state.charts.resultsVisible = true;
|
|
419
|
+
state.charts.detail = null;
|
|
420
|
+
state.charts.detailLoading = false;
|
|
421
|
+
state.charts.detailError = null;
|
|
422
|
+
state.charts.result = null;
|
|
423
|
+
state.charts.resultLoading = false;
|
|
424
|
+
state.charts.resultError = null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function normalizeChartsHeightPreset(value) {
|
|
428
|
+
const normalizedValue = String(value ?? "")
|
|
429
|
+
.trim()
|
|
430
|
+
.toLowerCase();
|
|
431
|
+
|
|
432
|
+
return CHART_HEIGHT_PRESETS.has(normalizedValue) ? normalizedValue : "medium";
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function mergeQueryHistoryItemWithChartSummary(updatedItem, fallbackItem = null) {
|
|
436
|
+
if (!updatedItem) {
|
|
437
|
+
return updatedItem;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
...(fallbackItem ?? {}),
|
|
442
|
+
...updatedItem,
|
|
443
|
+
chartCount:
|
|
444
|
+
updatedItem.chartCount !== undefined
|
|
445
|
+
? Number(updatedItem.chartCount ?? 0)
|
|
446
|
+
: Number(fallbackItem?.chartCount ?? 0),
|
|
447
|
+
chartTypes: Array.isArray(updatedItem.chartTypes)
|
|
448
|
+
? [...updatedItem.chartTypes]
|
|
449
|
+
: Array.isArray(fallbackItem?.chartTypes)
|
|
450
|
+
? [...fallbackItem.chartTypes]
|
|
451
|
+
: [],
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
333
455
|
function syncQueryHistoryItem(updatedItem) {
|
|
334
456
|
if (!updatedItem) {
|
|
335
457
|
return;
|
|
@@ -342,6 +464,61 @@ function syncQueryHistoryItem(updatedItem) {
|
|
|
342
464
|
if (state.editor.historyDetail?.id === updatedItem.id) {
|
|
343
465
|
state.editor.historyDetail = updatedItem;
|
|
344
466
|
}
|
|
467
|
+
|
|
468
|
+
const existingChartsEntry =
|
|
469
|
+
state.charts.queries.find((entry) => entry.id === updatedItem.id) ??
|
|
470
|
+
(state.charts.detail?.item?.id === updatedItem.id ? state.charts.detail.item : null);
|
|
471
|
+
const mergedChartsItem = mergeQueryHistoryItemWithChartSummary(updatedItem, existingChartsEntry);
|
|
472
|
+
|
|
473
|
+
state.charts.queries = state.charts.queries.map((entry) =>
|
|
474
|
+
entry.id === updatedItem.id ? mergedChartsItem : entry
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
if (state.charts.detail?.item?.id === updatedItem.id) {
|
|
478
|
+
state.charts.detail = {
|
|
479
|
+
...state.charts.detail,
|
|
480
|
+
item: mergeQueryHistoryItemWithChartSummary(updatedItem, state.charts.detail.item),
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function syncChartsQuerySummaryForHistory(historyId) {
|
|
486
|
+
const numericId = Number(historyId);
|
|
487
|
+
|
|
488
|
+
if (!Number.isInteger(numericId) || numericId < 1 || state.charts.detail?.item?.id !== numericId) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const charts = state.charts.detail?.charts ?? [];
|
|
493
|
+
const chartTypes = [...new Set(
|
|
494
|
+
charts
|
|
495
|
+
.map((chart) =>
|
|
496
|
+
String(chart?.chartType ?? "")
|
|
497
|
+
.trim()
|
|
498
|
+
.toLowerCase()
|
|
499
|
+
)
|
|
500
|
+
.filter(Boolean)
|
|
501
|
+
)];
|
|
502
|
+
const chartCount = charts.length;
|
|
503
|
+
|
|
504
|
+
state.charts.queries = state.charts.queries.map((entry) =>
|
|
505
|
+
entry.id === numericId
|
|
506
|
+
? {
|
|
507
|
+
...entry,
|
|
508
|
+
chartCount,
|
|
509
|
+
chartTypes,
|
|
510
|
+
}
|
|
511
|
+
: entry
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
state.charts.detail = {
|
|
515
|
+
...state.charts.detail,
|
|
516
|
+
item: {
|
|
517
|
+
...state.charts.detail.item,
|
|
518
|
+
chartCount,
|
|
519
|
+
chartTypes,
|
|
520
|
+
},
|
|
521
|
+
};
|
|
345
522
|
}
|
|
346
523
|
|
|
347
524
|
function resolveQueryHistorySql(historyId) {
|
|
@@ -351,6 +528,10 @@ function resolveQueryHistorySql(historyId) {
|
|
|
351
528
|
return state.editor.historyDetail.rawSql;
|
|
352
529
|
}
|
|
353
530
|
|
|
531
|
+
if (String(state.charts.detail?.item?.id ?? "") === historyIdAsString) {
|
|
532
|
+
return state.charts.detail.item.rawSql;
|
|
533
|
+
}
|
|
534
|
+
|
|
354
535
|
return findQueryHistoryItem(historyId)?.rawSql ?? null;
|
|
355
536
|
}
|
|
356
537
|
|
|
@@ -358,6 +539,11 @@ function clearRouteSlices() {
|
|
|
358
539
|
state.overview.error = null;
|
|
359
540
|
state.dataBrowser.error = null;
|
|
360
541
|
state.dataBrowser.saveError = null;
|
|
542
|
+
state.charts.error = null;
|
|
543
|
+
state.charts.detailError = null;
|
|
544
|
+
state.charts.resultError = null;
|
|
545
|
+
state.tableDesigner.error = null;
|
|
546
|
+
state.tableDesigner.saveError = null;
|
|
361
547
|
state.structure.error = null;
|
|
362
548
|
}
|
|
363
549
|
|
|
@@ -387,6 +573,21 @@ function setMissingDatabaseState() {
|
|
|
387
573
|
state.structure.detail = null;
|
|
388
574
|
state.structure.error = error;
|
|
389
575
|
|
|
576
|
+
state.tableDesigner.loading = false;
|
|
577
|
+
state.tableDesigner.detailLoading = false;
|
|
578
|
+
state.tableDesigner.tables = [];
|
|
579
|
+
state.tableDesigner.selectedTableName = null;
|
|
580
|
+
state.tableDesigner.draft = null;
|
|
581
|
+
state.tableDesigner.pendingImportedDraft = null;
|
|
582
|
+
state.tableDesigner.saving = false;
|
|
583
|
+
state.tableDesigner.searchQuery = "";
|
|
584
|
+
state.tableDesigner.supportedTypes = [];
|
|
585
|
+
state.tableDesigner.error = error;
|
|
586
|
+
state.tableDesigner.saveError = null;
|
|
587
|
+
|
|
588
|
+
resetChartsState();
|
|
589
|
+
state.charts.error = error;
|
|
590
|
+
|
|
390
591
|
resetQueryHistoryState({ preserveSearch: false });
|
|
391
592
|
}
|
|
392
593
|
|
|
@@ -423,6 +624,10 @@ function syncRouteContext() {
|
|
|
423
624
|
state.structure.detail = null;
|
|
424
625
|
state.structure.selectedName = null;
|
|
425
626
|
}
|
|
627
|
+
|
|
628
|
+
if (route.name !== "tableDesigner") {
|
|
629
|
+
state.tableDesigner.saveError = null;
|
|
630
|
+
}
|
|
426
631
|
}
|
|
427
632
|
|
|
428
633
|
async function refreshConnectionsState() {
|
|
@@ -595,6 +800,114 @@ async function refreshQueryHistoryState({ append = false } = {}) {
|
|
|
595
800
|
}
|
|
596
801
|
}
|
|
597
802
|
|
|
803
|
+
async function loadChartsDetail(historyId) {
|
|
804
|
+
const numericId = Number(historyId);
|
|
805
|
+
const requestVersion = ++chartsDetailLoadVersion;
|
|
806
|
+
|
|
807
|
+
if (!Number.isInteger(numericId) || numericId < 1) {
|
|
808
|
+
state.charts.selectedHistoryId = null;
|
|
809
|
+
state.charts.sqlExpanded = false;
|
|
810
|
+
state.charts.resultsVisible = true;
|
|
811
|
+
state.charts.detail = null;
|
|
812
|
+
state.charts.detailLoading = false;
|
|
813
|
+
state.charts.detailError = null;
|
|
814
|
+
state.charts.result = null;
|
|
815
|
+
state.charts.resultLoading = false;
|
|
816
|
+
state.charts.resultError = null;
|
|
817
|
+
emitChange();
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
state.charts.selectedHistoryId = numericId;
|
|
822
|
+
state.charts.sqlExpanded = false;
|
|
823
|
+
state.charts.resultsVisible = true;
|
|
824
|
+
state.charts.detail = null;
|
|
825
|
+
state.charts.detailLoading = true;
|
|
826
|
+
state.charts.detailError = null;
|
|
827
|
+
state.charts.result = null;
|
|
828
|
+
state.charts.resultLoading = true;
|
|
829
|
+
state.charts.resultError = null;
|
|
830
|
+
emitChange();
|
|
831
|
+
|
|
832
|
+
const [detailResponse, resultResponse] = await Promise.allSettled([
|
|
833
|
+
api.getChartsQueryHistoryDetail(numericId),
|
|
834
|
+
api.executeChartsQueryHistory(numericId),
|
|
835
|
+
]);
|
|
836
|
+
|
|
837
|
+
if (
|
|
838
|
+
requestVersion !== chartsDetailLoadVersion ||
|
|
839
|
+
state.charts.selectedHistoryId !== numericId
|
|
840
|
+
) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (detailResponse.status === "fulfilled") {
|
|
845
|
+
state.charts.detail = detailResponse.value.data ?? null;
|
|
846
|
+
state.charts.detailError = null;
|
|
847
|
+
if (detailResponse.value.data?.item) {
|
|
848
|
+
syncQueryHistoryItem(detailResponse.value.data.item);
|
|
849
|
+
syncChartsQuerySummaryForHistory(detailResponse.value.data.item.id);
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
state.charts.detail = null;
|
|
853
|
+
state.charts.detailError = normalizeError(detailResponse.reason);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (resultResponse.status === "fulfilled") {
|
|
857
|
+
state.charts.result = resultResponse.value.data ?? null;
|
|
858
|
+
state.charts.resultError = null;
|
|
859
|
+
} else {
|
|
860
|
+
state.charts.result = null;
|
|
861
|
+
state.charts.resultError = normalizeError(resultResponse.reason);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (requestVersion === chartsDetailLoadVersion) {
|
|
865
|
+
state.charts.detailLoading = false;
|
|
866
|
+
state.charts.resultLoading = false;
|
|
867
|
+
emitChange();
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
async function loadCharts(version, route) {
|
|
872
|
+
state.charts.loading = true;
|
|
873
|
+
state.charts.error = null;
|
|
874
|
+
emitChange();
|
|
875
|
+
|
|
876
|
+
try {
|
|
877
|
+
const response = await api.getChartsQueryHistory();
|
|
878
|
+
|
|
879
|
+
if (version !== routeLoadVersion) {
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
state.charts.queries = response.data ?? [];
|
|
884
|
+
state.charts.error = null;
|
|
885
|
+
} catch (error) {
|
|
886
|
+
if (version !== routeLoadVersion) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
state.charts.queries = [];
|
|
891
|
+
state.charts.error = normalizeError(error);
|
|
892
|
+
} finally {
|
|
893
|
+
if (version === routeLoadVersion) {
|
|
894
|
+
state.charts.loading = false;
|
|
895
|
+
emitChange();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (version !== routeLoadVersion) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const requestedHistoryId = Number(route.params?.historyId ?? null);
|
|
904
|
+
const canLoadRequestedHistory =
|
|
905
|
+
Number.isInteger(requestedHistoryId) &&
|
|
906
|
+
state.charts.queries.some((item) => item.id === requestedHistoryId);
|
|
907
|
+
|
|
908
|
+
await loadChartsDetail(canLoadRequestedHistory ? requestedHistoryId : null);
|
|
909
|
+
}
|
|
910
|
+
|
|
598
911
|
async function loadOverview(version) {
|
|
599
912
|
state.overview.loading = true;
|
|
600
913
|
state.overview.error = null;
|
|
@@ -824,6 +1137,103 @@ async function loadStructure(version) {
|
|
|
824
1137
|
}
|
|
825
1138
|
}
|
|
826
1139
|
|
|
1140
|
+
async function loadTableDesignerDetail(version, tableName) {
|
|
1141
|
+
if (!tableName) {
|
|
1142
|
+
state.tableDesigner.draft = null;
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
state.tableDesigner.detailLoading = true;
|
|
1147
|
+
state.tableDesigner.saveError = null;
|
|
1148
|
+
emitChange();
|
|
1149
|
+
|
|
1150
|
+
try {
|
|
1151
|
+
const response = await api.getTableDesignerTable(tableName);
|
|
1152
|
+
|
|
1153
|
+
if (version !== routeLoadVersion) {
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
state.tableDesigner.selectedTableName = tableName;
|
|
1158
|
+
state.tableDesigner.draft = decorateTableDesignerDraft(response.data?.draft ?? null);
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
if (version !== routeLoadVersion) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
state.tableDesigner.error = normalizeError(error);
|
|
1165
|
+
state.tableDesigner.draft = null;
|
|
1166
|
+
} finally {
|
|
1167
|
+
if (version === routeLoadVersion) {
|
|
1168
|
+
state.tableDesigner.detailLoading = false;
|
|
1169
|
+
emitChange();
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
async function loadTableDesigner(version, route) {
|
|
1175
|
+
state.tableDesigner.loading = true;
|
|
1176
|
+
state.tableDesigner.error = null;
|
|
1177
|
+
emitChange();
|
|
1178
|
+
|
|
1179
|
+
try {
|
|
1180
|
+
const response = await api.getTableDesignerOverview();
|
|
1181
|
+
|
|
1182
|
+
if (version !== routeLoadVersion) {
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
state.tableDesigner.tables = response.data?.tables ?? [];
|
|
1187
|
+
state.tableDesigner.supportedTypes = response.data?.supportedTypes ?? [];
|
|
1188
|
+
|
|
1189
|
+
if (route.params?.isNew) {
|
|
1190
|
+
const importedDraft = state.tableDesigner.pendingImportedDraft;
|
|
1191
|
+
state.tableDesigner.selectedTableName = null;
|
|
1192
|
+
state.tableDesigner.detailLoading = false;
|
|
1193
|
+
state.tableDesigner.pendingImportedDraft = null;
|
|
1194
|
+
state.tableDesigner.draft = decorateTableDesignerDraft(
|
|
1195
|
+
importedDraft ?? createNewTableDesignerDraft()
|
|
1196
|
+
);
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
const requestedTableName = route.params?.tableName ?? null;
|
|
1201
|
+
const tableName =
|
|
1202
|
+
requestedTableName &&
|
|
1203
|
+
state.tableDesigner.tables.some((table) => table.name === requestedTableName)
|
|
1204
|
+
? requestedTableName
|
|
1205
|
+
: state.tableDesigner.selectedTableName &&
|
|
1206
|
+
state.tableDesigner.tables.some(
|
|
1207
|
+
(table) => table.name === state.tableDesigner.selectedTableName
|
|
1208
|
+
)
|
|
1209
|
+
? state.tableDesigner.selectedTableName
|
|
1210
|
+
: state.tableDesigner.tables[0]?.name ?? null;
|
|
1211
|
+
|
|
1212
|
+
if (!tableName) {
|
|
1213
|
+
state.tableDesigner.selectedTableName = null;
|
|
1214
|
+
state.tableDesigner.detailLoading = false;
|
|
1215
|
+
state.tableDesigner.draft = null;
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
await loadTableDesignerDetail(version, tableName);
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
if (version !== routeLoadVersion) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
state.tableDesigner.tables = [];
|
|
1226
|
+
state.tableDesigner.selectedTableName = null;
|
|
1227
|
+
state.tableDesigner.draft = null;
|
|
1228
|
+
state.tableDesigner.error = normalizeError(error);
|
|
1229
|
+
} finally {
|
|
1230
|
+
if (version === routeLoadVersion) {
|
|
1231
|
+
state.tableDesigner.loading = false;
|
|
1232
|
+
emitChange();
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
827
1237
|
function invalidateDatabaseCaches() {
|
|
828
1238
|
state.overview.data = null;
|
|
829
1239
|
state.dataBrowser.tables = [];
|
|
@@ -835,6 +1245,16 @@ function invalidateDatabaseCaches() {
|
|
|
835
1245
|
state.dataBrowser.exportLoading = false;
|
|
836
1246
|
state.dataBrowser.error = null;
|
|
837
1247
|
state.dataBrowser.saveError = null;
|
|
1248
|
+
state.tableDesigner.tables = [];
|
|
1249
|
+
state.tableDesigner.selectedTableName = null;
|
|
1250
|
+
state.tableDesigner.draft = null;
|
|
1251
|
+
state.tableDesigner.pendingImportedDraft = null;
|
|
1252
|
+
state.tableDesigner.saving = false;
|
|
1253
|
+
state.tableDesigner.searchQuery = "";
|
|
1254
|
+
state.tableDesigner.supportedTypes = [];
|
|
1255
|
+
state.tableDesigner.error = null;
|
|
1256
|
+
state.tableDesigner.saveError = null;
|
|
1257
|
+
resetChartsState();
|
|
838
1258
|
state.structure.data = null;
|
|
839
1259
|
state.structure.detail = null;
|
|
840
1260
|
}
|
|
@@ -862,6 +1282,9 @@ async function loadRouteData(route) {
|
|
|
862
1282
|
case "data":
|
|
863
1283
|
await loadData(version, route);
|
|
864
1284
|
return;
|
|
1285
|
+
case "charts":
|
|
1286
|
+
await loadCharts(version, route);
|
|
1287
|
+
return;
|
|
865
1288
|
case "editor":
|
|
866
1289
|
case "editorResults":
|
|
867
1290
|
await refreshQueryHistoryState();
|
|
@@ -869,6 +1292,9 @@ async function loadRouteData(route) {
|
|
|
869
1292
|
case "structure":
|
|
870
1293
|
await loadStructure(version);
|
|
871
1294
|
return;
|
|
1295
|
+
case "tableDesigner":
|
|
1296
|
+
await loadTableDesigner(version, route);
|
|
1297
|
+
return;
|
|
872
1298
|
case "settings":
|
|
873
1299
|
await refreshSettingsState();
|
|
874
1300
|
return;
|
|
@@ -911,6 +1337,94 @@ function closeModalInternal() {
|
|
|
911
1337
|
emitChange();
|
|
912
1338
|
}
|
|
913
1339
|
|
|
1340
|
+
function getChartsResultAnalysis(snapshot = state) {
|
|
1341
|
+
return snapshot.charts.result ? analyzeQueryChartResult(snapshot.charts.result) : null;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function buildQueryChartDraft(mode, chart = null) {
|
|
1345
|
+
const queryItem = state.charts.detail?.item ?? null;
|
|
1346
|
+
const analysis = getChartsResultAnalysis();
|
|
1347
|
+
|
|
1348
|
+
if (!queryItem || !analysis) {
|
|
1349
|
+
return null;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
const existingCharts = state.charts.detail?.charts ?? [];
|
|
1353
|
+
|
|
1354
|
+
if (mode === "edit" && chart) {
|
|
1355
|
+
return {
|
|
1356
|
+
mode,
|
|
1357
|
+
chartId: chart.id,
|
|
1358
|
+
queryHistoryId: chart.queryHistoryId,
|
|
1359
|
+
chartType: chart.chartType,
|
|
1360
|
+
name: chart.name,
|
|
1361
|
+
nameTouched: true,
|
|
1362
|
+
config: structuredClone(chart.config),
|
|
1363
|
+
tableVisible: chart.tableVisible,
|
|
1364
|
+
resultColumns: buildQueryChartResultColumns(analysis),
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
const chartType = suggestQueryChartType(analysis);
|
|
1369
|
+
const name = resolveUniqueQueryChartName(
|
|
1370
|
+
buildDefaultQueryChartName(chartType, queryItem.displayTitle),
|
|
1371
|
+
existingCharts
|
|
1372
|
+
);
|
|
1373
|
+
|
|
1374
|
+
return {
|
|
1375
|
+
mode: "create",
|
|
1376
|
+
chartId: null,
|
|
1377
|
+
queryHistoryId: queryItem.id,
|
|
1378
|
+
chartType,
|
|
1379
|
+
name,
|
|
1380
|
+
nameTouched: false,
|
|
1381
|
+
config: buildSuggestedChartConfig(chartType, analysis),
|
|
1382
|
+
tableVisible: true,
|
|
1383
|
+
resultColumns: buildQueryChartResultColumns(analysis),
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
function upsertChartDetailItem(updatedChart) {
|
|
1388
|
+
if (!updatedChart || state.charts.detail?.item?.id !== updatedChart.queryHistoryId) {
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
const nextCharts = [...(state.charts.detail?.charts ?? [])];
|
|
1393
|
+
const existingIndex = nextCharts.findIndex((chart) => chart.id === updatedChart.id);
|
|
1394
|
+
|
|
1395
|
+
if (existingIndex >= 0) {
|
|
1396
|
+
nextCharts.splice(existingIndex, 1, updatedChart);
|
|
1397
|
+
} else {
|
|
1398
|
+
nextCharts.push(updatedChart);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
nextCharts.sort((left, right) => {
|
|
1402
|
+
const leftTime = Date.parse(left.createdAt ?? "") || 0;
|
|
1403
|
+
const rightTime = Date.parse(right.createdAt ?? "") || 0;
|
|
1404
|
+
return leftTime - rightTime || left.id - right.id;
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
state.charts.detail = {
|
|
1408
|
+
...state.charts.detail,
|
|
1409
|
+
charts: nextCharts,
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
syncChartsQuerySummaryForHistory(updatedChart.queryHistoryId);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function removeChartDetailItem(chartId) {
|
|
1416
|
+
if (!state.charts.detail) {
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
state.charts.detail = {
|
|
1421
|
+
...state.charts.detail,
|
|
1422
|
+
charts: (state.charts.detail.charts ?? []).filter((chart) => chart.id !== Number(chartId)),
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
syncChartsQuerySummaryForHistory(state.charts.detail.item?.id);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
914
1428
|
export function getState() {
|
|
915
1429
|
return clone(state);
|
|
916
1430
|
}
|
|
@@ -1033,10 +1547,147 @@ export function openDeleteEditorRowModal(rowIndex) {
|
|
|
1033
1547
|
emitChange();
|
|
1034
1548
|
}
|
|
1035
1549
|
|
|
1550
|
+
export function openCreateQueryChartModal() {
|
|
1551
|
+
const draft = buildQueryChartDraft("create");
|
|
1552
|
+
|
|
1553
|
+
if (!state.charts.detail?.item || !state.charts.result) {
|
|
1554
|
+
pushToast("The selected query has no chartable result set yet.", "alert");
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
if (!draft) {
|
|
1559
|
+
pushToast("The chart editor could not be opened for this query.", "alert");
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
state.modal = {
|
|
1564
|
+
kind: "chart-editor",
|
|
1565
|
+
error: null,
|
|
1566
|
+
submitting: false,
|
|
1567
|
+
draft,
|
|
1568
|
+
};
|
|
1569
|
+
emitChange();
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
export function openEditQueryChartModal(chartId) {
|
|
1573
|
+
if (!state.charts.result) {
|
|
1574
|
+
pushToast("Reload the query result before editing this chart.", "alert");
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
const chart = state.charts.detail?.charts?.find((entry) => entry.id === Number(chartId)) ?? null;
|
|
1579
|
+
const draft = chart ? buildQueryChartDraft("edit", chart) : null;
|
|
1580
|
+
|
|
1581
|
+
if (!chart || !draft) {
|
|
1582
|
+
pushToast("The selected chart could not be loaded.", "alert");
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
state.modal = {
|
|
1587
|
+
kind: "chart-editor",
|
|
1588
|
+
error: null,
|
|
1589
|
+
submitting: false,
|
|
1590
|
+
draft,
|
|
1591
|
+
};
|
|
1592
|
+
emitChange();
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
export function openDeleteQueryChartModal(chartId) {
|
|
1596
|
+
const chart = state.charts.detail?.charts?.find((entry) => entry.id === Number(chartId)) ?? null;
|
|
1597
|
+
|
|
1598
|
+
if (!chart) {
|
|
1599
|
+
pushToast("The selected chart could not be loaded.", "alert");
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
state.modal = {
|
|
1604
|
+
kind: "delete-chart",
|
|
1605
|
+
chartId: chart.id,
|
|
1606
|
+
chartName: chart.name,
|
|
1607
|
+
error: null,
|
|
1608
|
+
submitting: false,
|
|
1609
|
+
};
|
|
1610
|
+
emitChange();
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1036
1613
|
export function closeModal() {
|
|
1037
1614
|
closeModalInternal();
|
|
1038
1615
|
}
|
|
1039
1616
|
|
|
1617
|
+
export function toggleChartsSqlPanel() {
|
|
1618
|
+
state.charts.sqlExpanded = !state.charts.sqlExpanded;
|
|
1619
|
+
emitChange();
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
export function toggleChartsResultsPanel() {
|
|
1623
|
+
state.charts.resultsVisible = !state.charts.resultsVisible;
|
|
1624
|
+
emitChange();
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
export function setChartsHeightPreset(preset) {
|
|
1628
|
+
const nextPreset = normalizeChartsHeightPreset(preset);
|
|
1629
|
+
|
|
1630
|
+
if (state.charts.chartHeightPreset === nextPreset) {
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
state.charts.chartHeightPreset = nextPreset;
|
|
1635
|
+
emitChange();
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
export function updateCurrentQueryChartDraftField(field, value) {
|
|
1639
|
+
if (state.modal?.kind !== "chart-editor" || !state.modal.draft) {
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
state.modal.error = null;
|
|
1644
|
+
|
|
1645
|
+
if (field === "name") {
|
|
1646
|
+
state.modal.draft.name = String(value ?? "");
|
|
1647
|
+
state.modal.draft.nameTouched = true;
|
|
1648
|
+
emitChange();
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
if (field === "chartType") {
|
|
1653
|
+
const nextType = String(value ?? "").trim().toLowerCase();
|
|
1654
|
+
const analysis = getChartsResultAnalysis();
|
|
1655
|
+
|
|
1656
|
+
state.modal.draft.chartType = nextType;
|
|
1657
|
+
state.modal.draft.config = buildSuggestedChartConfig(nextType, analysis);
|
|
1658
|
+
|
|
1659
|
+
if (!state.modal.draft.nameTouched) {
|
|
1660
|
+
const existingCharts = state.charts.detail?.charts ?? [];
|
|
1661
|
+
state.modal.draft.name = resolveUniqueQueryChartName(
|
|
1662
|
+
buildDefaultQueryChartName(nextType, state.charts.detail?.item?.displayTitle),
|
|
1663
|
+
existingCharts,
|
|
1664
|
+
state.modal.draft.chartId
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
emitChange();
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
if (field === "tableVisible") {
|
|
1673
|
+
state.modal.draft.tableVisible = Boolean(value);
|
|
1674
|
+
emitChange();
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
export function updateCurrentQueryChartDraftConfigField(field, value) {
|
|
1679
|
+
if (state.modal?.kind !== "chart-editor" || !state.modal.draft) {
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
state.modal.error = null;
|
|
1684
|
+
state.modal.draft.config = {
|
|
1685
|
+
...state.modal.draft.config,
|
|
1686
|
+
[field]: value,
|
|
1687
|
+
};
|
|
1688
|
+
emitChange();
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1040
1691
|
export function dismissToast(id) {
|
|
1041
1692
|
const nextToasts = state.toasts.filter((toast) => toast.id !== id);
|
|
1042
1693
|
|
|
@@ -1236,6 +1887,18 @@ export function clearEditorResults() {
|
|
|
1236
1887
|
emitChange();
|
|
1237
1888
|
}
|
|
1238
1889
|
|
|
1890
|
+
export function setEditorPanelVisibility(visible) {
|
|
1891
|
+
const nextValue =
|
|
1892
|
+
typeof visible === "boolean" ? visible : !Boolean(state.editor.editorPanelVisible);
|
|
1893
|
+
|
|
1894
|
+
if (state.editor.editorPanelVisible === nextValue) {
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
state.editor.editorPanelVisible = nextValue;
|
|
1899
|
+
emitChange();
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1239
1902
|
export function setEditorTab(tab) {
|
|
1240
1903
|
state.editor.activeTab = tab;
|
|
1241
1904
|
emitChange();
|
|
@@ -1306,6 +1969,18 @@ export async function setQueryHistoryTab(tab) {
|
|
|
1306
1969
|
await refreshQueryHistoryState();
|
|
1307
1970
|
}
|
|
1308
1971
|
|
|
1972
|
+
export function setQueryHistoryPanelVisibility(visible) {
|
|
1973
|
+
const nextValue =
|
|
1974
|
+
typeof visible === "boolean" ? visible : !Boolean(state.editor.historyPanelVisible);
|
|
1975
|
+
|
|
1976
|
+
if (state.editor.historyPanelVisible === nextValue) {
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
state.editor.historyPanelVisible = nextValue;
|
|
1981
|
+
emitChange();
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1309
1984
|
export function setQueryHistorySearchInput(query) {
|
|
1310
1985
|
state.editor.historySearchInput = String(query ?? "");
|
|
1311
1986
|
emitChange();
|
|
@@ -1450,6 +2125,125 @@ export async function selectStructureEntry(name) {
|
|
|
1450
2125
|
await loadStructureDetail(++routeLoadVersion);
|
|
1451
2126
|
}
|
|
1452
2127
|
|
|
2128
|
+
export function setTableDesignerSearchQuery(query) {
|
|
2129
|
+
state.tableDesigner.searchQuery = String(query ?? "");
|
|
2130
|
+
emitChange();
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
export function updateCurrentTableDesignerField(field, value) {
|
|
2134
|
+
if (!state.tableDesigner.draft) {
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
state.tableDesigner.draft = updateTableDesignerDraftField(
|
|
2139
|
+
state.tableDesigner.draft,
|
|
2140
|
+
field,
|
|
2141
|
+
value,
|
|
2142
|
+
getTableDesignerContext()
|
|
2143
|
+
);
|
|
2144
|
+
state.tableDesigner.saveError = null;
|
|
2145
|
+
emitChange();
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
export function updateCurrentTableDesignerColumnField(columnId, field, value) {
|
|
2149
|
+
if (!state.tableDesigner.draft) {
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
state.tableDesigner.draft = updateTableDesignerColumnField(
|
|
2154
|
+
state.tableDesigner.draft,
|
|
2155
|
+
columnId,
|
|
2156
|
+
field,
|
|
2157
|
+
value,
|
|
2158
|
+
getTableDesignerContext()
|
|
2159
|
+
);
|
|
2160
|
+
state.tableDesigner.saveError = null;
|
|
2161
|
+
emitChange();
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
export function addCurrentTableDesignerColumn() {
|
|
2165
|
+
if (!state.tableDesigner.draft) {
|
|
2166
|
+
return null;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
const previousColumnIds = new Set(state.tableDesigner.draft.columns.map((column) => column.id));
|
|
2170
|
+
state.tableDesigner.draft = addTableDesignerColumn(
|
|
2171
|
+
state.tableDesigner.draft,
|
|
2172
|
+
getTableDesignerContext()
|
|
2173
|
+
);
|
|
2174
|
+
state.tableDesigner.saveError = null;
|
|
2175
|
+
emitChange();
|
|
2176
|
+
|
|
2177
|
+
const nextColumn = state.tableDesigner.draft.columns.find(
|
|
2178
|
+
(column) => !previousColumnIds.has(column.id)
|
|
2179
|
+
);
|
|
2180
|
+
|
|
2181
|
+
return nextColumn?.id ?? null;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
export function queueTableDesignerCsvImport(fileName, csvText) {
|
|
2185
|
+
try {
|
|
2186
|
+
const imported = createTableDesignerDraftFromCsvImport(
|
|
2187
|
+
{ fileName, csvText },
|
|
2188
|
+
getTableDesignerContext()
|
|
2189
|
+
);
|
|
2190
|
+
|
|
2191
|
+
state.tableDesigner.pendingImportedDraft = imported.draft;
|
|
2192
|
+
state.tableDesigner.selectedTableName = null;
|
|
2193
|
+
state.tableDesigner.saveError = null;
|
|
2194
|
+
state.tableDesigner.error = null;
|
|
2195
|
+
emitChange();
|
|
2196
|
+
return imported;
|
|
2197
|
+
} catch (error) {
|
|
2198
|
+
pushToast(error?.message || "CSV import failed.", "alert");
|
|
2199
|
+
return null;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
export function removeCurrentTableDesignerColumn(columnId) {
|
|
2204
|
+
if (!state.tableDesigner.draft) {
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
state.tableDesigner.draft = removeTableDesignerColumn(
|
|
2209
|
+
state.tableDesigner.draft,
|
|
2210
|
+
columnId,
|
|
2211
|
+
getTableDesignerContext()
|
|
2212
|
+
);
|
|
2213
|
+
state.tableDesigner.saveError = null;
|
|
2214
|
+
emitChange();
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
export async function saveCurrentTableDesignerDraft() {
|
|
2218
|
+
if (!state.tableDesigner.draft) {
|
|
2219
|
+
return null;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
state.tableDesigner.saving = true;
|
|
2223
|
+
state.tableDesigner.saveError = null;
|
|
2224
|
+
emitChange();
|
|
2225
|
+
|
|
2226
|
+
try {
|
|
2227
|
+
const response = await api.saveTableDesignerDraft({
|
|
2228
|
+
draft: state.tableDesigner.draft,
|
|
2229
|
+
});
|
|
2230
|
+
|
|
2231
|
+
state.tableDesigner.tables = response.data?.tables ?? state.tableDesigner.tables;
|
|
2232
|
+
state.tableDesigner.selectedTableName =
|
|
2233
|
+
response.data?.savedTableName ?? state.tableDesigner.selectedTableName;
|
|
2234
|
+
state.tableDesigner.draft = decorateTableDesignerDraft(response.data?.draft ?? null);
|
|
2235
|
+
pushToast(response.message || "Table schema saved.", "success");
|
|
2236
|
+
return response.data?.savedTableName ?? null;
|
|
2237
|
+
} catch (error) {
|
|
2238
|
+
state.tableDesigner.saveError = normalizeError(error);
|
|
2239
|
+
emitChange();
|
|
2240
|
+
return null;
|
|
2241
|
+
} finally {
|
|
2242
|
+
state.tableDesigner.saving = false;
|
|
2243
|
+
emitChange();
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
|
|
1453
2247
|
export function selectDataRow(index) {
|
|
1454
2248
|
const numericIndex = Number(index);
|
|
1455
2249
|
|
|
@@ -1508,6 +2302,11 @@ export function setDataSearchColumn(columnName) {
|
|
|
1508
2302
|
emitChange();
|
|
1509
2303
|
}
|
|
1510
2304
|
|
|
2305
|
+
export function toggleDataTablesPanel() {
|
|
2306
|
+
state.dataBrowser.tablesVisible = state.dataBrowser.tablesVisible === false;
|
|
2307
|
+
emitChange();
|
|
2308
|
+
}
|
|
2309
|
+
|
|
1511
2310
|
export async function setDataPage(page) {
|
|
1512
2311
|
const numericPage = Number(page);
|
|
1513
2312
|
|
|
@@ -1770,6 +2569,116 @@ export async function submitDeleteRowConfirmation() {
|
|
|
1770
2569
|
return result;
|
|
1771
2570
|
}
|
|
1772
2571
|
|
|
2572
|
+
export async function saveCurrentQueryChartDraft() {
|
|
2573
|
+
if (state.modal?.kind !== "chart-editor" || !state.modal.draft) {
|
|
2574
|
+
return null;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
const analysis = getChartsResultAnalysis();
|
|
2578
|
+
|
|
2579
|
+
if (!analysis) {
|
|
2580
|
+
state.modal.error = {
|
|
2581
|
+
code: "RESULT_SET_REQUIRED",
|
|
2582
|
+
message: "Reload the query result before saving a chart.",
|
|
2583
|
+
};
|
|
2584
|
+
emitChange();
|
|
2585
|
+
return null;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
const validation = validateQueryChartConfig(
|
|
2589
|
+
state.modal.draft.chartType,
|
|
2590
|
+
state.modal.draft.config,
|
|
2591
|
+
analysis
|
|
2592
|
+
);
|
|
2593
|
+
|
|
2594
|
+
if (!validation.valid) {
|
|
2595
|
+
state.modal.error = {
|
|
2596
|
+
code: "VALIDATION_ERROR",
|
|
2597
|
+
message: validation.errors.join(" "),
|
|
2598
|
+
};
|
|
2599
|
+
emitChange();
|
|
2600
|
+
return null;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
startModalSubmission();
|
|
2604
|
+
const draftMode = state.modal.draft.mode;
|
|
2605
|
+
|
|
2606
|
+
const payload = {
|
|
2607
|
+
queryHistoryId: state.modal.draft.queryHistoryId,
|
|
2608
|
+
name: state.modal.draft.name,
|
|
2609
|
+
chartType: state.modal.draft.chartType,
|
|
2610
|
+
config: state.modal.draft.config,
|
|
2611
|
+
resultColumns: buildQueryChartResultColumns(analysis),
|
|
2612
|
+
tableVisible: state.modal.draft.tableVisible,
|
|
2613
|
+
};
|
|
2614
|
+
|
|
2615
|
+
try {
|
|
2616
|
+
const response =
|
|
2617
|
+
state.modal.draft.mode === "edit"
|
|
2618
|
+
? await api.updateQueryHistoryChart(state.modal.draft.chartId, payload)
|
|
2619
|
+
: await api.createQueryHistoryChart(payload);
|
|
2620
|
+
const chart = response.data ?? null;
|
|
2621
|
+
|
|
2622
|
+
if (chart) {
|
|
2623
|
+
upsertChartDetailItem(chart);
|
|
2624
|
+
closeModalInternal();
|
|
2625
|
+
pushToast(response.message || (draftMode === "edit" ? "Chart updated." : "Chart created."), "success");
|
|
2626
|
+
return chart;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
closeModalInternal();
|
|
2630
|
+
return null;
|
|
2631
|
+
} catch (error) {
|
|
2632
|
+
withModalError(error);
|
|
2633
|
+
return null;
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
export async function toggleQueryChartTableVisibility(chartId) {
|
|
2638
|
+
const chart = state.charts.detail?.charts?.find((entry) => entry.id === Number(chartId)) ?? null;
|
|
2639
|
+
|
|
2640
|
+
if (!chart) {
|
|
2641
|
+
pushToast("The selected chart could not be loaded.", "alert");
|
|
2642
|
+
return null;
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
try {
|
|
2646
|
+
const response = await api.updateQueryHistoryChart(chart.id, {
|
|
2647
|
+
tableVisible: !chart.tableVisible,
|
|
2648
|
+
});
|
|
2649
|
+
|
|
2650
|
+
upsertChartDetailItem(response.data ?? null);
|
|
2651
|
+
emitChange();
|
|
2652
|
+
return response.data ?? null;
|
|
2653
|
+
} catch (error) {
|
|
2654
|
+
pushToast(normalizeError(error)?.message || "Chart update failed.", "alert");
|
|
2655
|
+
return null;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
export async function deleteQueryChart(chartId) {
|
|
2660
|
+
try {
|
|
2661
|
+
const response = await api.deleteQueryHistoryChart(chartId);
|
|
2662
|
+
removeChartDetailItem(chartId);
|
|
2663
|
+
closeModalInternal();
|
|
2664
|
+
pushToast(response.message || "Chart deleted.", "muted");
|
|
2665
|
+
emitChange();
|
|
2666
|
+
return true;
|
|
2667
|
+
} catch (error) {
|
|
2668
|
+
withModalError(error);
|
|
2669
|
+
return false;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
export async function submitDeleteChartConfirmation() {
|
|
2674
|
+
if (state.modal?.kind !== "delete-chart") {
|
|
2675
|
+
return false;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
startModalSubmission();
|
|
2679
|
+
return deleteQueryChart(state.modal.chartId);
|
|
2680
|
+
}
|
|
2681
|
+
|
|
1773
2682
|
export async function exportCurrentQueryCsv() {
|
|
1774
2683
|
state.editor.exportLoading = true;
|
|
1775
2684
|
emitChange();
|
|
@@ -1845,6 +2754,10 @@ export async function refreshCurrentRoute() {
|
|
|
1845
2754
|
await loadRouteData(state.route);
|
|
1846
2755
|
}
|
|
1847
2756
|
|
|
2757
|
+
export function showToast(message, tone = "muted") {
|
|
2758
|
+
pushToast(message, tone);
|
|
2759
|
+
}
|
|
2760
|
+
|
|
1848
2761
|
export function getCurrentConnection(snapshot = state) {
|
|
1849
2762
|
return snapshot.connections.active;
|
|
1850
2763
|
}
|