sqlite-hub 2.0.1 → 2.2.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 +21 -9
- package/docs/MCP.md +8 -0
- package/docs/changelog.md +16 -0
- package/docs/guidelines/DESIGN.md +64 -0
- package/docs/todo.md +2 -0
- package/frontend/assets/mockups/backups_1_1920.webp +0 -0
- package/frontend/assets/mockups/backups_2_compare_drawer_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_6_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_1_1920.webp +0 -0
- package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_1_1920.webp +0 -0
- package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_1_1920.webp +0 -0
- package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_generate_data_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_4_data_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_1_1920.webp +0 -0
- package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
- package/frontend/assets/mockups/logs_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/overview_1_1920.webp +0 -0
- package/frontend/assets/mockups/settings_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_1_1920.webp +0 -0
- package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
- package/frontend/js/api.js +18 -0
- package/frontend/js/app.js +567 -15
- package/frontend/js/components/badges.js +16 -0
- package/frontend/js/components/connectionCard.js +35 -0
- package/frontend/js/components/modal.js +268 -247
- package/frontend/js/components/queryHistoryDetail.js +25 -45
- package/frontend/js/components/rowEditorPanel.js +7 -60
- package/frontend/js/components/structureGraph.js +292 -13
- package/frontend/js/components/tableDesignerConstraintsDrawer.js +400 -0
- package/frontend/js/components/tableDesignerEditor.js +51 -16
- package/frontend/js/components/tableDesignerSidebar.js +9 -5
- package/frontend/js/store.js +1847 -190
- package/frontend/js/utils/connectionRegistry.js +76 -0
- package/frontend/js/utils/markdownDocuments.js +21 -4
- package/frontend/js/utils/tableDesigner.js +431 -62
- package/frontend/js/views/backups.js +2 -5
- package/frontend/js/views/charts.js +20 -10
- package/frontend/js/views/connections.js +101 -6
- package/frontend/js/views/data.js +52 -9
- package/frontend/js/views/documents.js +153 -59
- package/frontend/js/views/structure.js +28 -5
- package/frontend/js/views/tableAdvisor.js +14 -6
- package/frontend/js/views/tableDesigner.js +44 -9
- package/frontend/styles/base.css +9 -0
- package/frontend/styles/components.css +676 -79
- package/frontend/styles/structure-graph.css +49 -0
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/views.css +12 -0
- package/package.json +1 -1
- package/server/routes/backups.js +37 -1
- package/server/routes/charts.js +28 -0
- package/server/routes/connections.js +12 -0
- package/server/routes/data.js +30 -2
- package/server/routes/documents.js +61 -5
- package/server/routes/settings.js +32 -0
- package/server/routes/tableDesigner.js +35 -2
- package/server/server.js +3 -3
- package/server/services/mcpToolService.js +30 -0
- package/server/services/sqlite/connectionManager.js +25 -2
- package/server/services/sqlite/dataBrowserService.js +21 -0
- package/server/services/sqlite/introspection.js +39 -1
- package/server/services/sqlite/overviewService.js +1 -0
- package/server/services/sqlite/structureService.js +66 -11
- package/server/services/sqlite/tableAdvisor.js +6 -0
- package/server/services/sqlite/tableDesigner/schemaMapping.js +7 -1
- package/server/services/sqlite/tableDesigner/sql.js +31 -2
- package/server/services/sqlite/tableDesigner/validation.js +69 -9
- package/server/services/sqlite/tableDesignerService.js +110 -5
- package/server/services/storage/appStateStore.js +590 -10
- package/server/utils/fileValidation.js +5 -0
- package/server/utils/userActionLog.js +49 -0
- package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
|
@@ -3,3 +3,19 @@ import { escapeHtml } from "../utils/format.js";
|
|
|
3
3
|
export function renderStatusBadge(label, tone = "muted") {
|
|
4
4
|
return `<span class="status-badge status-badge--${tone}">${escapeHtml(label)}</span>`;
|
|
5
5
|
}
|
|
6
|
+
|
|
7
|
+
export function renderTableKindBadge(table) {
|
|
8
|
+
if (table?.isVirtual) {
|
|
9
|
+
return `<span class="status-badge status-badge--primary shrink-0" title="Virtual table">Virtual</span>`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (table?.isShadow) {
|
|
13
|
+
return `<span class="status-badge status-badge--muted shrink-0" title="Shadow table">Shadow</span>`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function renderVirtualTableBadge(table) {
|
|
20
|
+
return renderTableKindBadge(table);
|
|
21
|
+
}
|
|
@@ -7,6 +7,40 @@ import {
|
|
|
7
7
|
import { renderStatusBadge } from "./badges.js";
|
|
8
8
|
import { renderConnectionLogo } from "./connectionLogo.js";
|
|
9
9
|
|
|
10
|
+
function renderConnectionTags(tags = []) {
|
|
11
|
+
const normalizedTags = Array.isArray(tags)
|
|
12
|
+
? tags.filter((tag) => String(tag?.name ?? "").trim())
|
|
13
|
+
: [];
|
|
14
|
+
|
|
15
|
+
if (!normalizedTags.length) {
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const visibleTags = normalizedTags.slice(0, 2);
|
|
20
|
+
const hiddenCount = Math.max(0, normalizedTags.length - visibleTags.length);
|
|
21
|
+
const title = normalizedTags.map((tag) => tag.name).join(", ");
|
|
22
|
+
|
|
23
|
+
return [
|
|
24
|
+
'<div class="connection-user-tags" title="',
|
|
25
|
+
escapeHtml(title),
|
|
26
|
+
'">',
|
|
27
|
+
visibleTags
|
|
28
|
+
.map(
|
|
29
|
+
(tag) =>
|
|
30
|
+
'<span class="connection-user-tag">' +
|
|
31
|
+
escapeHtml(tag.name) +
|
|
32
|
+
"</span>"
|
|
33
|
+
)
|
|
34
|
+
.join(""),
|
|
35
|
+
hiddenCount
|
|
36
|
+
? '<span class="connection-user-tag connection-user-tag--more">+' +
|
|
37
|
+
escapeHtml(String(hiddenCount)) +
|
|
38
|
+
"</span>"
|
|
39
|
+
: "",
|
|
40
|
+
"</div>",
|
|
41
|
+
].join("");
|
|
42
|
+
}
|
|
43
|
+
|
|
10
44
|
export function renderConnectionCard(connection, activeConnectionId) {
|
|
11
45
|
const isActive = activeConnectionId
|
|
12
46
|
? connection.id === activeConnectionId
|
|
@@ -46,6 +80,7 @@ export function renderConnectionCard(connection, activeConnectionId) {
|
|
|
46
80
|
'">',
|
|
47
81
|
escapeHtml(truncateMiddle(connection.path, 68)),
|
|
48
82
|
"</p>",
|
|
83
|
+
renderConnectionTags(connection.tags),
|
|
49
84
|
'<div class="mt-8 grid grid-cols-2 gap-4">',
|
|
50
85
|
'<div><div class="mb-1 text-[9px] font-mono uppercase text-outline-variant">Allocation</div><div class="text-xs font-bold text-on-surface">',
|
|
51
86
|
escapeHtml(formatBytes(connection.sizeBytes)),
|
|
@@ -18,6 +18,11 @@ import {
|
|
|
18
18
|
MEDIA_TAGGING_DEFAULT_TAG_TABLE_SQL,
|
|
19
19
|
} from '../lib/mediaTaggingDefaults.js';
|
|
20
20
|
import { SYNTHETIC_GENERATOR_TYPES, getSyntheticGeneratorLabel } from '../utils/syntheticData.js';
|
|
21
|
+
import {
|
|
22
|
+
MAX_CONNECTION_TAG_NAME_LENGTH,
|
|
23
|
+
normalizeConnectionTagKey,
|
|
24
|
+
normalizeConnectionTagName,
|
|
25
|
+
} from '../utils/connectionRegistry.js';
|
|
21
26
|
|
|
22
27
|
function renderField({ label, name, type = 'text', placeholder = '', value = '' }) {
|
|
23
28
|
return `
|
|
@@ -192,7 +197,114 @@ export function renderOpenConnectionForm(modal) {
|
|
|
192
197
|
`;
|
|
193
198
|
}
|
|
194
199
|
|
|
195
|
-
function
|
|
200
|
+
function renderEditConnectionTagEditor(modal, state) {
|
|
201
|
+
const assignedTags = Array.isArray(modal.assignedTags) ? modal.assignedTags : [];
|
|
202
|
+
const assignedKeys = new Set(assignedTags.map(tag => normalizeConnectionTagKey(tag.name)));
|
|
203
|
+
const tagQuery = String(modal.tagQuery ?? '');
|
|
204
|
+
const normalizedQuery = normalizeConnectionTagName(tagQuery);
|
|
205
|
+
const queryKey = normalizeConnectionTagKey(normalizedQuery);
|
|
206
|
+
const matchingTags = (state.connections.tags ?? [])
|
|
207
|
+
.filter(tag => !assignedKeys.has(normalizeConnectionTagKey(tag.name)))
|
|
208
|
+
.filter(tag => {
|
|
209
|
+
if (!queryKey) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return normalizeConnectionTagKey(tag.name).includes(queryKey);
|
|
214
|
+
})
|
|
215
|
+
.slice(0, 6);
|
|
216
|
+
const exactMatch = (state.connections.tags ?? []).some(tag => normalizeConnectionTagKey(tag.name) === queryKey);
|
|
217
|
+
const canCreate = Boolean(normalizedQuery && !exactMatch);
|
|
218
|
+
const assignedMarkup = assignedTags.length
|
|
219
|
+
? assignedTags
|
|
220
|
+
.map(
|
|
221
|
+
tag => `
|
|
222
|
+
<span class="connection-edit-tag-chip">
|
|
223
|
+
<span class="connection-edit-tag-chip__label">${escapeHtml(tag.name)}</span>
|
|
224
|
+
<button
|
|
225
|
+
aria-label="Remove ${escapeHtml(tag.name)} tag"
|
|
226
|
+
class="connection-edit-tag-chip__remove"
|
|
227
|
+
data-action="remove-edit-connection-tag"
|
|
228
|
+
data-tag-name="${escapeHtml(tag.name)}"
|
|
229
|
+
type="button"
|
|
230
|
+
title="Remove tag"
|
|
231
|
+
>
|
|
232
|
+
<span aria-hidden="true">×</span>
|
|
233
|
+
</button>
|
|
234
|
+
<input name="tags" type="hidden" value="${escapeHtml(tag.name)}" />
|
|
235
|
+
</span>
|
|
236
|
+
`,
|
|
237
|
+
)
|
|
238
|
+
.join('')
|
|
239
|
+
: '<div class="connection-edit-tags__empty">NO_TAGS_ASSIGNED</div>';
|
|
240
|
+
const matchMarkup = matchingTags
|
|
241
|
+
.map(
|
|
242
|
+
tag => `
|
|
243
|
+
<button
|
|
244
|
+
class="connection-edit-tag-option"
|
|
245
|
+
data-action="add-edit-connection-tag"
|
|
246
|
+
data-tag-name="${escapeHtml(tag.name)}"
|
|
247
|
+
type="button"
|
|
248
|
+
>
|
|
249
|
+
<span>${escapeHtml(tag.name)}</span>
|
|
250
|
+
<span>${escapeHtml(String(tag.connectionCount ?? 0))}</span>
|
|
251
|
+
</button>
|
|
252
|
+
`,
|
|
253
|
+
)
|
|
254
|
+
.join('');
|
|
255
|
+
|
|
256
|
+
return `
|
|
257
|
+
<div class="connection-edit-tags">
|
|
258
|
+
<div class="connection-edit-tags__label">Tags</div>
|
|
259
|
+
<div class="connection-edit-tags__assigned">
|
|
260
|
+
${assignedMarkup}
|
|
261
|
+
</div>
|
|
262
|
+
<label class="connection-edit-tags__input-shell">
|
|
263
|
+
<span class="sr-only">Add or create tag</span>
|
|
264
|
+
<input
|
|
265
|
+
autocomplete="off"
|
|
266
|
+
class="control-input connection-edit-tags__input"
|
|
267
|
+
data-bind="edit-connection-tag-query"
|
|
268
|
+
maxlength="${MAX_CONNECTION_TAG_NAME_LENGTH}"
|
|
269
|
+
placeholder="ADD_OR_CREATE_TAG..."
|
|
270
|
+
spellcheck="false"
|
|
271
|
+
type="text"
|
|
272
|
+
value="${escapeHtml(tagQuery)}"
|
|
273
|
+
/>
|
|
274
|
+
</label>
|
|
275
|
+
${
|
|
276
|
+
modal.tagError
|
|
277
|
+
? `<div class="connection-edit-tags__error">${escapeHtml(modal.tagError)}</div>`
|
|
278
|
+
: ''
|
|
279
|
+
}
|
|
280
|
+
<div class="connection-edit-tag-options">
|
|
281
|
+
${
|
|
282
|
+
matchMarkup
|
|
283
|
+
? `<div class="connection-edit-tag-options__section">MATCHING_TAGS</div>${matchMarkup}`
|
|
284
|
+
: ''
|
|
285
|
+
}
|
|
286
|
+
${
|
|
287
|
+
canCreate
|
|
288
|
+
? `
|
|
289
|
+
<div class="connection-edit-tag-options__section">CREATE_NEW</div>
|
|
290
|
+
<button
|
|
291
|
+
class="connection-edit-tag-option connection-edit-tag-option--create"
|
|
292
|
+
data-action="create-edit-connection-tag"
|
|
293
|
+
data-edit-connection-tag-primary
|
|
294
|
+
data-tag-name="${escapeHtml(normalizedQuery)}"
|
|
295
|
+
type="button"
|
|
296
|
+
>
|
|
297
|
+
<span>CREATE "${escapeHtml(normalizedQuery)}"</span>
|
|
298
|
+
</button>
|
|
299
|
+
`
|
|
300
|
+
: ''
|
|
301
|
+
}
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function renderEditConnectionForm(modal, state) {
|
|
196
308
|
const connection = modal.connection ?? {};
|
|
197
309
|
|
|
198
310
|
return `
|
|
@@ -246,6 +358,7 @@ function renderEditConnectionForm(modal) {
|
|
|
246
358
|
checked: Boolean(connection.readOnly),
|
|
247
359
|
text: 'Open read-only',
|
|
248
360
|
})}
|
|
361
|
+
${renderEditConnectionTagEditor(modal, state)}
|
|
249
362
|
${renderError(modal.error)}
|
|
250
363
|
<div class="flex items-center justify-between gap-3 pt-2">
|
|
251
364
|
<button
|
|
@@ -1473,6 +1586,23 @@ function renderDeleteDocumentForm(modal) {
|
|
|
1473
1586
|
].join('');
|
|
1474
1587
|
}
|
|
1475
1588
|
|
|
1589
|
+
function renderCreateDocumentFolderForm(modal) {
|
|
1590
|
+
return [
|
|
1591
|
+
'<form class="space-y-5" data-form="create-document-folder"><label class="block space-y-2">',
|
|
1592
|
+
'<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">Folder Name</span>',
|
|
1593
|
+
'<input class="control-input w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors placeholder:text-on-surface-variant/35 focus:border-primary-container" name="name" type="text" autocomplete="off" maxlength="80" value="',
|
|
1594
|
+
escapeHtml(modal.name ?? ''),
|
|
1595
|
+
'" autofocus />',
|
|
1596
|
+
'</label>',
|
|
1597
|
+
renderError(modal.error),
|
|
1598
|
+
'<div class="flex items-center justify-between gap-3 pt-2">',
|
|
1599
|
+
'<button class="standard-button" data-action="close-modal" type="button">Cancel</button>',
|
|
1600
|
+
'<button class="signature-button" type="submit">',
|
|
1601
|
+
modal.submitting ? 'Creating...' : 'Create Folder',
|
|
1602
|
+
'</button></div></form>',
|
|
1603
|
+
].join('');
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1476
1606
|
export function renderDeleteApiTokenForm(modal) {
|
|
1477
1607
|
return [
|
|
1478
1608
|
'<form class="space-y-5" data-form="delete-api-token-confirm"><div class="space-y-3">',
|
|
@@ -1607,13 +1737,136 @@ function renderDocumentInsertNoteForm(modal) {
|
|
|
1607
1737
|
<div class="flex items-center justify-between gap-3 pt-2">
|
|
1608
1738
|
<button class="standard-button" data-action="close-modal" type="button">Cancel</button>
|
|
1609
1739
|
<button class="signature-button" type="submit" ${disabledAttribute}>
|
|
1610
|
-
${modal.submitting ? 'Inserting...' : 'Insert Note'}
|
|
1740
|
+
${modal.submitting ? 'Inserting...' : 'Insert Query Note'}
|
|
1611
1741
|
</button>
|
|
1612
1742
|
</div>
|
|
1613
1743
|
</form>
|
|
1614
1744
|
`;
|
|
1615
1745
|
}
|
|
1616
1746
|
|
|
1747
|
+
function renderDocumentTableDefinitionSelect(modal) {
|
|
1748
|
+
const tables = (modal.tables ?? []).filter(table => !table.isShadow && table.tableKind !== 'shadow');
|
|
1749
|
+
|
|
1750
|
+
if (modal.loading) {
|
|
1751
|
+
return '<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface-variant/65">Loading tables...</div>';
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
if (!tables.length) {
|
|
1755
|
+
return '<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface-variant/65">No regular or virtual tables are available for this database.</div>';
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
return `
|
|
1759
|
+
<label class="block space-y-2">
|
|
1760
|
+
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
1761
|
+
Table
|
|
1762
|
+
</span>
|
|
1763
|
+
<select
|
|
1764
|
+
class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
1765
|
+
data-bind="document-table-definition-select"
|
|
1766
|
+
name="tableName"
|
|
1767
|
+
>
|
|
1768
|
+
${tables
|
|
1769
|
+
.map(table => {
|
|
1770
|
+
const label = table.isVirtual || table.tableKind === 'virtual'
|
|
1771
|
+
? `${table.name} (virtual)`
|
|
1772
|
+
: table.name;
|
|
1773
|
+
|
|
1774
|
+
return `
|
|
1775
|
+
<option
|
|
1776
|
+
value="${escapeHtml(table.name)}"
|
|
1777
|
+
${String(table.name) === String(modal.selectedTableName) ? 'selected' : ''}
|
|
1778
|
+
>
|
|
1779
|
+
${escapeHtml(label)}
|
|
1780
|
+
</option>
|
|
1781
|
+
`;
|
|
1782
|
+
})
|
|
1783
|
+
.join('')}
|
|
1784
|
+
</select>
|
|
1785
|
+
</label>
|
|
1786
|
+
`;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
function renderDocumentTableDefinitionCheckbox({ label, option, checked, disabled }) {
|
|
1790
|
+
return `
|
|
1791
|
+
<label class="standard-checkbox ${disabled ? 'is-disabled' : ''}">
|
|
1792
|
+
<input
|
|
1793
|
+
data-bind="document-table-definition-option"
|
|
1794
|
+
data-option="${escapeHtml(option)}"
|
|
1795
|
+
name="${escapeHtml(option)}"
|
|
1796
|
+
type="checkbox"
|
|
1797
|
+
${checked ? 'checked' : ''}
|
|
1798
|
+
${disabled ? 'disabled' : ''}
|
|
1799
|
+
/>
|
|
1800
|
+
<span>${escapeHtml(label)}</span>
|
|
1801
|
+
</label>
|
|
1802
|
+
`;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
function renderDocumentInsertTableDefinitionForm(modal) {
|
|
1806
|
+
const hasTables = Boolean((modal.tables ?? []).length);
|
|
1807
|
+
const optionDisabled = modal.loading || modal.submitting || !hasTables;
|
|
1808
|
+
const hasSelection = Boolean(modal.markdownTable || modal.sqlDefinition || modal.sampleData);
|
|
1809
|
+
const disabledAttribute = optionDisabled || !hasSelection ? 'disabled aria-disabled="true"' : '';
|
|
1810
|
+
|
|
1811
|
+
return `
|
|
1812
|
+
<form class="space-y-5" data-form="document-insert-table-definition">
|
|
1813
|
+
${renderDocumentTableDefinitionSelect(modal)}
|
|
1814
|
+
<div class="grid gap-2">
|
|
1815
|
+
${renderDocumentTableDefinitionCheckbox({
|
|
1816
|
+
label: 'Markdown Table',
|
|
1817
|
+
option: 'markdownTable',
|
|
1818
|
+
checked: Boolean(modal.markdownTable),
|
|
1819
|
+
disabled: optionDisabled,
|
|
1820
|
+
})}
|
|
1821
|
+
${renderDocumentTableDefinitionCheckbox({
|
|
1822
|
+
label: 'SQL Definition',
|
|
1823
|
+
option: 'sqlDefinition',
|
|
1824
|
+
checked: Boolean(modal.sqlDefinition),
|
|
1825
|
+
disabled: optionDisabled,
|
|
1826
|
+
})}
|
|
1827
|
+
${renderDocumentTableDefinitionCheckbox({
|
|
1828
|
+
label: 'Sample Data',
|
|
1829
|
+
option: 'sampleData',
|
|
1830
|
+
checked: Boolean(modal.sampleData),
|
|
1831
|
+
disabled: optionDisabled,
|
|
1832
|
+
})}
|
|
1833
|
+
</div>
|
|
1834
|
+
${
|
|
1835
|
+
modal.sampleData
|
|
1836
|
+
? `
|
|
1837
|
+
<label class="block space-y-2">
|
|
1838
|
+
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
1839
|
+
Sample Rows
|
|
1840
|
+
</span>
|
|
1841
|
+
<select
|
|
1842
|
+
class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
1843
|
+
data-bind="document-table-definition-row-count"
|
|
1844
|
+
name="sampleRowCount"
|
|
1845
|
+
${optionDisabled ? 'disabled' : ''}
|
|
1846
|
+
>
|
|
1847
|
+
${[3, 5, 10]
|
|
1848
|
+
.map(count => `
|
|
1849
|
+
<option value="${count}" ${Number(modal.sampleRowCount ?? 5) === count ? 'selected' : ''}>
|
|
1850
|
+
${count}
|
|
1851
|
+
</option>
|
|
1852
|
+
`)
|
|
1853
|
+
.join('')}
|
|
1854
|
+
</select>
|
|
1855
|
+
</label>
|
|
1856
|
+
`
|
|
1857
|
+
: ''
|
|
1858
|
+
}
|
|
1859
|
+
${renderError(modal.error)}
|
|
1860
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
1861
|
+
<button class="standard-button" data-action="close-modal" type="button">Cancel</button>
|
|
1862
|
+
<button class="signature-button" type="submit" ${disabledAttribute}>
|
|
1863
|
+
${modal.submitting ? 'Inserting...' : 'Insert Table Definition'}
|
|
1864
|
+
</button>
|
|
1865
|
+
</div>
|
|
1866
|
+
</form>
|
|
1867
|
+
`;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1617
1870
|
function renderQueryExportPreview(lines = []) {
|
|
1618
1871
|
return lines.map(line => `<span class="block whitespace-pre">${escapeHtml(line)}</span>`).join('');
|
|
1619
1872
|
}
|
|
@@ -1926,242 +2179,6 @@ export function renderCopyColumnModal(modal, state) {
|
|
|
1926
2179
|
`;
|
|
1927
2180
|
}
|
|
1928
2181
|
|
|
1929
|
-
function getTableDesignerUniqueConstraintTypeLabel(constraint) {
|
|
1930
|
-
if (constraint.partial) {
|
|
1931
|
-
return 'Partial unique index';
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
if ((constraint.columns?.length ?? 0) > 1) {
|
|
1935
|
-
return 'Multi-column unique';
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
return 'Unique constraint';
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
function renderTableDesignerUniqueConstraintExpression(constraint) {
|
|
1942
|
-
const expression = String(constraint.expression || constraint.sql || '').trim();
|
|
1943
|
-
|
|
1944
|
-
if (expression) {
|
|
1945
|
-
return expression;
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
const columns = (constraint.columns ?? []).map(column => column.name).filter(Boolean);
|
|
1949
|
-
return columns.length ? `UNIQUE (${columns.join(', ')})` : 'UNIQUE constraint';
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
function renderTableDesignerCheckConstraintExpression(constraint) {
|
|
1953
|
-
return String(constraint.expression || '').trim() || 'CHECK constraint';
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
function normalizeTableDesignerConstraintColumnName(name) {
|
|
1957
|
-
return String(name ?? '')
|
|
1958
|
-
.trim()
|
|
1959
|
-
.toLowerCase();
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
function tableDesignerConstraintIncludesColumn(constraint, columnName) {
|
|
1963
|
-
const normalizedColumn = normalizeTableDesignerConstraintColumnName(columnName);
|
|
1964
|
-
|
|
1965
|
-
if (!normalizedColumn) {
|
|
1966
|
-
return false;
|
|
1967
|
-
}
|
|
1968
|
-
|
|
1969
|
-
return (constraint.columns ?? []).some(
|
|
1970
|
-
constraintColumn => normalizeTableDesignerConstraintColumnName(constraintColumn.name) === normalizedColumn,
|
|
1971
|
-
);
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
function renderTableDesignerUniqueConstraintEditor(constraint) {
|
|
1975
|
-
const columns = (constraint.columns ?? []).map(column => column.name).filter(Boolean);
|
|
1976
|
-
|
|
1977
|
-
return `
|
|
1978
|
-
<article class="table-designer-constraint-editor">
|
|
1979
|
-
<div class="table-designer-constraint-editor__header">
|
|
1980
|
-
<div class="min-w-0">
|
|
1981
|
-
<div class="table-designer-constraint-editor__type">
|
|
1982
|
-
${escapeHtml(getTableDesignerUniqueConstraintTypeLabel(constraint))}
|
|
1983
|
-
${constraint.origin ? ` // origin ${escapeHtml(constraint.origin)}` : ''}
|
|
1984
|
-
</div>
|
|
1985
|
-
<input
|
|
1986
|
-
class="table-designer-field table-designer-constraint-editor__name"
|
|
1987
|
-
data-bind="table-designer-constraint-field"
|
|
1988
|
-
data-constraint-id="${escapeHtml(constraint.id)}"
|
|
1989
|
-
data-constraint-kind="unique"
|
|
1990
|
-
data-field="name"
|
|
1991
|
-
spellcheck="false"
|
|
1992
|
-
type="text"
|
|
1993
|
-
value="${escapeHtml(constraint.name || 'UNIQUE constraint')}"
|
|
1994
|
-
/>
|
|
1995
|
-
</div>
|
|
1996
|
-
<div class="status-badge status-badge--muted">Rebuild</div>
|
|
1997
|
-
</div>
|
|
1998
|
-
${
|
|
1999
|
-
columns.length
|
|
2000
|
-
? `
|
|
2001
|
-
<div class="table-designer-constraint__columns">
|
|
2002
|
-
${columns.map(column => `<span>${escapeHtml(column)}</span>`).join('')}
|
|
2003
|
-
</div>
|
|
2004
|
-
`
|
|
2005
|
-
: ''
|
|
2006
|
-
}
|
|
2007
|
-
<textarea
|
|
2008
|
-
class="table-designer-constraint-editor__sql custom-scrollbar"
|
|
2009
|
-
data-bind="table-designer-constraint-field"
|
|
2010
|
-
data-constraint-id="${escapeHtml(constraint.id)}"
|
|
2011
|
-
data-constraint-kind="unique"
|
|
2012
|
-
data-field="expression"
|
|
2013
|
-
spellcheck="false"
|
|
2014
|
-
>${escapeHtml(renderTableDesignerUniqueConstraintExpression(constraint))}</textarea>
|
|
2015
|
-
</article>
|
|
2016
|
-
`;
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
function renderTableDesignerCheckConstraintEditor(constraint) {
|
|
2020
|
-
const columns = constraint.columns ?? [];
|
|
2021
|
-
const allowedValues = columns.flatMap(column =>
|
|
2022
|
-
(column.allowedValues ?? []).map(value => ({
|
|
2023
|
-
columnName: column.name,
|
|
2024
|
-
value,
|
|
2025
|
-
})),
|
|
2026
|
-
);
|
|
2027
|
-
|
|
2028
|
-
return `
|
|
2029
|
-
<article class="table-designer-constraint-editor">
|
|
2030
|
-
<div class="table-designer-constraint-editor__header">
|
|
2031
|
-
<div class="min-w-0">
|
|
2032
|
-
<div class="table-designer-constraint-editor__type">
|
|
2033
|
-
${columns.length ? escapeHtml(columns.map(column => column.name).join(', ')) : 'Table check'}
|
|
2034
|
-
</div>
|
|
2035
|
-
<input
|
|
2036
|
-
class="table-designer-field table-designer-constraint-editor__name"
|
|
2037
|
-
data-bind="table-designer-constraint-field"
|
|
2038
|
-
data-constraint-id="${escapeHtml(constraint.id)}"
|
|
2039
|
-
data-constraint-kind="check"
|
|
2040
|
-
data-field="name"
|
|
2041
|
-
spellcheck="false"
|
|
2042
|
-
type="text"
|
|
2043
|
-
value="${escapeHtml(constraint.name || 'CHECK constraint')}"
|
|
2044
|
-
/>
|
|
2045
|
-
</div>
|
|
2046
|
-
<div class="status-badge status-badge--muted">Rebuild</div>
|
|
2047
|
-
</div>
|
|
2048
|
-
${
|
|
2049
|
-
allowedValues.length
|
|
2050
|
-
? `
|
|
2051
|
-
<div class="table-designer-constraint__values custom-scrollbar">
|
|
2052
|
-
${allowedValues
|
|
2053
|
-
.map(
|
|
2054
|
-
entry => `
|
|
2055
|
-
<span title="${escapeHtml(entry.columnName)}">${escapeHtml(entry.value)}</span>
|
|
2056
|
-
`,
|
|
2057
|
-
)
|
|
2058
|
-
.join('')}
|
|
2059
|
-
</div>
|
|
2060
|
-
`
|
|
2061
|
-
: ''
|
|
2062
|
-
}
|
|
2063
|
-
<textarea
|
|
2064
|
-
class="table-designer-constraint-editor__sql custom-scrollbar"
|
|
2065
|
-
data-bind="table-designer-constraint-field"
|
|
2066
|
-
data-constraint-id="${escapeHtml(constraint.id)}"
|
|
2067
|
-
data-constraint-kind="check"
|
|
2068
|
-
data-field="expression"
|
|
2069
|
-
spellcheck="false"
|
|
2070
|
-
>${escapeHtml(renderTableDesignerCheckConstraintExpression(constraint))}</textarea>
|
|
2071
|
-
</article>
|
|
2072
|
-
`;
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
function renderTableDesignerConstraintSection({ title, count, emptyText, body }) {
|
|
2076
|
-
return `
|
|
2077
|
-
<section class="table-designer-constraints-modal__section">
|
|
2078
|
-
<div class="table-designer-constraints-modal__section-header">
|
|
2079
|
-
<div class="table-designer-constraints-modal__section-title">${escapeHtml(title)}</div>
|
|
2080
|
-
<div class="status-badge status-badge--muted">${formatNumber(count)}</div>
|
|
2081
|
-
</div>
|
|
2082
|
-
${
|
|
2083
|
-
count
|
|
2084
|
-
? `<div class="table-designer-constraints__list">${body}</div>`
|
|
2085
|
-
: `<div class="table-designer-constraints-modal__empty">${escapeHtml(emptyText)}</div>`
|
|
2086
|
-
}
|
|
2087
|
-
</section>
|
|
2088
|
-
`;
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
function renderTableDesignerConstraintsModal(modal, state) {
|
|
2092
|
-
const draft = state.tableDesigner?.draft;
|
|
2093
|
-
|
|
2094
|
-
if (!draft) {
|
|
2095
|
-
return `
|
|
2096
|
-
<div class="table-designer-constraints-modal__empty">
|
|
2097
|
-
No active table designer draft.
|
|
2098
|
-
</div>
|
|
2099
|
-
<div class="flex items-center justify-between gap-3 pt-2">
|
|
2100
|
-
<button class="standard-button" data-action="close-modal" type="button">Close</button>
|
|
2101
|
-
</div>
|
|
2102
|
-
`;
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
const selectedColumn = modal.columnId ? (draft.columns ?? []).find(column => column.id === modal.columnId) : null;
|
|
2106
|
-
const selectedColumnName = String(modal.columnName ?? selectedColumn?.name ?? '').trim();
|
|
2107
|
-
const hasSelectedColumn = Boolean(modal.columnId || selectedColumnName);
|
|
2108
|
-
const selectedColumnLabel = selectedColumnName || 'Unnamed column';
|
|
2109
|
-
const allUniqueConstraints = draft.mode === 'edit' ? (draft.uniqueConstraints ?? []) : [];
|
|
2110
|
-
const allCheckConstraints = draft.mode === 'edit' ? (draft.checkConstraints ?? []) : [];
|
|
2111
|
-
const uniqueConstraints = hasSelectedColumn
|
|
2112
|
-
? allUniqueConstraints.filter(constraint =>
|
|
2113
|
-
tableDesignerConstraintIncludesColumn(constraint, selectedColumnName),
|
|
2114
|
-
)
|
|
2115
|
-
: allUniqueConstraints;
|
|
2116
|
-
const checkConstraints = hasSelectedColumn
|
|
2117
|
-
? allCheckConstraints.filter(constraint =>
|
|
2118
|
-
tableDesignerConstraintIncludesColumn(constraint, selectedColumnName),
|
|
2119
|
-
)
|
|
2120
|
-
: allCheckConstraints;
|
|
2121
|
-
const totalCount = uniqueConstraints.length + checkConstraints.length;
|
|
2122
|
-
|
|
2123
|
-
return `
|
|
2124
|
-
<div class="table-designer-constraints-modal">
|
|
2125
|
-
<div class="table-designer-constraints-modal__summary">
|
|
2126
|
-
<div class="min-w-0">
|
|
2127
|
-
<div class="table-designer-constraints-modal__label">Table</div>
|
|
2128
|
-
<code title="${escapeHtml(draft.tableName ?? '')}">${escapeHtml(draft.tableName ?? '')}</code>
|
|
2129
|
-
</div>
|
|
2130
|
-
${
|
|
2131
|
-
hasSelectedColumn
|
|
2132
|
-
? `
|
|
2133
|
-
<div class="min-w-0">
|
|
2134
|
-
<div class="table-designer-constraints-modal__label">Column</div>
|
|
2135
|
-
<code title="${escapeHtml(selectedColumnLabel)}">${escapeHtml(selectedColumnLabel)}</code>
|
|
2136
|
-
</div>
|
|
2137
|
-
`
|
|
2138
|
-
: ''
|
|
2139
|
-
}
|
|
2140
|
-
<div class="status-badge status-badge--primary">V2 · ${formatNumber(totalCount)}</div>
|
|
2141
|
-
</div>
|
|
2142
|
-
${renderTableDesignerConstraintSection({
|
|
2143
|
-
title: 'Check constraints',
|
|
2144
|
-
count: checkConstraints.length,
|
|
2145
|
-
emptyText: hasSelectedColumn
|
|
2146
|
-
? 'No check constraints detected for this column.'
|
|
2147
|
-
: 'No check constraints detected.',
|
|
2148
|
-
body: checkConstraints.map(renderTableDesignerCheckConstraintEditor).join(''),
|
|
2149
|
-
})}
|
|
2150
|
-
${renderTableDesignerConstraintSection({
|
|
2151
|
-
title: hasSelectedColumn ? 'Related unique constraints' : 'Unique constraints',
|
|
2152
|
-
count: uniqueConstraints.length,
|
|
2153
|
-
emptyText: hasSelectedColumn
|
|
2154
|
-
? 'No related multi-column or partial unique constraints detected.'
|
|
2155
|
-
: 'No multi-column or partial unique constraints detected.',
|
|
2156
|
-
body: uniqueConstraints.map(renderTableDesignerUniqueConstraintEditor).join(''),
|
|
2157
|
-
})}
|
|
2158
|
-
<div class="flex items-center justify-between gap-3 pt-2">
|
|
2159
|
-
<button class="standard-button" data-action="close-modal" type="button">Close</button>
|
|
2160
|
-
</div>
|
|
2161
|
-
</div>
|
|
2162
|
-
`;
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
2182
|
function renderCreateMediaTaggingMappingTableForm(modal, state) {
|
|
2166
2183
|
const mappingExists = hasDefaultMediaTaggingMappingTable(state.mediaTagging.schemaTables ?? []);
|
|
2167
2184
|
const readOnly = Boolean(state.mediaTagging.connection?.readOnly);
|
|
@@ -2346,7 +2363,7 @@ export function renderModal(state) {
|
|
|
2346
2363
|
'edit-connection': {
|
|
2347
2364
|
eyebrow: 'Registry // Update saved SQLite target',
|
|
2348
2365
|
title: 'Edit Connection',
|
|
2349
|
-
body: renderEditConnectionForm(modal),
|
|
2366
|
+
body: renderEditConnectionForm(modal, state),
|
|
2350
2367
|
},
|
|
2351
2368
|
'delete-row': {
|
|
2352
2369
|
eyebrow: 'Mutation // Confirm row deletion',
|
|
@@ -2378,6 +2395,11 @@ export function renderModal(state) {
|
|
|
2378
2395
|
title: 'Delete Document',
|
|
2379
2396
|
body: renderDeleteDocumentForm(modal),
|
|
2380
2397
|
},
|
|
2398
|
+
'create-document-folder': {
|
|
2399
|
+
eyebrow: 'Documents // New folder',
|
|
2400
|
+
title: 'New Folder',
|
|
2401
|
+
body: renderCreateDocumentFolderForm(modal),
|
|
2402
|
+
},
|
|
2381
2403
|
'delete-api-token': {
|
|
2382
2404
|
eyebrow: 'Settings // Confirm token deletion',
|
|
2383
2405
|
title: 'Delete API Token',
|
|
@@ -2390,9 +2412,14 @@ export function renderModal(state) {
|
|
|
2390
2412
|
},
|
|
2391
2413
|
'document-insert-note': {
|
|
2392
2414
|
eyebrow: 'Documents // Saved query notes',
|
|
2393
|
-
title: 'Insert Note',
|
|
2415
|
+
title: 'Insert Query Note',
|
|
2394
2416
|
body: renderDocumentInsertNoteForm(modal),
|
|
2395
2417
|
},
|
|
2418
|
+
'document-insert-table-definition': {
|
|
2419
|
+
eyebrow: 'Documents // Table definition',
|
|
2420
|
+
title: 'Insert Table Definition',
|
|
2421
|
+
body: renderDocumentInsertTableDefinitionForm(modal),
|
|
2422
|
+
},
|
|
2396
2423
|
'query-export': {
|
|
2397
2424
|
eyebrow: 'SQL Editor // Export query result',
|
|
2398
2425
|
title: 'Export Query',
|
|
@@ -2408,11 +2435,6 @@ export function renderModal(state) {
|
|
|
2408
2435
|
title: isMarkdownTodoCopyColumnMode(modal.copyMode) ? 'Export Markdown Todo' : 'Copy column',
|
|
2409
2436
|
body: renderCopyColumnModal(modal, state),
|
|
2410
2437
|
},
|
|
2411
|
-
'table-designer-constraints': {
|
|
2412
|
-
eyebrow: 'Table Designer // Constraints',
|
|
2413
|
-
title: 'Checks',
|
|
2414
|
-
body: renderTableDesignerConstraintsModal(modal, state),
|
|
2415
|
-
},
|
|
2416
2438
|
'create-media-tagging-tag-table': {
|
|
2417
2439
|
eyebrow: 'Media Tagging // Create default tag table',
|
|
2418
2440
|
title: 'Create Tag Table',
|
|
@@ -2442,8 +2464,7 @@ export function renderModal(state) {
|
|
|
2442
2464
|
modal.kind === 'chart-editor' ||
|
|
2443
2465
|
modal.kind === 'document-insert-table' ||
|
|
2444
2466
|
modal.kind === 'document-insert-note' ||
|
|
2445
|
-
modal.kind === 'row-update-preview'
|
|
2446
|
-
modal.kind === 'table-designer-constraints'
|
|
2467
|
+
modal.kind === 'row-update-preview'
|
|
2447
2468
|
? 'max-w-3xl'
|
|
2448
2469
|
: modal.kind === 'generate-types' || modal.kind === 'generate-data'
|
|
2449
2470
|
? 'max-w-6xl'
|