sqlite-hub 0.9.12 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -385,6 +385,13 @@ export function updateDataTableRow(tableName, payload) {
385
385
  });
386
386
  }
387
387
 
388
+ export function previewDataTableRowUpdate(tableName, payload) {
389
+ return request(`/api/data/${encodeURIComponent(tableName)}/rows/preview-update`, {
390
+ method: "POST",
391
+ body: payload,
392
+ });
393
+ }
394
+
388
395
  export function deleteDataTableRow(tableName, payload) {
389
396
  return request(`/api/data/${encodeURIComponent(tableName)}/rows`, {
390
397
  method: "DELETE",
@@ -49,6 +49,8 @@ import {
49
49
  openEditConnectionModal,
50
50
  openCreateQueryChartModal,
51
51
  openEditQueryChartModal,
52
+ openDataRowUpdatePreview,
53
+ openEditorRowUpdatePreview,
52
54
  refreshCurrentRoute,
53
55
  refreshMediaTaggingPreview,
54
56
  removeConnection,
@@ -66,6 +68,8 @@ import {
66
68
  selectStructureEntry,
67
69
  setTableDesignerSearchQuery,
68
70
  setTableDesignerSqlPreviewVisibility,
71
+ setDataTableSearchQuery,
72
+ setStructureTableSearchQuery,
69
73
  toggleStructureTablesPanel,
70
74
  setDataPage,
71
75
  setDataPageSize,
@@ -75,12 +79,14 @@ import {
75
79
  setCurrentQuery,
76
80
  setChartsHeightPreset,
77
81
  setChartsHistoryTab,
82
+ setChartsHistoryPanelVisibility,
78
83
  setEditorPanelVisibility,
79
84
  setEditorTab,
80
85
  submitDeleteChartConfirmation,
81
86
  submitCreateMediaTaggingTagTable,
82
87
  submitCreateMediaTaggingMappingTable,
83
88
  submitDeleteQueryHistoryConfirmation,
89
+ submitRowUpdatePreviewConfirmation,
84
90
  setQueryHistoryPanelVisibility,
85
91
  sortDataTableByColumn,
86
92
  sortEditorResultsByColumn,
@@ -99,8 +105,6 @@ import {
99
105
  submitCreateConnection,
100
106
  createCurrentMediaTag,
101
107
  submitDeleteRowConfirmation,
102
- submitDataRowUpdate,
103
- submitEditorRowUpdate,
104
108
  submitEditConnection,
105
109
  submitImportSql,
106
110
  submitOpenConnection,
@@ -163,10 +167,47 @@ let pendingNewTableDesignerAutofocus = false;
163
167
  let pendingQueryEditorFocus = false;
164
168
  let pendingMediaTaggingTagSearchFocus = false;
165
169
 
170
+ const APP_TITLE = 'SQLite Hub';
171
+ const ROUTE_TITLE_SEGMENTS = {
172
+ connections: 'Connections',
173
+ overview: 'Overview',
174
+ data: 'Data',
175
+ structure: 'Structure',
176
+ editor: 'SQL Editor',
177
+ editorResults: 'SQL Editor',
178
+ charts: 'Charts',
179
+ tableDesigner: 'Table Designer',
180
+ mediaTaggingSetup: 'Media Tagging',
181
+ mediaTaggingQueue: 'Tagging Queue',
182
+ settings: 'Settings',
183
+ notFound: 'Not Found',
184
+ };
185
+
166
186
  function invalidateMainRenderCache() {
167
187
  lastRenderedMainMarkup = null;
168
188
  }
169
189
 
190
+ function isSqlEditorRouteName(routeName) {
191
+ return routeName === 'editor' || routeName === 'editorResults';
192
+ }
193
+
194
+ function resolveDocumentTitle(state) {
195
+ if (isSqlEditorRouteName(state.route.name) && state.editor.executing) {
196
+ return `${APP_TITLE} | Running`;
197
+ }
198
+
199
+ const segment = ROUTE_TITLE_SEGMENTS[state.route.name];
200
+ return segment ? `${APP_TITLE} | ${segment}` : APP_TITLE;
201
+ }
202
+
203
+ function syncDocumentTitle(state) {
204
+ const nextTitle = resolveDocumentTitle(state);
205
+
206
+ if (document.title !== nextTitle) {
207
+ document.title = nextTitle;
208
+ }
209
+ }
210
+
170
211
  function isMediaTaggingRouteName(routeName) {
171
212
  return routeName === 'mediaTaggingSetup' || routeName === 'mediaTaggingQueue';
172
213
  }
@@ -589,7 +630,7 @@ function buildChartsHistorySignature(state) {
589
630
  return '';
590
631
  }
591
632
 
592
- const historyVisible = state.editor.historyPanelVisible !== false || !state.charts.selectedHistoryId;
633
+ const historyVisible = state.charts.historyPanelVisible !== false || !state.charts.selectedHistoryId;
593
634
 
594
635
  if (!historyVisible) {
595
636
  return 'charts-history:hidden';
@@ -691,7 +732,7 @@ function patchChartsDetailUi(state) {
691
732
  return false;
692
733
  }
693
734
 
694
- const historyVisible = state.editor.historyPanelVisible !== false || !state.charts.selectedHistoryId;
735
+ const historyVisible = state.charts.historyPanelVisible !== false || !state.charts.selectedHistoryId;
695
736
  const sidebarNode = chartsView.querySelector('.charts-view__sidebar');
696
737
 
697
738
  if (Boolean(sidebarNode) !== historyVisible) {
@@ -1033,6 +1074,8 @@ async function handleTableDesignerCsvImport(fileInput) {
1033
1074
  }
1034
1075
 
1035
1076
  function renderApp(state) {
1077
+ syncDocumentTitle(state);
1078
+
1036
1079
  const previousRoutePath = lastRenderedRoutePath;
1037
1080
  const previousRouteName = lastRenderedRouteName;
1038
1081
  const { main, panel } = resolveView(state);
@@ -1208,7 +1251,8 @@ const router = createRouter(route => {
1208
1251
 
1209
1252
  async function executeEditorQueryAndNavigate() {
1210
1253
  const success = await executeCurrentQuery();
1211
- router.navigate(success ? '/editor/results' : '/editor');
1254
+ const activeTab = getState().editor.activeTab;
1255
+ router.navigate(success && activeTab === 'results' ? '/editor/results' : '/editor');
1212
1256
  }
1213
1257
 
1214
1258
  async function handleAction(actionNode) {
@@ -1337,9 +1381,15 @@ async function handleAction(actionNode) {
1337
1381
  }
1338
1382
  return;
1339
1383
  case 'toggle-query-history-panel':
1340
- setQueryHistoryPanelVisibility(
1341
- actionNode.dataset.nextValue ? actionNode.dataset.nextValue === 'true' : undefined,
1342
- );
1384
+ if (getState().route.name === 'charts') {
1385
+ setChartsHistoryPanelVisibility(
1386
+ actionNode.dataset.nextValue ? actionNode.dataset.nextValue === 'true' : undefined,
1387
+ );
1388
+ } else {
1389
+ setQueryHistoryPanelVisibility(
1390
+ actionNode.dataset.nextValue ? actionNode.dataset.nextValue === 'true' : undefined,
1391
+ );
1392
+ }
1343
1393
  return;
1344
1394
  case 'toggle-editor-panel':
1345
1395
  setEditorPanelVisibility(
@@ -1357,7 +1407,8 @@ async function handleAction(actionNode) {
1357
1407
  case 'run-query-history':
1358
1408
  if (actionNode.dataset.historyId) {
1359
1409
  const success = await runQueryHistoryItem(actionNode.dataset.historyId);
1360
- router.navigate(success ? '/editor/results' : '/editor');
1410
+ const activeTab = getState().editor.activeTab;
1411
+ router.navigate(success && activeTab === 'results' ? '/editor/results' : '/editor');
1361
1412
  }
1362
1413
  return;
1363
1414
  case 'toggle-query-history-saved':
@@ -1727,6 +1778,16 @@ document.addEventListener('input', event => {
1727
1778
  return;
1728
1779
  }
1729
1780
 
1781
+ if (bindNode.dataset.bind === 'data-table-search') {
1782
+ setDataTableSearchQuery(bindNode.value);
1783
+ return;
1784
+ }
1785
+
1786
+ if (bindNode.dataset.bind === 'structure-table-search') {
1787
+ setStructureTableSearchQuery(bindNode.value);
1788
+ return;
1789
+ }
1790
+
1730
1791
  if (bindNode.dataset.bind === 'table-designer-search') {
1731
1792
  setTableDesignerSearchQuery(bindNode.value);
1732
1793
  return;
@@ -2007,6 +2068,9 @@ document.addEventListener('submit', async event => {
2007
2068
  case 'delete-query-history-confirm':
2008
2069
  await submitDeleteQueryHistoryConfirmation();
2009
2070
  return;
2071
+ case 'apply-row-update-preview':
2072
+ await submitRowUpdatePreviewConfirmation();
2073
+ return;
2010
2074
  case 'create-media-tagging-tag-table':
2011
2075
  await submitCreateMediaTaggingTagTable();
2012
2076
  return;
@@ -2035,7 +2099,7 @@ document.addEventListener('submit', async event => {
2035
2099
  }
2036
2100
  }
2037
2101
 
2038
- await submitDataRowUpdate(String(formData.get('rowIndex') ?? ''), values, rowIdentity);
2102
+ await openDataRowUpdatePreview(String(formData.get('rowIndex') ?? ''), values, rowIdentity);
2039
2103
  return;
2040
2104
  }
2041
2105
  case 'save-editor-row': {
@@ -2049,7 +2113,7 @@ document.addEventListener('submit', async event => {
2049
2113
  values[key.slice('field:'.length)] = String(value ?? '');
2050
2114
  }
2051
2115
 
2052
- await submitEditorRowUpdate(String(formData.get('rowIndex') ?? ''), values);
2116
+ await openEditorRowUpdatePreview(String(formData.get('rowIndex') ?? ''), values);
2053
2117
  return;
2054
2118
  }
2055
2119
  case 'save-query-history-title': {
@@ -425,6 +425,98 @@ function renderDeleteRowConfirmForm(modal) {
425
425
  ].join("");
426
426
  }
427
427
 
428
+ function renderRowUpdatePreviewForm(modal) {
429
+ const preview = modal.preview ?? {};
430
+ const changes = preview.changes ?? [];
431
+ const params = preview.params ?? [];
432
+ const warnings = preview.warnings ?? [];
433
+ const changesMarkup = changes.length
434
+ ? [
435
+ '<div class="overflow-hidden border border-outline-variant/10">',
436
+ '<table class="w-full text-left text-sm">',
437
+ '<thead class="bg-surface-container-highest text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">',
438
+ '<tr><th class="px-3 py-2">Column</th><th class="px-3 py-2">Old</th><th class="px-3 py-2">New</th></tr>',
439
+ '</thead><tbody class="divide-y divide-outline-variant/10">',
440
+ changes
441
+ .map(
442
+ change => `
443
+ <tr>
444
+ <td class="px-3 py-2 font-mono text-xs text-primary-container">${escapeHtml(change.column)}</td>
445
+ <td class="px-3 py-2 text-on-surface-variant/70">${escapeHtml(change.oldValue)}</td>
446
+ <td class="px-3 py-2 text-on-surface">${escapeHtml(change.newValue)}</td>
447
+ </tr>
448
+ `,
449
+ )
450
+ .join(''),
451
+ '</tbody></table></div>',
452
+ ].join('')
453
+ : '<div class="border border-outline-variant/10 bg-surface-container-low px-4 py-3 text-sm text-on-surface-variant/65">No changed values were detected.</div>';
454
+ const paramsMarkup = params.length
455
+ ? `
456
+ <div class="space-y-2">
457
+ <div class="text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">Parameters</div>
458
+ <div class="space-y-2">
459
+ ${params
460
+ .map(
461
+ param => `
462
+ <div class="flex gap-3 border border-outline-variant/10 bg-surface-container-low px-3 py-2 text-xs">
463
+ <span class="font-mono text-primary-container">$${escapeHtml(param.index)}</span>
464
+ <span class="min-w-0 break-words text-on-surface">${escapeHtml(param.value)}</span>
465
+ </div>
466
+ `,
467
+ )
468
+ .join('')}
469
+ </div>
470
+ </div>
471
+ `
472
+ : '';
473
+ const warningsMarkup = warnings.length
474
+ ? `
475
+ <div class="space-y-2">
476
+ ${warnings
477
+ .map(
478
+ warning => `
479
+ <div class="border border-primary-container/20 bg-primary-container/10 px-4 py-3 text-sm text-on-surface">
480
+ ${escapeHtml(warning)}
481
+ </div>
482
+ `,
483
+ )
484
+ .join('')}
485
+ </div>
486
+ `
487
+ : '';
488
+
489
+ return `
490
+ <form class="space-y-5" data-form="apply-row-update-preview">
491
+ <div class="space-y-2">
492
+ <div class="text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
493
+ ${escapeHtml(modal.tableName ?? 'Table Row')}
494
+ </div>
495
+ <p class="text-sm leading-7 text-on-surface-variant/70">
496
+ Review the SQL and changed values before applying this row update.
497
+ </p>
498
+ </div>
499
+ ${warningsMarkup}
500
+ <div class="space-y-2">
501
+ <div class="text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">SQL Preview</div>
502
+ ${renderSqlPreviewField(preview.sql ?? '', 'sql-highlight-shell--compact')}
503
+ </div>
504
+ <div class="space-y-2">
505
+ <div class="text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">Changed Values</div>
506
+ ${changesMarkup}
507
+ </div>
508
+ ${paramsMarkup}
509
+ ${renderError(modal.error)}
510
+ <div class="flex items-center justify-end gap-3 pt-2">
511
+ <button class="standard-button" data-action="close-modal" type="button">Cancel</button>
512
+ <button class="signature-button" type="submit">
513
+ ${modal.submitting ? 'Applying...' : 'Apply Changes'}
514
+ </button>
515
+ </div>
516
+ </form>
517
+ `;
518
+ }
519
+
428
520
  function renderChartColumnOptions(analysis, { allowEmpty = false, includeNumericHint = false } = {}) {
429
521
  const options = allowEmpty ? [{ value: "", label: "None" }] : [];
430
522
 
@@ -869,6 +961,11 @@ export function renderModal(state) {
869
961
  title: "Delete Row",
870
962
  body: renderDeleteRowConfirmForm(modal),
871
963
  },
964
+ "row-update-preview": {
965
+ eyebrow: "Mutation // Review row update",
966
+ title: "Review Update",
967
+ body: renderRowUpdatePreviewForm(modal),
968
+ },
872
969
  "chart-editor": {
873
970
  eyebrow: "Charts // Configure query-based ECharts panel",
874
971
  title: modal.draft?.mode === "edit" ? "Edit Chart" : "New Chart",
@@ -904,7 +1001,7 @@ export function renderModal(state) {
904
1001
 
905
1002
  return `
906
1003
  <div class="fixed inset-0 z-50 flex items-center justify-center bg-background/85 px-4 backdrop-blur-sm">
907
- <div class="w-full ${modal.kind === "chart-editor" ? "max-w-3xl" : "max-w-xl"} border border-outline-variant/20 bg-surface-container shadow-[0_24px_80px_rgba(0,0,0,0.45)]">
1004
+ <div class="w-full ${modal.kind === "chart-editor" || modal.kind === "row-update-preview" ? "max-w-3xl" : "max-w-xl"} border border-outline-variant/20 bg-surface-container shadow-[0_24px_80px_rgba(0,0,0,0.45)]">
908
1005
  <div class="flex items-start justify-between gap-4 border-b border-outline-variant/10 bg-surface-container-low px-6 py-5">
909
1006
  <div>
910
1007
  <div class="text-[10px] font-mono uppercase tracking-[0.26em] text-primary-container/70">
@@ -38,7 +38,6 @@ function renderEditorSurface({ query }) {
38
38
 
39
39
  export function renderQueryEditor({
40
40
  query,
41
- title,
42
41
  executing = false,
43
42
  exporting = false,
44
43
  historyLoading = false,
@@ -48,13 +47,7 @@ export function renderQueryEditor({
48
47
  }) {
49
48
  const secondaryButtonClass = "standard-button";
50
49
  const left = `
51
- <div class="flex items-center gap-2 bg-surface-container-lowest px-3 py-1">
52
- <span class="material-symbols-outlined text-xs text-[#FCE300]">database</span>
53
- <span class="text-[10px] font-mono uppercase tracking-widest text-on-surface-variant">${escapeHtml(
54
- title
55
- )}</span>
56
- </div>
57
- <div class="hidden items-center gap-2 text-[10px] font-mono uppercase tracking-widest text-on-surface-variant/40 md:flex">
50
+ <div class="flex items-center gap-2 text-[10px] font-mono uppercase tracking-widest text-on-surface-variant/40">
58
51
  <span class="material-symbols-outlined text-xs">history</span>
59
52
  ${historyLoading ? "Loading history..." : `${historyTotal} queries tracked`}
60
53
  </div>
@@ -6,6 +6,33 @@ let cytoscapeFactory = null;
6
6
  let currentGraph = null;
7
7
  let mountVersion = 0;
8
8
  let persistedGraphState = null;
9
+ const STRUCTURE_INSPECTOR_VISIBLE_STORAGE_KEY = 'sqlite_hub_structure_inspector_visible';
10
+
11
+ function readStoredInspectorHidden() {
12
+ try {
13
+ const value = globalThis.localStorage?.getItem(STRUCTURE_INSPECTOR_VISIBLE_STORAGE_KEY);
14
+
15
+ if (value === 'true') {
16
+ return false;
17
+ }
18
+
19
+ if (value === 'false') {
20
+ return true;
21
+ }
22
+
23
+ return false;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ function storeInspectorHidden(hidden) {
30
+ try {
31
+ globalThis.localStorage?.setItem(STRUCTURE_INSPECTOR_VISIBLE_STORAGE_KEY, String(!hidden));
32
+ } catch {
33
+ // Ignore unavailable browser storage; the in-memory setting still applies.
34
+ }
35
+ }
9
36
 
10
37
  function getTableId(tableName) {
11
38
  return `table:${tableName}`;
@@ -690,6 +717,7 @@ export function setupToolbar(cy) {
690
717
  break;
691
718
  case 'toggle-inspector':
692
719
  currentGraph.inspectorHidden = !currentGraph.inspectorHidden;
720
+ storeInspectorHidden(currentGraph.inspectorHidden);
693
721
  updateInspectorToggleButton();
694
722
  syncInspectorLayout();
695
723
  break;
@@ -765,7 +793,7 @@ export async function mountStructureGraph(snapshot) {
765
793
  resizeHandler: null,
766
794
  openDataButton: null,
767
795
  inspectorToggleButton: null,
768
- inspectorHidden: cachedState?.inspectorHidden ?? false,
796
+ inspectorHidden: cachedState?.inspectorHidden ?? readStoredInspectorHidden(),
769
797
  persistStateOnDestroy: true,
770
798
  selectedTableName: null,
771
799
  };