sqlite-hub 2.0.1 → 2.1.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 +14 -9
- package/docs/MCP.md +8 -0
- package/docs/changelog.md +10 -0
- package/docs/guidelines/DESIGN.md +64 -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 +11 -0
- package/frontend/js/app.js +238 -8
- package/frontend/js/components/badges.js +16 -0
- package/frontend/js/components/connectionCard.js +35 -0
- package/frontend/js/components/modal.js +116 -245
- 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 +551 -7
- package/frontend/js/utils/connectionRegistry.js +76 -0
- 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 +103 -3
- package/frontend/js/views/data.js +19 -5
- 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/components.css +676 -79
- package/frontend/styles/structure-graph.css +49 -0
- package/frontend/styles/tailwind.generated.css +1 -1
- package/package.json +1 -1
- package/server/routes/connections.js +12 -0
- package/server/routes/data.js +1 -1
- package/server/routes/tableDesigner.js +15 -1
- package/server/services/mcpToolService.js +28 -0
- package/server/services/sqlite/connectionManager.js +23 -2
- package/server/services/sqlite/dataBrowserService.js +21 -0
- package/server/services/sqlite/introspection.js +39 -1
- 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 +303 -4
- 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
|
@@ -687,14 +687,11 @@ export function renderBackupDiffDrawer(state) {
|
|
|
687
687
|
<div class="flex items-center justify-between gap-3">
|
|
688
688
|
<div class="min-w-0">
|
|
689
689
|
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-primary-container/70">
|
|
690
|
-
Backup Compare
|
|
690
|
+
Backup Compare // ${escapeHtml(currentLabel)}
|
|
691
691
|
</div>
|
|
692
692
|
<h2 class="mt-1 truncate font-body text-lg font-black uppercase tracking-tight text-on-surface">
|
|
693
693
|
${escapeHtml(title)}
|
|
694
694
|
</h2>
|
|
695
|
-
<div class="mt-1 truncate text-xs text-on-surface-variant/60">
|
|
696
|
-
Current // ${escapeHtml(currentLabel)}
|
|
697
|
-
</div>
|
|
698
695
|
</div>
|
|
699
696
|
<button
|
|
700
697
|
class="query-history-icon-button"
|
|
@@ -708,7 +705,7 @@ export function renderBackupDiffDrawer(state) {
|
|
|
708
705
|
<div class="mt-4 flex flex-wrap gap-2">
|
|
709
706
|
${renderStatusBadge('read only', 'muted')}
|
|
710
707
|
${diffState.loading ? renderStatusBadge('loading', 'muted') : ''}
|
|
711
|
-
${diffState.error ? renderStatusBadge('error', '
|
|
708
|
+
${diffState.error ? renderStatusBadge('error', 'muted') : ''}
|
|
712
709
|
</div>
|
|
713
710
|
</div>
|
|
714
711
|
${renderBackupDiffDrawerBody(diffState)}
|
|
@@ -339,9 +339,14 @@ export function renderChartsDetailDrawer(state) {
|
|
|
339
339
|
<section class="flex h-full min-h-0 flex-col bg-surface-low">
|
|
340
340
|
<div class="border-b border-outline-variant/10 px-5 py-4">
|
|
341
341
|
<div class="flex items-center justify-between gap-3">
|
|
342
|
-
<
|
|
343
|
-
|
|
344
|
-
|
|
342
|
+
<div class="min-w-0">
|
|
343
|
+
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-primary-container/70">
|
|
344
|
+
Charts // Query Detail
|
|
345
|
+
</div>
|
|
346
|
+
<h2 class="mt-1 truncate font-body text-lg font-black uppercase tracking-tight text-on-surface">
|
|
347
|
+
Loading
|
|
348
|
+
</h2>
|
|
349
|
+
</div>
|
|
345
350
|
<button class="query-history-icon-button" data-action="close-charts-query-detail" type="button">
|
|
346
351
|
<span class="material-symbols-outlined text-[18px]">close</span>
|
|
347
352
|
</button>
|
|
@@ -359,9 +364,14 @@ export function renderChartsDetailDrawer(state) {
|
|
|
359
364
|
<section class="flex h-full min-h-0 flex-col bg-surface-low">
|
|
360
365
|
<div class="border-b border-outline-variant/10 px-5 py-4">
|
|
361
366
|
<div class="flex items-center justify-between gap-3">
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
367
|
+
<div class="min-w-0">
|
|
368
|
+
<div class="font-mono text-[10px] uppercase tracking-[0.18em] text-primary-container/70">
|
|
369
|
+
Charts // Query Detail
|
|
370
|
+
</div>
|
|
371
|
+
<h2 class="mt-1 truncate font-body text-lg font-black uppercase tracking-tight text-on-surface">
|
|
372
|
+
Error
|
|
373
|
+
</h2>
|
|
374
|
+
</div>
|
|
365
375
|
<button class="query-history-icon-button" data-action="close-charts-query-detail" type="button">
|
|
366
376
|
<span class="material-symbols-outlined text-[18px]">close</span>
|
|
367
377
|
</button>
|
|
@@ -383,7 +393,7 @@ export function renderChartsDetailDrawer(state) {
|
|
|
383
393
|
const charts = detail.charts ?? [];
|
|
384
394
|
const result = state.charts.result;
|
|
385
395
|
const chartTypeBadges = charts.length
|
|
386
|
-
? charts.map(chart => renderStatusBadge(getQueryChartTypeLabel(chart.chartType), '
|
|
396
|
+
? charts.map(chart => renderStatusBadge(getQueryChartTypeLabel(chart.chartType), 'muted')).join('')
|
|
387
397
|
: renderStatusBadge('no charts', 'muted');
|
|
388
398
|
const savedBadgeMarkup = `
|
|
389
399
|
<span
|
|
@@ -391,12 +401,12 @@ export function renderChartsDetailDrawer(state) {
|
|
|
391
401
|
data-history-id="${escapeHtml(detail.item.id)}"
|
|
392
402
|
${detail.item.isSaved ? '' : 'hidden'}
|
|
393
403
|
>
|
|
394
|
-
${renderStatusBadge('saved', '
|
|
404
|
+
${renderStatusBadge('saved', 'muted')}
|
|
395
405
|
</span>
|
|
396
406
|
`;
|
|
397
407
|
const statusMarkup = [
|
|
398
408
|
savedBadgeMarkup,
|
|
399
|
-
state.charts.resultError ? renderStatusBadge('result error', '
|
|
409
|
+
state.charts.resultError ? renderStatusBadge('result error', 'muted') : '',
|
|
400
410
|
state.charts.resultLoading ? renderStatusBadge('loading result', 'muted') : '',
|
|
401
411
|
chartTypeBadges,
|
|
402
412
|
].join('');
|
|
@@ -413,7 +423,7 @@ export function renderChartsDetailDrawer(state) {
|
|
|
413
423
|
<div class="flex items-center justify-between gap-3">
|
|
414
424
|
<div class="min-w-0">
|
|
415
425
|
<div class="text-[10px] font-mono uppercase tracking-[0.18em] text-primary-container/70">
|
|
416
|
-
|
|
426
|
+
Charts // Query Detail
|
|
417
427
|
</div>
|
|
418
428
|
<h2 class="mt-1 truncate font-body text-lg font-black uppercase tracking-tight text-on-surface">
|
|
419
429
|
${escapeHtml(detail.item.displayTitle)}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { renderConnectionCard } from '../components/connectionCard.js';
|
|
2
2
|
import { renderPageHeader } from '../components/pageHeader.js';
|
|
3
3
|
import { escapeHtml } from '../utils/format.js';
|
|
4
|
+
import {
|
|
5
|
+
filterConnections,
|
|
6
|
+
getConnectionTagCounts,
|
|
7
|
+
} from '../utils/connectionRegistry.js';
|
|
4
8
|
|
|
5
9
|
function renderConnectionsActionButton({
|
|
6
10
|
label,
|
|
@@ -28,7 +32,88 @@ function renderConnectionsActionButton({
|
|
|
28
32
|
`;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
function
|
|
35
|
+
function renderConnectionsToolbar(state, visibleCount) {
|
|
36
|
+
if (!state.connections.recent.length) {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const searchQuery = state.connections.searchQuery ?? '';
|
|
41
|
+
const selectedTagIds = new Set((state.connections.selectedTagIds ?? []).map(tagId => String(tagId)));
|
|
42
|
+
const selectedTags = (state.connections.tags ?? []).filter(tag => selectedTagIds.has(String(tag.id)));
|
|
43
|
+
const tagCounts = getConnectionTagCounts(state.connections.recent);
|
|
44
|
+
const filterLabel =
|
|
45
|
+
selectedTags.length === 0
|
|
46
|
+
? 'TAGS: ALL'
|
|
47
|
+
: selectedTags.length === 1
|
|
48
|
+
? `TAGS: ${selectedTags[0].name}`
|
|
49
|
+
: `TAGS: ${selectedTags.length}`;
|
|
50
|
+
const tagRows = (state.connections.tags ?? [])
|
|
51
|
+
.map(tag => {
|
|
52
|
+
const tagId = String(tag.id);
|
|
53
|
+
const isSelected = selectedTagIds.has(tagId);
|
|
54
|
+
|
|
55
|
+
return `
|
|
56
|
+
<label class="connection-tag-filter-row">
|
|
57
|
+
<input
|
|
58
|
+
${isSelected ? 'checked' : ''}
|
|
59
|
+
data-bind="connection-tag-filter"
|
|
60
|
+
data-tag-id="${escapeHtml(tagId)}"
|
|
61
|
+
type="checkbox"
|
|
62
|
+
/>
|
|
63
|
+
<span class="connection-tag-filter-row__name">${escapeHtml(tag.name)}</span>
|
|
64
|
+
<span class="connection-tag-filter-row__count">${escapeHtml(String(tagCounts.get(tagId) ?? tag.connectionCount ?? 0))}</span>
|
|
65
|
+
</label>
|
|
66
|
+
`;
|
|
67
|
+
})
|
|
68
|
+
.join('');
|
|
69
|
+
|
|
70
|
+
return `
|
|
71
|
+
<div class="connection-registry-toolbar" data-connection-registry-toolbar>
|
|
72
|
+
<label class="connection-registry-search">
|
|
73
|
+
<span class="sr-only">Search connections</span>
|
|
74
|
+
<span class="material-symbols-outlined connection-registry-search__icon" aria-hidden="true">search</span>
|
|
75
|
+
<input
|
|
76
|
+
autocomplete="off"
|
|
77
|
+
class="connection-registry-search__input"
|
|
78
|
+
data-bind="connections-search"
|
|
79
|
+
data-connections-search-input
|
|
80
|
+
placeholder="SEARCH_CONNECTIONS..."
|
|
81
|
+
spellcheck="false"
|
|
82
|
+
type="search"
|
|
83
|
+
value="${escapeHtml(searchQuery)}"
|
|
84
|
+
/>
|
|
85
|
+
<span class="connection-registry-search__shortcut" aria-hidden="true">/</span>
|
|
86
|
+
</label>
|
|
87
|
+
<details class="dropdown-button dropdown-button--align-right connection-tag-filter" data-dropdown-button>
|
|
88
|
+
<summary class="standard-button dropdown-button__toggle connection-tag-filter__toggle" title="${escapeHtml(filterLabel)}">
|
|
89
|
+
<span class="connection-tag-filter__toggle-label">${escapeHtml(filterLabel)}</span>
|
|
90
|
+
<span class="material-symbols-outlined dropdown-button__chevron" aria-hidden="true">expand_more</span>
|
|
91
|
+
</summary>
|
|
92
|
+
<div class="dropdown-button__panel connection-tag-filter__panel">
|
|
93
|
+
<div class="connection-tag-filter__header">
|
|
94
|
+
<span>FILTER_BY_TAG</span>
|
|
95
|
+
<span>${escapeHtml(String(visibleCount))}</span>
|
|
96
|
+
</div>
|
|
97
|
+
${
|
|
98
|
+
tagRows
|
|
99
|
+
? `<div class="connection-tag-filter__list custom-scrollbar">${tagRows}</div>`
|
|
100
|
+
: '<div class="connection-tag-filter__empty">NO_TAGS_AVAILABLE</div>'
|
|
101
|
+
}
|
|
102
|
+
<button
|
|
103
|
+
class="standard-button connection-tag-filter__reset"
|
|
104
|
+
data-action="clear-connection-tag-filters"
|
|
105
|
+
type="button"
|
|
106
|
+
${selectedTagIds.size ? '' : 'disabled'}
|
|
107
|
+
>
|
|
108
|
+
RESET_FILTER
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
</details>
|
|
112
|
+
</div>
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function renderConnectionsBody(state, visibleConnections) {
|
|
32
117
|
if (state.connections.loading && !state.connections.recent.length) {
|
|
33
118
|
return `
|
|
34
119
|
<div class="flex min-h-[280px] items-center justify-center border border-outline-variant/10 bg-surface-container-low">
|
|
@@ -78,9 +163,19 @@ function renderConnectionsBody(state) {
|
|
|
78
163
|
`;
|
|
79
164
|
}
|
|
80
165
|
|
|
166
|
+
if (!visibleConnections.length) {
|
|
167
|
+
return `
|
|
168
|
+
<div class="connection-filter-empty">
|
|
169
|
+
<span class="material-symbols-outlined connection-filter-empty__icon">search_off</span>
|
|
170
|
+
<p class="connection-filter-empty__title">NO_CONNECTIONS_FOUND</p>
|
|
171
|
+
<p class="connection-filter-empty__hint">TRY_ANOTHER_SEARCH_OR_FILTER</p>
|
|
172
|
+
</div>
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
|
|
81
176
|
return `
|
|
82
177
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
83
|
-
${
|
|
178
|
+
${visibleConnections
|
|
84
179
|
.map(connection => renderConnectionCard(connection, state.connections.active?.id))
|
|
85
180
|
.join('')}
|
|
86
181
|
</div>
|
|
@@ -88,6 +183,10 @@ function renderConnectionsBody(state) {
|
|
|
88
183
|
}
|
|
89
184
|
|
|
90
185
|
export function renderConnectionsView(state) {
|
|
186
|
+
const visibleConnections = filterConnections(state.connections.recent, {
|
|
187
|
+
searchQuery: state.connections.searchQuery,
|
|
188
|
+
selectedTagIds: state.connections.selectedTagIds,
|
|
189
|
+
});
|
|
91
190
|
const actions = `
|
|
92
191
|
${
|
|
93
192
|
state.connections.active
|
|
@@ -122,7 +221,8 @@ export function renderConnectionsView(state) {
|
|
|
122
221
|
subtitle: `Registry // Recent_Targets: ${String(state.connections.recent.length).padStart(2, '0')}`,
|
|
123
222
|
actions,
|
|
124
223
|
})}
|
|
125
|
-
${
|
|
224
|
+
${renderConnectionsToolbar(state, visibleConnections.length)}
|
|
225
|
+
${renderConnectionsBody(state, visibleConnections)}
|
|
126
226
|
</div>
|
|
127
227
|
</section>
|
|
128
228
|
`,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { renderDataGrid } from '../components/dataGrid.js';
|
|
2
2
|
import { renderRowEditorPanel } from '../components/rowEditorPanel.js';
|
|
3
|
+
import { renderVirtualTableBadge } from '../components/badges.js';
|
|
3
4
|
import { renderWorkspaceOpenDropdown } from '../components/workspaceOpenDropdown.js';
|
|
4
5
|
import { escapeHtml, formatCellValue, formatNumber, isBlobPreview, truncateMiddle } from '../utils/format.js';
|
|
5
6
|
import { detectEmailValue } from '../utils/emailPreview.js';
|
|
@@ -94,8 +95,13 @@ function renderTableList(state) {
|
|
|
94
95
|
data-to="/data/${encodeURIComponent(table.name)}"
|
|
95
96
|
type="button"
|
|
96
97
|
>
|
|
97
|
-
<div class="
|
|
98
|
-
${
|
|
98
|
+
<div class="flex min-w-0 items-center gap-2">
|
|
99
|
+
<div class="table-designer-sidebar__item-name min-w-0 flex-1 ${
|
|
100
|
+
table.name === activeName ? 'is-active' : ''
|
|
101
|
+
}">
|
|
102
|
+
${escapeHtml(table.name)}
|
|
103
|
+
</div>
|
|
104
|
+
${renderVirtualTableBadge(table)}
|
|
99
105
|
</div>
|
|
100
106
|
<div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
|
|
101
107
|
${escapeHtml(formatNumber(table.columnCount ?? 0))} column${
|
|
@@ -113,6 +119,12 @@ function renderTableList(state) {
|
|
|
113
119
|
function renderWorkspaceHeader(state) {
|
|
114
120
|
const table = state.dataBrowser.table;
|
|
115
121
|
const tablesVisible = state.dataBrowser.tablesVisible !== false;
|
|
122
|
+
const generateDisabled = !table || table.isShadow;
|
|
123
|
+
const generateTitle = !table
|
|
124
|
+
? 'Select a table before generating rows'
|
|
125
|
+
: table.isShadow
|
|
126
|
+
? 'Shadow tables are read-only in Data'
|
|
127
|
+
: 'Generate synthetic test rows for this table';
|
|
116
128
|
|
|
117
129
|
return `
|
|
118
130
|
<header class="workspace-header">
|
|
@@ -140,9 +152,9 @@ function renderWorkspaceHeader(state) {
|
|
|
140
152
|
<button
|
|
141
153
|
class="standard-button"
|
|
142
154
|
data-action="open-generate-data-modal"
|
|
143
|
-
title="${
|
|
155
|
+
title="${escapeHtml(generateTitle)}"
|
|
144
156
|
type="button"
|
|
145
|
-
${
|
|
157
|
+
${generateDisabled ? 'disabled aria-disabled="true"' : ''}
|
|
146
158
|
>
|
|
147
159
|
<span class="material-symbols-outlined text-sm">auto_awesome</span>
|
|
148
160
|
Generate
|
|
@@ -628,6 +640,8 @@ export function renderDataRowEditorPanel(state) {
|
|
|
628
640
|
: [{ name: 'rowIdentity', value: JSON.stringify(row.__identity ?? null) }],
|
|
629
641
|
disabledMessage: state.connections.active?.readOnly
|
|
630
642
|
? 'The active database is opened read-only, so row editing is disabled.'
|
|
643
|
+
: table.isShadow
|
|
644
|
+
? 'Shadow tables are read-only in Data.'
|
|
631
645
|
: table.notSafelyUpdatable
|
|
632
646
|
? 'This table has no stable identity column, so SQLite Hub cannot safely update rows.'
|
|
633
647
|
: '',
|
|
@@ -663,7 +677,7 @@ export function renderDataRowEditorPanel(state) {
|
|
|
663
677
|
deleting: state.dataBrowser.deleting,
|
|
664
678
|
deleteAction: 'delete-data-row',
|
|
665
679
|
deleteRowIndex: isIndexedRow ? rowIndex : null,
|
|
666
|
-
deleteEnabled: Boolean(row.__identity),
|
|
680
|
+
deleteEnabled: Boolean(row.__identity) && !table.isShadow,
|
|
667
681
|
reloadAction: 'reload-data-route',
|
|
668
682
|
jsonActionsEnabled: true,
|
|
669
683
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { clearInspector, renderDdlSection, renderInspector } from '../components/structureGraph.js';
|
|
2
|
+
import { renderVirtualTableBadge } from '../components/badges.js';
|
|
2
3
|
import { renderDropdownButton } from '../components/dropdownButton.js';
|
|
3
4
|
import { renderGenerateTypesDropdown } from '../components/generateTypesDropdown.js';
|
|
4
5
|
import { renderWorkspaceOpenDropdown } from '../components/workspaceOpenDropdown.js';
|
|
@@ -36,11 +37,14 @@ function renderEntryGroup(title, entries, activeName, options = {}) {
|
|
|
36
37
|
'" ',
|
|
37
38
|
entryAttributes,
|
|
38
39
|
' type="button">',
|
|
39
|
-
'<div class="
|
|
40
|
+
'<div class="flex min-w-0 items-center gap-2">',
|
|
41
|
+
'<div class="min-w-0 flex-1 truncate font-mono text-xs ',
|
|
40
42
|
isActive ? 'text-primary-container' : 'text-on-surface',
|
|
41
43
|
'">',
|
|
42
44
|
escapeHtml(entry.name),
|
|
43
45
|
'</div>',
|
|
46
|
+
renderVirtualTableBadge(entry),
|
|
47
|
+
'</div>',
|
|
44
48
|
metaMarkup,
|
|
45
49
|
'</button>',
|
|
46
50
|
].join('');
|
|
@@ -76,6 +80,10 @@ function getFilteredTables(tables, searchQuery) {
|
|
|
76
80
|
return tables.filter(table => table.name.toLowerCase().includes(normalizedSearch));
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
function isShadowTable(table) {
|
|
84
|
+
return Boolean(table?.isShadow);
|
|
85
|
+
}
|
|
86
|
+
|
|
79
87
|
function renderStructureTableSearch(state) {
|
|
80
88
|
return `
|
|
81
89
|
<label class="table-designer-sidebar__search">
|
|
@@ -129,7 +137,10 @@ function renderObjectInspector(detail) {
|
|
|
129
137
|
<span class="material-symbols-outlined text-[18px]">close</span>
|
|
130
138
|
</button>
|
|
131
139
|
</div>
|
|
132
|
-
<div class="
|
|
140
|
+
<div class="flex min-w-0 flex-wrap items-center gap-2">
|
|
141
|
+
<div class="structure-graph__title min-w-0">${escapeHtml(detail.name)}</div>
|
|
142
|
+
${renderVirtualTableBadge(detail)}
|
|
143
|
+
</div>
|
|
133
144
|
${
|
|
134
145
|
detail.tableName
|
|
135
146
|
? `
|
|
@@ -336,9 +347,10 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading, tabl
|
|
|
336
347
|
|
|
337
348
|
export function renderStructureView(state) {
|
|
338
349
|
const structure = state.structure.data;
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
350
|
+
const regularTables = (structure?.grouped.tables ?? []).filter(table => !isShadowTable(table));
|
|
351
|
+
const shadowTables = (structure?.grouped.tables ?? []).filter(isShadowTable);
|
|
352
|
+
const filteredTables = structure ? getFilteredTables(regularTables, state.structure.tableSearchQuery) : [];
|
|
353
|
+
const filteredShadowTables = structure ? getFilteredTables(shadowTables, state.structure.tableSearchQuery) : [];
|
|
342
354
|
const detail = state.structure.detail?.name === state.structure.selectedName ? state.structure.detail : null;
|
|
343
355
|
const tablesVisible = state.structure.tablesVisible !== false;
|
|
344
356
|
|
|
@@ -376,6 +388,17 @@ export function renderStructureView(state) {
|
|
|
376
388
|
? 'tables match the current search.'
|
|
377
389
|
: null,
|
|
378
390
|
})}
|
|
391
|
+
${
|
|
392
|
+
shadowTables.length
|
|
393
|
+
? renderEntryGroup('Shadow Tables', filteredShadowTables, state.structure.selectedName, {
|
|
394
|
+
compact: true,
|
|
395
|
+
showMeta: false,
|
|
396
|
+
emptyMessage: state.structure.tableSearchQuery
|
|
397
|
+
? 'shadow tables match the current search.'
|
|
398
|
+
: null,
|
|
399
|
+
})
|
|
400
|
+
: ''
|
|
401
|
+
}
|
|
379
402
|
${renderEntryGroup('Views', structure.grouped.views, state.structure.selectedName)}
|
|
380
403
|
${renderEntryGroup('Indexes', structure.grouped.indexes, state.structure.selectedName, {
|
|
381
404
|
compact: true,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
highlightSql,
|
|
6
6
|
truncateMiddle,
|
|
7
7
|
} from '../utils/format.js';
|
|
8
|
+
import { renderVirtualTableBadge } from '../components/badges.js';
|
|
8
9
|
import { renderWorkspaceOpenDropdown } from '../components/workspaceOpenDropdown.js';
|
|
9
10
|
|
|
10
11
|
const CATEGORY_LABELS = {
|
|
@@ -73,7 +74,8 @@ function renderRiskBadge(risk = 'low') {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
function renderTableList(state) {
|
|
76
|
-
const {
|
|
77
|
+
const { selectedTableName } = state.tableAdvisor;
|
|
78
|
+
const tables = (state.tableAdvisor.tables ?? []).filter(table => !table?.isShadow);
|
|
77
79
|
|
|
78
80
|
if (state.tableAdvisor.loading && !tables.length) {
|
|
79
81
|
return `
|
|
@@ -107,8 +109,11 @@ function renderTableList(state) {
|
|
|
107
109
|
href="#/table-advisor/${encodeURIComponent(table.name)}"
|
|
108
110
|
title="${escapeHtml(table.name)}"
|
|
109
111
|
>
|
|
110
|
-
<div class="
|
|
111
|
-
${
|
|
112
|
+
<div class="flex min-w-0 items-center gap-2">
|
|
113
|
+
<div class="table-designer-sidebar__item-name min-w-0 flex-1 ${isActive ? 'is-active' : ''}">
|
|
114
|
+
${escapeHtml(table.name)}
|
|
115
|
+
</div>
|
|
116
|
+
${renderVirtualTableBadge(table)}
|
|
112
117
|
</div>
|
|
113
118
|
<div class="table-designer-sidebar__item-meta">
|
|
114
119
|
${escapeHtml(formatNumber(table.columnCount ?? 0))} columns
|
|
@@ -128,9 +133,12 @@ function renderSummary(result = {}) {
|
|
|
128
133
|
<div class="grid grid-cols-1 gap-3 md:grid-cols-4">
|
|
129
134
|
<div class="border border-outline-variant/10 bg-surface-container-low px-4 py-3">
|
|
130
135
|
<div class="font-mono text-[10px] uppercase tracking-[0.2em] text-on-surface-variant/55">Table</div>
|
|
131
|
-
<div class="mt-2 min-w-0
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
<div class="mt-2 flex min-w-0 items-center gap-2">
|
|
137
|
+
<div class="min-w-0 truncate text-lg font-black text-on-surface" title="${escapeHtml(
|
|
138
|
+
result.tableName ?? '',
|
|
139
|
+
)}">${escapeHtml(result.tableName ?? 'Unknown')}</div>
|
|
140
|
+
${renderVirtualTableBadge(result)}
|
|
141
|
+
</div>
|
|
134
142
|
</div>
|
|
135
143
|
<div class="border border-outline-variant/10 bg-surface-container-low px-4 py-3">
|
|
136
144
|
<div class="font-mono text-[10px] uppercase tracking-[0.2em] text-on-surface-variant/55">Score</div>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { renderTableDesignerEditor } from "../components/tableDesignerEditor.js";
|
|
2
|
+
import { renderTableDesignerConstraintsDrawer } from "../components/tableDesignerConstraintsDrawer.js";
|
|
2
3
|
import { renderTableDesignerSidebar } from "../components/tableDesignerSidebar.js";
|
|
3
4
|
import { renderTableDesignerSqlPreview } from "../components/tableDesignerSqlPreview.js";
|
|
5
|
+
import { renderDropdownButton } from "../components/dropdownButton.js";
|
|
4
6
|
import { renderWorkspaceOpenDropdown } from "../components/workspaceOpenDropdown.js";
|
|
5
7
|
import { escapeHtml } from "../utils/format.js";
|
|
6
8
|
|
|
@@ -17,6 +19,44 @@ function renderRouteError(error) {
|
|
|
17
19
|
`;
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
function renderImportDropdown() {
|
|
23
|
+
return renderDropdownButton({
|
|
24
|
+
align: "right",
|
|
25
|
+
icon: "upload_file",
|
|
26
|
+
label: "Import",
|
|
27
|
+
title: "Import data",
|
|
28
|
+
items: [
|
|
29
|
+
{
|
|
30
|
+
action: "import-table-designer-data",
|
|
31
|
+
dataAttributes: {
|
|
32
|
+
importFormat: "csv",
|
|
33
|
+
importAccept: ".csv,text/csv",
|
|
34
|
+
},
|
|
35
|
+
icon: "table_chart",
|
|
36
|
+
label: "CSV",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
action: "import-table-designer-data",
|
|
40
|
+
dataAttributes: {
|
|
41
|
+
importFormat: "tsv",
|
|
42
|
+
importAccept: ".tsv,text/tab-separated-values",
|
|
43
|
+
},
|
|
44
|
+
icon: "table_rows",
|
|
45
|
+
label: "TSV",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
action: "import-table-designer-data",
|
|
49
|
+
dataAttributes: {
|
|
50
|
+
importFormat: "json",
|
|
51
|
+
importAccept: ".json,application/json",
|
|
52
|
+
},
|
|
53
|
+
icon: "data_object",
|
|
54
|
+
label: "JSON",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
20
60
|
function renderWorkspaceToolbar(state) {
|
|
21
61
|
const tablesVisible = state.tableDesigner.tablesVisible !== false;
|
|
22
62
|
const tableName = state.tableDesigner.selectedTableName ?? state.tableDesigner.draft?.tableName ?? "";
|
|
@@ -62,17 +102,12 @@ function renderWorkspaceToolbar(state) {
|
|
|
62
102
|
},
|
|
63
103
|
],
|
|
64
104
|
})}
|
|
65
|
-
|
|
66
|
-
class="standard-button"
|
|
67
|
-
data-action="import-table-designer-csv"
|
|
68
|
-
type="button"
|
|
69
|
-
>
|
|
70
|
-
Import CSV
|
|
71
|
-
</button>
|
|
105
|
+
${renderImportDropdown()}
|
|
72
106
|
<input
|
|
73
|
-
accept=".csv,text/csv"
|
|
107
|
+
accept=".csv,text/csv,.tsv,text/tab-separated-values,.json,application/json"
|
|
74
108
|
class="table-designer-workspace__file-input"
|
|
75
109
|
data-bind="table-designer-import-file"
|
|
110
|
+
data-import-format="csv"
|
|
76
111
|
type="file"
|
|
77
112
|
/>
|
|
78
113
|
<button
|
|
@@ -110,6 +145,6 @@ export function renderTableDesignerView(state) {
|
|
|
110
145
|
</div>
|
|
111
146
|
</section>
|
|
112
147
|
`,
|
|
113
|
-
panel:
|
|
148
|
+
panel: renderTableDesignerConstraintsDrawer(state),
|
|
114
149
|
};
|
|
115
150
|
}
|