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
|
@@ -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,7 @@
|
|
|
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 { filterConnections, getConnectionTagCounts } from '../utils/connectionRegistry.js';
|
|
4
5
|
|
|
5
6
|
function renderConnectionsActionButton({
|
|
6
7
|
label,
|
|
@@ -28,7 +29,88 @@ function renderConnectionsActionButton({
|
|
|
28
29
|
`;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
function
|
|
32
|
+
function renderConnectionsToolbar(state, visibleCount) {
|
|
33
|
+
if (!state.connections.recent.length) {
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const searchQuery = state.connections.searchQuery ?? '';
|
|
38
|
+
const selectedTagIds = new Set((state.connections.selectedTagIds ?? []).map(tagId => String(tagId)));
|
|
39
|
+
const selectedTags = (state.connections.tags ?? []).filter(tag => selectedTagIds.has(String(tag.id)));
|
|
40
|
+
const tagCounts = getConnectionTagCounts(state.connections.recent);
|
|
41
|
+
const filterLabel =
|
|
42
|
+
selectedTags.length === 0
|
|
43
|
+
? 'TAGS: ALL'
|
|
44
|
+
: selectedTags.length === 1
|
|
45
|
+
? `TAGS: ${selectedTags[0].name}`
|
|
46
|
+
: `TAGS: ${selectedTags.length}`;
|
|
47
|
+
const tagRows = (state.connections.tags ?? [])
|
|
48
|
+
.map(tag => {
|
|
49
|
+
const tagId = String(tag.id);
|
|
50
|
+
const isSelected = selectedTagIds.has(tagId);
|
|
51
|
+
|
|
52
|
+
return `
|
|
53
|
+
<label class="connection-tag-filter-row">
|
|
54
|
+
<input
|
|
55
|
+
${isSelected ? 'checked' : ''}
|
|
56
|
+
data-bind="connection-tag-filter"
|
|
57
|
+
data-tag-id="${escapeHtml(tagId)}"
|
|
58
|
+
type="checkbox"
|
|
59
|
+
/>
|
|
60
|
+
<span class="connection-tag-filter-row__name">${escapeHtml(tag.name)}</span>
|
|
61
|
+
<span class="connection-tag-filter-row__count">${escapeHtml(String(tagCounts.get(tagId) ?? tag.connectionCount ?? 0))}</span>
|
|
62
|
+
</label>
|
|
63
|
+
`;
|
|
64
|
+
})
|
|
65
|
+
.join('');
|
|
66
|
+
|
|
67
|
+
return `
|
|
68
|
+
<div class="connection-registry-toolbar" data-connection-registry-toolbar>
|
|
69
|
+
<label class="connection-registry-search">
|
|
70
|
+
<span class="sr-only">Search connections</span>
|
|
71
|
+
<span class="material-symbols-outlined connection-registry-search__icon" aria-hidden="true">search</span>
|
|
72
|
+
<input
|
|
73
|
+
autocomplete="off"
|
|
74
|
+
class="connection-registry-search__input"
|
|
75
|
+
data-bind="connections-search"
|
|
76
|
+
data-connections-search-input
|
|
77
|
+
placeholder="SEARCH_CONNECTIONS..."
|
|
78
|
+
spellcheck="false"
|
|
79
|
+
type="search"
|
|
80
|
+
value="${escapeHtml(searchQuery)}"
|
|
81
|
+
/>
|
|
82
|
+
<span class="connection-registry-search__shortcut" aria-hidden="true">/</span>
|
|
83
|
+
</label>
|
|
84
|
+
<details class="dropdown-button dropdown-button--align-right connection-tag-filter" data-dropdown-button>
|
|
85
|
+
<summary class="standard-button dropdown-button__toggle connection-tag-filter__toggle" title="${escapeHtml(filterLabel)}">
|
|
86
|
+
<span class="connection-tag-filter__toggle-label">${escapeHtml(filterLabel)}</span>
|
|
87
|
+
<span class="material-symbols-outlined dropdown-button__chevron" aria-hidden="true">expand_more</span>
|
|
88
|
+
</summary>
|
|
89
|
+
<div class="dropdown-button__panel connection-tag-filter__panel">
|
|
90
|
+
<div class="connection-tag-filter__header">
|
|
91
|
+
<span>FILTER_BY_TAG</span>
|
|
92
|
+
<span>${escapeHtml(String(visibleCount))}</span>
|
|
93
|
+
</div>
|
|
94
|
+
${
|
|
95
|
+
tagRows
|
|
96
|
+
? `<div class="connection-tag-filter__list custom-scrollbar">${tagRows}</div>`
|
|
97
|
+
: '<div class="connection-tag-filter__empty">NO_TAGS_AVAILABLE</div>'
|
|
98
|
+
}
|
|
99
|
+
<button
|
|
100
|
+
class="standard-button connection-tag-filter__reset"
|
|
101
|
+
data-action="clear-connection-tag-filters"
|
|
102
|
+
type="button"
|
|
103
|
+
${selectedTagIds.size ? '' : 'disabled'}
|
|
104
|
+
>
|
|
105
|
+
RESET_FILTER
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
</details>
|
|
109
|
+
</div>
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function renderConnectionsBody(state, visibleConnections) {
|
|
32
114
|
if (state.connections.loading && !state.connections.recent.length) {
|
|
33
115
|
return `
|
|
34
116
|
<div class="flex min-h-[280px] items-center justify-center border border-outline-variant/10 bg-surface-container-low">
|
|
@@ -78,21 +160,33 @@ function renderConnectionsBody(state) {
|
|
|
78
160
|
`;
|
|
79
161
|
}
|
|
80
162
|
|
|
163
|
+
if (!visibleConnections.length) {
|
|
164
|
+
return `
|
|
165
|
+
<div class="connection-filter-empty">
|
|
166
|
+
<span class="material-symbols-outlined connection-filter-empty__icon">search_off</span>
|
|
167
|
+
<p class="connection-filter-empty__title">NO_CONNECTIONS_FOUND</p>
|
|
168
|
+
<p class="connection-filter-empty__hint">TRY_ANOTHER_SEARCH_OR_FILTER</p>
|
|
169
|
+
</div>
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
|
|
81
173
|
return `
|
|
82
174
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
83
|
-
${state.connections.
|
|
84
|
-
.map(connection => renderConnectionCard(connection, state.connections.active?.id))
|
|
85
|
-
.join('')}
|
|
175
|
+
${visibleConnections.map(connection => renderConnectionCard(connection, state.connections.active?.id)).join('')}
|
|
86
176
|
</div>
|
|
87
177
|
`;
|
|
88
178
|
}
|
|
89
179
|
|
|
90
180
|
export function renderConnectionsView(state) {
|
|
181
|
+
const visibleConnections = filterConnections(state.connections.recent, {
|
|
182
|
+
searchQuery: state.connections.searchQuery,
|
|
183
|
+
selectedTagIds: state.connections.selectedTagIds,
|
|
184
|
+
});
|
|
91
185
|
const actions = `
|
|
92
186
|
${
|
|
93
187
|
state.connections.active
|
|
94
188
|
? renderConnectionsActionButton({
|
|
95
|
-
label: state.connections.backupLoading ? '
|
|
189
|
+
label: state.connections.backupLoading ? 'Create Backup...' : 'CreateBackup',
|
|
96
190
|
icon: 'inventory_2',
|
|
97
191
|
action: 'create-backup',
|
|
98
192
|
disabled: state.connections.backupLoading,
|
|
@@ -122,7 +216,8 @@ export function renderConnectionsView(state) {
|
|
|
122
216
|
subtitle: `Registry // Recent_Targets: ${String(state.connections.recent.length).padStart(2, '0')}`,
|
|
123
217
|
actions,
|
|
124
218
|
})}
|
|
125
|
-
${
|
|
219
|
+
${renderConnectionsToolbar(state, visibleConnections.length)}
|
|
220
|
+
${renderConnectionsBody(state, visibleConnections)}
|
|
126
221
|
</div>
|
|
127
222
|
</section>
|
|
128
223
|
`,
|
|
@@ -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';
|
|
@@ -31,6 +32,33 @@ function getFilteredTables(tables, searchQuery) {
|
|
|
31
32
|
return tables.filter(table => table.name.toLowerCase().includes(normalizedSearch));
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
export function buildDataSidebarSignature(state) {
|
|
36
|
+
if (state?.route?.name !== 'data') {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const dataBrowser = state.dataBrowser ?? {};
|
|
41
|
+
const tablesVisible = dataBrowser.tablesVisible !== false;
|
|
42
|
+
|
|
43
|
+
if (!tablesVisible) {
|
|
44
|
+
return 'data-sidebar:hidden';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const tables = dataBrowser.tables ?? [];
|
|
48
|
+
|
|
49
|
+
return JSON.stringify({
|
|
50
|
+
loadingEmpty: Boolean(dataBrowser.loading && !tables.length),
|
|
51
|
+
tableSearchQuery: dataBrowser.tableSearchQuery ?? '',
|
|
52
|
+
tables: tables.map(table => [
|
|
53
|
+
table.name,
|
|
54
|
+
table.columnCount ?? 0,
|
|
55
|
+
Boolean(table.isVirtual),
|
|
56
|
+
Boolean(table.isShadow),
|
|
57
|
+
table.tableKind ?? '',
|
|
58
|
+
]),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
34
62
|
function renderDataTableSearch(state) {
|
|
35
63
|
return `
|
|
36
64
|
<label class="table-designer-sidebar__search">
|
|
@@ -91,11 +119,18 @@ function renderTableList(state) {
|
|
|
91
119
|
: 'border-outline-variant/10 bg-surface-container-lowest hover:bg-surface-container-high'
|
|
92
120
|
}"
|
|
93
121
|
data-action="navigate"
|
|
122
|
+
data-data-table-item
|
|
123
|
+
data-data-table-name="${escapeHtml(table.name)}"
|
|
94
124
|
data-to="/data/${encodeURIComponent(table.name)}"
|
|
95
125
|
type="button"
|
|
96
126
|
>
|
|
97
|
-
<div class="
|
|
98
|
-
${
|
|
127
|
+
<div class="flex min-w-0 items-center gap-2">
|
|
128
|
+
<div class="table-designer-sidebar__item-name min-w-0 flex-1 ${
|
|
129
|
+
table.name === activeName ? 'is-active' : ''
|
|
130
|
+
}" data-data-table-name-label>
|
|
131
|
+
${escapeHtml(table.name)}
|
|
132
|
+
</div>
|
|
133
|
+
${renderVirtualTableBadge(table)}
|
|
99
134
|
</div>
|
|
100
135
|
<div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
|
|
101
136
|
${escapeHtml(formatNumber(table.columnCount ?? 0))} column${
|
|
@@ -113,6 +148,12 @@ function renderTableList(state) {
|
|
|
113
148
|
function renderWorkspaceHeader(state) {
|
|
114
149
|
const table = state.dataBrowser.table;
|
|
115
150
|
const tablesVisible = state.dataBrowser.tablesVisible !== false;
|
|
151
|
+
const generateDisabled = !table || table.isShadow;
|
|
152
|
+
const generateTitle = !table
|
|
153
|
+
? 'Select a table before generating rows'
|
|
154
|
+
: table.isShadow
|
|
155
|
+
? 'Shadow tables are read-only in Data'
|
|
156
|
+
: 'Generate synthetic test rows for this table';
|
|
116
157
|
|
|
117
158
|
return `
|
|
118
159
|
<header class="workspace-header">
|
|
@@ -140,9 +181,9 @@ function renderWorkspaceHeader(state) {
|
|
|
140
181
|
<button
|
|
141
182
|
class="standard-button"
|
|
142
183
|
data-action="open-generate-data-modal"
|
|
143
|
-
title="${
|
|
184
|
+
title="${escapeHtml(generateTitle)}"
|
|
144
185
|
type="button"
|
|
145
|
-
${
|
|
186
|
+
${generateDisabled ? 'disabled aria-disabled="true"' : ''}
|
|
146
187
|
>
|
|
147
188
|
<span class="material-symbols-outlined text-sm">auto_awesome</span>
|
|
148
189
|
Generate
|
|
@@ -628,6 +669,8 @@ export function renderDataRowEditorPanel(state) {
|
|
|
628
669
|
: [{ name: 'rowIdentity', value: JSON.stringify(row.__identity ?? null) }],
|
|
629
670
|
disabledMessage: state.connections.active?.readOnly
|
|
630
671
|
? 'The active database is opened read-only, so row editing is disabled.'
|
|
672
|
+
: table.isShadow
|
|
673
|
+
? 'Shadow tables are read-only in Data.'
|
|
631
674
|
: table.notSafelyUpdatable
|
|
632
675
|
? 'This table has no stable identity column, so SQLite Hub cannot safely update rows.'
|
|
633
676
|
: '',
|
|
@@ -663,7 +706,7 @@ export function renderDataRowEditorPanel(state) {
|
|
|
663
706
|
deleting: state.dataBrowser.deleting,
|
|
664
707
|
deleteAction: 'delete-data-row',
|
|
665
708
|
deleteRowIndex: isIndexedRow ? rowIndex : null,
|
|
666
|
-
deleteEnabled: Boolean(row.__identity),
|
|
709
|
+
deleteEnabled: Boolean(row.__identity) && !table.isShadow,
|
|
667
710
|
reloadAction: 'reload-data-route',
|
|
668
711
|
jsonActionsEnabled: true,
|
|
669
712
|
});
|
|
@@ -674,12 +717,12 @@ export function renderDataView(state) {
|
|
|
674
717
|
|
|
675
718
|
return {
|
|
676
719
|
main: `
|
|
677
|
-
<section class="view-surface min-h-full bg-surface-container flex h-full min-h-0 flex-col overflow-hidden">
|
|
678
|
-
<div class="data-view-grid ${tablesVisible ? 'data-view-grid--with-subnavi' : ''}">
|
|
720
|
+
<section class="view-surface min-h-full bg-surface-container flex h-full min-h-0 flex-col overflow-hidden" data-data-view>
|
|
721
|
+
<div class="data-view-grid ${tablesVisible ? 'data-view-grid--with-subnavi' : ''}" data-data-view-grid>
|
|
679
722
|
${
|
|
680
723
|
tablesVisible
|
|
681
724
|
? `
|
|
682
|
-
<aside class="data-view__sidebar subnavi-panel">
|
|
725
|
+
<aside class="data-view__sidebar subnavi-panel" data-data-sidebar>
|
|
683
726
|
<div class="subnavi-header">
|
|
684
727
|
<div>
|
|
685
728
|
<div class="subnavi-header-title">Tables</div>
|
|
@@ -694,7 +737,7 @@ export function renderDataView(state) {
|
|
|
694
737
|
`
|
|
695
738
|
: ''
|
|
696
739
|
}
|
|
697
|
-
<section class="flex min-h-0 flex-col overflow-hidden">
|
|
740
|
+
<section class="flex min-h-0 flex-col overflow-hidden" data-data-workspace>
|
|
698
741
|
${renderWorkspaceHeader(state)}
|
|
699
742
|
${renderWorkspaceError(state)}
|
|
700
743
|
${renderTableSurface(state)}
|
|
@@ -35,7 +35,7 @@ function renderDocumentListItem(item, selectedId) {
|
|
|
35
35
|
<span class="documents-list-item__title" title="${escapeHtml(item.filename)}">
|
|
36
36
|
${escapeHtml(item.filename)}
|
|
37
37
|
</span>
|
|
38
|
-
<span class="documents-list-item__meta">
|
|
38
|
+
<span class="documents-list-item__meta" data-document-list-meta>
|
|
39
39
|
${escapeHtml(formatCompactDateTime(item.updatedAt))} // ${formatNumber(item.contentLength ?? 0)} chars
|
|
40
40
|
</span>
|
|
41
41
|
</span>
|
|
@@ -77,7 +77,45 @@ function renderDocumentsSearch(documents) {
|
|
|
77
77
|
|
|
78
78
|
function renderDocumentsSidebar(documents) {
|
|
79
79
|
const items = documents.items ?? [];
|
|
80
|
+
const folders = documents.folders ?? [];
|
|
80
81
|
const filteredItems = getFilteredDocuments(items, documents.searchQuery);
|
|
82
|
+
const searchActive = Boolean(String(documents.searchQuery ?? '').trim());
|
|
83
|
+
const rootItems = filteredItems.filter(item => !item.folderId);
|
|
84
|
+
const renderFolderGroup = (label, groupItems, options = {}) => {
|
|
85
|
+
if (searchActive && !groupItems.length) {
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return `
|
|
90
|
+
<div class="documents-folder-group">
|
|
91
|
+
<div class="px-3 pb-1 pt-3 text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/55">
|
|
92
|
+
${escapeHtml(label)}
|
|
93
|
+
</div>
|
|
94
|
+
${
|
|
95
|
+
groupItems.length
|
|
96
|
+
? groupItems.map(item => renderDocumentListItem(item, documents.selectedId)).join('')
|
|
97
|
+
: `<div class="documents-list-empty">${escapeHtml(options.emptyText ?? 'No documents')}</div>`
|
|
98
|
+
}
|
|
99
|
+
</div>
|
|
100
|
+
`;
|
|
101
|
+
};
|
|
102
|
+
const folderGroups = folders
|
|
103
|
+
.map(folder =>
|
|
104
|
+
renderFolderGroup(
|
|
105
|
+
folder.name,
|
|
106
|
+
filteredItems.filter(item => String(item.folderId ?? '') === String(folder.id)),
|
|
107
|
+
{ emptyText: 'Empty folder' },
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
.join('');
|
|
111
|
+
const rootGroup = renderFolderGroup('No Folder', rootItems, {
|
|
112
|
+
emptyText: folders.length ? 'No root documents' : 'No documents yet',
|
|
113
|
+
});
|
|
114
|
+
const listMarkup = rootGroup || folderGroups
|
|
115
|
+
? `${rootGroup}${folderGroups}`
|
|
116
|
+
: `<div class="documents-list-empty">${
|
|
117
|
+
items.length ? 'No documents match the current search.' : 'No documents yet'
|
|
118
|
+
}</div>`;
|
|
81
119
|
|
|
82
120
|
return `
|
|
83
121
|
<aside class="documents-view__sidebar subnavi-panel">
|
|
@@ -89,13 +127,7 @@ function renderDocumentsSidebar(documents) {
|
|
|
89
127
|
</div>
|
|
90
128
|
${renderDocumentsSearch(documents)}
|
|
91
129
|
<div class="documents-view__sidebar-body subnavi-list custom-scrollbar">
|
|
92
|
-
${
|
|
93
|
-
filteredItems.length
|
|
94
|
-
? filteredItems.map(item => renderDocumentListItem(item, documents.selectedId)).join('')
|
|
95
|
-
: `<div class="documents-list-empty">${
|
|
96
|
-
items.length ? 'No documents match the current search.' : 'No documents yet'
|
|
97
|
-
}</div>`
|
|
98
|
-
}
|
|
130
|
+
${listMarkup}
|
|
99
131
|
</div>
|
|
100
132
|
</aside>
|
|
101
133
|
`;
|
|
@@ -118,18 +150,18 @@ function renderNewDocumentDropdown(documents, className = '') {
|
|
|
118
150
|
${renderDropdownButton({
|
|
119
151
|
disabled: documents.saving,
|
|
120
152
|
icon: 'add',
|
|
121
|
-
label: 'New
|
|
122
|
-
title: 'New
|
|
153
|
+
label: 'New',
|
|
154
|
+
title: 'New',
|
|
123
155
|
items: [
|
|
124
156
|
{
|
|
125
157
|
action: 'create-document',
|
|
126
158
|
icon: 'draft',
|
|
127
|
-
label: '
|
|
159
|
+
label: 'New Document',
|
|
128
160
|
},
|
|
129
161
|
{
|
|
130
|
-
action: '
|
|
131
|
-
icon: '
|
|
132
|
-
label: '
|
|
162
|
+
action: 'open-create-document-folder-modal',
|
|
163
|
+
icon: 'create_new_folder',
|
|
164
|
+
label: 'New Folder',
|
|
133
165
|
},
|
|
134
166
|
],
|
|
135
167
|
})}
|
|
@@ -137,40 +169,86 @@ function renderNewDocumentDropdown(documents, className = '') {
|
|
|
137
169
|
`;
|
|
138
170
|
}
|
|
139
171
|
|
|
140
|
-
function
|
|
141
|
-
const
|
|
172
|
+
function renderDocumentsViewDropdown(documents) {
|
|
173
|
+
const documentsVisible = documents.documentsVisible !== false;
|
|
174
|
+
const editorVisible = documents.editorVisible !== false;
|
|
175
|
+
const previewVisible = documents.previewVisible !== false;
|
|
142
176
|
|
|
143
|
-
return
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
177
|
+
return renderDropdownButton({
|
|
178
|
+
icon: 'visibility',
|
|
179
|
+
label: 'View',
|
|
180
|
+
title: 'View options',
|
|
181
|
+
items: [
|
|
182
|
+
{
|
|
183
|
+
action: 'toggle-document-pane',
|
|
184
|
+
dataAttributes: { pane: 'editor' },
|
|
185
|
+
icon: editorVisible ? 'visibility_off' : 'edit_note',
|
|
186
|
+
label: `${editorVisible ? 'Hide' : 'Show'} Editor`,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
action: 'toggle-document-pane',
|
|
190
|
+
dataAttributes: { pane: 'preview' },
|
|
191
|
+
icon: previewVisible ? 'visibility_off' : 'visibility',
|
|
192
|
+
label: `${previewVisible ? 'Hide' : 'Show'} Preview`,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
action: 'toggle-documents-panel',
|
|
196
|
+
icon: documentsVisible ? 'visibility_off' : 'description',
|
|
197
|
+
label: `${documentsVisible ? 'Hide' : 'Show'} Documents`,
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
});
|
|
154
201
|
}
|
|
155
202
|
|
|
156
|
-
function
|
|
157
|
-
const
|
|
158
|
-
const visible = isEditor ? documents.editorVisible : documents.previewVisible;
|
|
159
|
-
const label = `${visible ? 'Hide' : 'Show'} ${isEditor ? 'Editor' : 'Preview'}`;
|
|
160
|
-
const icon = visible ? 'visibility_off' : isEditor ? 'edit_note' : 'visibility';
|
|
203
|
+
function renderDocumentsFileDropdown(documents) {
|
|
204
|
+
const exportDisabled = documents.saving || documents.detailLoading || !documents.selectedId;
|
|
161
205
|
|
|
162
|
-
return
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
206
|
+
return renderDropdownButton({
|
|
207
|
+
disabled: documents.saving,
|
|
208
|
+
icon: 'folder_open',
|
|
209
|
+
label: 'File',
|
|
210
|
+
title: 'File actions',
|
|
211
|
+
items: [
|
|
212
|
+
{
|
|
213
|
+
action: 'import-document-markdown',
|
|
214
|
+
icon: 'upload_file',
|
|
215
|
+
label: 'Import MD',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
action: 'export-document-markdown',
|
|
219
|
+
disabled: exportDisabled,
|
|
220
|
+
icon: 'download',
|
|
221
|
+
label: 'Export MD',
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function renderDocumentsMoveDropdown(documents) {
|
|
228
|
+
const folders = documents.folders ?? [];
|
|
229
|
+
const selectedFolderId = String(documents.selected?.folderId ?? '');
|
|
230
|
+
|
|
231
|
+
return renderDropdownButton({
|
|
232
|
+
disabled: documents.saving || documents.detailLoading || !documents.selectedId,
|
|
233
|
+
icon: 'drive_file_move',
|
|
234
|
+
label: 'Move to',
|
|
235
|
+
title: 'Move document to folder',
|
|
236
|
+
items: [
|
|
237
|
+
{
|
|
238
|
+
action: 'move-document-to-folder',
|
|
239
|
+
disabled: !selectedFolderId,
|
|
240
|
+
icon: 'folder_off',
|
|
241
|
+
label: 'No Folder',
|
|
242
|
+
},
|
|
243
|
+
...folders.map(folder => ({
|
|
244
|
+
action: 'move-document-to-folder',
|
|
245
|
+
dataAttributes: { folderId: folder.id },
|
|
246
|
+
disabled: selectedFolderId === String(folder.id),
|
|
247
|
+
icon: 'folder',
|
|
248
|
+
label: folder.name,
|
|
249
|
+
})),
|
|
250
|
+
],
|
|
251
|
+
});
|
|
174
252
|
}
|
|
175
253
|
|
|
176
254
|
function renderDocumentsTitlebar(documents, options = {}) {
|
|
@@ -179,7 +257,6 @@ function renderDocumentsTitlebar(documents, options = {}) {
|
|
|
179
257
|
|
|
180
258
|
return `
|
|
181
259
|
<div class="documents-titlebar">
|
|
182
|
-
${renderDocumentsPanelToggle(documents)}
|
|
183
260
|
${
|
|
184
261
|
showFilename
|
|
185
262
|
? `
|
|
@@ -216,19 +293,33 @@ function renderDocumentsTitlebar(documents, options = {}) {
|
|
|
216
293
|
{
|
|
217
294
|
action: 'open-document-insert-note-modal',
|
|
218
295
|
icon: 'note_add',
|
|
219
|
-
label: 'Insert Note',
|
|
296
|
+
label: 'Insert Query Note',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
action: 'open-document-insert-table-definition-modal',
|
|
300
|
+
icon: 'schema',
|
|
301
|
+
label: 'Insert Table Definition',
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
action: 'insert-document-saved-queries',
|
|
305
|
+
icon: 'bookmark',
|
|
306
|
+
label: 'Insert Saved Queries',
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
action: 'insert-document-time-metadata',
|
|
310
|
+
icon: 'schedule',
|
|
311
|
+
label: 'Insert Time Metadata',
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
action: 'insert-document-database-info',
|
|
315
|
+
icon: 'database',
|
|
316
|
+
label: 'Insert Database Info',
|
|
220
317
|
},
|
|
221
318
|
],
|
|
222
319
|
})}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
type="button"
|
|
227
|
-
${disabled ? 'disabled aria-disabled="true"' : ''}
|
|
228
|
-
>
|
|
229
|
-
<span class="material-symbols-outlined">download</span>
|
|
230
|
-
Export .md
|
|
231
|
-
</button>
|
|
320
|
+
${renderDocumentsViewDropdown(documents)}
|
|
321
|
+
${renderDocumentsFileDropdown(documents)}
|
|
322
|
+
${renderDocumentsMoveDropdown(documents)}
|
|
232
323
|
<button
|
|
233
324
|
class="delete-button"
|
|
234
325
|
data-action="delete-document"
|
|
@@ -239,7 +330,10 @@ function renderDocumentsTitlebar(documents, options = {}) {
|
|
|
239
330
|
Delete
|
|
240
331
|
</button>
|
|
241
332
|
`
|
|
242
|
-
:
|
|
333
|
+
: `
|
|
334
|
+
${renderDocumentsViewDropdown(documents)}
|
|
335
|
+
${renderDocumentsFileDropdown(documents)}
|
|
336
|
+
`
|
|
243
337
|
}
|
|
244
338
|
</div>
|
|
245
339
|
`;
|
|
@@ -249,7 +343,6 @@ function renderDocumentEditor(documents) {
|
|
|
249
343
|
return `
|
|
250
344
|
<section class="documents-pane documents-pane--editor ${documents.editorVisible ? '' : 'documents-pane--collapsed'}">
|
|
251
345
|
<div class="documents-pane__header">
|
|
252
|
-
${renderDocumentPaneToggle(documents, 'editor')}
|
|
253
346
|
<span class="documents-pane__meta">${formatNumber(documents.draftContent.length)} chars</span>
|
|
254
347
|
</div>
|
|
255
348
|
${
|
|
@@ -271,11 +364,12 @@ function renderDocumentEditor(documents) {
|
|
|
271
364
|
}
|
|
272
365
|
|
|
273
366
|
function renderDocumentPreview(documents) {
|
|
367
|
+
const saveState = documents.dirty ? 'unsaved' : 'saved';
|
|
368
|
+
|
|
274
369
|
return `
|
|
275
370
|
<section class="documents-pane documents-pane--preview ${documents.previewVisible ? '' : 'documents-pane--collapsed'}">
|
|
276
371
|
<div class="documents-pane__header">
|
|
277
|
-
${
|
|
278
|
-
<span class="documents-pane__meta">${escapeHtml(documents.dirty ? 'unsaved' : 'saved')}</span>
|
|
372
|
+
<span class="documents-pane__meta" data-document-save-state="${saveState}">${escapeHtml(saveState)}</span>
|
|
279
373
|
</div>
|
|
280
374
|
${
|
|
281
375
|
documents.previewVisible
|