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.
- package/changelog.md +14 -0
- package/docs/DESIGN_GUIDELINES.md +36 -0
- package/frontend/index.html +79 -0
- package/frontend/js/app.js +89 -8
- package/frontend/js/components/connectionCard.js +3 -4
- package/frontend/js/components/emptyState.js +4 -4
- package/frontend/js/components/modal.js +32 -28
- package/frontend/js/components/queryEditor.js +2 -4
- package/frontend/js/components/queryHistoryDetail.js +16 -7
- package/frontend/js/components/queryHistoryPanel.js +2 -2
- package/frontend/js/components/rowEditorPanel.js +59 -54
- package/frontend/js/components/sidebar.js +32 -33
- package/frontend/js/components/structureGraph.js +54 -122
- package/frontend/js/components/tableDesignerEditor.js +9 -8
- package/frontend/js/components/tableDesignerSidebar.js +11 -10
- package/frontend/js/components/tableDesignerSqlPreview.js +60 -28
- package/frontend/js/store.js +23 -0
- package/frontend/js/views/charts.js +66 -38
- package/frontend/js/views/connections.js +5 -17
- package/frontend/js/views/data.js +12 -13
- package/frontend/js/views/overview.js +149 -10
- package/frontend/js/views/settings.js +2 -2
- package/frontend/js/views/structure.js +74 -70
- package/frontend/js/views/tableDesigner.js +7 -2
- package/frontend/styles/base.css +62 -0
- package/frontend/styles/components.css +63 -105
- package/frontend/styles/structure-graph.css +19 -82
- package/frontend/styles/tokens.css +2 -0
- package/frontend/styles/views.css +33 -36
- package/package.json +1 -1
- 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
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
|
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="
|
|
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="
|
|
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
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
2
|
-
import { renderConnectionLogo } from
|
|
1
|
+
import { escapeHtml } from '../utils/format.js';
|
|
2
|
+
import { renderConnectionLogo } from './connectionLogo.js';
|
|
3
3
|
|
|
4
4
|
const sidebarItems = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
if (routeName === 'landing') {
|
|
17
|
+
return 'connections';
|
|
18
|
+
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
if (routeName === 'editorResults') {
|
|
21
|
+
return 'editor';
|
|
22
|
+
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
return routeName;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function renderSidebar(state) {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const activeKey = getActiveSidebarKey(state.route.name);
|
|
29
|
+
const activeConnection = state.connections.active;
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
return `
|
|
32
32
|
<nav class="sidebar-links">
|
|
33
33
|
${sidebarItems
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<a class="sidebar-link ${item.key === activeKey ?
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 ??
|
|
54
|
+
${escapeHtml(activeConnection?.label ?? 'NO_ACTIVE_DATABASE')}
|
|
56
55
|
</p>
|
|
57
56
|
<p class="text-[8px] text-on-surface-variant/60">
|
|
58
|
-
${activeConnection?.readOnly ?
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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));
|