sqlite-hub 0.7.0 → 0.8.7

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.
Files changed (31) hide show
  1. package/changelog.md +14 -0
  2. package/docs/DESIGN_GUIDELINES.md +36 -0
  3. package/frontend/index.html +79 -0
  4. package/frontend/js/app.js +89 -8
  5. package/frontend/js/components/connectionCard.js +3 -4
  6. package/frontend/js/components/emptyState.js +4 -4
  7. package/frontend/js/components/modal.js +32 -28
  8. package/frontend/js/components/queryEditor.js +2 -4
  9. package/frontend/js/components/queryHistoryDetail.js +16 -7
  10. package/frontend/js/components/queryHistoryPanel.js +2 -2
  11. package/frontend/js/components/rowEditorPanel.js +59 -54
  12. package/frontend/js/components/sidebar.js +32 -33
  13. package/frontend/js/components/structureGraph.js +54 -122
  14. package/frontend/js/components/tableDesignerEditor.js +9 -8
  15. package/frontend/js/components/tableDesignerSidebar.js +11 -10
  16. package/frontend/js/components/tableDesignerSqlPreview.js +60 -28
  17. package/frontend/js/store.js +23 -0
  18. package/frontend/js/views/charts.js +66 -38
  19. package/frontend/js/views/connections.js +5 -17
  20. package/frontend/js/views/data.js +12 -13
  21. package/frontend/js/views/overview.js +149 -10
  22. package/frontend/js/views/settings.js +2 -2
  23. package/frontend/js/views/structure.js +74 -70
  24. package/frontend/js/views/tableDesigner.js +7 -2
  25. package/frontend/styles/base.css +62 -0
  26. package/frontend/styles/components.css +63 -105
  27. package/frontend/styles/structure-graph.css +19 -82
  28. package/frontend/styles/tokens.css +2 -0
  29. package/frontend/styles/views.css +33 -36
  30. package/package.json +1 -1
  31. package/server/services/sqlite/overviewService.js +68 -0
@@ -155,14 +155,14 @@ export function renderQueryHistoryDetail({
155
155
  </label>
156
156
  <div class="mt-2 flex gap-2">
157
157
  <input
158
- class="flex-1 border border-outline-variant/20 bg-surface-container px-3 py-2 text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
158
+ class="control-input flex-1 border border-outline-variant/20 bg-surface-container text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
159
159
  name="title"
160
160
  placeholder="${escapeHtml(item.displayTitle)}"
161
161
  type="text"
162
162
  value="${escapeHtml(item.title ?? "")}"
163
163
  />
164
164
  <button
165
- class="border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface transition-colors hover:border-primary-container hover:text-primary-container"
165
+ class="standard-button"
166
166
  type="submit"
167
167
  >
168
168
  Save
@@ -175,7 +175,7 @@ export function renderQueryHistoryDetail({
175
175
 
176
176
  <div class="mt-5 flex flex-wrap gap-2">
177
177
  <button
178
- class="toolbar-button border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface transition-colors hover:border-primary-container hover:text-primary-container"
178
+ class="standard-button"
179
179
  data-action="open-query-history"
180
180
  data-history-id="${escapeHtml(item.id)}"
181
181
  type="button"
@@ -183,7 +183,16 @@ export function renderQueryHistoryDetail({
183
183
  Open In Editor
184
184
  </button>
185
185
  <button
186
- class="toolbar-button border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface transition-colors hover:border-primary-container hover:text-primary-container"
186
+ class="standard-button"
187
+ data-action="navigate"
188
+ data-to="/charts/${encodeURIComponent(item.id)}"
189
+ type="button"
190
+ >
191
+ <span class="material-symbols-outlined text-sm">bar_chart</span>
192
+ Open In Charts
193
+ </button>
194
+ <button
195
+ class="standard-button"
187
196
  data-action="run-query-history"
188
197
  data-history-id="${escapeHtml(item.id)}"
189
198
  type="button"
@@ -191,7 +200,7 @@ export function renderQueryHistoryDetail({
191
200
  Run Now
192
201
  </button>
193
202
  <button
194
- class="toolbar-button border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface transition-colors hover:border-primary-container hover:text-primary-container"
203
+ class="standard-button"
195
204
  data-action="toggle-query-history-saved"
196
205
  data-history-id="${escapeHtml(item.id)}"
197
206
  data-next-value="${item.isSaved ? "false" : "true"}"
@@ -200,7 +209,7 @@ export function renderQueryHistoryDetail({
200
209
  ${item.isSaved ? "Unsave" : "Save"}
201
210
  </button>
202
211
  <button
203
- class="toolbar-button border border-error/25 bg-error-container/20 px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-error transition-colors hover:border-error hover:bg-error-container/30"
212
+ class="delete-button"
204
213
  data-action="delete-query-history"
205
214
  data-history-id="${escapeHtml(item.id)}"
206
215
  type="button"
@@ -228,7 +237,7 @@ export function renderQueryHistoryDetail({
228
237
  )}</textarea>
229
238
  <div class="mt-2 flex justify-end">
230
239
  <button
231
- class="border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface transition-colors hover:border-primary-container hover:text-primary-container"
240
+ class="standard-button"
232
241
  type="submit"
233
242
  >
234
243
  Save Notes
@@ -171,7 +171,7 @@ export function renderQueryHistoryPanel({
171
171
  <label class="mt-4 block">
172
172
  <span class="sr-only">Search query history</span>
173
173
  <input
174
- class="w-full border border-outline-variant/20 bg-surface-container px-3 py-2 text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
174
+ class="control-input w-full border border-outline-variant/20 bg-surface-container text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
175
175
  data-bind="query-history-search"
176
176
  placeholder="Search SQL, titles, notes..."
177
177
  type="search"
@@ -223,7 +223,7 @@ export function renderQueryHistoryPanel({
223
223
  ? `
224
224
  <div class="mt-4 flex justify-center">
225
225
  <button
226
- class="border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant transition-colors hover:border-primary-container hover:text-primary-container"
226
+ class="standard-button"
227
227
  data-action="load-more-query-history"
228
228
  type="button"
229
229
  >
@@ -106,12 +106,52 @@ export function renderRowEditorPanel({
106
106
  const canSubmit = !disabledMessage && editableFields.length > 0;
107
107
  const canDelete = !disabledMessage && deleteEnabled;
108
108
  const formId = `${formName}-panel-form`;
109
+ const headerActions = [
110
+ reloadAction
111
+ ? `
112
+ <button
113
+ class="standard-button"
114
+ data-action="${escapeHtml(reloadAction)}"
115
+ type="button"
116
+ >
117
+ Reload
118
+ </button>
119
+ `
120
+ : "",
121
+ canSubmit
122
+ ? `
123
+ <button
124
+ class="standard-button"
125
+ form="${escapeHtml(formId)}"
126
+ type="submit"
127
+ ${saving || deleting ? "disabled" : ""}
128
+ >
129
+ ${escapeHtml(saving ? "Saving..." : submitLabel)}
130
+ </button>
131
+ `
132
+ : "",
133
+ canDelete
134
+ ? `
135
+ <button
136
+ class="delete-button"
137
+ data-action="${escapeHtml(deleteAction)}"
138
+ data-row-index="${escapeHtml(String(deleteRowIndex ?? ""))}"
139
+ type="button"
140
+ ${saving || deleting ? "disabled" : ""}
141
+ >
142
+ ${escapeHtml(deleting ? "Deleting..." : deleteLabel)}
143
+ </button>
144
+ `
145
+ : "",
146
+ ]
147
+ .filter(Boolean)
148
+ .join("");
109
149
 
110
150
  return `
111
151
  <section class="flex h-full min-h-0 flex-col bg-surface-low">
112
152
  <header class="border-b border-outline-variant/10 bg-surface-container px-6 py-5">
113
- <div class="space-y-4">
114
- <div>
153
+ <div class="flex items-start justify-between gap-4">
154
+ <div class="min-w-0 flex-1">
115
155
  <div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
116
156
  ${escapeHtml(sectionLabel)}
117
157
  </div>
@@ -128,59 +168,24 @@ export function renderRowEditorPanel({
128
168
  : ""
129
169
  }
130
170
  </div>
131
- <div class="flex flex-wrap items-center justify-end gap-2">
132
- ${
133
- reloadAction
134
- ? `
135
- <button
136
- class="border border-outline-variant/20 px-4 py-3 text-[10px] font-bold uppercase tracking-[0.16em] text-on-surface hover:bg-surface-container-highest"
137
- data-action="${escapeHtml(reloadAction)}"
138
- type="button"
139
- >
140
- Reload
141
- </button>
142
- `
143
- : ""
144
- }
145
- ${
146
- canSubmit
147
- ? `
148
- <button
149
- class="bg-primary-container px-5 py-3 text-[10px] font-black uppercase tracking-[0.16em] text-on-primary disabled:cursor-default disabled:opacity-40"
150
- form="${escapeHtml(formId)}"
151
- type="submit"
152
- ${saving || deleting ? "disabled" : ""}
153
- >
154
- ${escapeHtml(saving ? "Saving..." : submitLabel)}
155
- </button>
156
- `
157
- : ""
158
- }
159
- ${
160
- canDelete
161
- ? `
162
- <button
163
- class="border border-error/25 bg-error-container/10 px-4 py-3 text-[10px] font-bold uppercase tracking-[0.16em] text-error transition-colors hover:bg-error-container/20 disabled:cursor-default disabled:opacity-40"
164
- data-action="${escapeHtml(deleteAction)}"
165
- data-row-index="${escapeHtml(String(deleteRowIndex ?? ""))}"
166
- type="button"
167
- ${saving || deleting ? "disabled" : ""}
168
- >
169
- ${escapeHtml(deleting ? "Deleting..." : deleteLabel)}
170
- </button>
171
- `
172
- : ""
173
- }
174
- <button
175
- aria-label="Close panel"
176
- class="flex h-11 w-11 items-center justify-center border border-outline-variant/20 text-on-surface hover:bg-surface-container-highest"
177
- data-action="${escapeHtml(closeAction)}"
178
- type="button"
179
- >
180
- <span class="material-symbols-outlined text-base">close</span>
181
- </button>
182
- </div>
171
+ <button
172
+ aria-label="Close panel"
173
+ class="query-history-icon-button shrink-0"
174
+ data-action="${escapeHtml(closeAction)}"
175
+ type="button"
176
+ >
177
+ <span class="material-symbols-outlined text-[18px]">close</span>
178
+ </button>
183
179
  </div>
180
+ ${
181
+ headerActions
182
+ ? `
183
+ <div class="mt-4 flex flex-wrap items-center justify-end gap-2">
184
+ ${headerActions}
185
+ </div>
186
+ `
187
+ : ""
188
+ }
184
189
  </header>
185
190
  <div class="custom-scrollbar flex-1 overflow-auto px-6 py-6">
186
191
  ${
@@ -1,61 +1,60 @@
1
- import { escapeHtml } from "../utils/format.js";
2
- import { renderConnectionLogo } from "./connectionLogo.js";
1
+ import { escapeHtml } from '../utils/format.js';
2
+ import { renderConnectionLogo } from './connectionLogo.js';
3
3
 
4
4
  const sidebarItems = [
5
- { label: "Connections", href: "#/connections", key: "connections", icon: "database" },
6
- { label: "Overview", href: "#/overview", key: "overview", icon: "dashboard" },
7
- { label: "Data", href: "#/data", key: "data", icon: "table_rows" },
8
- { label: "Charts", href: "#/charts", key: "charts", icon: "bar_chart" },
9
- { label: "SQL Editor", href: "#/editor", key: "editor", icon: "terminal" },
10
- { label: "Structure", href: "#/structure", key: "structure", icon: "account_tree" },
11
- { label: "Table Designer", href: "#/table-designer", key: "tableDesigner", icon: "table_chart" },
12
- { label: "Settings", href: "#/settings", key: "settings", icon: "settings" },
5
+ { label: 'Connections', href: '#/connections', key: 'connections', icon: 'database' },
6
+ { label: 'Overview', href: '#/overview', key: 'overview', icon: 'dashboard' },
7
+ { label: 'Data', href: '#/data', key: 'data', icon: 'table_rows' },
8
+ { label: 'SQL Editor', href: '#/editor', key: 'editor', icon: 'terminal' },
9
+ { label: 'Structure', href: '#/structure', key: 'structure', icon: 'account_tree' },
10
+ { label: 'Charts', href: '#/charts', key: 'charts', icon: 'bar_chart' },
11
+ { label: 'Table Designer', href: '#/table-designer', key: 'tableDesigner', icon: 'table_chart' },
12
+ { label: 'Settings', href: '#/settings', key: 'settings', icon: 'settings' },
13
13
  ];
14
14
 
15
15
  function getActiveSidebarKey(routeName) {
16
- if (routeName === "landing") {
17
- return "connections";
18
- }
16
+ if (routeName === 'landing') {
17
+ return 'connections';
18
+ }
19
19
 
20
- if (routeName === "editorResults") {
21
- return "editor";
22
- }
20
+ if (routeName === 'editorResults') {
21
+ return 'editor';
22
+ }
23
23
 
24
- return routeName;
24
+ return routeName;
25
25
  }
26
26
 
27
27
  export function renderSidebar(state) {
28
- const activeKey = getActiveSidebarKey(state.route.name);
29
- const activeConnection = state.connections.active;
28
+ const activeKey = getActiveSidebarKey(state.route.name);
29
+ const activeConnection = state.connections.active;
30
30
 
31
- return `
31
+ return `
32
32
  <nav class="sidebar-links">
33
33
  ${sidebarItems
34
- .map(
35
- (item) => `
36
- <a class="sidebar-link ${item.key === activeKey ? "is-active" : ""}" href="${item.href}">
34
+ .map(
35
+ item => `
36
+ <a class="sidebar-link ${item.key === activeKey ? 'is-active' : ''}" href="${item.href}">
37
37
  <span class="material-symbols-outlined">${item.icon}</span>
38
38
  <span>${item.label}</span>
39
39
  </a>
40
- `
41
- )
42
- .join("")}
40
+ `,
41
+ )
42
+ .join('')}
43
43
  </nav>
44
44
  <div class="sidebar-footer">
45
45
  <div class="sidebar-footer-card">
46
46
  ${renderConnectionLogo(activeConnection, {
47
- containerClass:
48
- "sidebar-footer-mark overflow-hidden bg-primary-container text-on-primary",
49
- imageClassName: "h-full w-full object-cover",
50
- iconClassName: "text-[15px]",
51
- icon: "memory",
47
+ containerClass: 'sidebar-footer-mark overflow-hidden bg-primary-container text-on-primary',
48
+ imageClassName: 'h-full w-full object-cover',
49
+ iconClassName: 'text-[15px]',
50
+ icon: 'memory',
52
51
  })}
53
52
  <div class="min-w-0">
54
53
  <p class="truncate text-[10px] font-bold text-on-surface">
55
- ${escapeHtml(activeConnection?.label ?? "NO_ACTIVE_DATABASE")}
54
+ ${escapeHtml(activeConnection?.label ?? 'NO_ACTIVE_DATABASE')}
56
55
  </p>
57
56
  <p class="text-[8px] text-on-surface-variant/60">
58
- ${activeConnection?.readOnly ? "READ_ONLY" : "READ_WRITE"}
57
+ ${activeConnection?.readOnly ? 'READ_ONLY' : 'READ_WRITE'}
59
58
  </p>
60
59
  </div>
61
60
  </div>
@@ -1,3 +1,4 @@
1
+ import { showToast } from "../store.js";
1
2
  import { escapeHtml, formatNumber } from "../utils/format.js";
2
3
 
3
4
  let cytoscapeFactory = null;
@@ -98,6 +99,31 @@ function createColumnFlags(column, foreignKeyColumns) {
98
99
  return flags.join("");
99
100
  }
100
101
 
102
+ export function renderDdlSection(ddl, emptyLabel = "No DDL available.") {
103
+ const ddlText = typeof ddl === "string" ? ddl : "";
104
+ const hasDdl = Boolean(ddlText.trim());
105
+
106
+ return `
107
+ <section class="structure-graph__section">
108
+ <div class="structure-graph__section-header">
109
+ <div class="structure-graph__section-title">DDL</div>
110
+ <button
111
+ class="standard-button"
112
+ data-structure-graph-action="copy-ddl"
113
+ ${hasDdl ? "" : "disabled"}
114
+ type="button"
115
+ >
116
+ <span class="material-symbols-outlined text-sm">content_copy</span>
117
+ Copy
118
+ </button>
119
+ </div>
120
+ <pre class="structure-graph__ddl custom-scrollbar" data-structure-graph-ddl>${escapeHtml(
121
+ hasDdl ? ddlText : emptyLabel
122
+ )}</pre>
123
+ </section>
124
+ `;
125
+ }
126
+
101
127
  export function clearInspector() {
102
128
  return `
103
129
  <div class="structure-graph__panel is-empty">
@@ -165,12 +191,7 @@ export function renderInspector(tableData) {
165
191
  </div>
166
192
  </section>
167
193
 
168
- <section class="structure-graph__section">
169
- <div class="structure-graph__section-title">DDL</div>
170
- <pre class="structure-graph__ddl custom-scrollbar">${escapeHtml(
171
- tableData.ddl || "No DDL available."
172
- )}</pre>
173
- </section>
194
+ ${renderDdlSection(tableData.ddl)}
174
195
  </div>
175
196
  `;
176
197
  }
@@ -398,60 +419,6 @@ export function highlightConnectedElements(cy, element) {
398
419
  element.addClass("hovered");
399
420
  }
400
421
 
401
- function getBestMatchingNode(cy, name) {
402
- const normalizedQuery = String(name ?? "").trim().toLowerCase();
403
-
404
- if (!normalizedQuery) {
405
- return null;
406
- }
407
-
408
- const nodes = cy.nodes().toArray();
409
- const exactMatch = nodes.find(
410
- (node) => String(node.data("tableName")).toLowerCase() === normalizedQuery
411
- );
412
-
413
- if (exactMatch) {
414
- return exactMatch;
415
- }
416
-
417
- const startsWithMatch = nodes.find((node) =>
418
- String(node.data("tableName")).toLowerCase().startsWith(normalizedQuery)
419
- );
420
-
421
- if (startsWithMatch) {
422
- return startsWithMatch;
423
- }
424
-
425
- return (
426
- nodes.find((node) =>
427
- String(node.data("tableName")).toLowerCase().includes(normalizedQuery)
428
- ) ?? null
429
- );
430
- }
431
-
432
- export function focusTableByName(cy, name) {
433
- const node = getBestMatchingNode(cy, name);
434
-
435
- if (!node) {
436
- return null;
437
- }
438
-
439
- cy.animate(
440
- {
441
- fit: {
442
- eles: node.closedNeighborhood(),
443
- padding: 120,
444
- },
445
- duration: 240,
446
- },
447
- {
448
- queue: false,
449
- }
450
- );
451
-
452
- return node;
453
- }
454
-
455
422
  function destroyCurrentGraph() {
456
423
  if (!currentGraph) {
457
424
  return;
@@ -473,8 +440,6 @@ function destroyCurrentGraph() {
473
440
  zoom: currentGraph.cy.zoom(),
474
441
  inspectorHidden: currentGraph.inspectorHidden,
475
442
  selectedTableName: currentGraph.selectedTableName,
476
- searchValue: currentGraph.searchInput?.value ?? "",
477
- searchMiss: currentGraph.searchRoot?.classList.contains("is-miss") ?? false,
478
443
  };
479
444
  }
480
445
 
@@ -542,6 +507,25 @@ function updateOpenDataButton() {
542
507
  currentGraph.openDataButton.classList.toggle("is-disabled", !isEnabled);
543
508
  }
544
509
 
510
+ async function copyInspectorDdl(button) {
511
+ const ddlNode = button
512
+ ?.closest(".structure-graph__section")
513
+ ?.querySelector("[data-structure-graph-ddl]");
514
+ const ddl = ddlNode?.textContent ?? "";
515
+
516
+ if (!ddl.trim()) {
517
+ showToast("No DDL available to copy.", "alert");
518
+ return;
519
+ }
520
+
521
+ try {
522
+ await navigator.clipboard.writeText(ddl);
523
+ showToast("DDL copied.", "success");
524
+ } catch (error) {
525
+ showToast("Clipboard access failed.", "alert");
526
+ }
527
+ }
528
+
545
529
  function syncInspectorLayout() {
546
530
  if (!currentGraph?.root) {
547
531
  return;
@@ -565,17 +549,12 @@ function updateInspectorToggleButton() {
565
549
  `;
566
550
  }
567
551
 
568
- function clearSelection({ preserveSearch = true } = {}) {
552
+ function clearSelection() {
569
553
  if (!currentGraph) {
570
554
  return;
571
555
  }
572
556
 
573
557
  currentGraph.selectedTableName = null;
574
- if (!preserveSearch && currentGraph.searchInput) {
575
- currentGraph.searchInput.value = "";
576
- currentGraph.searchRoot.classList.remove("is-miss");
577
- }
578
-
579
558
  resetHighlights(currentGraph.cy);
580
559
  syncEntryHighlights();
581
560
  currentGraph.inspector.innerHTML = getDefaultInspectorMarkup();
@@ -677,61 +656,24 @@ function restorePersistedViewport(cy, persistedState) {
677
656
  return true;
678
657
  }
679
658
 
680
- export function setupToolbar(cy, schema) {
659
+ export function setupToolbar(cy) {
681
660
  if (!currentGraph) {
682
661
  return () => {};
683
662
  }
684
663
 
685
664
  const { root } = currentGraph;
686
- const searchRoot = root.querySelector("[data-structure-graph-search]");
687
- const searchInput = root.querySelector("[data-structure-graph-search-input]");
688
665
  const openDataButton = root.querySelector('[data-structure-graph-action="open-data"]');
689
666
  const inspectorToggleButton = root.querySelector(
690
667
  '[data-structure-graph-action="toggle-inspector"]'
691
668
  );
692
- let searchTimer = null;
693
669
 
694
- currentGraph.searchRoot = searchRoot;
695
- currentGraph.searchInput = searchInput;
696
670
  currentGraph.openDataButton = openDataButton;
697
671
  currentGraph.inspectorToggleButton = inspectorToggleButton;
698
672
  updateOpenDataButton();
699
673
  updateInspectorToggleButton();
700
674
  syncInspectorLayout();
701
675
 
702
- const handleSearch = () => {
703
- if (!searchInput || !searchRoot) {
704
- return;
705
- }
706
-
707
- const value = searchInput.value.trim();
708
-
709
- if (!value) {
710
- searchRoot.classList.remove("is-miss");
711
- restoreGraphState();
712
- if (!currentGraph.selectedTableName) {
713
- currentGraph.inspector.innerHTML = getDefaultInspectorMarkup();
714
- }
715
- return;
716
- }
717
-
718
- const matchedNode = focusTableByName(cy, value);
719
-
720
- if (!matchedNode) {
721
- searchRoot.classList.add("is-miss");
722
- return;
723
- }
724
-
725
- searchRoot.classList.remove("is-miss");
726
- applyTableSelection(matchedNode, { focus: true });
727
- };
728
-
729
- const onSearchInput = () => {
730
- window.clearTimeout(searchTimer);
731
- searchTimer = window.setTimeout(handleSearch, 140);
732
- };
733
-
734
- const onToolbarClick = (event) => {
676
+ const onToolbarClick = async (event) => {
735
677
  const button = event.target.closest("[data-structure-graph-action]");
736
678
 
737
679
  if (!button) {
@@ -755,7 +697,7 @@ export function setupToolbar(cy, schema) {
755
697
  });
756
698
  break;
757
699
  case "clear":
758
- clearSelection({ preserveSearch: false });
700
+ clearSelection();
759
701
  break;
760
702
  case "open-data":
761
703
  if (currentGraph?.selectedTableName) {
@@ -767,16 +709,16 @@ export function setupToolbar(cy, schema) {
767
709
  updateInspectorToggleButton();
768
710
  syncInspectorLayout();
769
711
  break;
712
+ case "copy-ddl":
713
+ await copyInspectorDdl(button);
714
+ break;
770
715
  default:
771
716
  }
772
717
  };
773
718
 
774
- searchInput?.addEventListener("input", onSearchInput);
775
719
  root.addEventListener("click", onToolbarClick);
776
720
 
777
721
  return () => {
778
- window.clearTimeout(searchTimer);
779
- searchInput?.removeEventListener("input", onSearchInput);
780
722
  root.removeEventListener("click", onToolbarClick);
781
723
  };
782
724
  }
@@ -837,8 +779,6 @@ export async function mountStructureGraph(snapshot) {
837
779
  cleanup: [],
838
780
  resizeObserver: null,
839
781
  resizeHandler: null,
840
- searchRoot: null,
841
- searchInput: null,
842
782
  openDataButton: null,
843
783
  inspectorToggleButton: null,
844
784
  inspectorHidden: cachedState?.inspectorHidden ?? false,
@@ -846,7 +786,7 @@ export async function mountStructureGraph(snapshot) {
846
786
  selectedTableName: null,
847
787
  };
848
788
 
849
- currentGraph.cleanup.push(setupToolbar(cy, schema));
789
+ currentGraph.cleanup.push(setupToolbar(cy));
850
790
 
851
791
  cy.on("tap", "node", (event) => {
852
792
  applyTableSelection(event.target, { focus: false });
@@ -877,14 +817,6 @@ export async function mountStructureGraph(snapshot) {
877
817
 
878
818
  currentGraph.initialSelectedTableName = selectedTableName;
879
819
 
880
- if (currentGraph.searchInput && cachedState?.searchValue) {
881
- currentGraph.searchInput.value = cachedState.searchValue;
882
- }
883
-
884
- if (currentGraph.searchRoot) {
885
- currentGraph.searchRoot.classList.toggle("is-miss", Boolean(cachedState?.searchMiss));
886
- }
887
-
888
820
  if (restorePersistedViewport(cy, cachedState)) {
889
821
  if (selectedTableName) {
890
822
  const selectedNode = cy.getElementById(getTableId(selectedTableName));