sqlite-hub 0.7.0 → 0.9.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/changelog.md +25 -0
- package/docs/DESIGN_GUIDELINES.md +36 -0
- package/frontend/index.html +79 -0
- package/frontend/js/api.js +67 -0
- package/frontend/js/app.js +1401 -921
- package/frontend/js/components/connectionCard.js +3 -4
- package/frontend/js/components/emptyState.js +4 -4
- package/frontend/js/components/modal.js +239 -30
- package/frontend/js/components/queryEditor.js +11 -8
- package/frontend/js/components/queryHistoryDetail.js +27 -8
- package/frontend/js/components/queryHistoryPanel.js +6 -5
- package/frontend/js/components/rowEditorPanel.js +84 -58
- package/frontend/js/components/sidebar.js +76 -33
- package/frontend/js/components/structureGraph.js +629 -715
- package/frontend/js/components/tableDesignerEditor.js +9 -8
- package/frontend/js/components/tableDesignerSidebar.js +11 -10
- package/frontend/js/components/tableDesignerSqlPreview.js +61 -30
- package/frontend/js/lib/mediaTaggingDefaults.js +27 -0
- package/frontend/js/router.js +81 -71
- package/frontend/js/store.js +3095 -2165
- package/frontend/js/views/charts.js +68 -40
- package/frontend/js/views/connections.js +5 -17
- package/frontend/js/views/data.js +40 -27
- package/frontend/js/views/editor.js +172 -177
- package/frontend/js/views/mediaTagging.js +861 -0
- package/frontend/js/views/overview.js +149 -10
- package/frontend/js/views/settings.js +2 -2
- package/frontend/js/views/structure.js +74 -70
- package/frontend/js/views/tableDesigner.js +7 -2
- package/frontend/styles/base.css +73 -1
- package/frontend/styles/components.css +105 -105
- package/frontend/styles/structure-graph.css +19 -82
- package/frontend/styles/tokens.css +2 -0
- package/frontend/styles/views.css +823 -30
- package/package.json +1 -1
- package/server/routes/charts.js +4 -1
- package/server/routes/data.js +14 -0
- package/server/routes/mediaTagging.js +166 -0
- package/server/routes/sql.js +1 -0
- package/server/server.js +4 -0
- package/server/services/sqlite/dataBrowserService.js +25 -0
- package/server/services/sqlite/exportService.js +31 -1
- package/server/services/sqlite/mediaTaggingService.js +1689 -0
- package/server/services/sqlite/overviewService.js +68 -0
- package/server/services/storage/appStateStore.js +321 -2
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
import { renderBottomTabs } from
|
|
2
|
-
import { renderQueryEditor } from
|
|
3
|
-
import { renderQueryHistoryDetail } from
|
|
4
|
-
import { renderQueryHistoryPanel } from
|
|
5
|
-
import { renderRowEditorPanel } from
|
|
6
|
-
import { renderQueryResultsPane } from
|
|
7
|
-
import { getCurrentConnection, getQueryMessages, getQueryPerformance } from
|
|
8
|
-
import {
|
|
9
|
-
escapeHtml,
|
|
10
|
-
formatCellValue,
|
|
11
|
-
formatNumber,
|
|
12
|
-
isBlobPreview,
|
|
13
|
-
} from "../utils/format.js";
|
|
1
|
+
import { renderBottomTabs } from '../components/bottomTabs.js';
|
|
2
|
+
import { renderQueryEditor } from '../components/queryEditor.js';
|
|
3
|
+
import { renderQueryHistoryDetail } from '../components/queryHistoryDetail.js';
|
|
4
|
+
import { renderQueryHistoryPanel } from '../components/queryHistoryPanel.js';
|
|
5
|
+
import { renderRowEditorPanel } from '../components/rowEditorPanel.js';
|
|
6
|
+
import { renderQueryResultsPane } from '../components/queryResults.js';
|
|
7
|
+
import { getCurrentConnection, getQueryMessages, getQueryPerformance } from '../store.js';
|
|
8
|
+
import { escapeHtml, formatCellValue, formatNumber, isBlobPreview } from '../utils/format.js';
|
|
14
9
|
|
|
15
10
|
function renderMissingDatabase() {
|
|
16
|
-
|
|
11
|
+
return `
|
|
17
12
|
<div class="flex flex-1 flex-col items-center justify-center bg-surface-container-lowest px-8 text-center">
|
|
18
13
|
<span class="material-symbols-outlined mb-3 text-5xl text-on-surface-variant/25">database_off</span>
|
|
19
14
|
<p class="font-headline text-xl font-black uppercase tracking-tight text-primary-container">
|
|
@@ -27,60 +22,60 @@ function renderMissingDatabase() {
|
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
function renderMessagesPane(state) {
|
|
30
|
-
|
|
25
|
+
const items = getQueryMessages(state);
|
|
31
26
|
|
|
32
|
-
|
|
27
|
+
return `
|
|
33
28
|
<div class="custom-scrollbar h-full overflow-auto bg-surface-container-lowest px-6 py-6">
|
|
34
29
|
<div class="space-y-4">
|
|
35
30
|
${items
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
.map(
|
|
32
|
+
item => `
|
|
38
33
|
<div class="border border-outline-variant/10 bg-surface-container-low px-4 py-4">
|
|
39
34
|
<div class="text-[10px] font-mono uppercase tracking-[0.2em] ${
|
|
40
|
-
|
|
35
|
+
item.tone === 'alert' ? 'text-error' : 'text-primary-container'
|
|
41
36
|
}">
|
|
42
37
|
${escapeHtml(item.label)}
|
|
43
38
|
</div>
|
|
44
39
|
<div class="mt-2 text-sm text-on-surface">${escapeHtml(item.value)}</div>
|
|
45
40
|
</div>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
`,
|
|
42
|
+
)
|
|
43
|
+
.join('')}
|
|
49
44
|
</div>
|
|
50
45
|
</div>
|
|
51
46
|
`;
|
|
52
47
|
}
|
|
53
48
|
|
|
54
49
|
function renderPerformancePane(state) {
|
|
55
|
-
|
|
50
|
+
const metrics = getQueryPerformance(state);
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
return `
|
|
58
53
|
<div class="grid flex-1 grid-cols-1 gap-4 bg-surface-container-lowest p-6 md:grid-cols-4">
|
|
59
54
|
<div class="metric-card">
|
|
60
55
|
<span class="text-[10px] font-mono uppercase text-on-surface/40">Exec_Time</span>
|
|
61
56
|
<span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
|
|
62
|
-
|
|
57
|
+
String(metrics.timingMs ?? 0),
|
|
63
58
|
)}ms</span>
|
|
64
59
|
<span class="text-[10px] text-primary-container">Measured backend execution time</span>
|
|
65
60
|
</div>
|
|
66
61
|
<div class="metric-card">
|
|
67
62
|
<span class="text-[10px] font-mono uppercase text-on-surface/40">Statements</span>
|
|
68
63
|
<span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
|
|
69
|
-
|
|
64
|
+
formatNumber(metrics.statementCount),
|
|
70
65
|
)}</span>
|
|
71
66
|
<span class="text-[10px] text-on-surface/40">Split and executed by SQLite</span>
|
|
72
67
|
</div>
|
|
73
68
|
<div class="metric-card">
|
|
74
69
|
<span class="text-[10px] font-mono uppercase text-on-surface/40">Rows_Returned</span>
|
|
75
70
|
<span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
|
|
76
|
-
|
|
71
|
+
formatNumber(metrics.rowCount),
|
|
77
72
|
)}</span>
|
|
78
73
|
<span class="text-[10px] text-on-surface/40">Visible result set size</span>
|
|
79
74
|
</div>
|
|
80
75
|
<div class="metric-card metric-card--accent">
|
|
81
76
|
<span class="text-[10px] font-mono uppercase text-on-surface/40">Rows_Affected</span>
|
|
82
77
|
<span class="font-headline text-3xl font-bold text-on-surface">${escapeHtml(
|
|
83
|
-
|
|
78
|
+
formatNumber(metrics.affectedRowCount),
|
|
84
79
|
)}</span>
|
|
85
80
|
<span class="text-[10px] text-primary-container">INSERT / UPDATE / DELETE impact</span>
|
|
86
81
|
</div>
|
|
@@ -89,144 +84,144 @@ function renderPerformancePane(state) {
|
|
|
89
84
|
}
|
|
90
85
|
|
|
91
86
|
function getResultEditingState(state) {
|
|
92
|
-
|
|
87
|
+
const editing = state.editor.result?.editing;
|
|
93
88
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
89
|
+
if (!editing) {
|
|
90
|
+
return {
|
|
91
|
+
enabled: false,
|
|
92
|
+
message: '',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
100
95
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
96
|
+
if (state.connections.active?.readOnly) {
|
|
97
|
+
return {
|
|
98
|
+
enabled: false,
|
|
99
|
+
message: 'The active database is opened read-only, so query result editing is disabled.',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!editing.enabled) {
|
|
104
|
+
return {
|
|
105
|
+
enabled: false,
|
|
106
|
+
message: editing.reason || 'Only direct single-table SELECT results can be edited here.',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
107
109
|
|
|
108
|
-
if (!editing.enabled) {
|
|
109
110
|
return {
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
enabled: true,
|
|
112
|
+
message: `Click a row to edit it in ${editing.tableName}.`,
|
|
112
113
|
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
enabled: true,
|
|
117
|
-
message: `Click a row to edit it in ${editing.tableName}.`,
|
|
118
|
-
};
|
|
119
114
|
}
|
|
120
115
|
|
|
121
116
|
function getUniqueResultColumns(columns = []) {
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
const uniqueColumns = [];
|
|
118
|
+
const seen = new Set();
|
|
124
119
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
120
|
+
columns.forEach(column => {
|
|
121
|
+
if (!column?.sourceColumn || seen.has(column.sourceColumn)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
129
124
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
seen.add(column.sourceColumn);
|
|
126
|
+
uniqueColumns.push(column);
|
|
127
|
+
});
|
|
133
128
|
|
|
134
|
-
|
|
129
|
+
return uniqueColumns;
|
|
135
130
|
}
|
|
136
131
|
|
|
137
132
|
function renderEditorRowPanel(state) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
const result = state.editor.result;
|
|
134
|
+
const rowIndex = state.editor.selectedRowIndex;
|
|
135
|
+
const row = typeof rowIndex === 'number' ? (result?.rows?.[rowIndex] ?? null) : null;
|
|
141
136
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const uniqueColumns = getUniqueResultColumns(result.editing?.columns ?? []);
|
|
147
|
-
const editableColumns = uniqueColumns.filter((column) => {
|
|
148
|
-
if (column.identity || column.generated || !column.visible) {
|
|
149
|
-
return false;
|
|
137
|
+
if (!result || !row || typeof rowIndex !== 'number') {
|
|
138
|
+
return '';
|
|
150
139
|
}
|
|
151
140
|
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
141
|
+
const uniqueColumns = getUniqueResultColumns(result.editing?.columns ?? []);
|
|
142
|
+
const editableColumns = uniqueColumns.filter(column => {
|
|
143
|
+
if (column.identity || column.generated || !column.visible) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
156
146
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
147
|
+
const value = row[column.resultName];
|
|
148
|
+
if (isBlobPreview(value) || (value && typeof value === 'object')) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
163
151
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
152
|
+
return true;
|
|
153
|
+
});
|
|
154
|
+
const readonlyColumns = uniqueColumns.filter(column => {
|
|
155
|
+
if (!column.visible) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
167
158
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
159
|
+
if (column.identity || column.generated) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const value = row[column.resultName];
|
|
164
|
+
return isBlobPreview(value) || (value && typeof value === 'object');
|
|
165
|
+
});
|
|
166
|
+
const editingState = getResultEditingState(state);
|
|
172
167
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
168
|
+
return renderRowEditorPanel({
|
|
169
|
+
title: result.editing?.tableName ?? 'Query Result',
|
|
170
|
+
sectionLabel: 'Row Editor',
|
|
171
|
+
subtitle: `query row ${rowIndex + 1}`,
|
|
172
|
+
closeAction: 'clear-editor-row-selection',
|
|
173
|
+
formName: 'save-editor-row',
|
|
174
|
+
hiddenFields: [{ name: 'rowIndex', value: String(rowIndex) }],
|
|
175
|
+
disabledMessage: editingState.enabled ? '' : editingState.message,
|
|
176
|
+
editableFields: editableColumns.map(column => {
|
|
177
|
+
const value = row[column.resultName];
|
|
183
178
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
179
|
+
return {
|
|
180
|
+
name: column.sourceColumn,
|
|
181
|
+
label: column.sourceColumn,
|
|
182
|
+
value: value === null || value === undefined ? '' : String(value),
|
|
183
|
+
};
|
|
184
|
+
}),
|
|
185
|
+
readonlyFields: readonlyColumns.map(column => ({
|
|
186
|
+
name: column.sourceColumn,
|
|
187
|
+
label: column.sourceColumn,
|
|
188
|
+
value: formatCellValue(row[column.resultName]),
|
|
189
|
+
})),
|
|
190
|
+
saveError: state.editor.saveError,
|
|
191
|
+
saving: state.editor.saving,
|
|
192
|
+
deleting: state.editor.deleting,
|
|
193
|
+
deleteAction: 'delete-editor-row',
|
|
194
|
+
deleteRowIndex: rowIndex,
|
|
195
|
+
deleteEnabled: editingState.enabled && Boolean(row.__identity),
|
|
196
|
+
});
|
|
202
197
|
}
|
|
203
198
|
|
|
204
199
|
function renderResultsSurface(state, isResultsRoute) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
200
|
+
const activeTab = state.editor.activeTab;
|
|
201
|
+
const counts = {
|
|
202
|
+
resultRows: state.editor.result?.rows?.length ?? 0,
|
|
203
|
+
messages: getQueryMessages(state).length,
|
|
204
|
+
statementCount: state.editor.result?.statementCount ?? 0,
|
|
205
|
+
};
|
|
211
206
|
|
|
212
|
-
|
|
207
|
+
let content = renderMessagesPane(state);
|
|
213
208
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
209
|
+
if (activeTab === 'performance') {
|
|
210
|
+
content = renderPerformancePane(state);
|
|
211
|
+
} else if (activeTab === 'results') {
|
|
212
|
+
const editingState = getResultEditingState(state);
|
|
218
213
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
214
|
+
content = state.connections.active
|
|
215
|
+
? renderQueryResultsPane(state.editor.result, {
|
|
216
|
+
selectedRowIndex: state.editor.selectedRowIndex,
|
|
217
|
+
editable: editingState.enabled,
|
|
218
|
+
sortColumn: state.editor.resultSortColumn,
|
|
219
|
+
sortDirection: state.editor.resultSortDirection,
|
|
220
|
+
})
|
|
221
|
+
: renderMissingDatabase();
|
|
222
|
+
}
|
|
228
223
|
|
|
229
|
-
|
|
224
|
+
return `
|
|
230
225
|
<div class="flex h-full min-h-0 flex-col border-t border-outline-variant/10 bg-surface-container-lowest">
|
|
231
226
|
${renderBottomTabs(state.editor.activeTab, counts)}
|
|
232
227
|
<div class="min-h-0 flex-1">${content}</div>
|
|
@@ -235,26 +230,26 @@ function renderResultsSurface(state, isResultsRoute) {
|
|
|
235
230
|
}
|
|
236
231
|
|
|
237
232
|
export function renderEditorView(state, { isResultsRoute = false } = {}) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
233
|
+
const connection = getCurrentConnection(state);
|
|
234
|
+
const editorSectionClass = state.editor.editorPanelVisible ? 'min-h-[27.5%] max-h-[60%]' : 'flex-none';
|
|
235
|
+
const resultsSectionClass = 'flex-1';
|
|
241
236
|
|
|
242
|
-
|
|
243
|
-
|
|
237
|
+
return {
|
|
238
|
+
main: `
|
|
244
239
|
<section class="view-surface flex h-full min-h-0 flex-col overflow-hidden xl:flex-row">
|
|
245
240
|
<div class="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
|
246
241
|
<section class="${editorSectionClass} flex min-h-0 flex-col ${
|
|
247
|
-
|
|
242
|
+
isResultsRoute ? 'border-b-4 border-background' : ''
|
|
248
243
|
}">
|
|
249
244
|
${renderQueryEditor({
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
245
|
+
query: state.editor.sqlText,
|
|
246
|
+
executing: state.editor.executing,
|
|
247
|
+
exporting: state.editor.exportLoading,
|
|
248
|
+
historyLoading: state.editor.historyLoading,
|
|
249
|
+
historyTotal: state.editor.historyTotal,
|
|
250
|
+
editorVisible: state.editor.editorPanelVisible,
|
|
251
|
+
historyVisible: state.editor.historyPanelVisible,
|
|
252
|
+
title: connection?.label ?? 'SQLite Query Workspace',
|
|
258
253
|
})}
|
|
259
254
|
</section>
|
|
260
255
|
<section class="${resultsSectionClass} flex min-h-0 min-w-0 flex-col overflow-hidden">
|
|
@@ -262,33 +257,33 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
|
|
|
262
257
|
</section>
|
|
263
258
|
</div>
|
|
264
259
|
${
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
260
|
+
state.editor.historyPanelVisible
|
|
261
|
+
? renderQueryHistoryPanel({
|
|
262
|
+
items: state.editor.history,
|
|
263
|
+
loading: state.editor.historyLoading,
|
|
264
|
+
loadingMore: state.editor.historyLoadingMore,
|
|
265
|
+
error: state.editor.historyError,
|
|
266
|
+
activeTab: state.editor.historyTab,
|
|
267
|
+
search: state.editor.historySearchInput,
|
|
268
|
+
total: state.editor.historyTotal,
|
|
269
|
+
hasMore: state.editor.historyHasMore,
|
|
270
|
+
activeHistoryId: state.editor.historyActiveId,
|
|
271
|
+
selectedHistoryId: state.editor.historySelectedId,
|
|
272
|
+
})
|
|
273
|
+
: ''
|
|
279
274
|
}
|
|
280
275
|
</section>
|
|
281
276
|
`,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
277
|
+
panel:
|
|
278
|
+
isResultsRoute && state.editor.selectedRowIndex !== null
|
|
279
|
+
? renderEditorRowPanel(state)
|
|
280
|
+
: state.editor.historySelectedId
|
|
281
|
+
? renderQueryHistoryDetail({
|
|
282
|
+
item: state.editor.historyDetail,
|
|
283
|
+
runs: state.editor.historyRuns,
|
|
284
|
+
loading: state.editor.historyDetailLoading,
|
|
285
|
+
error: state.editor.historyDetailError,
|
|
286
|
+
})
|
|
287
|
+
: '',
|
|
288
|
+
};
|
|
294
289
|
}
|