sqlite-hub 0.6.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 +107 -0
- package/bin/sqlite-hub.js +285 -4
- package/changelog.md +7 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/index.html +1 -0
- package/frontend/js/api.js +34 -0
- package/frontend/js/app.js +99 -2
- package/frontend/js/components/modal.js +314 -1
- package/frontend/js/components/queryChartRenderer.js +125 -0
- package/frontend/js/components/queryEditor.js +35 -6
- package/frontend/js/components/queryHistoryPanel.js +16 -5
- package/frontend/js/components/sidebar.js +1 -0
- package/frontend/js/components/tableDesignerSidebar.js +41 -38
- package/frontend/js/lib/queryChartOptions.js +283 -0
- package/frontend/js/lib/queryCharts.js +560 -0
- package/frontend/js/router.js +8 -0
- package/frontend/js/store.js +618 -0
- package/frontend/js/views/charts.js +471 -0
- package/frontend/js/views/data.js +29 -15
- package/frontend/js/views/editor.js +19 -13
- package/frontend/js/views/structure.js +81 -39
- package/frontend/styles/components.css +5 -13
- package/frontend/styles/views.css +296 -66
- package/package.json +2 -1
- package/server/routes/charts.js +153 -0
- package/server/server.js +6 -0
- package/server/services/storage/appStateStore.js +400 -1
- package/server/services/storage/queryHistoryChartUtils.js +145 -0
package/frontend/js/store.js
CHANGED
|
@@ -9,6 +9,15 @@ import {
|
|
|
9
9
|
updateTableDesignerColumnField,
|
|
10
10
|
updateTableDesignerDraftField,
|
|
11
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";
|
|
12
21
|
|
|
13
22
|
const listeners = new Set();
|
|
14
23
|
const DEFAULT_SETTINGS = {
|
|
@@ -19,6 +28,7 @@ const DEFAULT_SETTINGS = {
|
|
|
19
28
|
const DATA_PAGE_SIZES = [25, 50, 100];
|
|
20
29
|
const QUERY_HISTORY_PAGE_SIZE = 30;
|
|
21
30
|
const QUERY_HISTORY_RUN_LIMIT = 8;
|
|
31
|
+
const CHART_HEIGHT_PRESETS = new Set(["small", "medium", "large"]);
|
|
22
32
|
const MISSING_DATABASE_ERROR = {
|
|
23
33
|
code: "ACTIVE_DATABASE_REQUIRED",
|
|
24
34
|
message: "No active SQLite database selected.",
|
|
@@ -28,6 +38,8 @@ let routeLoadVersion = 0;
|
|
|
28
38
|
let queryHistoryLoadVersion = 0;
|
|
29
39
|
let queryHistoryDetailLoadVersion = 0;
|
|
30
40
|
let queryHistorySearchTimer = null;
|
|
41
|
+
let chartsLoadVersion = 0;
|
|
42
|
+
let chartsDetailLoadVersion = 0;
|
|
31
43
|
|
|
32
44
|
const state = {
|
|
33
45
|
ready: false,
|
|
@@ -55,6 +67,7 @@ const state = {
|
|
|
55
67
|
dataBrowser: {
|
|
56
68
|
tables: [],
|
|
57
69
|
selectedTable: null,
|
|
70
|
+
tablesVisible: true,
|
|
58
71
|
table: null,
|
|
59
72
|
loading: false,
|
|
60
73
|
tableLoading: false,
|
|
@@ -73,7 +86,9 @@ const state = {
|
|
|
73
86
|
},
|
|
74
87
|
editor: {
|
|
75
88
|
sqlText: "",
|
|
89
|
+
editorPanelVisible: true,
|
|
76
90
|
history: [],
|
|
91
|
+
historyPanelVisible: true,
|
|
77
92
|
historyLoading: false,
|
|
78
93
|
historyLoadingMore: false,
|
|
79
94
|
historyError: null,
|
|
@@ -101,6 +116,21 @@ const state = {
|
|
|
101
116
|
deleting: false,
|
|
102
117
|
saveError: null,
|
|
103
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
|
+
},
|
|
104
134
|
tableDesigner: {
|
|
105
135
|
tables: [],
|
|
106
136
|
selectedTableName: null,
|
|
@@ -172,6 +202,7 @@ function requiresActiveDatabase(routeName) {
|
|
|
172
202
|
"data",
|
|
173
203
|
"editor",
|
|
174
204
|
"editorResults",
|
|
205
|
+
"charts",
|
|
175
206
|
"structure",
|
|
176
207
|
"tableDesigner",
|
|
177
208
|
].includes(routeName);
|
|
@@ -375,6 +406,52 @@ function resetQueryHistoryState({ preserveSearch = true } = {}) {
|
|
|
375
406
|
}
|
|
376
407
|
}
|
|
377
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
|
+
|
|
378
455
|
function syncQueryHistoryItem(updatedItem) {
|
|
379
456
|
if (!updatedItem) {
|
|
380
457
|
return;
|
|
@@ -387,6 +464,61 @@ function syncQueryHistoryItem(updatedItem) {
|
|
|
387
464
|
if (state.editor.historyDetail?.id === updatedItem.id) {
|
|
388
465
|
state.editor.historyDetail = updatedItem;
|
|
389
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
|
+
};
|
|
390
522
|
}
|
|
391
523
|
|
|
392
524
|
function resolveQueryHistorySql(historyId) {
|
|
@@ -396,6 +528,10 @@ function resolveQueryHistorySql(historyId) {
|
|
|
396
528
|
return state.editor.historyDetail.rawSql;
|
|
397
529
|
}
|
|
398
530
|
|
|
531
|
+
if (String(state.charts.detail?.item?.id ?? "") === historyIdAsString) {
|
|
532
|
+
return state.charts.detail.item.rawSql;
|
|
533
|
+
}
|
|
534
|
+
|
|
399
535
|
return findQueryHistoryItem(historyId)?.rawSql ?? null;
|
|
400
536
|
}
|
|
401
537
|
|
|
@@ -403,6 +539,9 @@ function clearRouteSlices() {
|
|
|
403
539
|
state.overview.error = null;
|
|
404
540
|
state.dataBrowser.error = null;
|
|
405
541
|
state.dataBrowser.saveError = null;
|
|
542
|
+
state.charts.error = null;
|
|
543
|
+
state.charts.detailError = null;
|
|
544
|
+
state.charts.resultError = null;
|
|
406
545
|
state.tableDesigner.error = null;
|
|
407
546
|
state.tableDesigner.saveError = null;
|
|
408
547
|
state.structure.error = null;
|
|
@@ -446,6 +585,9 @@ function setMissingDatabaseState() {
|
|
|
446
585
|
state.tableDesigner.error = error;
|
|
447
586
|
state.tableDesigner.saveError = null;
|
|
448
587
|
|
|
588
|
+
resetChartsState();
|
|
589
|
+
state.charts.error = error;
|
|
590
|
+
|
|
449
591
|
resetQueryHistoryState({ preserveSearch: false });
|
|
450
592
|
}
|
|
451
593
|
|
|
@@ -658,6 +800,114 @@ async function refreshQueryHistoryState({ append = false } = {}) {
|
|
|
658
800
|
}
|
|
659
801
|
}
|
|
660
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
|
+
|
|
661
911
|
async function loadOverview(version) {
|
|
662
912
|
state.overview.loading = true;
|
|
663
913
|
state.overview.error = null;
|
|
@@ -1004,6 +1254,7 @@ function invalidateDatabaseCaches() {
|
|
|
1004
1254
|
state.tableDesigner.supportedTypes = [];
|
|
1005
1255
|
state.tableDesigner.error = null;
|
|
1006
1256
|
state.tableDesigner.saveError = null;
|
|
1257
|
+
resetChartsState();
|
|
1007
1258
|
state.structure.data = null;
|
|
1008
1259
|
state.structure.detail = null;
|
|
1009
1260
|
}
|
|
@@ -1031,6 +1282,9 @@ async function loadRouteData(route) {
|
|
|
1031
1282
|
case "data":
|
|
1032
1283
|
await loadData(version, route);
|
|
1033
1284
|
return;
|
|
1285
|
+
case "charts":
|
|
1286
|
+
await loadCharts(version, route);
|
|
1287
|
+
return;
|
|
1034
1288
|
case "editor":
|
|
1035
1289
|
case "editorResults":
|
|
1036
1290
|
await refreshQueryHistoryState();
|
|
@@ -1083,6 +1337,94 @@ function closeModalInternal() {
|
|
|
1083
1337
|
emitChange();
|
|
1084
1338
|
}
|
|
1085
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
|
+
|
|
1086
1428
|
export function getState() {
|
|
1087
1429
|
return clone(state);
|
|
1088
1430
|
}
|
|
@@ -1205,10 +1547,147 @@ export function openDeleteEditorRowModal(rowIndex) {
|
|
|
1205
1547
|
emitChange();
|
|
1206
1548
|
}
|
|
1207
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
|
+
|
|
1208
1613
|
export function closeModal() {
|
|
1209
1614
|
closeModalInternal();
|
|
1210
1615
|
}
|
|
1211
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
|
+
|
|
1212
1691
|
export function dismissToast(id) {
|
|
1213
1692
|
const nextToasts = state.toasts.filter((toast) => toast.id !== id);
|
|
1214
1693
|
|
|
@@ -1408,6 +1887,18 @@ export function clearEditorResults() {
|
|
|
1408
1887
|
emitChange();
|
|
1409
1888
|
}
|
|
1410
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
|
+
|
|
1411
1902
|
export function setEditorTab(tab) {
|
|
1412
1903
|
state.editor.activeTab = tab;
|
|
1413
1904
|
emitChange();
|
|
@@ -1478,6 +1969,18 @@ export async function setQueryHistoryTab(tab) {
|
|
|
1478
1969
|
await refreshQueryHistoryState();
|
|
1479
1970
|
}
|
|
1480
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
|
+
|
|
1481
1984
|
export function setQueryHistorySearchInput(query) {
|
|
1482
1985
|
state.editor.historySearchInput = String(query ?? "");
|
|
1483
1986
|
emitChange();
|
|
@@ -1799,6 +2302,11 @@ export function setDataSearchColumn(columnName) {
|
|
|
1799
2302
|
emitChange();
|
|
1800
2303
|
}
|
|
1801
2304
|
|
|
2305
|
+
export function toggleDataTablesPanel() {
|
|
2306
|
+
state.dataBrowser.tablesVisible = state.dataBrowser.tablesVisible === false;
|
|
2307
|
+
emitChange();
|
|
2308
|
+
}
|
|
2309
|
+
|
|
1802
2310
|
export async function setDataPage(page) {
|
|
1803
2311
|
const numericPage = Number(page);
|
|
1804
2312
|
|
|
@@ -2061,6 +2569,116 @@ export async function submitDeleteRowConfirmation() {
|
|
|
2061
2569
|
return result;
|
|
2062
2570
|
}
|
|
2063
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
|
+
|
|
2064
2682
|
export async function exportCurrentQueryCsv() {
|
|
2065
2683
|
state.editor.exportLoading = true;
|
|
2066
2684
|
emitChange();
|