sqlite-hub 0.5.0 → 0.7.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.
- package/README.md +109 -2
- package/bin/sqlite-hub.js +285 -4
- package/changelog.md +15 -0
- package/frontend/assets/mockups/connections.png +0 -0
- package/frontend/assets/mockups/data.png +0 -0
- package/frontend/assets/mockups/data_row_editor.png +0 -0
- package/frontend/assets/mockups/home.png +0 -0
- package/frontend/assets/mockups/sql_editor.png +0 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
- package/frontend/assets/mockups/structure.png +0 -0
- package/frontend/assets/mockups/structure_inspector.png +0 -0
- package/frontend/index.html +1 -0
- package/frontend/js/api.js +49 -0
- package/frontend/js/app.js +377 -9
- package/frontend/js/components/modal.js +314 -1
- package/frontend/js/components/queryChartRenderer.js +125 -0
- package/frontend/js/components/queryEditor.js +37 -8
- package/frontend/js/components/queryHistoryPanel.js +16 -5
- package/frontend/js/components/sidebar.js +2 -0
- package/frontend/js/components/tableDesignerEditor.js +356 -0
- package/frontend/js/components/tableDesignerSidebar.js +129 -0
- package/frontend/js/components/tableDesignerSqlPreview.js +40 -0
- package/frontend/js/lib/queryChartOptions.js +283 -0
- package/frontend/js/lib/queryCharts.js +560 -0
- package/frontend/js/router.js +18 -0
- package/frontend/js/store.js +914 -1
- package/frontend/js/utils/tableDesigner.js +1192 -0
- package/frontend/js/views/charts.js +471 -0
- package/frontend/js/views/data.js +281 -277
- package/frontend/js/views/editor.js +19 -13
- package/frontend/js/views/structure.js +81 -39
- package/frontend/js/views/tableDesigner.js +37 -0
- package/frontend/styles/base.css +87 -73
- package/frontend/styles/components.css +790 -251
- package/frontend/styles/views.css +316 -46
- package/package.json +2 -1
- package/server/routes/charts.js +153 -0
- package/server/routes/tableDesigner.js +60 -0
- package/server/server.js +10 -0
- package/server/services/sqlite/tableDesigner/changeAnalysis.js +295 -0
- package/server/services/sqlite/tableDesigner/schemaMapping.js +233 -0
- package/server/services/sqlite/tableDesigner/sql.js +63 -0
- package/server/services/sqlite/tableDesigner/validation.js +245 -0
- package/server/services/sqlite/tableDesignerService.js +181 -0
- package/server/services/storage/appStateStore.js +400 -1
- package/server/services/storage/queryHistoryChartUtils.js +145 -0
- package/frontend/assets/mockups/data_edit.png +0 -0
- package/frontend/assets/mockups/graph_visualize.png +0 -0
- package/frontend/assets/mockups/overview.png +0 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { escapeHtml } from "../utils/format.js";
|
|
2
|
+
|
|
3
|
+
function renderWarningList(items = [], className, title, tone = "alert") {
|
|
4
|
+
if (!items.length) {
|
|
5
|
+
return "";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return `
|
|
9
|
+
<section class="${className}">
|
|
10
|
+
<div class="table-designer-banner__header">
|
|
11
|
+
<div class="table-designer-banner__title">${escapeHtml(title)}</div>
|
|
12
|
+
<div class="status-badge status-badge--${tone}">
|
|
13
|
+
${escapeHtml(String(items.length))}
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="table-designer-banner__list">
|
|
17
|
+
${items
|
|
18
|
+
.map(
|
|
19
|
+
(item) => `
|
|
20
|
+
<div class="table-designer-banner__item">
|
|
21
|
+
<div class="table-designer-banner__item-title">${escapeHtml(
|
|
22
|
+
item.title || item
|
|
23
|
+
)}</div>
|
|
24
|
+
${
|
|
25
|
+
item.message
|
|
26
|
+
? `<div class="table-designer-banner__item-text">${escapeHtml(item.message)}</div>`
|
|
27
|
+
: ""
|
|
28
|
+
}
|
|
29
|
+
</div>
|
|
30
|
+
`
|
|
31
|
+
)
|
|
32
|
+
.join("")}
|
|
33
|
+
</div>
|
|
34
|
+
</section>
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getReferencedColumns(draft, catalogTables, referencesTable) {
|
|
39
|
+
const normalizedReference = String(referencesTable ?? "").trim().toLowerCase();
|
|
40
|
+
const normalizedDraftTable = String(draft.tableName ?? "").trim().toLowerCase();
|
|
41
|
+
|
|
42
|
+
if (!normalizedReference) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (normalizedReference === normalizedDraftTable) {
|
|
47
|
+
return draft.columns
|
|
48
|
+
.filter((column) => !column.deleted)
|
|
49
|
+
.map((column) => column.name)
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
catalogTables.find((table) => table.name.toLowerCase() === normalizedReference)?.columns ?? []
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderColumnRow(column, draft, catalogTables) {
|
|
59
|
+
const referenceColumns = getReferencedColumns(draft, catalogTables, column.referencesTable);
|
|
60
|
+
|
|
61
|
+
return `
|
|
62
|
+
<div class="table-designer-grid__row">
|
|
63
|
+
<input
|
|
64
|
+
class="table-designer-field"
|
|
65
|
+
data-bind="table-designer-column-field"
|
|
66
|
+
data-column-id="${escapeHtml(column.id)}"
|
|
67
|
+
data-field="name"
|
|
68
|
+
placeholder="column_name"
|
|
69
|
+
spellcheck="false"
|
|
70
|
+
type="text"
|
|
71
|
+
value="${escapeHtml(column.name)}"
|
|
72
|
+
/>
|
|
73
|
+
<select
|
|
74
|
+
class="table-designer-field"
|
|
75
|
+
data-bind="table-designer-column-field"
|
|
76
|
+
data-column-id="${escapeHtml(column.id)}"
|
|
77
|
+
data-field="type"
|
|
78
|
+
>
|
|
79
|
+
${draft.supportedTypes
|
|
80
|
+
.map(
|
|
81
|
+
(type) => `
|
|
82
|
+
<option value="${escapeHtml(type)}" ${type === column.type ? "selected" : ""}>
|
|
83
|
+
${escapeHtml(type)}
|
|
84
|
+
</option>
|
|
85
|
+
`
|
|
86
|
+
)
|
|
87
|
+
.join("")}
|
|
88
|
+
</select>
|
|
89
|
+
<label class="table-designer-check">
|
|
90
|
+
<input
|
|
91
|
+
data-bind="table-designer-column-flag"
|
|
92
|
+
data-column-id="${escapeHtml(column.id)}"
|
|
93
|
+
data-field="notNull"
|
|
94
|
+
type="checkbox"
|
|
95
|
+
${column.notNull ? "checked" : ""}
|
|
96
|
+
/>
|
|
97
|
+
<span>Not null</span>
|
|
98
|
+
</label>
|
|
99
|
+
<label class="table-designer-check">
|
|
100
|
+
<input
|
|
101
|
+
data-bind="table-designer-column-flag"
|
|
102
|
+
data-column-id="${escapeHtml(column.id)}"
|
|
103
|
+
data-field="unique"
|
|
104
|
+
type="checkbox"
|
|
105
|
+
${column.unique ? "checked" : ""}
|
|
106
|
+
/>
|
|
107
|
+
<span>Unique</span>
|
|
108
|
+
</label>
|
|
109
|
+
<label class="table-designer-check">
|
|
110
|
+
<input
|
|
111
|
+
data-bind="table-designer-column-flag"
|
|
112
|
+
data-column-id="${escapeHtml(column.id)}"
|
|
113
|
+
data-field="primaryKey"
|
|
114
|
+
type="checkbox"
|
|
115
|
+
${column.primaryKey ? "checked" : ""}
|
|
116
|
+
/>
|
|
117
|
+
<span>PK</span>
|
|
118
|
+
</label>
|
|
119
|
+
<input
|
|
120
|
+
class="table-designer-field"
|
|
121
|
+
data-bind="table-designer-column-field"
|
|
122
|
+
data-column-id="${escapeHtml(column.id)}"
|
|
123
|
+
data-field="defaultValue"
|
|
124
|
+
placeholder="SQL default"
|
|
125
|
+
spellcheck="false"
|
|
126
|
+
type="text"
|
|
127
|
+
value="${escapeHtml(column.defaultValue)}"
|
|
128
|
+
/>
|
|
129
|
+
<select
|
|
130
|
+
class="table-designer-field"
|
|
131
|
+
data-bind="table-designer-column-field"
|
|
132
|
+
data-column-id="${escapeHtml(column.id)}"
|
|
133
|
+
data-field="referencesTable"
|
|
134
|
+
>
|
|
135
|
+
<option value="">No FK table</option>
|
|
136
|
+
${catalogTables
|
|
137
|
+
.map(
|
|
138
|
+
(table) => `
|
|
139
|
+
<option
|
|
140
|
+
value="${escapeHtml(table.name)}"
|
|
141
|
+
${table.name === column.referencesTable ? "selected" : ""}
|
|
142
|
+
>
|
|
143
|
+
${escapeHtml(table.name)}
|
|
144
|
+
</option>
|
|
145
|
+
`
|
|
146
|
+
)
|
|
147
|
+
.join("")}
|
|
148
|
+
</select>
|
|
149
|
+
<select
|
|
150
|
+
class="table-designer-field"
|
|
151
|
+
data-bind="table-designer-column-field"
|
|
152
|
+
data-column-id="${escapeHtml(column.id)}"
|
|
153
|
+
data-field="referencesColumn"
|
|
154
|
+
>
|
|
155
|
+
<option value="">No FK column</option>
|
|
156
|
+
${referenceColumns
|
|
157
|
+
.map(
|
|
158
|
+
(name) => `
|
|
159
|
+
<option value="${escapeHtml(name)}" ${name === column.referencesColumn ? "selected" : ""}>
|
|
160
|
+
${escapeHtml(name)}
|
|
161
|
+
</option>
|
|
162
|
+
`
|
|
163
|
+
)
|
|
164
|
+
.join("")}
|
|
165
|
+
</select>
|
|
166
|
+
<button
|
|
167
|
+
class="table-designer-delete"
|
|
168
|
+
data-action="remove-table-designer-column"
|
|
169
|
+
data-column-id="${escapeHtml(column.id)}"
|
|
170
|
+
type="button"
|
|
171
|
+
>
|
|
172
|
+
<span class="material-symbols-outlined text-base">delete</span>
|
|
173
|
+
</button>
|
|
174
|
+
</div>
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function renderColumnGrid(draft, catalogTables) {
|
|
179
|
+
const visibleColumns = draft.columns.filter((column) => !column.deleted);
|
|
180
|
+
|
|
181
|
+
return `
|
|
182
|
+
<div class="table-designer-grid custom-scrollbar">
|
|
183
|
+
<div class="table-designer-grid__header">
|
|
184
|
+
<div>Name</div>
|
|
185
|
+
<div>Type</div>
|
|
186
|
+
<div>Not Null</div>
|
|
187
|
+
<div>Unique</div>
|
|
188
|
+
<div>Primary Key</div>
|
|
189
|
+
<div>Default</div>
|
|
190
|
+
<div>FK Table</div>
|
|
191
|
+
<div>FK Column</div>
|
|
192
|
+
<div></div>
|
|
193
|
+
</div>
|
|
194
|
+
${visibleColumns.map((column) => renderColumnRow(column, draft, catalogTables)).join("")}
|
|
195
|
+
</div>
|
|
196
|
+
`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function renderFillToggle(draft) {
|
|
200
|
+
if (draft.mode !== "create") {
|
|
201
|
+
return "";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const hasImportedRows = (draft.importedCsvRows?.length ?? 0) > 0;
|
|
205
|
+
|
|
206
|
+
return `
|
|
207
|
+
<label class="table-designer-fill-toggle ${hasImportedRows ? "" : "is-disabled"}">
|
|
208
|
+
<input
|
|
209
|
+
data-bind="table-designer-field"
|
|
210
|
+
data-field="fillImportedRows"
|
|
211
|
+
type="checkbox"
|
|
212
|
+
${draft.fillImportedRows ? "checked" : ""}
|
|
213
|
+
${hasImportedRows ? "" : "disabled"}
|
|
214
|
+
/>
|
|
215
|
+
<span>Fill</span>
|
|
216
|
+
<span class="table-designer-fill-toggle__meta">
|
|
217
|
+
${
|
|
218
|
+
hasImportedRows
|
|
219
|
+
? `${escapeHtml(String(draft.importedCsvRows.length))} imported row${
|
|
220
|
+
draft.importedCsvRows.length === 1 ? "" : "s"
|
|
221
|
+
}`
|
|
222
|
+
: "Available after CSV import"
|
|
223
|
+
}
|
|
224
|
+
</span>
|
|
225
|
+
</label>
|
|
226
|
+
`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function renderTableDesignerEditor(state) {
|
|
230
|
+
const draft = state.tableDesigner.draft;
|
|
231
|
+
|
|
232
|
+
if (state.tableDesigner.loading || state.tableDesigner.detailLoading) {
|
|
233
|
+
return `
|
|
234
|
+
<section class="table-designer-main__empty shell-section">
|
|
235
|
+
<span class="material-symbols-outlined mb-3 text-4xl">progress_activity</span>
|
|
236
|
+
<div class="table-designer-main__empty-title">Loading Schema Draft</div>
|
|
237
|
+
<div class="table-designer-main__empty-text">
|
|
238
|
+
Reading the current SQLite schema and preparing the editable draft.
|
|
239
|
+
</div>
|
|
240
|
+
</section>
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!draft) {
|
|
245
|
+
return `
|
|
246
|
+
<section class="table-designer-main__empty shell-section">
|
|
247
|
+
<div class="table-designer-main__empty-title">No Table Selected</div>
|
|
248
|
+
<div class="table-designer-main__empty-text">
|
|
249
|
+
Open an existing table from the left or start a new draft with <strong>+ New Table</strong>.
|
|
250
|
+
</div>
|
|
251
|
+
</section>
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const validationItems = (draft.validationErrors ?? []).map((message) => ({
|
|
256
|
+
title: message,
|
|
257
|
+
}));
|
|
258
|
+
const warningItems = draft.warnings ?? [];
|
|
259
|
+
const catalogTables = state.tableDesigner.tables ?? [];
|
|
260
|
+
const visibleColumns = draft.columns.filter((column) => !column.deleted);
|
|
261
|
+
const saveLabel =
|
|
262
|
+
draft.mode === "create"
|
|
263
|
+
? state.tableDesigner.saving
|
|
264
|
+
? "Creating..."
|
|
265
|
+
: "Create Table"
|
|
266
|
+
: state.tableDesigner.saving
|
|
267
|
+
? "Saving..."
|
|
268
|
+
: "Save Changes";
|
|
269
|
+
|
|
270
|
+
return `
|
|
271
|
+
<section class="table-designer-main shell-section">
|
|
272
|
+
<header class="table-designer-main__header">
|
|
273
|
+
<div>
|
|
274
|
+
<div class="table-designer-main__eyebrow">
|
|
275
|
+
${draft.mode === "create" ? "New Table Draft" : "Editing Existing Table"}
|
|
276
|
+
</div>
|
|
277
|
+
<div class="table-designer-main__title-row">
|
|
278
|
+
<input
|
|
279
|
+
class="table-designer-main__name"
|
|
280
|
+
data-bind="table-designer-field"
|
|
281
|
+
data-field="tableName"
|
|
282
|
+
placeholder="table_name"
|
|
283
|
+
spellcheck="false"
|
|
284
|
+
type="text"
|
|
285
|
+
value="${escapeHtml(draft.tableName)}"
|
|
286
|
+
/>
|
|
287
|
+
${
|
|
288
|
+
draft.mode === "edit"
|
|
289
|
+
? `<div class="status-badge status-badge--muted">FROM ${escapeHtml(
|
|
290
|
+
draft.originalTableName
|
|
291
|
+
)}</div>`
|
|
292
|
+
: `<div class="status-badge status-badge--primary">CREATE</div>`
|
|
293
|
+
}
|
|
294
|
+
</div>
|
|
295
|
+
<div class="table-designer-main__subtitle">
|
|
296
|
+
${escapeHtml(String(visibleColumns.length))} visible column${
|
|
297
|
+
visibleColumns.length === 1 ? "" : "s"
|
|
298
|
+
} // SQLite-safe operations only
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="table-designer-main__actions">
|
|
302
|
+
${renderFillToggle(draft)}
|
|
303
|
+
<button
|
|
304
|
+
class="table-designer-main__secondary"
|
|
305
|
+
data-action="refresh-view"
|
|
306
|
+
type="button"
|
|
307
|
+
>
|
|
308
|
+
Reload Schema
|
|
309
|
+
</button>
|
|
310
|
+
<button
|
|
311
|
+
class="table-designer-main__primary ${draft.canSave ? "" : "is-disabled"}"
|
|
312
|
+
data-action="save-table-designer"
|
|
313
|
+
${draft.canSave ? "" : "disabled"}
|
|
314
|
+
type="button"
|
|
315
|
+
>
|
|
316
|
+
${escapeHtml(saveLabel)}
|
|
317
|
+
</button>
|
|
318
|
+
</div>
|
|
319
|
+
</header>
|
|
320
|
+
|
|
321
|
+
${
|
|
322
|
+
state.tableDesigner.saveError
|
|
323
|
+
? `
|
|
324
|
+
<div class="table-designer-main__error">
|
|
325
|
+
<div class="table-designer-main__error-code">${escapeHtml(
|
|
326
|
+
state.tableDesigner.saveError.code
|
|
327
|
+
)}</div>
|
|
328
|
+
<div class="table-designer-main__error-text">${escapeHtml(
|
|
329
|
+
state.tableDesigner.saveError.message
|
|
330
|
+
)}</div>
|
|
331
|
+
</div>
|
|
332
|
+
`
|
|
333
|
+
: ""
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
${renderWarningList(validationItems, "table-designer-banner is-validation", "Validation", "alert")}
|
|
337
|
+
${renderWarningList(warningItems, "table-designer-banner is-warning", "Warnings", "alert")}
|
|
338
|
+
|
|
339
|
+
<section class="table-designer-main__section">
|
|
340
|
+
<div class="table-designer-main__section-header">
|
|
341
|
+
<div>
|
|
342
|
+
<div class="table-designer-main__section-title">Columns</div>
|
|
343
|
+
</div>
|
|
344
|
+
<button
|
|
345
|
+
class="table-designer-main__secondary"
|
|
346
|
+
data-action="add-table-designer-column"
|
|
347
|
+
type="button"
|
|
348
|
+
>
|
|
349
|
+
+ Add Column
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
${renderColumnGrid(draft, catalogTables)}
|
|
353
|
+
</section>
|
|
354
|
+
</section>
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { escapeHtml, formatNumber } from '../utils/format.js';
|
|
2
|
+
|
|
3
|
+
function getFilteredTables(tables, searchQuery) {
|
|
4
|
+
const normalizedSearch = String(searchQuery ?? '')
|
|
5
|
+
.trim()
|
|
6
|
+
.toLowerCase();
|
|
7
|
+
|
|
8
|
+
if (!normalizedSearch) {
|
|
9
|
+
return tables;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return tables.filter(table => table.name.toLowerCase().includes(normalizedSearch));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function renderTableDesignerSidebar(state) {
|
|
16
|
+
const tables = state.tableDesigner.tables ?? [];
|
|
17
|
+
const filteredTables = getFilteredTables(tables, state.tableDesigner.searchQuery);
|
|
18
|
+
const isNewDraft = state.tableDesigner.draft?.mode === 'create';
|
|
19
|
+
|
|
20
|
+
return `
|
|
21
|
+
<aside class="table-designer-sidebar">
|
|
22
|
+
<div class="table-designer-sidebar__header">
|
|
23
|
+
<div>
|
|
24
|
+
<div class="table-designer-sidebar__eyebrow">Table Designer</div>
|
|
25
|
+
<div class="table-designer-sidebar__meta">
|
|
26
|
+
${escapeHtml(formatNumber(tables.length))} table${tables.length === 1 ? '' : 's'}
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="table-designer-sidebar__header-actions">
|
|
30
|
+
<button
|
|
31
|
+
class="table-designer-sidebar__new-button"
|
|
32
|
+
data-action="navigate"
|
|
33
|
+
data-to="/table-designer/new"
|
|
34
|
+
type="button"
|
|
35
|
+
>
|
|
36
|
+
+ New Table
|
|
37
|
+
</button>
|
|
38
|
+
<button
|
|
39
|
+
class="table-designer-sidebar__import-button"
|
|
40
|
+
data-action="import-table-designer-csv"
|
|
41
|
+
type="button"
|
|
42
|
+
>
|
|
43
|
+
Import CSV
|
|
44
|
+
</button>
|
|
45
|
+
<input
|
|
46
|
+
accept=".csv,text/csv"
|
|
47
|
+
class="table-designer-sidebar__file-input"
|
|
48
|
+
data-bind="table-designer-import-file"
|
|
49
|
+
type="file"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<label class="table-designer-sidebar__search">
|
|
55
|
+
<span class="material-symbols-outlined text-sm text-on-surface-variant/55">search</span>
|
|
56
|
+
<input
|
|
57
|
+
class="table-designer-sidebar__search-input"
|
|
58
|
+
data-bind="table-designer-search"
|
|
59
|
+
placeholder="Search tables..."
|
|
60
|
+
spellcheck="false"
|
|
61
|
+
type="search"
|
|
62
|
+
value="${escapeHtml(state.tableDesigner.searchQuery ?? '')}"
|
|
63
|
+
/>
|
|
64
|
+
</label>
|
|
65
|
+
|
|
66
|
+
<div class="table-designer-sidebar__list custom-scrollbar">
|
|
67
|
+
${
|
|
68
|
+
state.tableDesigner.loading && !tables.length
|
|
69
|
+
? `
|
|
70
|
+
<div class="table-designer-sidebar__empty">
|
|
71
|
+
<span class="material-symbols-outlined mb-2 text-3xl">progress_activity</span>
|
|
72
|
+
<div>Loading SQLite schema...</div>
|
|
73
|
+
</div>
|
|
74
|
+
`
|
|
75
|
+
: isNewDraft
|
|
76
|
+
? `
|
|
77
|
+
<button
|
|
78
|
+
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"
|
|
79
|
+
data-action="navigate"
|
|
80
|
+
data-to="/table-designer/new"
|
|
81
|
+
type="button"
|
|
82
|
+
>
|
|
83
|
+
<div class="table-designer-sidebar__item-name">New Table Draft</div>
|
|
84
|
+
<div class="table-designer-sidebar__item-meta">unsaved schema</div>
|
|
85
|
+
</button>
|
|
86
|
+
`
|
|
87
|
+
: ''
|
|
88
|
+
}
|
|
89
|
+
${
|
|
90
|
+
!filteredTables.length && !state.tableDesigner.loading
|
|
91
|
+
? `
|
|
92
|
+
<div class="table-designer-sidebar__empty">
|
|
93
|
+
${
|
|
94
|
+
tables.length
|
|
95
|
+
? 'No tables match the current search.'
|
|
96
|
+
: 'No tables found. Create the first table in this database.'
|
|
97
|
+
}
|
|
98
|
+
</div>
|
|
99
|
+
`
|
|
100
|
+
: filteredTables
|
|
101
|
+
.map(
|
|
102
|
+
table => `
|
|
103
|
+
<button
|
|
104
|
+
class="table-designer-sidebar__item w-full border px-4 py-3 text-left transition-colors ${
|
|
105
|
+
!isNewDraft && table.name === state.tableDesigner.selectedTableName
|
|
106
|
+
? 'is-active border-primary-container/30 bg-surface-container-high'
|
|
107
|
+
: 'border-outline-variant/10 bg-surface-container-lowest hover:bg-surface-container-high'
|
|
108
|
+
}"
|
|
109
|
+
data-action="navigate"
|
|
110
|
+
data-to="/table-designer/${encodeURIComponent(table.name)}"
|
|
111
|
+
type="button"
|
|
112
|
+
>
|
|
113
|
+
<div class="table-designer-sidebar__item-name ${
|
|
114
|
+
!isNewDraft && table.name === state.tableDesigner.selectedTableName ? 'is-active' : ''
|
|
115
|
+
}">${escapeHtml(table.name)}</div>
|
|
116
|
+
<div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
|
|
117
|
+
${escapeHtml(formatNumber(table.columnCount ?? 0))} column${
|
|
118
|
+
Number(table.columnCount ?? 0) === 1 ? '' : 's'
|
|
119
|
+
}
|
|
120
|
+
</div>
|
|
121
|
+
</button>
|
|
122
|
+
`,
|
|
123
|
+
)
|
|
124
|
+
.join('')
|
|
125
|
+
}
|
|
126
|
+
</div>
|
|
127
|
+
</aside>
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { escapeHtml } from "../utils/format.js";
|
|
2
|
+
|
|
3
|
+
export function renderTableDesignerSqlPreview(draft) {
|
|
4
|
+
if (!draft) {
|
|
5
|
+
return `
|
|
6
|
+
<section class="table-designer-preview shell-section">
|
|
7
|
+
<div class="table-designer-preview__header">
|
|
8
|
+
<div>
|
|
9
|
+
<div class="table-designer-preview__eyebrow">SQL Preview</div>
|
|
10
|
+
<div class="table-designer-preview__title">No Draft Selected</div>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="table-designer-preview__empty">
|
|
14
|
+
Select a table or create a new one to inspect the generated SQLite statements.
|
|
15
|
+
</div>
|
|
16
|
+
</section>
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return `
|
|
21
|
+
<section class="table-designer-preview shell-section">
|
|
22
|
+
<div class="table-designer-preview__header">
|
|
23
|
+
<div>
|
|
24
|
+
<div class="table-designer-preview__eyebrow">SQL Preview</div>
|
|
25
|
+
<div class="table-designer-preview__title">Live SQLite Output</div>
|
|
26
|
+
</div>
|
|
27
|
+
<button
|
|
28
|
+
class="table-designer-preview__copy"
|
|
29
|
+
data-action="copy-table-designer-sql"
|
|
30
|
+
type="button"
|
|
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>
|
|
38
|
+
</section>
|
|
39
|
+
`;
|
|
40
|
+
}
|