sqlite-hub 0.6.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/README.md +107 -0
- package/bin/sqlite-hub.js +285 -4
- package/changelog.md +21 -0
- package/docs/DESIGN_GUIDELINES.md +36 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/index.html +80 -0
- package/frontend/js/api.js +34 -0
- package/frontend/js/app.js +188 -10
- package/frontend/js/components/connectionCard.js +3 -4
- package/frontend/js/components/emptyState.js +4 -4
- package/frontend/js/components/modal.js +341 -24
- package/frontend/js/components/queryChartRenderer.js +125 -0
- package/frontend/js/components/queryEditor.js +33 -6
- package/frontend/js/components/queryHistoryDetail.js +16 -7
- package/frontend/js/components/queryHistoryPanel.js +18 -7
- package/frontend/js/components/rowEditorPanel.js +59 -54
- package/frontend/js/components/sidebar.js +32 -32
- package/frontend/js/components/structureGraph.js +54 -122
- package/frontend/js/components/tableDesignerEditor.js +9 -8
- package/frontend/js/components/tableDesignerSidebar.js +52 -48
- package/frontend/js/components/tableDesignerSqlPreview.js +60 -28
- package/frontend/js/lib/queryChartOptions.js +283 -0
- package/frontend/js/lib/queryCharts.js +560 -0
- package/frontend/js/router.js +8 -0
- package/frontend/js/store.js +641 -0
- package/frontend/js/views/charts.js +499 -0
- package/frontend/js/views/connections.js +5 -17
- package/frontend/js/views/data.js +40 -27
- package/frontend/js/views/editor.js +19 -13
- package/frontend/js/views/overview.js +149 -10
- package/frontend/js/views/settings.js +2 -2
- package/frontend/js/views/structure.js +105 -59
- package/frontend/js/views/tableDesigner.js +7 -2
- package/frontend/styles/base.css +62 -0
- package/frontend/styles/components.css +68 -118
- package/frontend/styles/structure-graph.css +19 -82
- package/frontend/styles/tokens.css +2 -0
- package/frontend/styles/views.css +293 -66
- package/package.json +2 -1
- package/server/routes/charts.js +153 -0
- package/server/server.js +6 -0
- package/server/services/sqlite/overviewService.js +68 -0
- package/server/services/storage/appStateStore.js +400 -1
- package/server/services/storage/queryHistoryChartUtils.js +145 -0
|
@@ -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));
|
|
@@ -86,7 +86,7 @@ function renderColumnRow(column, draft, catalogTables) {
|
|
|
86
86
|
)
|
|
87
87
|
.join("")}
|
|
88
88
|
</select>
|
|
89
|
-
<label class="table-designer-check">
|
|
89
|
+
<label class="standard-checkbox table-designer-check table-designer-checkbox-override">
|
|
90
90
|
<input
|
|
91
91
|
data-bind="table-designer-column-flag"
|
|
92
92
|
data-column-id="${escapeHtml(column.id)}"
|
|
@@ -96,7 +96,7 @@ function renderColumnRow(column, draft, catalogTables) {
|
|
|
96
96
|
/>
|
|
97
97
|
<span>Not null</span>
|
|
98
98
|
</label>
|
|
99
|
-
<label class="table-designer-check">
|
|
99
|
+
<label class="standard-checkbox table-designer-check table-designer-checkbox-override">
|
|
100
100
|
<input
|
|
101
101
|
data-bind="table-designer-column-flag"
|
|
102
102
|
data-column-id="${escapeHtml(column.id)}"
|
|
@@ -106,7 +106,7 @@ function renderColumnRow(column, draft, catalogTables) {
|
|
|
106
106
|
/>
|
|
107
107
|
<span>Unique</span>
|
|
108
108
|
</label>
|
|
109
|
-
<label class="table-designer-check">
|
|
109
|
+
<label class="standard-checkbox table-designer-check table-designer-checkbox-override">
|
|
110
110
|
<input
|
|
111
111
|
data-bind="table-designer-column-flag"
|
|
112
112
|
data-column-id="${escapeHtml(column.id)}"
|
|
@@ -164,12 +164,13 @@ function renderColumnRow(column, draft, catalogTables) {
|
|
|
164
164
|
.join("")}
|
|
165
165
|
</select>
|
|
166
166
|
<button
|
|
167
|
-
class="
|
|
167
|
+
class="delete-button"
|
|
168
168
|
data-action="remove-table-designer-column"
|
|
169
169
|
data-column-id="${escapeHtml(column.id)}"
|
|
170
170
|
type="button"
|
|
171
171
|
>
|
|
172
172
|
<span class="material-symbols-outlined text-base">delete</span>
|
|
173
|
+
<span>Remove</span>
|
|
173
174
|
</button>
|
|
174
175
|
</div>
|
|
175
176
|
`;
|
|
@@ -204,7 +205,7 @@ function renderFillToggle(draft) {
|
|
|
204
205
|
const hasImportedRows = (draft.importedCsvRows?.length ?? 0) > 0;
|
|
205
206
|
|
|
206
207
|
return `
|
|
207
|
-
<label class="table-designer-fill-toggle ${hasImportedRows ? "" : "is-disabled"}">
|
|
208
|
+
<label class="standard-checkbox table-designer-fill-toggle table-designer-checkbox-override ${hasImportedRows ? "" : "is-disabled"}">
|
|
208
209
|
<input
|
|
209
210
|
data-bind="table-designer-field"
|
|
210
211
|
data-field="fillImportedRows"
|
|
@@ -301,14 +302,14 @@ export function renderTableDesignerEditor(state) {
|
|
|
301
302
|
<div class="table-designer-main__actions">
|
|
302
303
|
${renderFillToggle(draft)}
|
|
303
304
|
<button
|
|
304
|
-
class="
|
|
305
|
+
class="standard-button"
|
|
305
306
|
data-action="refresh-view"
|
|
306
307
|
type="button"
|
|
307
308
|
>
|
|
308
309
|
Reload Schema
|
|
309
310
|
</button>
|
|
310
311
|
<button
|
|
311
|
-
class="
|
|
312
|
+
class="standard-button"
|
|
312
313
|
data-action="save-table-designer"
|
|
313
314
|
${draft.canSave ? "" : "disabled"}
|
|
314
315
|
type="button"
|
|
@@ -342,7 +343,7 @@ export function renderTableDesignerEditor(state) {
|
|
|
342
343
|
<div class="table-designer-main__section-title">Columns</div>
|
|
343
344
|
</div>
|
|
344
345
|
<button
|
|
345
|
-
class="
|
|
346
|
+
class="standard-button"
|
|
346
347
|
data-action="add-table-designer-column"
|
|
347
348
|
type="button"
|
|
348
349
|
>
|
|
@@ -1,41 +1,34 @@
|
|
|
1
|
-
import { escapeHtml, formatNumber } from
|
|
1
|
+
import { escapeHtml, formatNumber } from '../utils/format.js';
|
|
2
2
|
|
|
3
3
|
function getFilteredTables(tables, searchQuery) {
|
|
4
|
-
|
|
4
|
+
const normalizedSearch = String(searchQuery ?? '')
|
|
5
|
+
.trim()
|
|
6
|
+
.toLowerCase();
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
if (!normalizedSearch) {
|
|
9
|
+
return tables;
|
|
10
|
+
}
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
return tables.filter(table => table.name.toLowerCase().includes(normalizedSearch));
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function renderTableDesignerSidebar(state) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
const tables = state.tableDesigner.tables ?? [];
|
|
17
|
+
const filteredTables = getFilteredTables(tables, state.tableDesigner.searchQuery);
|
|
18
|
+
const isNewDraft = state.tableDesigner.draft?.mode === 'create';
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
return `
|
|
19
21
|
<aside class="table-designer-sidebar">
|
|
20
22
|
<div class="table-designer-sidebar__header">
|
|
21
23
|
<div>
|
|
22
|
-
<div class="table-designer-sidebar__eyebrow">
|
|
23
|
-
<div class="table-designer-sidebar__title">Table Designer</div>
|
|
24
|
+
<div class="table-designer-sidebar__eyebrow">Table Designer</div>
|
|
24
25
|
<div class="table-designer-sidebar__meta">
|
|
25
|
-
${escapeHtml(formatNumber(tables.length))} table${tables.length === 1 ?
|
|
26
|
+
${escapeHtml(formatNumber(tables.length))} table${tables.length === 1 ? '' : 's'}
|
|
26
27
|
</div>
|
|
27
28
|
</div>
|
|
28
29
|
<div class="table-designer-sidebar__header-actions">
|
|
29
30
|
<button
|
|
30
|
-
class="
|
|
31
|
-
data-action="navigate"
|
|
32
|
-
data-to="/table-designer/new"
|
|
33
|
-
type="button"
|
|
34
|
-
>
|
|
35
|
-
+ New Table
|
|
36
|
-
</button>
|
|
37
|
-
<button
|
|
38
|
-
class="table-designer-sidebar__import-button"
|
|
31
|
+
class="standard-button"
|
|
39
32
|
data-action="import-table-designer-csv"
|
|
40
33
|
type="button"
|
|
41
34
|
>
|
|
@@ -46,7 +39,16 @@ export function renderTableDesignerSidebar(state) {
|
|
|
46
39
|
class="table-designer-sidebar__file-input"
|
|
47
40
|
data-bind="table-designer-import-file"
|
|
48
41
|
type="file"
|
|
49
|
-
/>
|
|
42
|
+
/>
|
|
43
|
+
<button
|
|
44
|
+
class="signature-button"
|
|
45
|
+
data-action="navigate"
|
|
46
|
+
data-to="/table-designer/new"
|
|
47
|
+
type="button"
|
|
48
|
+
>
|
|
49
|
+
+ New Table
|
|
50
|
+
</button>
|
|
51
|
+
|
|
50
52
|
</div>
|
|
51
53
|
</div>
|
|
52
54
|
|
|
@@ -58,23 +60,23 @@ export function renderTableDesignerSidebar(state) {
|
|
|
58
60
|
placeholder="Search tables..."
|
|
59
61
|
spellcheck="false"
|
|
60
62
|
type="search"
|
|
61
|
-
value="${escapeHtml(state.tableDesigner.searchQuery ??
|
|
63
|
+
value="${escapeHtml(state.tableDesigner.searchQuery ?? '')}"
|
|
62
64
|
/>
|
|
63
65
|
</label>
|
|
64
66
|
|
|
65
67
|
<div class="table-designer-sidebar__list custom-scrollbar">
|
|
66
68
|
${
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
state.tableDesigner.loading && !tables.length
|
|
70
|
+
? `
|
|
69
71
|
<div class="table-designer-sidebar__empty">
|
|
70
72
|
<span class="material-symbols-outlined mb-2 text-3xl">progress_activity</span>
|
|
71
73
|
<div>Loading SQLite schema...</div>
|
|
72
74
|
</div>
|
|
73
75
|
`
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
: isNewDraft
|
|
77
|
+
? `
|
|
76
78
|
<button
|
|
77
|
-
class="table-designer-sidebar__item is-active"
|
|
79
|
+
class="table-designer-sidebar__item is-active w-full border border-primary-container/30 bg-surface-container-high px-4 py-3 text-left transition-colors"
|
|
78
80
|
data-action="navigate"
|
|
79
81
|
data-to="/table-designer/new"
|
|
80
82
|
type="button"
|
|
@@ -83,42 +85,44 @@ export function renderTableDesignerSidebar(state) {
|
|
|
83
85
|
<div class="table-designer-sidebar__item-meta">unsaved schema</div>
|
|
84
86
|
</button>
|
|
85
87
|
`
|
|
86
|
-
|
|
88
|
+
: ''
|
|
87
89
|
}
|
|
88
90
|
${
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
!filteredTables.length && !state.tableDesigner.loading
|
|
92
|
+
? `
|
|
91
93
|
<div class="table-designer-sidebar__empty">
|
|
92
94
|
${
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
tables.length
|
|
96
|
+
? 'No tables match the current search.'
|
|
97
|
+
: 'No tables found. Create the first table in this database.'
|
|
96
98
|
}
|
|
97
99
|
</div>
|
|
98
100
|
`
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
: filteredTables
|
|
102
|
+
.map(
|
|
103
|
+
table => `
|
|
102
104
|
<button
|
|
103
|
-
class="table-designer-sidebar__item ${
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
class="table-designer-sidebar__item w-full border px-4 py-3 text-left transition-colors ${
|
|
106
|
+
!isNewDraft && table.name === state.tableDesigner.selectedTableName
|
|
107
|
+
? 'is-active border-primary-container/30 bg-surface-container-high'
|
|
108
|
+
: 'border-outline-variant/10 bg-surface-container-lowest hover:bg-surface-container-high'
|
|
107
109
|
}"
|
|
108
110
|
data-action="navigate"
|
|
109
111
|
data-to="/table-designer/${encodeURIComponent(table.name)}"
|
|
110
112
|
type="button"
|
|
111
113
|
>
|
|
112
|
-
<div class="table-designer-sidebar__item-name
|
|
113
|
-
|
|
114
|
+
<div class="table-designer-sidebar__item-name ${
|
|
115
|
+
!isNewDraft && table.name === state.tableDesigner.selectedTableName ? 'is-active' : ''
|
|
116
|
+
}">${escapeHtml(table.name)}</div>
|
|
117
|
+
<div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
|
|
114
118
|
${escapeHtml(formatNumber(table.columnCount ?? 0))} column${
|
|
115
|
-
|
|
119
|
+
Number(table.columnCount ?? 0) === 1 ? '' : 's'
|
|
116
120
|
}
|
|
117
121
|
</div>
|
|
118
122
|
</button>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
`,
|
|
124
|
+
)
|
|
125
|
+
.join('')
|
|
122
126
|
}
|
|
123
127
|
</div>
|
|
124
128
|
</aside>
|
|
@@ -1,40 +1,72 @@
|
|
|
1
1
|
import { escapeHtml } from "../utils/format.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function renderPreviewHeader(draft, isVisible) {
|
|
4
|
+
return `
|
|
5
|
+
<div class="table-designer-preview__header">
|
|
6
|
+
<div>
|
|
7
|
+
<div class="table-designer-preview__eyebrow">SQL Preview</div>
|
|
8
|
+
<div class="table-designer-preview__title">${
|
|
9
|
+
draft ? "Live SQLite Output" : "No Draft Selected"
|
|
10
|
+
}</div>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="table-designer-preview__actions">
|
|
13
|
+
${
|
|
14
|
+
draft && isVisible
|
|
15
|
+
? `
|
|
16
|
+
<button
|
|
17
|
+
class="standard-button"
|
|
18
|
+
data-action="copy-table-designer-sql"
|
|
19
|
+
type="button"
|
|
20
|
+
>
|
|
21
|
+
Copy SQL
|
|
22
|
+
</button>
|
|
23
|
+
`
|
|
24
|
+
: ""
|
|
25
|
+
}
|
|
26
|
+
<button
|
|
27
|
+
class="standard-button"
|
|
28
|
+
data-action="toggle-table-designer-sql-preview"
|
|
29
|
+
data-next-value="${isVisible ? "false" : "true"}"
|
|
30
|
+
type="button"
|
|
31
|
+
>
|
|
32
|
+
<span class="material-symbols-outlined">${isVisible ? "visibility_off" : "expand_less"}</span>
|
|
33
|
+
${isVisible ? "Hide" : "Show"}
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function renderTableDesignerSqlPreview(draft, isVisible = true) {
|
|
4
41
|
if (!draft) {
|
|
5
42
|
return `
|
|
6
|
-
<section class="table-designer-preview shell-section">
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
43
|
+
<section class="table-designer-preview shell-section${isVisible ? "" : " is-collapsed"}">
|
|
44
|
+
${renderPreviewHeader(draft, isVisible)}
|
|
45
|
+
${
|
|
46
|
+
isVisible
|
|
47
|
+
? `
|
|
48
|
+
<div class="table-designer-preview__empty">
|
|
49
|
+
Select a table or create a new one to inspect the generated SQLite statements.
|
|
50
|
+
</div>
|
|
51
|
+
`
|
|
52
|
+
: ""
|
|
53
|
+
}
|
|
16
54
|
</section>
|
|
17
55
|
`;
|
|
18
56
|
}
|
|
19
57
|
|
|
20
58
|
return `
|
|
21
|
-
<section class="table-designer-preview shell-section">
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Copy SQL
|
|
33
|
-
</button>
|
|
34
|
-
</div>
|
|
35
|
-
<pre class="table-designer-preview__body custom-scrollbar">${escapeHtml(
|
|
36
|
-
draft.sqlPreview || "-- SQL preview unavailable."
|
|
37
|
-
)}</pre>
|
|
59
|
+
<section class="table-designer-preview shell-section${isVisible ? "" : " is-collapsed"}">
|
|
60
|
+
${renderPreviewHeader(draft, isVisible)}
|
|
61
|
+
${
|
|
62
|
+
isVisible
|
|
63
|
+
? `
|
|
64
|
+
<pre class="table-designer-preview__body custom-scrollbar">${escapeHtml(
|
|
65
|
+
draft.sqlPreview || "-- SQL preview unavailable."
|
|
66
|
+
)}</pre>
|
|
67
|
+
`
|
|
68
|
+
: ""
|
|
69
|
+
}
|
|
38
70
|
</section>
|
|
39
71
|
`;
|
|
40
72
|
}
|