sqlite-hub 0.9.1 → 0.9.4
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/.github/workflows/ci.yml +36 -0
- package/README.md +2 -2
- package/bin/sqlite-hub.js +1 -1
- package/frontend/index.html +3 -158
- package/frontend/js/app.js +231 -5
- package/frontend/js/components/connectionCard.js +62 -87
- package/frontend/js/components/emptyState.js +20 -23
- package/frontend/js/components/modal.js +158 -199
- package/frontend/js/components/pageHeader.js +1 -1
- package/frontend/js/components/queryEditor.js +16 -30
- package/frontend/js/components/queryHistoryDetail.js +93 -164
- package/frontend/js/components/queryHistoryPanel.js +90 -100
- package/frontend/js/components/queryResults.js +3 -1
- package/frontend/js/components/rowEditorPanel.js +76 -56
- package/frontend/js/components/tableDesignerEditor.js +91 -116
- package/frontend/js/lib/queryChartOptions.js +5 -1
- package/frontend/js/lib/queryCharts.js +50 -2
- package/frontend/js/store.js +161 -23
- package/frontend/js/utils/tableDesigner.js +8 -2
- package/frontend/js/views/charts.js +126 -73
- package/frontend/js/views/data.js +147 -133
- package/frontend/js/views/editor.js +1 -0
- package/frontend/js/views/mediaTagging.js +131 -164
- package/frontend/js/views/structure.js +52 -48
- 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/tailwind.css +81 -0
- package/frontend/styles/tailwind.generated.css +1 -0
- package/frontend/styles/tokens.css +50 -7
- package/frontend/styles/utilities.css +21 -0
- package/frontend/styles/views.css +94 -86
- package/package.json +16 -3
- package/server/routes/mediaTagging.js +2 -10
- package/server/routes/sql.js +35 -10
- package/server/server.js +24 -0
- package/server/services/sqlite/dataBrowserService.js +25 -5
- package/server/services/sqlite/exportService.js +4 -2
- package/server/services/sqlite/introspection.js +2 -2
- package/server/services/sqlite/mediaTaggingService.js +166 -53
- package/server/services/sqlite/structureService.js +2 -2
- package/server/services/sqlite/tableDesigner/sql.js +19 -3
- package/server/services/storage/appStateStore.js +227 -87
- package/server/services/storage/queryHistoryChartUtils.js +19 -2
- package/server/utils/appPaths.js +55 -19
- package/server/utils/fileValidation.js +94 -8
- package/tailwind.config.cjs +73 -0
- package/tests/security-paths.test.js +84 -0
- package/tests/sql-identifier-safety.test.js +66 -0
- package/.npmingnore +0 -4
- package/changelog.md +0 -77
- package/docs/DESIGN_GUIDELINES.md +0 -36
- package/scripts/publish_brew.sh +0 -466
- package/scripts/publish_npm.sh +0 -241
- package/shortkeys.md +0 -5
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build-test-audit:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Setup Node
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: 26
|
|
24
|
+
cache: npm
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: npm ci
|
|
28
|
+
|
|
29
|
+
- name: Build
|
|
30
|
+
run: npm run build
|
|
31
|
+
|
|
32
|
+
- name: Test
|
|
33
|
+
run: npm test
|
|
34
|
+
|
|
35
|
+
- name: Dependency audit
|
|
36
|
+
run: npm run audit
|
package/README.md
CHANGED
package/bin/sqlite-hub.js
CHANGED
|
@@ -334,7 +334,7 @@ async function main() {
|
|
|
334
334
|
} else if (stmt.kind === 'mutation') {
|
|
335
335
|
console.log(`Changes: ${stmt.changes}`);
|
|
336
336
|
if (stmt.lastInsertRowid) {
|
|
337
|
-
console.log(
|
|
337
|
+
console.log('Last insert rowid:', stmt.lastInsertRowid);
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
});
|
package/frontend/index.html
CHANGED
|
@@ -6,166 +6,11 @@
|
|
|
6
6
|
<title>SQLite Hub</title>
|
|
7
7
|
<link href="/assets/images/logo_raw.png" rel="icon" type="image/png" />
|
|
8
8
|
<link href="/assets/images/logo_raw.png" rel="shortcut icon" type="image/png" />
|
|
9
|
-
<
|
|
10
|
-
<link
|
|
11
|
-
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700;800;900&family=Inter:wght@300;400;500;600;700;800&family=Roboto+Mono:wght@400;500;700&display=swap"
|
|
12
|
-
rel="stylesheet"
|
|
13
|
-
/>
|
|
14
|
-
<link
|
|
15
|
-
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
|
|
16
|
-
rel="stylesheet"
|
|
17
|
-
/>
|
|
18
|
-
<script id="tailwind-config">
|
|
19
|
-
tailwind.config = {
|
|
20
|
-
darkMode: "class",
|
|
21
|
-
theme: {
|
|
22
|
-
extend: {
|
|
23
|
-
colors: {
|
|
24
|
-
"on-secondary-fixed": "#1c1b1b",
|
|
25
|
-
background: "#131313",
|
|
26
|
-
"inverse-primary": "#6a5f00",
|
|
27
|
-
"primary-fixed": "#fde403",
|
|
28
|
-
tertiary: "#fbfffe",
|
|
29
|
-
outline: "#979177",
|
|
30
|
-
"on-secondary": "#313030",
|
|
31
|
-
"error-container": "#93000a",
|
|
32
|
-
"on-primary-fixed-variant": "#504700",
|
|
33
|
-
"on-tertiary-container": "#006f72",
|
|
34
|
-
"on-tertiary-fixed-variant": "#004f51",
|
|
35
|
-
"secondary-fixed-dim": "#c8c6c5",
|
|
36
|
-
"tertiary-fixed-dim": "#00dce1",
|
|
37
|
-
"on-background": "#e5e2e1",
|
|
38
|
-
"surface-tint": "#dec800",
|
|
39
|
-
"primary-fixed-dim": "#dec800",
|
|
40
|
-
"on-surface": "#e5e2e1",
|
|
41
|
-
"surface-container-low": "#1c1b1b",
|
|
42
|
-
"on-tertiary": "#003738",
|
|
43
|
-
"surface-container": "#201f1f",
|
|
44
|
-
"on-surface-variant": "#cdc7ab",
|
|
45
|
-
"on-primary-container": "#706400",
|
|
46
|
-
"on-error": "#690005",
|
|
47
|
-
"surface-container-high": "#2a2a2a",
|
|
48
|
-
"surface-container-highest": "#353534",
|
|
49
|
-
error: "#ffb4ab",
|
|
50
|
-
primary: "#fffeff",
|
|
51
|
-
"outline-variant": "#4b4732",
|
|
52
|
-
"on-error-container": "#ffdad6",
|
|
53
|
-
"on-secondary-fixed-variant": "#474746",
|
|
54
|
-
"surface-container-lowest": "#0e0e0e",
|
|
55
|
-
"inverse-surface": "#e5e2e1",
|
|
56
|
-
surface: "#131313",
|
|
57
|
-
"tertiary-container": "#04faff",
|
|
58
|
-
"on-tertiary-fixed": "#002021",
|
|
59
|
-
"tertiary-fixed": "#2dfaff",
|
|
60
|
-
"surface-variant": "#353534",
|
|
61
|
-
"on-primary": "#373100",
|
|
62
|
-
"primary-container": "#fce300",
|
|
63
|
-
"inverse-on-surface": "#313030",
|
|
64
|
-
secondary: "#c8c6c5",
|
|
65
|
-
"on-secondary-container": "#b7b5b4",
|
|
66
|
-
"on-primary-fixed": "#201c00",
|
|
67
|
-
"secondary-container": "#474746",
|
|
68
|
-
"surface-dim": "#131313",
|
|
69
|
-
"surface-bright": "#3a3939",
|
|
70
|
-
"secondary-fixed": "#e5e2e1",
|
|
71
|
-
},
|
|
72
|
-
fontFamily: {
|
|
73
|
-
headline: ["Space Grotesk"],
|
|
74
|
-
body: ["Inter"],
|
|
75
|
-
label: ["Inter"],
|
|
76
|
-
mono: ["Roboto Mono"],
|
|
77
|
-
},
|
|
78
|
-
borderRadius: {
|
|
79
|
-
DEFAULT: "0px",
|
|
80
|
-
lg: "0px",
|
|
81
|
-
xl: "0px",
|
|
82
|
-
full: "9999px",
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
</script>
|
|
88
|
-
<style type="text/tailwindcss">
|
|
89
|
-
@layer components {
|
|
90
|
-
.signature-button {
|
|
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 rgba(252, 227, 0, 0.7);
|
|
93
|
-
clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.signature-button:hover {
|
|
97
|
-
@apply -translate-y-px bg-primary-fixed text-on-primary;
|
|
98
|
-
border-color: rgba(252, 227, 0, 0.55);
|
|
99
|
-
box-shadow: 0 0 24px -8px rgba(252, 227, 0, 0.92);
|
|
100
|
-
filter: brightness(1.03);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.signature-button:disabled,
|
|
104
|
-
.signature-button[aria-disabled="true"] {
|
|
105
|
-
@apply cursor-not-allowed opacity-45;
|
|
106
|
-
box-shadow: none;
|
|
107
|
-
filter: none;
|
|
108
|
-
transform: none;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.signature-button .material-symbols-outlined {
|
|
112
|
-
@apply text-base;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.standard-button {
|
|
116
|
-
@apply inline-flex items-center justify-center gap-2 h-[var(--control-height)] min-h-[var(--control-height)] border border-outline-variant/20 bg-surface-container px-[var(--control-padding-inline)] font-mono text-[10px] font-bold uppercase tracking-[0.16em] text-on-surface transition-colors;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.standard-button:hover {
|
|
120
|
-
@apply bg-surface-container-highest text-primary-container;
|
|
121
|
-
border-color: rgba(252, 227, 0, 0.24);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.standard-button.is-active {
|
|
125
|
-
@apply bg-surface-container-high text-primary-container;
|
|
126
|
-
border-color: rgba(252, 227, 0, 0.3);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.standard-button:disabled,
|
|
130
|
-
.standard-button[aria-disabled="true"] {
|
|
131
|
-
@apply cursor-not-allowed opacity-40;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.standard-button .material-symbols-outlined {
|
|
135
|
-
@apply text-base;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.standard-checkbox {
|
|
139
|
-
@apply inline-flex w-full items-center gap-3 min-h-[var(--control-height)] border border-outline-variant/10 bg-surface-container-lowest px-[var(--control-padding-inline)] text-sm text-on-surface;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.standard-checkbox input[type="checkbox"] {
|
|
143
|
-
@apply m-0 rounded-none border-outline bg-surface-container-lowest text-primary-container focus:ring-primary-container;
|
|
144
|
-
accent-color: var(--color-primary-container);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.standard-checkbox.is-disabled,
|
|
148
|
-
.standard-checkbox:has(input:disabled) {
|
|
149
|
-
@apply opacity-45;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.delete-button {
|
|
153
|
-
@apply inline-flex items-center justify-center gap-2 h-[var(--control-height)] min-h-[var(--control-height)] border border-error/25 bg-error-container/10 px-[var(--control-padding-inline)] font-mono text-[10px] font-bold uppercase tracking-[0.16em] text-error transition-all;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.delete-button:hover {
|
|
157
|
-
@apply -translate-y-px border-error bg-error-container/20 text-error;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.delete-button:disabled,
|
|
161
|
-
.delete-button[aria-disabled="true"] {
|
|
162
|
-
@apply cursor-not-allowed opacity-45;
|
|
163
|
-
transform: none;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
</style>
|
|
9
|
+
<link href="/vendor/material-symbols/outlined.css" rel="stylesheet" />
|
|
10
|
+
<link href="styles/tailwind.generated.css" rel="stylesheet" />
|
|
167
11
|
<link href="styles/tokens.css" rel="stylesheet" />
|
|
168
12
|
<link href="styles/base.css" rel="stylesheet" />
|
|
13
|
+
<link href="styles/utilities.css" rel="stylesheet" />
|
|
169
14
|
<link href="styles/layout.css" rel="stylesheet" />
|
|
170
15
|
<link href="styles/components.css" rel="stylesheet" />
|
|
171
16
|
<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;
|
|
@@ -365,7 +367,11 @@ function syncQueryHistoryUi(historyId) {
|
|
|
365
367
|
|
|
366
368
|
const historyItem = state.editor.history.find(entry => Number(entry.id) === numericId) ?? state.editor.historyDetail ?? null;
|
|
367
369
|
const listItemNode = shellRefs.view.querySelector(
|
|
368
|
-
|
|
370
|
+
[
|
|
371
|
+
'[data-action="select-query-history-item"][data-history-id="',
|
|
372
|
+
String(numericId),
|
|
373
|
+
'"]',
|
|
374
|
+
].join(''),
|
|
369
375
|
)?.closest('.query-history-item');
|
|
370
376
|
|
|
371
377
|
if (historyItem && listItemNode instanceof HTMLElement) {
|
|
@@ -417,6 +423,188 @@ function syncQueryHistorySelectionUi(selectedHistoryId = null) {
|
|
|
417
423
|
return true;
|
|
418
424
|
}
|
|
419
425
|
|
|
426
|
+
function isEditorRouteName(routeName) {
|
|
427
|
+
return routeName === 'editor' || routeName === 'editorResults';
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function captureQueryHistoryScrollState() {
|
|
431
|
+
if (!isEditorRouteName(lastRenderedRouteName)) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const scrollNode = shellRefs.view.querySelector('[data-query-history-scroll]');
|
|
436
|
+
|
|
437
|
+
if (!(scrollNode instanceof HTMLElement)) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
committedSearch: scrollNode.dataset.queryHistoryCommittedSearch ?? '',
|
|
443
|
+
historyTab: scrollNode.dataset.queryHistoryTab ?? '',
|
|
444
|
+
routeName: lastRenderedRouteName,
|
|
445
|
+
renderedItemCount: scrollNode.querySelectorAll('.query-history-item').length,
|
|
446
|
+
renderedLoadingMore: scrollNode.dataset.queryHistoryLoadingMore === 'true',
|
|
447
|
+
searchInput: scrollNode.dataset.queryHistorySearch ?? '',
|
|
448
|
+
scrollLeft: scrollNode.scrollLeft,
|
|
449
|
+
scrollTop: scrollNode.scrollTop,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function shouldRestoreQueryHistoryScroll(snapshot, state) {
|
|
454
|
+
if (!snapshot || !isEditorRouteName(state.route.name) || snapshot.routeName !== state.route.name) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (!state.editor.historyPanelVisible) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (
|
|
463
|
+
snapshot.historyTab !== state.editor.historyTab ||
|
|
464
|
+
snapshot.searchInput !== state.editor.historySearchInput ||
|
|
465
|
+
snapshot.committedSearch !== state.editor.historySearch
|
|
466
|
+
) {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return (
|
|
471
|
+
state.editor.historyLoadingMore ||
|
|
472
|
+
snapshot.renderedLoadingMore ||
|
|
473
|
+
state.editor.history.length > snapshot.renderedItemCount
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function restoreQueryHistoryScrollState(snapshot) {
|
|
478
|
+
const scrollNode = shellRefs.view.querySelector('[data-query-history-scroll]');
|
|
479
|
+
|
|
480
|
+
if (!(scrollNode instanceof HTMLElement)) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
scrollNode.scrollLeft = snapshot.scrollLeft;
|
|
485
|
+
scrollNode.scrollTop = snapshot.scrollTop;
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function buildChartsHistorySignature(state) {
|
|
490
|
+
if (state.route.name !== 'charts') {
|
|
491
|
+
return '';
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const historyVisible = state.editor.historyPanelVisible !== false || !state.charts.selectedHistoryId;
|
|
495
|
+
|
|
496
|
+
if (!historyVisible) {
|
|
497
|
+
return 'charts-history:hidden';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const queries = state.charts.queries ?? [];
|
|
501
|
+
|
|
502
|
+
if (!queries.length) {
|
|
503
|
+
if (state.charts.loading) {
|
|
504
|
+
return 'charts-history:loading-empty';
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (state.charts.error) {
|
|
508
|
+
return `charts-history:error:${state.charts.error.code}:${state.charts.error.message}`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return 'charts-history:empty';
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return JSON.stringify({
|
|
515
|
+
tab: state.charts.historyTab ?? 'recent',
|
|
516
|
+
queries: queries.map(item => [
|
|
517
|
+
item.id,
|
|
518
|
+
item.displayTitle,
|
|
519
|
+
item.previewSql,
|
|
520
|
+
Boolean(item.isSaved),
|
|
521
|
+
Array.isArray(item.chartTypes) ? item.chartTypes.join(',') : '',
|
|
522
|
+
]),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function syncChartsHistorySelectionUi(state) {
|
|
527
|
+
const selectedHistoryId = String(state.charts.selectedHistoryId ?? '');
|
|
528
|
+
const historyButtons = shellRefs.view.querySelectorAll(
|
|
529
|
+
'.charts-view__sidebar [data-action="navigate"][data-history-id]',
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
for (const button of historyButtons) {
|
|
533
|
+
if (!(button instanceof HTMLElement)) {
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const itemNode = button.closest('[data-charts-history-item]');
|
|
538
|
+
const isSelected = button.dataset.historyId === selectedHistoryId;
|
|
539
|
+
const titleNode = button.querySelector('[data-charts-history-title]');
|
|
540
|
+
const selectionNode = itemNode instanceof HTMLElement ? itemNode : button;
|
|
541
|
+
|
|
542
|
+
selectionNode.classList.toggle('border-primary-container/30', isSelected);
|
|
543
|
+
selectionNode.classList.toggle('bg-surface-container-high', isSelected);
|
|
544
|
+
selectionNode.classList.toggle('border-outline-variant/10', !isSelected);
|
|
545
|
+
selectionNode.classList.toggle('bg-surface-container-lowest', !isSelected);
|
|
546
|
+
selectionNode.classList.toggle('hover:bg-surface-container-high', !isSelected);
|
|
547
|
+
|
|
548
|
+
if (titleNode instanceof HTMLElement) {
|
|
549
|
+
titleNode.classList.toggle('text-primary-container', isSelected);
|
|
550
|
+
titleNode.classList.toggle('text-on-surface', !isSelected);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function syncChartsSavedToggleUi(actionNode, nextValue) {
|
|
558
|
+
const itemNode = actionNode.closest('[data-charts-history-item]');
|
|
559
|
+
|
|
560
|
+
actionNode.classList.toggle('is-active', nextValue);
|
|
561
|
+
actionNode.dataset.nextValue = nextValue ? 'false' : 'true';
|
|
562
|
+
actionNode.title = nextValue ? 'Remove from saved' : 'Save query';
|
|
563
|
+
|
|
564
|
+
const iconNode = actionNode.querySelector('.material-symbols-outlined');
|
|
565
|
+
if (iconNode instanceof HTMLElement) {
|
|
566
|
+
iconNode.textContent = nextValue ? 'bookmark' : 'bookmark_add';
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (itemNode instanceof HTMLElement) {
|
|
570
|
+
itemNode.querySelector('[data-charts-saved-badge]')?.toggleAttribute('hidden', !nextValue);
|
|
571
|
+
|
|
572
|
+
if (!nextValue && getState().charts.historyTab === 'saved') {
|
|
573
|
+
itemNode.remove();
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const countNode = shellRefs.view.querySelector('[data-charts-history-count]');
|
|
578
|
+
if (countNode instanceof HTMLElement) {
|
|
579
|
+
const state = getState();
|
|
580
|
+
const count =
|
|
581
|
+
state.charts.historyTab === 'saved'
|
|
582
|
+
? (state.charts.queries ?? []).filter(item => item.isSaved).length
|
|
583
|
+
: (state.charts.queries ?? []).length;
|
|
584
|
+
countNode.textContent = String(count);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function patchChartsDetailUi(state) {
|
|
589
|
+
const chartsView = shellRefs.view.querySelector('.charts-view');
|
|
590
|
+
const detailNode = chartsView?.querySelector('.charts-view__detail');
|
|
591
|
+
|
|
592
|
+
if (!(chartsView instanceof HTMLElement) || !(detailNode instanceof HTMLElement)) {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const historyVisible = state.editor.historyPanelVisible !== false || !state.charts.selectedHistoryId;
|
|
597
|
+
const sidebarNode = chartsView.querySelector('.charts-view__sidebar');
|
|
598
|
+
|
|
599
|
+
if (Boolean(sidebarNode) !== historyVisible) {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
detailNode.innerHTML = renderChartsDetail(state);
|
|
604
|
+
syncChartsHistorySelectionUi(state);
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
|
|
420
608
|
function readFileAsBase64(file) {
|
|
421
609
|
return new Promise((resolve, reject) => {
|
|
422
610
|
const reader = new FileReader();
|
|
@@ -755,6 +943,7 @@ function renderApp(state) {
|
|
|
755
943
|
const statusBarMarkup = renderStatusBar(state);
|
|
756
944
|
const modalMarkup = renderModal(state);
|
|
757
945
|
const toastMarkup = renderToasts(state.toasts);
|
|
946
|
+
const chartsHistorySignature = buildChartsHistorySignature(state);
|
|
758
947
|
const isLockedRoute = [
|
|
759
948
|
'editor',
|
|
760
949
|
'editorResults',
|
|
@@ -772,6 +961,7 @@ function renderApp(state) {
|
|
|
772
961
|
const mainChanged = main !== lastRenderedMainMarkup;
|
|
773
962
|
const panelChanged = panel !== lastRenderedPanelMarkup;
|
|
774
963
|
const modalChanged = modalMarkup !== lastRenderedModalMarkup;
|
|
964
|
+
const chartsHistoryChanged = chartsHistorySignature !== lastRenderedChartsHistorySignature;
|
|
775
965
|
const panelOpenChanged = panelOpen !== lastRenderedPanelOpen;
|
|
776
966
|
const lockedRouteChanged = isLockedRoute !== lastRenderedLockedRoute;
|
|
777
967
|
const shellMarkupUnchanged =
|
|
@@ -796,6 +986,7 @@ function renderApp(state) {
|
|
|
796
986
|
}
|
|
797
987
|
|
|
798
988
|
const focusedInput = captureFocusedInputState();
|
|
989
|
+
const queryHistoryScrollState = captureQueryHistoryScrollState();
|
|
799
990
|
const isEnteringNewTableDesignerRoute =
|
|
800
991
|
state.route.name === 'tableDesigner' && state.route.params?.isNew && previousRoutePath !== state.route.path;
|
|
801
992
|
|
|
@@ -805,9 +996,20 @@ function renderApp(state) {
|
|
|
805
996
|
pendingNewTableDesignerAutofocus = false;
|
|
806
997
|
}
|
|
807
998
|
|
|
808
|
-
|
|
809
|
-
|
|
999
|
+
const canPatchChartsMain =
|
|
1000
|
+
mainChanged && previousRouteName === 'charts' && state.route.name === 'charts' && !chartsHistoryChanged;
|
|
1001
|
+
let mainPatched = false;
|
|
1002
|
+
|
|
1003
|
+
if (canPatchChartsMain) {
|
|
810
1004
|
teardownQueryChartRenderer();
|
|
1005
|
+
mainPatched = patchChartsDetailUi(state);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (mainChanged) {
|
|
1009
|
+
if (!mainPatched) {
|
|
1010
|
+
teardownStructureGraph();
|
|
1011
|
+
teardownQueryChartRenderer();
|
|
1012
|
+
}
|
|
811
1013
|
}
|
|
812
1014
|
|
|
813
1015
|
if (topNavChanged) {
|
|
@@ -829,7 +1031,7 @@ function renderApp(state) {
|
|
|
829
1031
|
shellRefs.statusBar.innerHTML = statusBarMarkup;
|
|
830
1032
|
}
|
|
831
1033
|
|
|
832
|
-
if (mainChanged) {
|
|
1034
|
+
if (mainChanged && !mainPatched) {
|
|
833
1035
|
shellRefs.view.innerHTML = main;
|
|
834
1036
|
}
|
|
835
1037
|
|
|
@@ -853,6 +1055,10 @@ function renderApp(state) {
|
|
|
853
1055
|
shellRefs.shell.classList.toggle('panel-open', panelOpen);
|
|
854
1056
|
}
|
|
855
1057
|
|
|
1058
|
+
if (shouldRestoreQueryHistoryScroll(queryHistoryScrollState, state)) {
|
|
1059
|
+
restoreQueryHistoryScrollState(queryHistoryScrollState);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
856
1062
|
if (pendingQueryEditorFocus && (state.route.name === 'editor' || state.route.name === 'editorResults')) {
|
|
857
1063
|
if (focusQueryEditorInput()) {
|
|
858
1064
|
pendingQueryEditorFocus = false;
|
|
@@ -883,6 +1089,7 @@ function renderApp(state) {
|
|
|
883
1089
|
lastRenderedPanelMarkup = panel;
|
|
884
1090
|
lastRenderedModalMarkup = modalMarkup;
|
|
885
1091
|
lastRenderedToastMarkup = toastMarkup;
|
|
1092
|
+
lastRenderedChartsHistorySignature = chartsHistorySignature;
|
|
886
1093
|
lastRenderedPanelOpen = panelOpen;
|
|
887
1094
|
lastRenderedLockedRoute = isLockedRoute;
|
|
888
1095
|
|
|
@@ -1063,6 +1270,20 @@ async function handleAction(actionNode) {
|
|
|
1063
1270
|
);
|
|
1064
1271
|
}
|
|
1065
1272
|
return;
|
|
1273
|
+
case 'toggle-charts-query-history-saved':
|
|
1274
|
+
if (actionNode.dataset.historyId) {
|
|
1275
|
+
const nextValue = actionNode.dataset.nextValue === 'true';
|
|
1276
|
+
const updated = await toggleQueryHistorySavedState(actionNode.dataset.historyId, nextValue, {
|
|
1277
|
+
notify: false,
|
|
1278
|
+
refresh: false,
|
|
1279
|
+
toast: false,
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
if (updated) {
|
|
1283
|
+
syncChartsSavedToggleUi(actionNode, nextValue);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
return;
|
|
1066
1287
|
case 'toggle-charts-sql-panel':
|
|
1067
1288
|
toggleChartsSqlPanel();
|
|
1068
1289
|
return;
|
|
@@ -1074,6 +1295,11 @@ async function handleAction(actionNode) {
|
|
|
1074
1295
|
setChartsHeightPreset(actionNode.dataset.preset);
|
|
1075
1296
|
}
|
|
1076
1297
|
return;
|
|
1298
|
+
case 'set-charts-history-tab':
|
|
1299
|
+
if (actionNode.dataset.tab) {
|
|
1300
|
+
setChartsHistoryTab(actionNode.dataset.tab);
|
|
1301
|
+
}
|
|
1302
|
+
return;
|
|
1077
1303
|
case 'export-query-chart-png':
|
|
1078
1304
|
if (actionNode.dataset.chartId && !exportQueryChartAsPng(actionNode.dataset.chartId)) {
|
|
1079
1305
|
showToast('The selected chart is not ready for PNG export.', 'alert');
|