sqlite-hub 1.0.0 → 1.1.1
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 +47 -33
- package/bin/sqlite-hub.js +127 -47
- package/frontend/js/api.js +6 -0
- package/frontend/js/app.js +229 -44
- package/frontend/js/components/modal.js +17 -1
- package/frontend/js/components/queryChartRenderer.js +28 -3
- package/frontend/js/components/queryEditor.js +24 -26
- package/frontend/js/components/queryHistoryDetail.js +1 -1
- package/frontend/js/components/queryHistoryHeader.js +48 -0
- package/frontend/js/components/queryHistoryList.js +132 -0
- package/frontend/js/components/queryHistoryPanel.js +72 -136
- package/frontend/js/components/structureGraph.js +700 -89
- package/frontend/js/components/tableDesignerEditor.js +26 -47
- package/frontend/js/components/tableDesignerSidebar.js +7 -31
- package/frontend/js/components/tableDesignerSqlPreview.js +2 -1
- package/frontend/js/store.js +97 -8
- package/frontend/js/utils/exportFilenames.js +3 -1
- package/frontend/js/views/charts.js +326 -174
- package/frontend/js/views/connections.js +59 -64
- package/frontend/js/views/data.js +48 -54
- package/frontend/js/views/documents.js +62 -23
- package/frontend/js/views/editor.js +0 -2
- package/frontend/js/views/mediaTagging.js +6 -5
- package/frontend/js/views/settings.js +78 -0
- package/frontend/js/views/structure.js +48 -32
- package/frontend/js/views/tableDesigner.js +45 -1
- package/frontend/styles/components.css +259 -54
- package/frontend/styles/structure-graph.css +150 -37
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/tokens.css +2 -0
- package/frontend/styles/views.css +75 -38
- package/package.json +3 -2
- package/server/routes/export.js +89 -22
- package/server/routes/externalApi.js +84 -2
- package/server/routes/settings.js +37 -28
- package/server/services/appInfoService.js +215 -0
- package/server/services/databaseCommandService.js +50 -6
- package/server/services/sqlite/dataBrowserService.js +11 -3
- package/server/services/sqlite/exportService.js +307 -22
- package/tests/api-token-auth.test.js +110 -1
- package/tests/cli-args.test.js +16 -3
- package/tests/database-command-service.test.js +40 -3
- package/tests/export-blob.test.js +54 -1
- package/tests/export-filenames.test.js +4 -0
- package/tests/settings-api-tokens-route.test.js +22 -1
- package/tests/settings-metadata.test.js +99 -1
- package/tests/settings-view.test.js +28 -0
package/frontend/js/app.js
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
clearEditorResults,
|
|
36
36
|
clearQueryHistorySelection,
|
|
37
37
|
closeModal,
|
|
38
|
+
checkSettingsAppVersion,
|
|
38
39
|
dismissMediaTaggingIssue,
|
|
39
40
|
dismissToast,
|
|
40
41
|
executeCurrentQuery,
|
|
@@ -82,8 +83,10 @@ import {
|
|
|
82
83
|
selectConnection,
|
|
83
84
|
selectQueryHistoryItem,
|
|
84
85
|
selectStructureEntry,
|
|
86
|
+
setDocumentsSearchQuery,
|
|
85
87
|
setTableDesignerSearchQuery,
|
|
86
88
|
setTableDesignerSqlPreviewVisibility,
|
|
89
|
+
toggleTableDesignerTablesPanel,
|
|
87
90
|
setDataTableSearchQuery,
|
|
88
91
|
setStructureTableSearchQuery,
|
|
89
92
|
toggleStructureTablesPanel,
|
|
@@ -95,7 +98,9 @@ import {
|
|
|
95
98
|
toggleDataTablesPanel,
|
|
96
99
|
setCurrentQuery,
|
|
97
100
|
setChartsHeightPreset,
|
|
101
|
+
setChartsDetailPanelVisibility,
|
|
98
102
|
setChartsHistoryTab,
|
|
103
|
+
setChartsHistorySearchInput,
|
|
99
104
|
setChartsHistoryPanelVisibility,
|
|
100
105
|
setEditorPanelVisibility,
|
|
101
106
|
setEditorTab,
|
|
@@ -222,6 +227,7 @@ let lastRenderedPanelMarkup = '';
|
|
|
222
227
|
let lastRenderedModalMarkup = '';
|
|
223
228
|
let lastRenderedToastMarkup = '';
|
|
224
229
|
let lastRenderedChartsHistorySignature = '';
|
|
230
|
+
let lastRenderedChartsCardSignature = '';
|
|
225
231
|
let lastRenderedPanelOpen = false;
|
|
226
232
|
let lastRenderedLockedRoute = false;
|
|
227
233
|
let pendingNewTableDesignerAutofocus = false;
|
|
@@ -372,21 +378,19 @@ function syncMediaTaggingCurrentMediaUi(button, detailsVisible) {
|
|
|
372
378
|
}
|
|
373
379
|
|
|
374
380
|
const nextVisible = Boolean(detailsVisible);
|
|
375
|
-
const expandedLabel = button.dataset.expandedLabel || '
|
|
376
|
-
const collapsedLabel = button.dataset.collapsedLabel || 'Show
|
|
381
|
+
const expandedLabel = button.dataset.expandedLabel || 'Hide Viewer';
|
|
382
|
+
const collapsedLabel = button.dataset.collapsedLabel || 'Show Viewer';
|
|
377
383
|
preview.classList.toggle('media-tagging-preview--meta-hidden', !nextVisible);
|
|
384
|
+
button.classList.toggle('is-active', !nextVisible);
|
|
378
385
|
button.dataset.nextValue = nextVisible ? 'false' : 'true';
|
|
379
386
|
button.setAttribute('aria-expanded', nextVisible ? 'true' : 'false');
|
|
387
|
+
button.setAttribute('aria-pressed', nextVisible ? 'false' : 'true');
|
|
380
388
|
|
|
381
|
-
|
|
382
|
-
const icon = document.createElement('span');
|
|
389
|
+
const icon = document.createElement('span');
|
|
383
390
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
} else {
|
|
388
|
-
button.textContent = collapsedLabel;
|
|
389
|
-
}
|
|
391
|
+
icon.className = 'material-symbols-outlined';
|
|
392
|
+
icon.textContent = nextVisible ? 'visibility_off' : 'visibility';
|
|
393
|
+
button.replaceChildren(icon, document.createTextNode(` ${nextVisible ? expandedLabel : collapsedLabel}`));
|
|
390
394
|
|
|
391
395
|
return true;
|
|
392
396
|
}
|
|
@@ -714,7 +718,11 @@ function buildChartsHistorySignature(state) {
|
|
|
714
718
|
}
|
|
715
719
|
|
|
716
720
|
return JSON.stringify({
|
|
721
|
+
detailPanelVisible: Boolean(state.charts.detailPanelVisible),
|
|
722
|
+
detailPanelHistoryId: state.charts.detailPanelVisible ? state.charts.selectedHistoryId : null,
|
|
717
723
|
tab: state.charts.historyTab ?? 'recent',
|
|
724
|
+
searchInput: state.charts.historySearchInput ?? '',
|
|
725
|
+
search: state.charts.historySearch ?? '',
|
|
718
726
|
queries: queries.map(item => [
|
|
719
727
|
item.id,
|
|
720
728
|
item.displayTitle,
|
|
@@ -725,6 +733,46 @@ function buildChartsHistorySignature(state) {
|
|
|
725
733
|
});
|
|
726
734
|
}
|
|
727
735
|
|
|
736
|
+
const chartSignatureObjectIds = new WeakMap();
|
|
737
|
+
let nextChartSignatureObjectId = 1;
|
|
738
|
+
|
|
739
|
+
function getChartSignatureObjectId(value) {
|
|
740
|
+
if (!value || typeof value !== 'object') {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (!chartSignatureObjectIds.has(value)) {
|
|
745
|
+
chartSignatureObjectIds.set(value, nextChartSignatureObjectId);
|
|
746
|
+
nextChartSignatureObjectId += 1;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return chartSignatureObjectIds.get(value);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function buildChartsCardSignature(state) {
|
|
753
|
+
if (state.route.name !== 'charts' || !state.charts.selectedHistoryId || !state.charts.detail?.item) {
|
|
754
|
+
return '';
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const charts = state.charts.detail?.charts ?? [];
|
|
758
|
+
|
|
759
|
+
return JSON.stringify({
|
|
760
|
+
selectedHistoryId: state.charts.selectedHistoryId,
|
|
761
|
+
chartHeightPreset: state.charts.chartHeightPreset ?? 'medium',
|
|
762
|
+
resultObjectId: getChartSignatureObjectId(state.charts.result),
|
|
763
|
+
resultLoading: Boolean(state.charts.resultLoading),
|
|
764
|
+
resultError: state.charts.resultError
|
|
765
|
+
? [state.charts.resultError.code ?? '', state.charts.resultError.message ?? '']
|
|
766
|
+
: null,
|
|
767
|
+
charts: charts.map(chart => [
|
|
768
|
+
chart.id,
|
|
769
|
+
chart.name,
|
|
770
|
+
chart.chartType,
|
|
771
|
+
JSON.stringify(chart.config ?? {}),
|
|
772
|
+
]),
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
728
776
|
function syncChartsHistorySelectionUi(state) {
|
|
729
777
|
const selectedHistoryId = String(state.charts.selectedHistoryId ?? '');
|
|
730
778
|
const historyButtons = shellRefs.view.querySelectorAll(
|
|
@@ -738,56 +786,123 @@ function syncChartsHistorySelectionUi(state) {
|
|
|
738
786
|
|
|
739
787
|
const itemNode = button.closest('[data-charts-history-item]');
|
|
740
788
|
const isSelected = button.dataset.historyId === selectedHistoryId;
|
|
741
|
-
const titleNode = button.querySelector('[data-charts-history-title]');
|
|
742
789
|
const selectionNode = itemNode instanceof HTMLElement ? itemNode : button;
|
|
743
790
|
|
|
744
|
-
selectionNode.classList.toggle('
|
|
745
|
-
|
|
746
|
-
selectionNode.classList.toggle('border-outline-variant/10', !isSelected);
|
|
747
|
-
selectionNode.classList.toggle('bg-surface-container-lowest', !isSelected);
|
|
748
|
-
selectionNode.classList.toggle('hover:bg-surface-container-high', !isSelected);
|
|
749
|
-
|
|
750
|
-
if (titleNode instanceof HTMLElement) {
|
|
751
|
-
titleNode.classList.toggle('text-primary-container', isSelected);
|
|
752
|
-
titleNode.classList.toggle('text-on-surface', !isSelected);
|
|
753
|
-
}
|
|
791
|
+
selectionNode.classList.toggle('is-active', isSelected);
|
|
792
|
+
button.classList.toggle('is-active', isSelected);
|
|
754
793
|
}
|
|
755
794
|
|
|
756
795
|
return true;
|
|
757
796
|
}
|
|
758
797
|
|
|
759
|
-
function
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
actionNode.dataset.nextValue = nextValue ? 'false' : 'true';
|
|
764
|
-
actionNode.title = nextValue ? 'Remove from saved' : 'Save query';
|
|
798
|
+
function syncChartsSavedToggleButtonUi(button, nextValue) {
|
|
799
|
+
button.classList.toggle('is-active', nextValue);
|
|
800
|
+
button.dataset.nextValue = nextValue ? 'false' : 'true';
|
|
801
|
+
button.title = nextValue ? 'Remove from saved' : 'Save query';
|
|
765
802
|
|
|
766
|
-
const iconNode =
|
|
803
|
+
const iconNode = button.querySelector('.material-symbols-outlined');
|
|
767
804
|
if (iconNode instanceof HTMLElement) {
|
|
768
805
|
iconNode.textContent = nextValue ? 'bookmark' : 'bookmark_add';
|
|
769
806
|
}
|
|
770
807
|
|
|
771
|
-
|
|
772
|
-
|
|
808
|
+
const labelNode = button.querySelector('[data-charts-saved-label]');
|
|
809
|
+
if (labelNode instanceof HTMLElement) {
|
|
810
|
+
labelNode.textContent = nextValue ? 'Unsave' : 'Save';
|
|
811
|
+
}
|
|
812
|
+
}
|
|
773
813
|
|
|
774
|
-
|
|
814
|
+
function syncChartsSavedToggleUi(actionNode, nextValue) {
|
|
815
|
+
const historyId = actionNode.dataset.historyId;
|
|
816
|
+
const relatedButtons = [
|
|
817
|
+
actionNode,
|
|
818
|
+
...shellRefs.view.querySelectorAll('[data-action="toggle-charts-query-history-saved"][data-history-id]'),
|
|
819
|
+
...shellRefs.panel.querySelectorAll('[data-action="toggle-charts-query-history-saved"][data-history-id]'),
|
|
820
|
+
].filter((button, index, buttons) => {
|
|
821
|
+
return (
|
|
822
|
+
button instanceof HTMLElement &&
|
|
823
|
+
button.dataset.historyId === historyId &&
|
|
824
|
+
buttons.indexOf(button) === index
|
|
825
|
+
);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
for (const button of relatedButtons) {
|
|
829
|
+
syncChartsSavedToggleButtonUi(button, nextValue);
|
|
830
|
+
|
|
831
|
+
const itemNode = button.closest('[data-charts-history-item]');
|
|
832
|
+
const historyTab = getState().charts.historyTab;
|
|
833
|
+
if (
|
|
834
|
+
itemNode instanceof HTMLElement &&
|
|
835
|
+
((historyTab === 'saved' && !nextValue) || (historyTab === 'unsaved' && nextValue))
|
|
836
|
+
) {
|
|
775
837
|
itemNode.remove();
|
|
776
838
|
}
|
|
777
839
|
}
|
|
778
840
|
|
|
841
|
+
for (const badgeNode of [
|
|
842
|
+
...shellRefs.view.querySelectorAll('[data-charts-saved-badge][data-history-id]'),
|
|
843
|
+
...shellRefs.panel.querySelectorAll('[data-charts-saved-badge][data-history-id]'),
|
|
844
|
+
]) {
|
|
845
|
+
if (badgeNode instanceof HTMLElement && badgeNode.dataset.historyId === historyId) {
|
|
846
|
+
badgeNode.toggleAttribute('hidden', !nextValue);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
779
850
|
const countNode = shellRefs.view.querySelector('[data-charts-history-count]');
|
|
780
851
|
if (countNode instanceof HTMLElement) {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
852
|
+
countNode.textContent = String(shellRefs.view.querySelectorAll('[data-charts-history-item]').length);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function renderChartsMainIntoScratch(state) {
|
|
857
|
+
const scratch = document.createElement('div');
|
|
858
|
+
|
|
859
|
+
replaceChildrenFromRenderedMarkup(scratch, renderChartsView(state).main);
|
|
860
|
+
return scratch;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function patchChartsHistoryUi(state) {
|
|
864
|
+
const chartsView = shellRefs.view.querySelector('.charts-view');
|
|
865
|
+
|
|
866
|
+
if (!(chartsView instanceof HTMLElement)) {
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const currentSidebar = chartsView.querySelector('.charts-view__sidebar');
|
|
871
|
+
const scratch = renderChartsMainIntoScratch(state);
|
|
872
|
+
const nextSidebar = scratch.querySelector('.charts-view__sidebar');
|
|
873
|
+
|
|
874
|
+
if (!currentSidebar && !nextSidebar) {
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (!(currentSidebar instanceof HTMLElement) || !(nextSidebar instanceof HTMLElement)) {
|
|
879
|
+
return false;
|
|
787
880
|
}
|
|
881
|
+
|
|
882
|
+
currentSidebar.replaceWith(nextSidebar);
|
|
883
|
+
return true;
|
|
788
884
|
}
|
|
789
885
|
|
|
790
|
-
function
|
|
886
|
+
function renderChartsDetailIntoScratch(state) {
|
|
887
|
+
const scratch = document.createElement('div');
|
|
888
|
+
|
|
889
|
+
replaceChildrenFromRenderedMarkup(scratch, renderChartsDetail(state));
|
|
890
|
+
return scratch;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function replaceChartsDetailSection(detailNode, scratchNode, selector) {
|
|
894
|
+
const currentNode = detailNode.querySelector(selector);
|
|
895
|
+
const nextNode = scratchNode.querySelector(selector);
|
|
896
|
+
|
|
897
|
+
if (!(currentNode instanceof HTMLElement) || !(nextNode instanceof HTMLElement)) {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
currentNode.replaceWith(nextNode);
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function patchChartsDetailUi(state, { preserveCharts = false } = {}) {
|
|
791
906
|
const chartsView = shellRefs.view.querySelector('.charts-view');
|
|
792
907
|
const detailNode = chartsView?.querySelector('.charts-view__detail');
|
|
793
908
|
|
|
@@ -802,7 +917,17 @@ function patchChartsDetailUi(state) {
|
|
|
802
917
|
return false;
|
|
803
918
|
}
|
|
804
919
|
|
|
805
|
-
|
|
920
|
+
if (preserveCharts) {
|
|
921
|
+
const scratch = renderChartsDetailIntoScratch(state);
|
|
922
|
+
const patched = replaceChartsDetailSection(detailNode, scratch, '[data-charts-detail-header]');
|
|
923
|
+
|
|
924
|
+
if (!patched) {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
} else {
|
|
928
|
+
replaceChildrenFromRenderedMarkup(detailNode, renderChartsDetail(state));
|
|
929
|
+
}
|
|
930
|
+
|
|
806
931
|
syncChartsHistorySelectionUi(state);
|
|
807
932
|
return true;
|
|
808
933
|
}
|
|
@@ -1228,6 +1353,7 @@ function renderApp(state) {
|
|
|
1228
1353
|
const modalMarkup = renderModal(state);
|
|
1229
1354
|
const toastMarkup = renderToasts(state.toasts);
|
|
1230
1355
|
const chartsHistorySignature = buildChartsHistorySignature(state);
|
|
1356
|
+
const chartsCardSignature = buildChartsCardSignature(state);
|
|
1231
1357
|
const isLockedRoute = [
|
|
1232
1358
|
'editor',
|
|
1233
1359
|
'editorResults',
|
|
@@ -1247,6 +1373,7 @@ function renderApp(state) {
|
|
|
1247
1373
|
const panelChanged = panel !== lastRenderedPanelMarkup;
|
|
1248
1374
|
const modalChanged = modalMarkup !== lastRenderedModalMarkup;
|
|
1249
1375
|
const chartsHistoryChanged = chartsHistorySignature !== lastRenderedChartsHistorySignature;
|
|
1376
|
+
const chartsCardsChanged = chartsCardSignature !== lastRenderedChartsCardSignature;
|
|
1250
1377
|
const panelOpenChanged = panelOpen !== lastRenderedPanelOpen;
|
|
1251
1378
|
const lockedRouteChanged = isLockedRoute !== lastRenderedLockedRoute;
|
|
1252
1379
|
const shellMarkupUnchanged =
|
|
@@ -1286,12 +1413,25 @@ function renderApp(state) {
|
|
|
1286
1413
|
}
|
|
1287
1414
|
|
|
1288
1415
|
const canPatchChartsMain =
|
|
1289
|
-
mainChanged && previousRouteName === 'charts' && state.route.name === 'charts'
|
|
1416
|
+
mainChanged && previousRouteName === 'charts' && state.route.name === 'charts';
|
|
1290
1417
|
let mainPatched = false;
|
|
1418
|
+
let preservedChartsDom = false;
|
|
1291
1419
|
|
|
1292
1420
|
if (canPatchChartsMain) {
|
|
1293
|
-
|
|
1294
|
-
|
|
1421
|
+
const historyPatched = !chartsHistoryChanged || patchChartsHistoryUi(state);
|
|
1422
|
+
|
|
1423
|
+
if (historyPatched) {
|
|
1424
|
+
if (chartsCardsChanged) {
|
|
1425
|
+
teardownQueryChartRenderer();
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
preservedChartsDom = !chartsCardsChanged;
|
|
1429
|
+
mainPatched = patchChartsDetailUi(state, { preserveCharts: preservedChartsDom });
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
if (!mainPatched) {
|
|
1433
|
+
preservedChartsDom = false;
|
|
1434
|
+
}
|
|
1295
1435
|
}
|
|
1296
1436
|
|
|
1297
1437
|
if (mainChanged) {
|
|
@@ -1387,6 +1527,7 @@ function renderApp(state) {
|
|
|
1387
1527
|
lastRenderedModalMarkup = modalMarkup;
|
|
1388
1528
|
lastRenderedToastMarkup = toastMarkup;
|
|
1389
1529
|
lastRenderedChartsHistorySignature = chartsHistorySignature;
|
|
1530
|
+
lastRenderedChartsCardSignature = chartsCardSignature;
|
|
1390
1531
|
lastRenderedPanelOpen = panelOpen;
|
|
1391
1532
|
lastRenderedLockedRoute = isLockedRoute;
|
|
1392
1533
|
|
|
@@ -1396,7 +1537,7 @@ function renderApp(state) {
|
|
|
1396
1537
|
});
|
|
1397
1538
|
}
|
|
1398
1539
|
|
|
1399
|
-
if (state.route.name === 'charts') {
|
|
1540
|
+
if (state.route.name === 'charts' && !preservedChartsDom) {
|
|
1400
1541
|
mountQueryChartRenderer(state);
|
|
1401
1542
|
}
|
|
1402
1543
|
}
|
|
@@ -2099,6 +2240,9 @@ async function handleAction(actionNode) {
|
|
|
2099
2240
|
case 'set-settings-section':
|
|
2100
2241
|
setSettingsSection(actionNode.dataset.section);
|
|
2101
2242
|
return;
|
|
2243
|
+
case 'check-app-version':
|
|
2244
|
+
await checkSettingsAppVersion();
|
|
2245
|
+
return;
|
|
2102
2246
|
case 'open-delete-api-token-modal':
|
|
2103
2247
|
openDeleteSettingsApiTokenModal(actionNode.dataset.tokenId);
|
|
2104
2248
|
return;
|
|
@@ -2387,7 +2531,12 @@ async function handleAction(actionNode) {
|
|
|
2387
2531
|
await loadMoreQueryHistory();
|
|
2388
2532
|
return;
|
|
2389
2533
|
case 'open-query-history':
|
|
2390
|
-
if (
|
|
2534
|
+
if (
|
|
2535
|
+
actionNode.dataset.historyId &&
|
|
2536
|
+
openQueryHistoryInEditor(actionNode.dataset.historyId, {
|
|
2537
|
+
notify: getState().route.name !== 'charts',
|
|
2538
|
+
})
|
|
2539
|
+
) {
|
|
2391
2540
|
router.navigate('/editor');
|
|
2392
2541
|
}
|
|
2393
2542
|
return;
|
|
@@ -2420,6 +2569,17 @@ async function handleAction(actionNode) {
|
|
|
2420
2569
|
}
|
|
2421
2570
|
}
|
|
2422
2571
|
return;
|
|
2572
|
+
case 'open-charts-query-detail':
|
|
2573
|
+
if (actionNode.dataset.historyId) {
|
|
2574
|
+
setChartsDetailPanelVisibility(true);
|
|
2575
|
+
if (getState().route.params?.historyId !== actionNode.dataset.historyId) {
|
|
2576
|
+
router.navigate(`/charts/${encodeURIComponent(actionNode.dataset.historyId)}`);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
return;
|
|
2580
|
+
case 'close-charts-query-detail':
|
|
2581
|
+
setChartsDetailPanelVisibility(false);
|
|
2582
|
+
return;
|
|
2423
2583
|
case 'toggle-charts-sql-panel':
|
|
2424
2584
|
toggleChartsSqlPanel();
|
|
2425
2585
|
return;
|
|
@@ -2496,6 +2656,9 @@ async function handleAction(actionNode) {
|
|
|
2496
2656
|
case 'toggle-structure-tables':
|
|
2497
2657
|
toggleStructureTablesPanel();
|
|
2498
2658
|
return;
|
|
2659
|
+
case 'toggle-table-designer-tables':
|
|
2660
|
+
toggleTableDesignerTablesPanel();
|
|
2661
|
+
return;
|
|
2499
2662
|
case 'select-structure-entry':
|
|
2500
2663
|
if (actionNode.dataset.entryName) {
|
|
2501
2664
|
await selectStructureEntry(actionNode.dataset.entryName);
|
|
@@ -2829,6 +2992,18 @@ document.addEventListener('keydown', event => {
|
|
|
2829
2992
|
return;
|
|
2830
2993
|
}
|
|
2831
2994
|
|
|
2995
|
+
if (state.route.name === 'charts' && state.charts.detailPanelVisible) {
|
|
2996
|
+
event.preventDefault();
|
|
2997
|
+
setChartsDetailPanelVisibility(false);
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
if (state.editor.historySelectedId !== null || state.editor.historyDetail) {
|
|
3002
|
+
event.preventDefault();
|
|
3003
|
+
clearQueryHistorySelection();
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
2832
3007
|
if (
|
|
2833
3008
|
state.route.name === 'data' &&
|
|
2834
3009
|
(typeof state.dataBrowser.selectedRowIndex === 'number' || Boolean(state.dataBrowser.selectedRow))
|
|
@@ -2891,6 +3066,11 @@ document.addEventListener('input', event => {
|
|
|
2891
3066
|
return;
|
|
2892
3067
|
}
|
|
2893
3068
|
|
|
3069
|
+
if (bindNode.dataset.bind === 'documents-search') {
|
|
3070
|
+
setDocumentsSearchQuery(bindNode.value);
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
|
|
2894
3074
|
if (bindNode.dataset.bind === 'data-search-query') {
|
|
2895
3075
|
void setDataSearchQuery(bindNode.value);
|
|
2896
3076
|
return;
|
|
@@ -2982,6 +3162,11 @@ document.addEventListener('input', event => {
|
|
|
2982
3162
|
return;
|
|
2983
3163
|
}
|
|
2984
3164
|
|
|
3165
|
+
if (bindNode.dataset.bind === 'charts-history-search') {
|
|
3166
|
+
setChartsHistorySearchInput(bindNode.value);
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
|
|
2985
3170
|
if (bindNode.dataset.bind === 'query-chart-draft:name') {
|
|
2986
3171
|
updateCurrentQueryChartDraftField('name', bindNode.value);
|
|
2987
3172
|
}
|
|
@@ -1018,6 +1018,20 @@ function getExportOptions() {
|
|
|
1018
1018
|
meta: ".md",
|
|
1019
1019
|
preview: ["| id | name |", "| --- | --- |"],
|
|
1020
1020
|
},
|
|
1021
|
+
{
|
|
1022
|
+
label: "JSON",
|
|
1023
|
+
format: "json",
|
|
1024
|
+
icon: "data_object",
|
|
1025
|
+
meta: ".json",
|
|
1026
|
+
preview: ['[', ' {"id":1,"name":"Acme"}', ']'],
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
label: "Parquet",
|
|
1030
|
+
format: "parquet",
|
|
1031
|
+
icon: "dataset",
|
|
1032
|
+
meta: ".parquet",
|
|
1033
|
+
preview: ["columnar binary", "typed row groups"],
|
|
1034
|
+
},
|
|
1021
1035
|
{
|
|
1022
1036
|
label: "Duplicate",
|
|
1023
1037
|
format: "table",
|
|
@@ -1047,7 +1061,7 @@ function renderTextExportModal(modal, action) {
|
|
|
1047
1061
|
${disabledAttribute}
|
|
1048
1062
|
/>
|
|
1049
1063
|
</label>
|
|
1050
|
-
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
1064
|
+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
|
|
1051
1065
|
${getExportOptions()
|
|
1052
1066
|
.map(
|
|
1053
1067
|
(option) => `
|
|
@@ -1784,6 +1798,8 @@ export function renderModal(state) {
|
|
|
1784
1798
|
modal.kind === "row-update-preview" ||
|
|
1785
1799
|
modal.kind === "table-designer-constraints"
|
|
1786
1800
|
? "max-w-3xl"
|
|
1801
|
+
: modal.kind === "query-export" || modal.kind === "data-export"
|
|
1802
|
+
? "max-w-4xl"
|
|
1787
1803
|
: "max-w-xl"
|
|
1788
1804
|
} border border-outline-variant/20 bg-surface-container shadow-[0_24px_80px_rgba(0,0,0,0.45)]">
|
|
1789
1805
|
<div class="flex items-start justify-between gap-4 border-b border-outline-variant/10 bg-surface-container-low px-6 py-5">
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { analyzeQueryChartResult, validateQueryChartConfig } from "../lib/queryCharts.js";
|
|
8
8
|
|
|
9
9
|
const chartInstances = new Map();
|
|
10
|
+
const chartHosts = new Map();
|
|
10
11
|
const resizeObservers = new Map();
|
|
11
12
|
|
|
12
13
|
function getEchartsRuntime() {
|
|
@@ -41,6 +42,8 @@ function disposeChart(chartId) {
|
|
|
41
42
|
chartInstances.delete(chartId);
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
chartHosts.delete(chartId);
|
|
46
|
+
|
|
44
47
|
const observer = resizeObservers.get(chartId);
|
|
45
48
|
|
|
46
49
|
if (observer) {
|
|
@@ -58,7 +61,15 @@ export function mountQueryChartRenderer(state) {
|
|
|
58
61
|
const charts = state.charts.detail?.charts ?? [];
|
|
59
62
|
const result = state.charts.result;
|
|
60
63
|
|
|
61
|
-
|
|
64
|
+
const activeChartIds = new Set(charts.map((chart) => String(chart.id)));
|
|
65
|
+
|
|
66
|
+
Array.from(chartInstances.keys()).forEach((chartId) => {
|
|
67
|
+
const host = chartHosts.get(chartId);
|
|
68
|
+
|
|
69
|
+
if (!activeChartIds.has(chartId) || !(host instanceof HTMLElement) || !host.isConnected) {
|
|
70
|
+
disposeChart(chartId);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
62
73
|
|
|
63
74
|
if (!echartsRuntime || !result || !charts.length) {
|
|
64
75
|
return;
|
|
@@ -85,17 +96,31 @@ export function mountQueryChartRenderer(state) {
|
|
|
85
96
|
return;
|
|
86
97
|
}
|
|
87
98
|
|
|
99
|
+
const chartId = String(chart.id);
|
|
100
|
+
const existingInstance = chartInstances.get(chartId);
|
|
101
|
+
const existingHost = chartHosts.get(chartId);
|
|
102
|
+
|
|
103
|
+
if (existingInstance && existingHost === host) {
|
|
104
|
+
existingInstance.resize();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (existingInstance) {
|
|
109
|
+
disposeChart(chartId);
|
|
110
|
+
}
|
|
111
|
+
|
|
88
112
|
const instance = echartsRuntime.init(host);
|
|
89
113
|
|
|
90
114
|
instance.setOption(buildOption(chart, result.rows ?? [], analysis), true);
|
|
91
|
-
chartInstances.set(
|
|
115
|
+
chartInstances.set(chartId, instance);
|
|
116
|
+
chartHosts.set(chartId, host);
|
|
92
117
|
|
|
93
118
|
const resizeObserver = new ResizeObserver(() => {
|
|
94
119
|
instance.resize();
|
|
95
120
|
});
|
|
96
121
|
|
|
97
122
|
resizeObserver.observe(host);
|
|
98
|
-
resizeObservers.set(
|
|
123
|
+
resizeObservers.set(chartId, resizeObserver);
|
|
99
124
|
});
|
|
100
125
|
}
|
|
101
126
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { escapeHtml, highlightSql } from "../utils/format.js";
|
|
2
|
-
import { renderActionBar } from "./actionBar.js";
|
|
3
2
|
|
|
4
3
|
function renderLineNumbers(query) {
|
|
5
4
|
const lineCount = Math.max(1, String(query || "").split("\n").length);
|
|
@@ -40,22 +39,14 @@ export function renderQueryEditor({
|
|
|
40
39
|
query,
|
|
41
40
|
executing = false,
|
|
42
41
|
exporting = false,
|
|
43
|
-
historyLoading = false,
|
|
44
|
-
historyTotal = 0,
|
|
45
42
|
editorVisible = true,
|
|
46
43
|
historyVisible = true,
|
|
47
44
|
}) {
|
|
48
45
|
const secondaryButtonClass = "standard-button";
|
|
49
46
|
const left = `
|
|
50
|
-
<div class="flex items-center gap-2 text-[10px] font-mono uppercase tracking-widest text-on-surface-variant/40">
|
|
51
|
-
<span class="material-symbols-outlined text-xs">history</span>
|
|
52
|
-
${historyLoading ? "Loading history..." : `${historyTotal} queries tracked`}
|
|
53
|
-
</div>
|
|
54
|
-
`;
|
|
55
|
-
|
|
56
|
-
const right = `
|
|
57
47
|
<button
|
|
58
|
-
class="${secondaryButtonClass}"
|
|
48
|
+
class="${secondaryButtonClass} panel-toggle-button ${editorVisible ? "" : "is-active"}"
|
|
49
|
+
aria-pressed="${editorVisible ? "false" : "true"}"
|
|
59
50
|
data-action="toggle-editor-panel"
|
|
60
51
|
data-next-value="${editorVisible ? "false" : "true"}"
|
|
61
52
|
type="button"
|
|
@@ -63,15 +54,9 @@ export function renderQueryEditor({
|
|
|
63
54
|
<span class="material-symbols-outlined text-sm">${editorVisible ? "keyboard_arrow_down" : "terminal"}</span>
|
|
64
55
|
${editorVisible ? "Hide Editor" : "Show Editor"}
|
|
65
56
|
</button>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
data-next-value="${historyVisible ? "false" : "true"}"
|
|
70
|
-
type="button"
|
|
71
|
-
>
|
|
72
|
-
<span class="material-symbols-outlined text-sm">${historyVisible ? "visibility_off" : "visibility"}</span>
|
|
73
|
-
${historyVisible ? "Hide History" : "Show History"}
|
|
74
|
-
</button>
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
const center = `
|
|
75
60
|
<button
|
|
76
61
|
class="${secondaryButtonClass}"
|
|
77
62
|
data-action="format-current-query"
|
|
@@ -104,14 +89,27 @@ export function renderQueryEditor({
|
|
|
104
89
|
</button>
|
|
105
90
|
`;
|
|
106
91
|
|
|
92
|
+
const right = `
|
|
93
|
+
<button
|
|
94
|
+
class="${secondaryButtonClass} panel-toggle-button ${historyVisible ? "" : "is-active"}"
|
|
95
|
+
aria-pressed="${historyVisible ? "false" : "true"}"
|
|
96
|
+
data-action="toggle-query-history-panel"
|
|
97
|
+
data-next-value="${historyVisible ? "false" : "true"}"
|
|
98
|
+
type="button"
|
|
99
|
+
>
|
|
100
|
+
<span class="material-symbols-outlined text-sm">${historyVisible ? "visibility_off" : "visibility"}</span>
|
|
101
|
+
${historyVisible ? "Hide History" : "Show History"}
|
|
102
|
+
</button>
|
|
103
|
+
`;
|
|
104
|
+
|
|
107
105
|
return `
|
|
108
106
|
<div class="flex h-full min-h-0 flex-col">
|
|
109
|
-
<div class="
|
|
110
|
-
|
|
111
|
-
left
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
<div class="workspace-header workspace-header--low">
|
|
108
|
+
<div class="query-editor-toolbar">
|
|
109
|
+
<div class="query-editor-toolbar__left">${left}</div>
|
|
110
|
+
<div class="query-editor-toolbar__center">${center}</div>
|
|
111
|
+
<div class="query-editor-toolbar__right">${right}</div>
|
|
112
|
+
</div>
|
|
115
113
|
</div>
|
|
116
114
|
${
|
|
117
115
|
editorVisible
|
|
@@ -163,7 +163,7 @@ export function renderQueryHistoryDetail({
|
|
|
163
163
|
escapeHtml(item.displayTitle),
|
|
164
164
|
"</h2></div>",
|
|
165
165
|
'<button class="query-history-icon-button" data-action="clear-query-history-selection" type="button"><span class="material-symbols-outlined text-[18px]">close</span></button>',
|
|
166
|
-
'</div><div class="mt-4 flex flex-wrap gap-2">',
|
|
166
|
+
'</div><div class="query-history-badge-row mt-4 flex flex-wrap gap-2">',
|
|
167
167
|
statusMarkup,
|
|
168
168
|
"</div></div>",
|
|
169
169
|
'<div class="custom-scrollbar min-h-0 flex-1 overflow-auto px-5 py-5">',
|