sqlite-hub 0.9.0 → 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 +12 -0
- package/frontend/index.html +21 -20
- package/frontend/js/app.js +386 -8
- package/frontend/js/components/modal.js +13 -4
- package/frontend/js/components/queryHistoryDetail.js +14 -2
- package/frontend/js/components/queryHistoryPanel.js +13 -9
- 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 +177 -21
- 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/js/views/mediaTagging.js +146 -76
- package/frontend/styles/base.css +57 -13
- package/frontend/styles/components.css +172 -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 +218 -96
- package/package.json +1 -1
- package/server/services/storage/appStateStore.js +9 -6
- package/server/services/storage/queryHistoryChartUtils.js +19 -2
- package/server/services/storage/queryHistoryUtils.js +165 -2
- package/shortkeys.md +5 -0
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,
|
|
@@ -87,6 +88,7 @@ import {
|
|
|
87
88
|
toggleChartsResultsPanel,
|
|
88
89
|
toggleChartsSqlPanel,
|
|
89
90
|
setMediaTaggingWorkflowMediaDetailsVisible,
|
|
91
|
+
setMediaTaggingWorkflowMediaRotationDegrees,
|
|
90
92
|
queueTableDesignerCsvImport,
|
|
91
93
|
showToast,
|
|
92
94
|
submitCreateConnection,
|
|
@@ -110,7 +112,7 @@ import {
|
|
|
110
112
|
applyCurrentMediaTaggingSelection,
|
|
111
113
|
removeCurrentTableDesignerColumn,
|
|
112
114
|
} from './store.js';
|
|
113
|
-
import { renderChartsView } from './views/charts.js';
|
|
115
|
+
import { renderChartsDetail, renderChartsView } from './views/charts.js';
|
|
114
116
|
import { renderConnectionsView } from './views/connections.js';
|
|
115
117
|
import { renderDataRowEditorPanel, renderDataView } from './views/data.js';
|
|
116
118
|
import { renderEditorView } from './views/editor.js';
|
|
@@ -137,6 +139,7 @@ const shellRefs = {
|
|
|
137
139
|
toast: document.querySelector('#toast-root'),
|
|
138
140
|
};
|
|
139
141
|
let lastRenderedRoutePath = null;
|
|
142
|
+
let lastRenderedRouteName = null;
|
|
140
143
|
let lastRenderedTopNavMarkup = '';
|
|
141
144
|
let lastRenderedSidebarMarkup = '';
|
|
142
145
|
let lastRenderedStatusBarMarkup = '';
|
|
@@ -144,15 +147,21 @@ let lastRenderedMainMarkup = '';
|
|
|
144
147
|
let lastRenderedPanelMarkup = '';
|
|
145
148
|
let lastRenderedModalMarkup = '';
|
|
146
149
|
let lastRenderedToastMarkup = '';
|
|
150
|
+
let lastRenderedChartsHistorySignature = '';
|
|
147
151
|
let lastRenderedPanelOpen = false;
|
|
148
152
|
let lastRenderedLockedRoute = false;
|
|
149
153
|
let pendingNewTableDesignerAutofocus = false;
|
|
150
154
|
let pendingQueryEditorFocus = false;
|
|
155
|
+
let pendingMediaTaggingTagSearchFocus = false;
|
|
151
156
|
|
|
152
157
|
function invalidateMainRenderCache() {
|
|
153
158
|
lastRenderedMainMarkup = null;
|
|
154
159
|
}
|
|
155
160
|
|
|
161
|
+
function isMediaTaggingRouteName(routeName) {
|
|
162
|
+
return routeName === 'mediaTaggingSetup' || routeName === 'mediaTaggingQueue';
|
|
163
|
+
}
|
|
164
|
+
|
|
156
165
|
function resetStructureGraphForDatabaseChange() {
|
|
157
166
|
resetPersistedStructureGraphState();
|
|
158
167
|
}
|
|
@@ -258,6 +267,45 @@ function syncMediaTaggingCurrentMediaUi(button, detailsVisible) {
|
|
|
258
267
|
return true;
|
|
259
268
|
}
|
|
260
269
|
|
|
270
|
+
function normalizeMediaTaggingRotationDegrees(value) {
|
|
271
|
+
const numericValue = Number(value);
|
|
272
|
+
|
|
273
|
+
if (!Number.isFinite(numericValue)) {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return ((Math.round(numericValue / 90) * 90) % 360 + 360) % 360;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function syncMediaTaggingMediaRotationUi(node, rotationDegrees) {
|
|
281
|
+
if (!(node instanceof HTMLElement)) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const preview = node.closest('.media-tagging-preview');
|
|
286
|
+
const target = preview?.querySelector('[data-media-tagging-rotation-target]');
|
|
287
|
+
|
|
288
|
+
if (!(preview instanceof HTMLElement) || !(target instanceof HTMLElement)) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const normalizedRotation = normalizeMediaTaggingRotationDegrees(rotationDegrees);
|
|
293
|
+
|
|
294
|
+
target.style.setProperty('--media-tagging-preview-rotation', `${normalizedRotation}deg`);
|
|
295
|
+
target.dataset.rotationDegrees = String(normalizedRotation);
|
|
296
|
+
target.classList.toggle('is-rotated-quarter', normalizedRotation === 90 || normalizedRotation === 270);
|
|
297
|
+
|
|
298
|
+
const resetButton = preview.querySelector(
|
|
299
|
+
'[data-action="rotate-media-tagging-current-media"][data-rotation-command="reset"]',
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
if (resetButton instanceof HTMLButtonElement) {
|
|
303
|
+
resetButton.disabled = normalizedRotation === 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
261
309
|
function syncMediaTaggingTagSearchUi(input) {
|
|
262
310
|
if (!(input instanceof HTMLInputElement)) {
|
|
263
311
|
return false;
|
|
@@ -371,6 +419,188 @@ function syncQueryHistorySelectionUi(selectedHistoryId = null) {
|
|
|
371
419
|
return true;
|
|
372
420
|
}
|
|
373
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
|
+
|
|
374
604
|
function readFileAsBase64(file) {
|
|
375
605
|
return new Promise((resolve, reject) => {
|
|
376
606
|
const reader = new FileReader();
|
|
@@ -615,6 +845,52 @@ function focusQueryEditorInput() {
|
|
|
615
845
|
return true;
|
|
616
846
|
}
|
|
617
847
|
|
|
848
|
+
function focusMediaTaggingTagSearchInput() {
|
|
849
|
+
const input = document.querySelector('[data-bind="media-tagging-tag-search"]');
|
|
850
|
+
|
|
851
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
input.focus({ preventScroll: true });
|
|
856
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function syncSidebarActiveRoute(routeName) {
|
|
861
|
+
if (!isMediaTaggingRouteName(routeName)) {
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const mediaTaggingLink = shellRefs.sidebar.querySelector('a.sidebar-link[data-group="mediaTagging"]');
|
|
866
|
+
const setupLink = shellRefs.sidebar.querySelector('a.sidebar-sublink[href="#/media-tagging"]');
|
|
867
|
+
const queueLink = shellRefs.sidebar.querySelector('a.sidebar-sublink[href="#/media-tagging/queue"]');
|
|
868
|
+
|
|
869
|
+
if (
|
|
870
|
+
!(mediaTaggingLink instanceof HTMLAnchorElement) ||
|
|
871
|
+
!(setupLink instanceof HTMLAnchorElement) ||
|
|
872
|
+
!(queueLink instanceof HTMLAnchorElement)
|
|
873
|
+
) {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
mediaTaggingLink.classList.add('is-active');
|
|
878
|
+
setupLink.classList.toggle('is-active', routeName === 'mediaTaggingSetup');
|
|
879
|
+
queueLink.classList.toggle('is-active', routeName === 'mediaTaggingQueue');
|
|
880
|
+
return true;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
async function applyMediaTaggingAndFocusSearch() {
|
|
884
|
+
pendingMediaTaggingTagSearchFocus = true;
|
|
885
|
+
const result = await applyCurrentMediaTaggingSelection();
|
|
886
|
+
|
|
887
|
+
if (!result) {
|
|
888
|
+
pendingMediaTaggingTagSearchFocus = false;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return result;
|
|
892
|
+
}
|
|
893
|
+
|
|
618
894
|
async function handleTableDesignerCsvImport(fileInput) {
|
|
619
895
|
if (!(fileInput instanceof HTMLInputElement)) {
|
|
620
896
|
return;
|
|
@@ -656,12 +932,14 @@ async function handleTableDesignerCsvImport(fileInput) {
|
|
|
656
932
|
|
|
657
933
|
function renderApp(state) {
|
|
658
934
|
const previousRoutePath = lastRenderedRoutePath;
|
|
935
|
+
const previousRouteName = lastRenderedRouteName;
|
|
659
936
|
const { main, panel } = resolveView(state);
|
|
660
937
|
const topNavMarkup = renderTopNav(state);
|
|
661
938
|
const sidebarMarkup = renderSidebar(state);
|
|
662
939
|
const statusBarMarkup = renderStatusBar(state);
|
|
663
940
|
const modalMarkup = renderModal(state);
|
|
664
941
|
const toastMarkup = renderToasts(state.toasts);
|
|
942
|
+
const chartsHistorySignature = buildChartsHistorySignature(state);
|
|
665
943
|
const isLockedRoute = [
|
|
666
944
|
'editor',
|
|
667
945
|
'editorResults',
|
|
@@ -679,6 +957,7 @@ function renderApp(state) {
|
|
|
679
957
|
const mainChanged = main !== lastRenderedMainMarkup;
|
|
680
958
|
const panelChanged = panel !== lastRenderedPanelMarkup;
|
|
681
959
|
const modalChanged = modalMarkup !== lastRenderedModalMarkup;
|
|
960
|
+
const chartsHistoryChanged = chartsHistorySignature !== lastRenderedChartsHistorySignature;
|
|
682
961
|
const panelOpenChanged = panelOpen !== lastRenderedPanelOpen;
|
|
683
962
|
const lockedRouteChanged = isLockedRoute !== lastRenderedLockedRoute;
|
|
684
963
|
const shellMarkupUnchanged =
|
|
@@ -703,6 +982,7 @@ function renderApp(state) {
|
|
|
703
982
|
}
|
|
704
983
|
|
|
705
984
|
const focusedInput = captureFocusedInputState();
|
|
985
|
+
const queryHistoryScrollState = captureQueryHistoryScrollState();
|
|
706
986
|
const isEnteringNewTableDesignerRoute =
|
|
707
987
|
state.route.name === 'tableDesigner' && state.route.params?.isNew && previousRoutePath !== state.route.path;
|
|
708
988
|
|
|
@@ -712,9 +992,20 @@ function renderApp(state) {
|
|
|
712
992
|
pendingNewTableDesignerAutofocus = false;
|
|
713
993
|
}
|
|
714
994
|
|
|
715
|
-
|
|
716
|
-
|
|
995
|
+
const canPatchChartsMain =
|
|
996
|
+
mainChanged && previousRouteName === 'charts' && state.route.name === 'charts' && !chartsHistoryChanged;
|
|
997
|
+
let mainPatched = false;
|
|
998
|
+
|
|
999
|
+
if (canPatchChartsMain) {
|
|
717
1000
|
teardownQueryChartRenderer();
|
|
1001
|
+
mainPatched = patchChartsDetailUi(state);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (mainChanged) {
|
|
1005
|
+
if (!mainPatched) {
|
|
1006
|
+
teardownStructureGraph();
|
|
1007
|
+
teardownQueryChartRenderer();
|
|
1008
|
+
}
|
|
718
1009
|
}
|
|
719
1010
|
|
|
720
1011
|
if (topNavChanged) {
|
|
@@ -722,14 +1013,21 @@ function renderApp(state) {
|
|
|
722
1013
|
}
|
|
723
1014
|
|
|
724
1015
|
if (sidebarChanged) {
|
|
725
|
-
|
|
1016
|
+
const sidebarSynced =
|
|
1017
|
+
isMediaTaggingRouteName(previousRouteName) &&
|
|
1018
|
+
isMediaTaggingRouteName(state.route.name) &&
|
|
1019
|
+
syncSidebarActiveRoute(state.route.name);
|
|
1020
|
+
|
|
1021
|
+
if (!sidebarSynced) {
|
|
1022
|
+
shellRefs.sidebar.innerHTML = sidebarMarkup;
|
|
1023
|
+
}
|
|
726
1024
|
}
|
|
727
1025
|
|
|
728
1026
|
if (statusBarChanged) {
|
|
729
1027
|
shellRefs.statusBar.innerHTML = statusBarMarkup;
|
|
730
1028
|
}
|
|
731
1029
|
|
|
732
|
-
if (mainChanged) {
|
|
1030
|
+
if (mainChanged && !mainPatched) {
|
|
733
1031
|
shellRefs.view.innerHTML = main;
|
|
734
1032
|
}
|
|
735
1033
|
|
|
@@ -753,10 +1051,18 @@ function renderApp(state) {
|
|
|
753
1051
|
shellRefs.shell.classList.toggle('panel-open', panelOpen);
|
|
754
1052
|
}
|
|
755
1053
|
|
|
1054
|
+
if (shouldRestoreQueryHistoryScroll(queryHistoryScrollState, state)) {
|
|
1055
|
+
restoreQueryHistoryScrollState(queryHistoryScrollState);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
756
1058
|
if (pendingQueryEditorFocus && (state.route.name === 'editor' || state.route.name === 'editorResults')) {
|
|
757
1059
|
if (focusQueryEditorInput()) {
|
|
758
1060
|
pendingQueryEditorFocus = false;
|
|
759
1061
|
}
|
|
1062
|
+
} else if (pendingMediaTaggingTagSearchFocus && state.route.name === 'mediaTaggingQueue') {
|
|
1063
|
+
if (focusMediaTaggingTagSearchInput()) {
|
|
1064
|
+
pendingMediaTaggingTagSearchFocus = false;
|
|
1065
|
+
}
|
|
760
1066
|
} else if (
|
|
761
1067
|
pendingNewTableDesignerAutofocus &&
|
|
762
1068
|
state.route.name === 'tableDesigner' &&
|
|
@@ -771,6 +1077,7 @@ function renderApp(state) {
|
|
|
771
1077
|
}
|
|
772
1078
|
|
|
773
1079
|
lastRenderedRoutePath = state.route.path;
|
|
1080
|
+
lastRenderedRouteName = state.route.name;
|
|
774
1081
|
lastRenderedTopNavMarkup = topNavMarkup;
|
|
775
1082
|
lastRenderedSidebarMarkup = sidebarMarkup;
|
|
776
1083
|
lastRenderedStatusBarMarkup = statusBarMarkup;
|
|
@@ -778,6 +1085,7 @@ function renderApp(state) {
|
|
|
778
1085
|
lastRenderedPanelMarkup = panel;
|
|
779
1086
|
lastRenderedModalMarkup = modalMarkup;
|
|
780
1087
|
lastRenderedToastMarkup = toastMarkup;
|
|
1088
|
+
lastRenderedChartsHistorySignature = chartsHistorySignature;
|
|
781
1089
|
lastRenderedPanelOpen = panelOpen;
|
|
782
1090
|
lastRenderedLockedRoute = isLockedRoute;
|
|
783
1091
|
|
|
@@ -958,6 +1266,20 @@ async function handleAction(actionNode) {
|
|
|
958
1266
|
);
|
|
959
1267
|
}
|
|
960
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;
|
|
961
1283
|
case 'toggle-charts-sql-panel':
|
|
962
1284
|
toggleChartsSqlPanel();
|
|
963
1285
|
return;
|
|
@@ -969,6 +1291,11 @@ async function handleAction(actionNode) {
|
|
|
969
1291
|
setChartsHeightPreset(actionNode.dataset.preset);
|
|
970
1292
|
}
|
|
971
1293
|
return;
|
|
1294
|
+
case 'set-charts-history-tab':
|
|
1295
|
+
if (actionNode.dataset.tab) {
|
|
1296
|
+
setChartsHistoryTab(actionNode.dataset.tab);
|
|
1297
|
+
}
|
|
1298
|
+
return;
|
|
972
1299
|
case 'export-query-chart-png':
|
|
973
1300
|
if (actionNode.dataset.chartId && !exportQueryChartAsPng(actionNode.dataset.chartId)) {
|
|
974
1301
|
showToast('The selected chart is not ready for PNG export.', 'alert');
|
|
@@ -1071,7 +1398,7 @@ async function handleAction(actionNode) {
|
|
|
1071
1398
|
await resetSkippedMediaTaggingItems();
|
|
1072
1399
|
return;
|
|
1073
1400
|
case 'apply-media-tagging':
|
|
1074
|
-
await
|
|
1401
|
+
await applyMediaTaggingAndFocusSearch();
|
|
1075
1402
|
return;
|
|
1076
1403
|
case 'toggle-media-tagging-current-media':
|
|
1077
1404
|
if (actionNode instanceof HTMLButtonElement) {
|
|
@@ -1080,6 +1407,20 @@ async function handleAction(actionNode) {
|
|
|
1080
1407
|
setMediaTaggingWorkflowMediaDetailsVisible(nextValue, { notify: false });
|
|
1081
1408
|
}
|
|
1082
1409
|
return;
|
|
1410
|
+
case 'rotate-media-tagging-current-media': {
|
|
1411
|
+
const currentRotation = getState().mediaTagging.workflowMediaRotationDegrees ?? 0;
|
|
1412
|
+
const command = actionNode.dataset.rotationCommand;
|
|
1413
|
+
const nextRotation =
|
|
1414
|
+
command === 'left'
|
|
1415
|
+
? currentRotation - 90
|
|
1416
|
+
: command === 'right'
|
|
1417
|
+
? currentRotation + 90
|
|
1418
|
+
: 0;
|
|
1419
|
+
|
|
1420
|
+
syncMediaTaggingMediaRotationUi(actionNode, nextRotation);
|
|
1421
|
+
setMediaTaggingWorkflowMediaRotationDegrees(nextRotation, { notify: false });
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1083
1424
|
case 'open-media-tagging-current-in-data': {
|
|
1084
1425
|
const currentState = getState();
|
|
1085
1426
|
const mediaTableName = currentState.mediaTagging.draft?.mediaTable ?? '';
|
|
@@ -1093,6 +1434,17 @@ async function handleAction(actionNode) {
|
|
|
1093
1434
|
router.navigate(`/data/${encodeURIComponent(mediaTableName)}`);
|
|
1094
1435
|
return;
|
|
1095
1436
|
}
|
|
1437
|
+
case 'open-media-tagging-current-in-structure': {
|
|
1438
|
+
const mediaTableName = String(getState().mediaTagging.draft?.mediaTable ?? '').trim();
|
|
1439
|
+
|
|
1440
|
+
if (!mediaTableName) {
|
|
1441
|
+
showToast('The current media table could not be opened in Structure.', 'alert');
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
router.navigate(`/structure/${encodeURIComponent(mediaTableName)}`);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1096
1448
|
case 'import-table-designer-csv': {
|
|
1097
1449
|
const fileInput = document.querySelector('[data-bind="table-designer-import-file"]');
|
|
1098
1450
|
|
|
@@ -1149,6 +1501,19 @@ async function handleAction(actionNode) {
|
|
|
1149
1501
|
}
|
|
1150
1502
|
}
|
|
1151
1503
|
|
|
1504
|
+
function isMediaTaggingMenuActive(routeName) {
|
|
1505
|
+
return routeName === 'mediaTaggingSetup' || routeName === 'mediaTaggingQueue';
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function canApplyMediaTaggingShortcut(state) {
|
|
1509
|
+
return (
|
|
1510
|
+
isMediaTaggingMenuActive(state.route.name) &&
|
|
1511
|
+
Boolean(state.mediaTagging.workflow?.currentItem) &&
|
|
1512
|
+
!state.mediaTagging.applying &&
|
|
1513
|
+
(state.mediaTagging.selectedTagKeys?.length ?? 0) > 0
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1152
1517
|
document.addEventListener('click', event => {
|
|
1153
1518
|
const actionNode = event.target.closest('[data-action]');
|
|
1154
1519
|
|
|
@@ -1161,6 +1526,7 @@ document.addEventListener('click', event => {
|
|
|
1161
1526
|
|
|
1162
1527
|
document.addEventListener('keydown', event => {
|
|
1163
1528
|
const target = event.target;
|
|
1529
|
+
const state = getState();
|
|
1164
1530
|
|
|
1165
1531
|
// Handle Enter key in tag form fields to trigger create tag
|
|
1166
1532
|
if (
|
|
@@ -1181,6 +1547,20 @@ document.addEventListener('keydown', event => {
|
|
|
1181
1547
|
return;
|
|
1182
1548
|
}
|
|
1183
1549
|
|
|
1550
|
+
if (
|
|
1551
|
+
(event.key === 'Enter' || event.code === 'Enter' || event.code === 'NumpadEnter') &&
|
|
1552
|
+
event.shiftKey &&
|
|
1553
|
+
!event.altKey &&
|
|
1554
|
+
!event.ctrlKey &&
|
|
1555
|
+
!event.metaKey &&
|
|
1556
|
+
!event.defaultPrevented &&
|
|
1557
|
+
canApplyMediaTaggingShortcut(state)
|
|
1558
|
+
) {
|
|
1559
|
+
event.preventDefault();
|
|
1560
|
+
void applyMediaTaggingAndFocusSearch();
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1184
1564
|
if (
|
|
1185
1565
|
event.key === 'Enter' &&
|
|
1186
1566
|
event.shiftKey &&
|
|
@@ -1204,8 +1584,6 @@ document.addEventListener('keydown', event => {
|
|
|
1204
1584
|
return;
|
|
1205
1585
|
}
|
|
1206
1586
|
|
|
1207
|
-
const state = getState();
|
|
1208
|
-
|
|
1209
1587
|
if (state.modal) {
|
|
1210
1588
|
event.preventDefault();
|
|
1211
1589
|
closeModal();
|
|
@@ -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
|
})}
|
|
@@ -23,6 +23,18 @@ function canOpenQueryHistoryInCharts(item) {
|
|
|
23
23
|
return Boolean(item?.chartsEligible);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function getQueryTypeTone(queryType) {
|
|
27
|
+
if (queryType === "select" || queryType === "update") {
|
|
28
|
+
return "success";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (queryType === "pragma") {
|
|
32
|
+
return "primary";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return "muted";
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
function renderRunItem(run) {
|
|
27
39
|
return `
|
|
28
40
|
<div class="border border-outline-variant/10 bg-surface-container px-3 py-3">
|
|
@@ -135,9 +147,9 @@ export function renderQueryHistoryDetail({
|
|
|
135
147
|
</button>
|
|
136
148
|
</div>
|
|
137
149
|
<div class="mt-4 flex flex-wrap gap-2">
|
|
138
|
-
${renderStatusBadge(item.queryType, item.
|
|
150
|
+
${renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType))}
|
|
139
151
|
${item.isSaved ? renderStatusBadge("saved", "primary") : ""}
|
|
140
|
-
${item.isDestructive ? renderStatusBadge("destructive", "
|
|
152
|
+
${item.isDestructive ? renderStatusBadge("destructive", "warning") : ""}
|
|
141
153
|
${item.lastRun ? renderStatusBadge(item.lastRun.status, item.lastRun.status === "error" ? "alert" : "success") : ""}
|
|
142
154
|
</div>
|
|
143
155
|
</div>
|
|
@@ -4,12 +4,8 @@ import {
|
|
|
4
4
|
} from "../utils/format.js";
|
|
5
5
|
import { renderStatusBadge } from "./badges.js";
|
|
6
6
|
|
|
7
|
-
function getQueryTypeTone(queryType
|
|
8
|
-
if (
|
|
9
|
-
return "alert";
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (queryType === "select") {
|
|
7
|
+
function getQueryTypeTone(queryType) {
|
|
8
|
+
if (queryType === "select" || queryType === "update") {
|
|
13
9
|
return "success";
|
|
14
10
|
}
|
|
15
11
|
|
|
@@ -39,9 +35,9 @@ export function renderQueryHistoryListItem(item, activeHistoryId, selectedHistor
|
|
|
39
35
|
<span class="truncate font-headline text-sm font-bold uppercase tracking-tight text-on-surface">
|
|
40
36
|
${escapeHtml(item.displayTitle)}
|
|
41
37
|
</span>
|
|
42
|
-
${renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType
|
|
38
|
+
${renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType))}
|
|
43
39
|
${item.isSaved ? renderStatusBadge("saved", "primary") : ""}
|
|
44
|
-
${item.isDestructive ? renderStatusBadge("destructive", "
|
|
40
|
+
${item.isDestructive ? renderStatusBadge("destructive", "warning") : ""}
|
|
45
41
|
</div>
|
|
46
42
|
<p class="query-history-sql-preview mt-2 text-left font-mono text-xs leading-5 text-on-surface-variant/75">
|
|
47
43
|
${escapeHtml(item.previewSql)}
|
|
@@ -143,6 +139,7 @@ export function renderQueryHistoryPanel({
|
|
|
143
139
|
error = null,
|
|
144
140
|
activeTab = "recent",
|
|
145
141
|
search = "",
|
|
142
|
+
committedSearch = "",
|
|
146
143
|
total = 0,
|
|
147
144
|
hasMore = false,
|
|
148
145
|
activeHistoryId = null,
|
|
@@ -180,7 +177,14 @@ export function renderQueryHistoryPanel({
|
|
|
180
177
|
/>
|
|
181
178
|
</label>
|
|
182
179
|
</div>
|
|
183
|
-
<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
|
+
>
|
|
184
188
|
${
|
|
185
189
|
error
|
|
186
190
|
? `
|