sqlite-hub 0.9.1 → 0.9.3
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/changelog.md +7 -0
- package/frontend/index.html +21 -20
- package/frontend/js/app.js +226 -4
- package/frontend/js/components/modal.js +13 -4
- package/frontend/js/components/queryHistoryPanel.js +9 -1
- package/frontend/js/components/rowEditorPanel.js +48 -25
- package/frontend/js/lib/queryChartOptions.js +5 -1
- package/frontend/js/lib/queryCharts.js +50 -2
- package/frontend/js/store.js +124 -22
- package/frontend/js/views/charts.js +103 -30
- package/frontend/js/views/data.js +31 -1
- package/frontend/js/views/editor.js +1 -0
- package/frontend/styles/base.css +57 -13
- package/frontend/styles/components.css +166 -96
- package/frontend/styles/layout.css +1 -1
- package/frontend/styles/structure-graph.css +11 -11
- package/frontend/styles/tokens.css +47 -4
- package/frontend/styles/utilities.css +21 -0
- package/frontend/styles/views.css +94 -86
- package/package.json +1 -1
- package/server/services/storage/queryHistoryChartUtils.js +19 -2
package/changelog.md
CHANGED
package/frontend/index.html
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
extend: {
|
|
23
23
|
colors: {
|
|
24
24
|
"on-secondary-fixed": "#1c1b1b",
|
|
25
|
-
background: "
|
|
25
|
+
background: "rgb(var(--rgb-background) / <alpha-value>)",
|
|
26
26
|
"inverse-primary": "#6a5f00",
|
|
27
27
|
"primary-fixed": "#fde403",
|
|
28
28
|
tertiary: "#fbfffe",
|
|
@@ -34,39 +34,39 @@
|
|
|
34
34
|
"on-tertiary-fixed-variant": "#004f51",
|
|
35
35
|
"secondary-fixed-dim": "#c8c6c5",
|
|
36
36
|
"tertiary-fixed-dim": "#00dce1",
|
|
37
|
-
"on-background": "
|
|
37
|
+
"on-background": "rgb(var(--rgb-on-surface) / <alpha-value>)",
|
|
38
38
|
"surface-tint": "#dec800",
|
|
39
39
|
"primary-fixed-dim": "#dec800",
|
|
40
|
-
"on-surface": "
|
|
41
|
-
"surface-container-low": "
|
|
40
|
+
"on-surface": "rgb(var(--rgb-on-surface) / <alpha-value>)",
|
|
41
|
+
"surface-container-low": "rgb(var(--rgb-surface-low) / <alpha-value>)",
|
|
42
42
|
"on-tertiary": "#003738",
|
|
43
|
-
"surface-container": "
|
|
44
|
-
"on-surface-variant": "
|
|
43
|
+
"surface-container": "rgb(var(--rgb-surface-container) / <alpha-value>)",
|
|
44
|
+
"on-surface-variant": "rgb(var(--rgb-on-surface-variant) / <alpha-value>)",
|
|
45
45
|
"on-primary-container": "#706400",
|
|
46
46
|
"on-error": "#690005",
|
|
47
|
-
"surface-container-high": "
|
|
48
|
-
"surface-container-highest": "
|
|
49
|
-
error: "
|
|
47
|
+
"surface-container-high": "rgb(var(--rgb-surface-high) / <alpha-value>)",
|
|
48
|
+
"surface-container-highest": "rgb(var(--rgb-surface-highest) / <alpha-value>)",
|
|
49
|
+
error: "rgb(var(--rgb-error) / <alpha-value>)",
|
|
50
50
|
primary: "#fffeff",
|
|
51
|
-
"outline-variant": "
|
|
51
|
+
"outline-variant": "rgb(var(--rgb-outline) / <alpha-value>)",
|
|
52
52
|
"on-error-container": "#ffdad6",
|
|
53
53
|
"on-secondary-fixed-variant": "#474746",
|
|
54
|
-
"surface-container-lowest": "
|
|
54
|
+
"surface-container-lowest": "rgb(var(--rgb-surface-lowest) / <alpha-value>)",
|
|
55
55
|
"inverse-surface": "#e5e2e1",
|
|
56
|
-
surface: "
|
|
56
|
+
surface: "rgb(var(--rgb-surface) / <alpha-value>)",
|
|
57
57
|
"tertiary-container": "#04faff",
|
|
58
58
|
"on-tertiary-fixed": "#002021",
|
|
59
59
|
"tertiary-fixed": "#2dfaff",
|
|
60
60
|
"surface-variant": "#353534",
|
|
61
61
|
"on-primary": "#373100",
|
|
62
|
-
"primary-container": "
|
|
62
|
+
"primary-container": "rgb(var(--rgb-primary) / <alpha-value>)",
|
|
63
63
|
"inverse-on-surface": "#313030",
|
|
64
64
|
secondary: "#c8c6c5",
|
|
65
65
|
"on-secondary-container": "#b7b5b4",
|
|
66
66
|
"on-primary-fixed": "#201c00",
|
|
67
67
|
"secondary-container": "#474746",
|
|
68
|
-
"surface-dim": "
|
|
69
|
-
"surface-bright": "
|
|
68
|
+
"surface-dim": "rgb(var(--rgb-surface) / <alpha-value>)",
|
|
69
|
+
"surface-bright": "rgb(var(--rgb-surface-bright) / <alpha-value>)",
|
|
70
70
|
"secondary-fixed": "#e5e2e1",
|
|
71
71
|
},
|
|
72
72
|
fontFamily: {
|
|
@@ -89,14 +89,14 @@
|
|
|
89
89
|
@layer components {
|
|
90
90
|
.signature-button {
|
|
91
91
|
@apply inline-flex items-center justify-center gap-2 h-[var(--control-height)] min-h-[var(--control-height)] border border-primary-container bg-primary-container px-[var(--control-padding-inline)] font-headline text-xs font-bold uppercase tracking-[0.16em] text-on-primary transition-all;
|
|
92
|
-
box-shadow: 0 0 18px -10px
|
|
92
|
+
box-shadow: 0 0 18px -10px rgb(var(--rgb-primary) / 0.7);
|
|
93
93
|
clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
.signature-button:hover {
|
|
97
97
|
@apply -translate-y-px bg-primary-fixed text-on-primary;
|
|
98
|
-
border-color:
|
|
99
|
-
box-shadow: 0 0 24px -8px
|
|
98
|
+
border-color: rgb(var(--rgb-primary) / 0.55);
|
|
99
|
+
box-shadow: 0 0 24px -8px rgb(var(--rgb-primary) / 0.92);
|
|
100
100
|
filter: brightness(1.03);
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -118,12 +118,12 @@
|
|
|
118
118
|
|
|
119
119
|
.standard-button:hover {
|
|
120
120
|
@apply bg-surface-container-highest text-primary-container;
|
|
121
|
-
border-color:
|
|
121
|
+
border-color: var(--primary-alpha-24);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
.standard-button.is-active {
|
|
125
125
|
@apply bg-surface-container-high text-primary-container;
|
|
126
|
-
border-color:
|
|
126
|
+
border-color: var(--primary-alpha-30);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
.standard-button:disabled,
|
|
@@ -166,6 +166,7 @@
|
|
|
166
166
|
</style>
|
|
167
167
|
<link href="styles/tokens.css" rel="stylesheet" />
|
|
168
168
|
<link href="styles/base.css" rel="stylesheet" />
|
|
169
|
+
<link href="styles/utilities.css" rel="stylesheet" />
|
|
169
170
|
<link href="styles/layout.css" rel="stylesheet" />
|
|
170
171
|
<link href="styles/components.css" rel="stylesheet" />
|
|
171
172
|
<link href="styles/views.css" rel="stylesheet" />
|
package/frontend/js/app.js
CHANGED
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
toggleDataTablesPanel,
|
|
70
70
|
setCurrentQuery,
|
|
71
71
|
setChartsHeightPreset,
|
|
72
|
+
setChartsHistoryTab,
|
|
72
73
|
setEditorPanelVisibility,
|
|
73
74
|
setEditorTab,
|
|
74
75
|
submitDeleteChartConfirmation,
|
|
@@ -111,7 +112,7 @@ import {
|
|
|
111
112
|
applyCurrentMediaTaggingSelection,
|
|
112
113
|
removeCurrentTableDesignerColumn,
|
|
113
114
|
} from './store.js';
|
|
114
|
-
import { renderChartsView } from './views/charts.js';
|
|
115
|
+
import { renderChartsDetail, renderChartsView } from './views/charts.js';
|
|
115
116
|
import { renderConnectionsView } from './views/connections.js';
|
|
116
117
|
import { renderDataRowEditorPanel, renderDataView } from './views/data.js';
|
|
117
118
|
import { renderEditorView } from './views/editor.js';
|
|
@@ -146,6 +147,7 @@ let lastRenderedMainMarkup = '';
|
|
|
146
147
|
let lastRenderedPanelMarkup = '';
|
|
147
148
|
let lastRenderedModalMarkup = '';
|
|
148
149
|
let lastRenderedToastMarkup = '';
|
|
150
|
+
let lastRenderedChartsHistorySignature = '';
|
|
149
151
|
let lastRenderedPanelOpen = false;
|
|
150
152
|
let lastRenderedLockedRoute = false;
|
|
151
153
|
let pendingNewTableDesignerAutofocus = false;
|
|
@@ -417,6 +419,188 @@ function syncQueryHistorySelectionUi(selectedHistoryId = null) {
|
|
|
417
419
|
return true;
|
|
418
420
|
}
|
|
419
421
|
|
|
422
|
+
function isEditorRouteName(routeName) {
|
|
423
|
+
return routeName === 'editor' || routeName === 'editorResults';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function captureQueryHistoryScrollState() {
|
|
427
|
+
if (!isEditorRouteName(lastRenderedRouteName)) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const scrollNode = shellRefs.view.querySelector('[data-query-history-scroll]');
|
|
432
|
+
|
|
433
|
+
if (!(scrollNode instanceof HTMLElement)) {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
committedSearch: scrollNode.dataset.queryHistoryCommittedSearch ?? '',
|
|
439
|
+
historyTab: scrollNode.dataset.queryHistoryTab ?? '',
|
|
440
|
+
routeName: lastRenderedRouteName,
|
|
441
|
+
renderedItemCount: scrollNode.querySelectorAll('.query-history-item').length,
|
|
442
|
+
renderedLoadingMore: scrollNode.dataset.queryHistoryLoadingMore === 'true',
|
|
443
|
+
searchInput: scrollNode.dataset.queryHistorySearch ?? '',
|
|
444
|
+
scrollLeft: scrollNode.scrollLeft,
|
|
445
|
+
scrollTop: scrollNode.scrollTop,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function shouldRestoreQueryHistoryScroll(snapshot, state) {
|
|
450
|
+
if (!snapshot || !isEditorRouteName(state.route.name) || snapshot.routeName !== state.route.name) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (!state.editor.historyPanelVisible) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (
|
|
459
|
+
snapshot.historyTab !== state.editor.historyTab ||
|
|
460
|
+
snapshot.searchInput !== state.editor.historySearchInput ||
|
|
461
|
+
snapshot.committedSearch !== state.editor.historySearch
|
|
462
|
+
) {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return (
|
|
467
|
+
state.editor.historyLoadingMore ||
|
|
468
|
+
snapshot.renderedLoadingMore ||
|
|
469
|
+
state.editor.history.length > snapshot.renderedItemCount
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function restoreQueryHistoryScrollState(snapshot) {
|
|
474
|
+
const scrollNode = shellRefs.view.querySelector('[data-query-history-scroll]');
|
|
475
|
+
|
|
476
|
+
if (!(scrollNode instanceof HTMLElement)) {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
scrollNode.scrollLeft = snapshot.scrollLeft;
|
|
481
|
+
scrollNode.scrollTop = snapshot.scrollTop;
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function buildChartsHistorySignature(state) {
|
|
486
|
+
if (state.route.name !== 'charts') {
|
|
487
|
+
return '';
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const historyVisible = state.editor.historyPanelVisible !== false || !state.charts.selectedHistoryId;
|
|
491
|
+
|
|
492
|
+
if (!historyVisible) {
|
|
493
|
+
return 'charts-history:hidden';
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const queries = state.charts.queries ?? [];
|
|
497
|
+
|
|
498
|
+
if (!queries.length) {
|
|
499
|
+
if (state.charts.loading) {
|
|
500
|
+
return 'charts-history:loading-empty';
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (state.charts.error) {
|
|
504
|
+
return `charts-history:error:${state.charts.error.code}:${state.charts.error.message}`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return 'charts-history:empty';
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return JSON.stringify({
|
|
511
|
+
tab: state.charts.historyTab ?? 'recent',
|
|
512
|
+
queries: queries.map(item => [
|
|
513
|
+
item.id,
|
|
514
|
+
item.displayTitle,
|
|
515
|
+
item.previewSql,
|
|
516
|
+
Boolean(item.isSaved),
|
|
517
|
+
Array.isArray(item.chartTypes) ? item.chartTypes.join(',') : '',
|
|
518
|
+
]),
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function syncChartsHistorySelectionUi(state) {
|
|
523
|
+
const selectedHistoryId = String(state.charts.selectedHistoryId ?? '');
|
|
524
|
+
const historyButtons = shellRefs.view.querySelectorAll(
|
|
525
|
+
'.charts-view__sidebar [data-action="navigate"][data-history-id]',
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
for (const button of historyButtons) {
|
|
529
|
+
if (!(button instanceof HTMLElement)) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const itemNode = button.closest('[data-charts-history-item]');
|
|
534
|
+
const isSelected = button.dataset.historyId === selectedHistoryId;
|
|
535
|
+
const titleNode = button.querySelector('[data-charts-history-title]');
|
|
536
|
+
const selectionNode = itemNode instanceof HTMLElement ? itemNode : button;
|
|
537
|
+
|
|
538
|
+
selectionNode.classList.toggle('border-primary-container/30', isSelected);
|
|
539
|
+
selectionNode.classList.toggle('bg-surface-container-high', isSelected);
|
|
540
|
+
selectionNode.classList.toggle('border-outline-variant/10', !isSelected);
|
|
541
|
+
selectionNode.classList.toggle('bg-surface-container-lowest', !isSelected);
|
|
542
|
+
selectionNode.classList.toggle('hover:bg-surface-container-high', !isSelected);
|
|
543
|
+
|
|
544
|
+
if (titleNode instanceof HTMLElement) {
|
|
545
|
+
titleNode.classList.toggle('text-primary-container', isSelected);
|
|
546
|
+
titleNode.classList.toggle('text-on-surface', !isSelected);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function syncChartsSavedToggleUi(actionNode, nextValue) {
|
|
554
|
+
const itemNode = actionNode.closest('[data-charts-history-item]');
|
|
555
|
+
|
|
556
|
+
actionNode.classList.toggle('is-active', nextValue);
|
|
557
|
+
actionNode.dataset.nextValue = nextValue ? 'false' : 'true';
|
|
558
|
+
actionNode.title = nextValue ? 'Remove from saved' : 'Save query';
|
|
559
|
+
|
|
560
|
+
const iconNode = actionNode.querySelector('.material-symbols-outlined');
|
|
561
|
+
if (iconNode instanceof HTMLElement) {
|
|
562
|
+
iconNode.textContent = nextValue ? 'bookmark' : 'bookmark_add';
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (itemNode instanceof HTMLElement) {
|
|
566
|
+
itemNode.querySelector('[data-charts-saved-badge]')?.toggleAttribute('hidden', !nextValue);
|
|
567
|
+
|
|
568
|
+
if (!nextValue && getState().charts.historyTab === 'saved') {
|
|
569
|
+
itemNode.remove();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const countNode = shellRefs.view.querySelector('[data-charts-history-count]');
|
|
574
|
+
if (countNode instanceof HTMLElement) {
|
|
575
|
+
const state = getState();
|
|
576
|
+
const count =
|
|
577
|
+
state.charts.historyTab === 'saved'
|
|
578
|
+
? (state.charts.queries ?? []).filter(item => item.isSaved).length
|
|
579
|
+
: (state.charts.queries ?? []).length;
|
|
580
|
+
countNode.textContent = String(count);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function patchChartsDetailUi(state) {
|
|
585
|
+
const chartsView = shellRefs.view.querySelector('.charts-view');
|
|
586
|
+
const detailNode = chartsView?.querySelector('.charts-view__detail');
|
|
587
|
+
|
|
588
|
+
if (!(chartsView instanceof HTMLElement) || !(detailNode instanceof HTMLElement)) {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const historyVisible = state.editor.historyPanelVisible !== false || !state.charts.selectedHistoryId;
|
|
593
|
+
const sidebarNode = chartsView.querySelector('.charts-view__sidebar');
|
|
594
|
+
|
|
595
|
+
if (Boolean(sidebarNode) !== historyVisible) {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
detailNode.innerHTML = renderChartsDetail(state);
|
|
600
|
+
syncChartsHistorySelectionUi(state);
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
|
|
420
604
|
function readFileAsBase64(file) {
|
|
421
605
|
return new Promise((resolve, reject) => {
|
|
422
606
|
const reader = new FileReader();
|
|
@@ -755,6 +939,7 @@ function renderApp(state) {
|
|
|
755
939
|
const statusBarMarkup = renderStatusBar(state);
|
|
756
940
|
const modalMarkup = renderModal(state);
|
|
757
941
|
const toastMarkup = renderToasts(state.toasts);
|
|
942
|
+
const chartsHistorySignature = buildChartsHistorySignature(state);
|
|
758
943
|
const isLockedRoute = [
|
|
759
944
|
'editor',
|
|
760
945
|
'editorResults',
|
|
@@ -772,6 +957,7 @@ function renderApp(state) {
|
|
|
772
957
|
const mainChanged = main !== lastRenderedMainMarkup;
|
|
773
958
|
const panelChanged = panel !== lastRenderedPanelMarkup;
|
|
774
959
|
const modalChanged = modalMarkup !== lastRenderedModalMarkup;
|
|
960
|
+
const chartsHistoryChanged = chartsHistorySignature !== lastRenderedChartsHistorySignature;
|
|
775
961
|
const panelOpenChanged = panelOpen !== lastRenderedPanelOpen;
|
|
776
962
|
const lockedRouteChanged = isLockedRoute !== lastRenderedLockedRoute;
|
|
777
963
|
const shellMarkupUnchanged =
|
|
@@ -796,6 +982,7 @@ function renderApp(state) {
|
|
|
796
982
|
}
|
|
797
983
|
|
|
798
984
|
const focusedInput = captureFocusedInputState();
|
|
985
|
+
const queryHistoryScrollState = captureQueryHistoryScrollState();
|
|
799
986
|
const isEnteringNewTableDesignerRoute =
|
|
800
987
|
state.route.name === 'tableDesigner' && state.route.params?.isNew && previousRoutePath !== state.route.path;
|
|
801
988
|
|
|
@@ -805,9 +992,20 @@ function renderApp(state) {
|
|
|
805
992
|
pendingNewTableDesignerAutofocus = false;
|
|
806
993
|
}
|
|
807
994
|
|
|
808
|
-
|
|
809
|
-
|
|
995
|
+
const canPatchChartsMain =
|
|
996
|
+
mainChanged && previousRouteName === 'charts' && state.route.name === 'charts' && !chartsHistoryChanged;
|
|
997
|
+
let mainPatched = false;
|
|
998
|
+
|
|
999
|
+
if (canPatchChartsMain) {
|
|
810
1000
|
teardownQueryChartRenderer();
|
|
1001
|
+
mainPatched = patchChartsDetailUi(state);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (mainChanged) {
|
|
1005
|
+
if (!mainPatched) {
|
|
1006
|
+
teardownStructureGraph();
|
|
1007
|
+
teardownQueryChartRenderer();
|
|
1008
|
+
}
|
|
811
1009
|
}
|
|
812
1010
|
|
|
813
1011
|
if (topNavChanged) {
|
|
@@ -829,7 +1027,7 @@ function renderApp(state) {
|
|
|
829
1027
|
shellRefs.statusBar.innerHTML = statusBarMarkup;
|
|
830
1028
|
}
|
|
831
1029
|
|
|
832
|
-
if (mainChanged) {
|
|
1030
|
+
if (mainChanged && !mainPatched) {
|
|
833
1031
|
shellRefs.view.innerHTML = main;
|
|
834
1032
|
}
|
|
835
1033
|
|
|
@@ -853,6 +1051,10 @@ function renderApp(state) {
|
|
|
853
1051
|
shellRefs.shell.classList.toggle('panel-open', panelOpen);
|
|
854
1052
|
}
|
|
855
1053
|
|
|
1054
|
+
if (shouldRestoreQueryHistoryScroll(queryHistoryScrollState, state)) {
|
|
1055
|
+
restoreQueryHistoryScrollState(queryHistoryScrollState);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
856
1058
|
if (pendingQueryEditorFocus && (state.route.name === 'editor' || state.route.name === 'editorResults')) {
|
|
857
1059
|
if (focusQueryEditorInput()) {
|
|
858
1060
|
pendingQueryEditorFocus = false;
|
|
@@ -883,6 +1085,7 @@ function renderApp(state) {
|
|
|
883
1085
|
lastRenderedPanelMarkup = panel;
|
|
884
1086
|
lastRenderedModalMarkup = modalMarkup;
|
|
885
1087
|
lastRenderedToastMarkup = toastMarkup;
|
|
1088
|
+
lastRenderedChartsHistorySignature = chartsHistorySignature;
|
|
886
1089
|
lastRenderedPanelOpen = panelOpen;
|
|
887
1090
|
lastRenderedLockedRoute = isLockedRoute;
|
|
888
1091
|
|
|
@@ -1063,6 +1266,20 @@ async function handleAction(actionNode) {
|
|
|
1063
1266
|
);
|
|
1064
1267
|
}
|
|
1065
1268
|
return;
|
|
1269
|
+
case 'toggle-charts-query-history-saved':
|
|
1270
|
+
if (actionNode.dataset.historyId) {
|
|
1271
|
+
const nextValue = actionNode.dataset.nextValue === 'true';
|
|
1272
|
+
const updated = await toggleQueryHistorySavedState(actionNode.dataset.historyId, nextValue, {
|
|
1273
|
+
notify: false,
|
|
1274
|
+
refresh: false,
|
|
1275
|
+
toast: false,
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
if (updated) {
|
|
1279
|
+
syncChartsSavedToggleUi(actionNode, nextValue);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return;
|
|
1066
1283
|
case 'toggle-charts-sql-panel':
|
|
1067
1284
|
toggleChartsSqlPanel();
|
|
1068
1285
|
return;
|
|
@@ -1074,6 +1291,11 @@ async function handleAction(actionNode) {
|
|
|
1074
1291
|
setChartsHeightPreset(actionNode.dataset.preset);
|
|
1075
1292
|
}
|
|
1076
1293
|
return;
|
|
1294
|
+
case 'set-charts-history-tab':
|
|
1295
|
+
if (actionNode.dataset.tab) {
|
|
1296
|
+
setChartsHistoryTab(actionNode.dataset.tab);
|
|
1297
|
+
}
|
|
1298
|
+
return;
|
|
1077
1299
|
case 'export-query-chart-png':
|
|
1078
1300
|
if (actionNode.dataset.chartId && !exportQueryChartAsPng(actionNode.dataset.chartId)) {
|
|
1079
1301
|
showToast('The selected chart is not ready for PNG export.', 'alert');
|
|
@@ -481,13 +481,22 @@ function renderChartEditorForm(modal, state) {
|
|
|
481
481
|
bind: "query-chart-draft-config:y_column",
|
|
482
482
|
})}
|
|
483
483
|
</div>
|
|
484
|
-
<div class="grid grid-cols-1 gap-4 md:grid-cols-
|
|
484
|
+
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
|
|
485
|
+
${renderSelectField({
|
|
486
|
+
label: "Sort By",
|
|
487
|
+
value: draft.config?.sort_by ?? "y",
|
|
488
|
+
options: [
|
|
489
|
+
{ value: "x", label: "X column" },
|
|
490
|
+
{ value: "y", label: "Y value" },
|
|
491
|
+
],
|
|
492
|
+
bind: "query-chart-draft-config:sort_by",
|
|
493
|
+
})}
|
|
485
494
|
${renderSelectField({
|
|
486
495
|
label: "Sort Direction",
|
|
487
|
-
value: draft.config?.sort_direction ?? "
|
|
496
|
+
value: draft.config?.sort_direction ?? "desc",
|
|
488
497
|
options: [
|
|
489
|
-
{ value: "asc", label: "Ascending" },
|
|
490
|
-
{ value: "desc", label: "Descending" },
|
|
498
|
+
{ value: "asc", label: "Ascending / smallest first" },
|
|
499
|
+
{ value: "desc", label: "Descending / largest first" },
|
|
491
500
|
],
|
|
492
501
|
bind: "query-chart-draft-config:sort_direction",
|
|
493
502
|
})}
|
|
@@ -139,6 +139,7 @@ export function renderQueryHistoryPanel({
|
|
|
139
139
|
error = null,
|
|
140
140
|
activeTab = "recent",
|
|
141
141
|
search = "",
|
|
142
|
+
committedSearch = "",
|
|
142
143
|
total = 0,
|
|
143
144
|
hasMore = false,
|
|
144
145
|
activeHistoryId = null,
|
|
@@ -176,7 +177,14 @@ export function renderQueryHistoryPanel({
|
|
|
176
177
|
/>
|
|
177
178
|
</label>
|
|
178
179
|
</div>
|
|
179
|
-
<div
|
|
180
|
+
<div
|
|
181
|
+
class="custom-scrollbar min-h-0 flex-1 overflow-auto px-3 py-3"
|
|
182
|
+
data-query-history-committed-search="${escapeHtml(committedSearch)}"
|
|
183
|
+
data-query-history-loading-more="${loadingMore ? "true" : "false"}"
|
|
184
|
+
data-query-history-search="${escapeHtml(search)}"
|
|
185
|
+
data-query-history-scroll
|
|
186
|
+
data-query-history-tab="${escapeHtml(activeTab)}"
|
|
187
|
+
>
|
|
180
188
|
${
|
|
181
189
|
error
|
|
182
190
|
? `
|
|
@@ -50,15 +50,7 @@ function renderReadonlyField(label, value) {
|
|
|
50
50
|
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3">
|
|
51
51
|
<div class="flex flex-wrap items-center gap-2 text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
52
52
|
<span>${escapeHtml(displayLabel)}</span>
|
|
53
|
-
${badges
|
|
54
|
-
.map(
|
|
55
|
-
(badge) => `
|
|
56
|
-
<span class="border border-outline-variant/20 bg-surface-container px-2 py-1 text-[9px] text-primary-container">
|
|
57
|
-
${escapeHtml(badge)}
|
|
58
|
-
</span>
|
|
59
|
-
`
|
|
60
|
-
)
|
|
61
|
-
.join("")}
|
|
53
|
+
${badges.map((badge) => renderFieldBadge(badge)).join("")}
|
|
62
54
|
</div>
|
|
63
55
|
${
|
|
64
56
|
jsonPreview
|
|
@@ -72,37 +64,68 @@ function renderReadonlyField(label, value) {
|
|
|
72
64
|
function renderEditableField(field) {
|
|
73
65
|
const badges = Array.isArray(field.badges) ? field.badges : [];
|
|
74
66
|
const jsonPreview = getJsonPreview(field.value);
|
|
67
|
+
const inputType = field.inputType === "number" ? "number" : "text";
|
|
68
|
+
const numberStep = field.numberStep === "1" ? "1" : "any";
|
|
75
69
|
|
|
76
70
|
return `
|
|
77
71
|
<label class="block space-y-2">
|
|
78
72
|
<span class="flex flex-wrap items-center gap-2 text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
79
73
|
<span>${escapeHtml(field.label ?? field.name)}</span>
|
|
80
|
-
${badges
|
|
81
|
-
.map(
|
|
82
|
-
(badge) => `
|
|
83
|
-
<span class="border border-outline-variant/20 bg-surface-container px-2 py-1 text-[9px] text-primary-container">
|
|
84
|
-
${escapeHtml(badge)}
|
|
85
|
-
</span>
|
|
86
|
-
`
|
|
87
|
-
)
|
|
88
|
-
.join("")}
|
|
74
|
+
${badges.map((badge) => renderFieldBadge(badge)).join("")}
|
|
89
75
|
</span>
|
|
90
76
|
${
|
|
91
77
|
jsonPreview
|
|
92
78
|
? renderJsonViewer(jsonPreview, "JSON Preview")
|
|
93
79
|
: ""
|
|
94
80
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
81
|
+
${
|
|
82
|
+
inputType === "number" && !jsonPreview
|
|
83
|
+
? `
|
|
84
|
+
<input
|
|
85
|
+
class="w-full border border-outline-variant/20 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
86
|
+
name="field:${escapeHtml(field.name)}"
|
|
87
|
+
step="${escapeHtml(numberStep)}"
|
|
88
|
+
type="number"
|
|
89
|
+
value="${escapeHtml(field.value ?? "")}"
|
|
90
|
+
/>
|
|
91
|
+
`
|
|
92
|
+
: `
|
|
93
|
+
<textarea
|
|
94
|
+
class="w-full border border-outline-variant/20 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface outline-none transition-colors focus:border-primary-container ${
|
|
95
|
+
jsonPreview ? "min-h-[14rem] font-mono leading-6" : "min-h-[56px]"
|
|
96
|
+
}"
|
|
97
|
+
name="field:${escapeHtml(field.name)}"
|
|
98
|
+
spellcheck="false"
|
|
99
|
+
>${escapeHtml(field.value ?? "")}</textarea>
|
|
100
|
+
`
|
|
101
|
+
}
|
|
102
102
|
</label>
|
|
103
103
|
`;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
function getFieldBadgeClassName(tone) {
|
|
107
|
+
if (tone === "primary-key") {
|
|
108
|
+
return "border-primary-container/35 bg-primary-container/15 text-primary-container";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (tone === "foreign-key") {
|
|
112
|
+
return "border-tertiary-fixed-dim/35 bg-tertiary-fixed-dim/15 text-tertiary-fixed-dim";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return "border-outline-variant/20 bg-surface-container text-on-surface-variant";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function renderFieldBadge(badge) {
|
|
119
|
+
const label = typeof badge === "object" ? badge.label : badge;
|
|
120
|
+
const tone = typeof badge === "object" ? badge.tone : "";
|
|
121
|
+
|
|
122
|
+
return `
|
|
123
|
+
<span class="border px-2 py-1 text-[9px] ${getFieldBadgeClassName(tone)}">
|
|
124
|
+
${escapeHtml(label)}
|
|
125
|
+
</span>
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
export function renderRowEditorPanel({
|
|
107
130
|
title,
|
|
108
131
|
sectionLabel = "Row Editor",
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
formatQueryChartAxisValue,
|
|
6
6
|
getAnalysisColumn,
|
|
7
7
|
sortQueryChartRows,
|
|
8
|
+
sortQueryChartRowsByNumericColumn,
|
|
8
9
|
} from "./queryCharts.js";
|
|
9
10
|
|
|
10
11
|
const CHART_PALETTE = ["#FCE300", "#2DFAFF", "#FFB4AB", "#CDC7AB", "#7DD3FC", "#86EFAC"];
|
|
@@ -55,7 +56,10 @@ function buildLineLabelConfig(enabled) {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export function buildBarChartOption(chart, rows) {
|
|
58
|
-
const sortedRows =
|
|
59
|
+
const sortedRows =
|
|
60
|
+
chart.config.sort_by === "y"
|
|
61
|
+
? sortQueryChartRowsByNumericColumn(rows, chart.config.y_column, chart.config.sort_direction)
|
|
62
|
+
: sortQueryChartRows(rows, chart.config.x_column, chart.config.sort_direction);
|
|
59
63
|
|
|
60
64
|
return {
|
|
61
65
|
...buildCommonOption(),
|
|
@@ -312,7 +312,43 @@ function getFirstColumn(columns, predicate) {
|
|
|
312
312
|
|
|
313
313
|
function getNextNumericColumn(analysis, excludedNames = []) {
|
|
314
314
|
const excluded = new Set(excludedNames);
|
|
315
|
-
|
|
315
|
+
const candidates = (analysis?.numberColumns ?? []).filter((column) => !excluded.has(column.name));
|
|
316
|
+
|
|
317
|
+
if (!candidates.length) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const scoredCandidates = candidates
|
|
322
|
+
.map((column, index) => ({
|
|
323
|
+
column,
|
|
324
|
+
index,
|
|
325
|
+
score: getNumericMeasureScore(column.name),
|
|
326
|
+
}))
|
|
327
|
+
.sort((left, right) => right.score - left.score || left.index - right.index);
|
|
328
|
+
|
|
329
|
+
return scoredCandidates[0]?.column ?? null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function getNumericMeasureScore(columnName = "") {
|
|
333
|
+
const normalized = String(columnName ?? "")
|
|
334
|
+
.trim()
|
|
335
|
+
.toLowerCase();
|
|
336
|
+
|
|
337
|
+
let score = 0;
|
|
338
|
+
|
|
339
|
+
if (
|
|
340
|
+
/(count|total|sum|avg|average|amount|score|size|value|price|cost|rate|percent|pct|percentage|number)/.test(
|
|
341
|
+
normalized
|
|
342
|
+
)
|
|
343
|
+
) {
|
|
344
|
+
score += 20;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (/(^|_)(id|uuid|pk|key)$/.test(normalized) || /(^|_)id$/.test(normalized)) {
|
|
348
|
+
score -= 40;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return score;
|
|
316
352
|
}
|
|
317
353
|
|
|
318
354
|
function getPrimaryDimensionColumn(analysis) {
|
|
@@ -406,7 +442,8 @@ export function buildSuggestedChartConfig(chartType, analysis) {
|
|
|
406
442
|
y_column: firstNumeric?.name ?? "",
|
|
407
443
|
show_legend: true,
|
|
408
444
|
show_labels: false,
|
|
409
|
-
|
|
445
|
+
sort_by: "y",
|
|
446
|
+
sort_direction: "desc",
|
|
410
447
|
};
|
|
411
448
|
}
|
|
412
449
|
}
|
|
@@ -524,6 +561,17 @@ export function sortQueryChartRows(rows, columnName, direction = "asc") {
|
|
|
524
561
|
);
|
|
525
562
|
}
|
|
526
563
|
|
|
564
|
+
export function sortQueryChartRowsByNumericColumn(rows, columnName, direction = "asc") {
|
|
565
|
+
const multiplier = direction === "desc" ? -1 : 1;
|
|
566
|
+
|
|
567
|
+
return [...(rows ?? [])].sort((left, right) => {
|
|
568
|
+
const leftValue = coerceNumericChartValue(left?.[columnName]);
|
|
569
|
+
const rightValue = coerceNumericChartValue(right?.[columnName]);
|
|
570
|
+
|
|
571
|
+
return compareValues(leftValue, rightValue) * multiplier;
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
527
575
|
export function formatQueryChartAxisValue(value) {
|
|
528
576
|
if (value === null || value === undefined) {
|
|
529
577
|
return "NULL";
|