sqlite-hub 0.4.0 → 0.6.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 +2 -2
- package/changelog.md +15 -0
- package/frontend/assets/mockups/connections.png +0 -0
- package/frontend/assets/mockups/data.png +0 -0
- package/frontend/assets/mockups/data_row_editor.png +0 -0
- package/frontend/assets/mockups/home.png +0 -0
- package/frontend/assets/mockups/sql_editor.png +0 -0
- package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
- package/frontend/assets/mockups/structure.png +0 -0
- package/frontend/assets/mockups/structure_inspector.png +0 -0
- package/frontend/js/api.js +114 -5
- package/frontend/js/app.js +368 -18
- package/frontend/js/components/bottomTabs.js +1 -1
- package/frontend/js/components/dataGrid.js +3 -3
- package/frontend/js/components/queryEditor.js +33 -55
- package/frontend/js/components/queryHistoryDetail.js +263 -0
- package/frontend/js/components/queryHistoryPanel.js +228 -0
- package/frontend/js/components/queryResults.js +32 -46
- package/frontend/js/components/rowEditorPanel.js +73 -14
- package/frontend/js/components/sidebar.js +1 -0
- package/frontend/js/components/tableDesignerEditor.js +356 -0
- package/frontend/js/components/tableDesignerSidebar.js +126 -0
- package/frontend/js/components/tableDesignerSqlPreview.js +40 -0
- package/frontend/js/router.js +10 -0
- package/frontend/js/store.js +841 -22
- package/frontend/js/utils/format.js +23 -0
- package/frontend/js/utils/tableDesigner.js +1192 -0
- package/frontend/js/views/data.js +273 -250
- package/frontend/js/views/editor.js +34 -10
- package/frontend/js/views/overview.js +15 -0
- package/frontend/js/views/tableDesigner.js +37 -0
- package/frontend/styles/base.css +87 -73
- package/frontend/styles/components.css +841 -188
- package/frontend/styles/views.css +40 -0
- package/package.json +1 -1
- package/server/routes/data.js +2 -0
- package/server/routes/export.js +4 -1
- package/server/routes/overview.js +12 -0
- package/server/routes/sql.js +163 -5
- package/server/routes/tableDesigner.js +60 -0
- package/server/server.js +5 -1
- package/server/services/sqlite/dataBrowserService.js +4 -16
- package/server/services/sqlite/exportService.js +4 -16
- package/server/services/sqlite/overviewService.js +34 -0
- package/server/services/sqlite/sqlExecutor.js +83 -63
- package/server/services/sqlite/tableDesigner/changeAnalysis.js +295 -0
- package/server/services/sqlite/tableDesigner/schemaMapping.js +233 -0
- package/server/services/sqlite/tableDesigner/sql.js +63 -0
- package/server/services/sqlite/tableDesigner/validation.js +245 -0
- package/server/services/sqlite/tableDesignerService.js +181 -0
- package/server/services/sqlite/tableSort.js +63 -0
- package/server/services/storage/appStateStore.js +674 -1
- package/server/services/storage/queryHistoryUtils.js +169 -0
- package/frontend/assets/mockups/data_edit.png +0 -0
- package/frontend/assets/mockups/graph_visualize.png +0 -0
- package/frontend/assets/mockups/overview.png +0 -0
package/frontend/js/store.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import * as api from "./api.js";
|
|
2
2
|
import { formatCellValue, inferStatusTone, truncateMiddle } from "./utils/format.js";
|
|
3
|
+
import {
|
|
4
|
+
addTableDesignerColumn,
|
|
5
|
+
createTableDesignerDraftFromCsvImport,
|
|
6
|
+
createNewTableDesignerDraft,
|
|
7
|
+
hydrateTableDesignerDraft,
|
|
8
|
+
removeTableDesignerColumn,
|
|
9
|
+
updateTableDesignerColumnField,
|
|
10
|
+
updateTableDesignerDraftField,
|
|
11
|
+
} from "./utils/tableDesigner.js";
|
|
3
12
|
|
|
4
13
|
const listeners = new Set();
|
|
5
14
|
const DEFAULT_SETTINGS = {
|
|
@@ -8,12 +17,17 @@ const DEFAULT_SETTINGS = {
|
|
|
8
17
|
csvDelimiter: ",",
|
|
9
18
|
};
|
|
10
19
|
const DATA_PAGE_SIZES = [25, 50, 100];
|
|
20
|
+
const QUERY_HISTORY_PAGE_SIZE = 30;
|
|
21
|
+
const QUERY_HISTORY_RUN_LIMIT = 8;
|
|
11
22
|
const MISSING_DATABASE_ERROR = {
|
|
12
23
|
code: "ACTIVE_DATABASE_REQUIRED",
|
|
13
24
|
message: "No active SQLite database selected.",
|
|
14
25
|
};
|
|
15
26
|
|
|
16
27
|
let routeLoadVersion = 0;
|
|
28
|
+
let queryHistoryLoadVersion = 0;
|
|
29
|
+
let queryHistoryDetailLoadVersion = 0;
|
|
30
|
+
let queryHistorySearchTimer = null;
|
|
17
31
|
|
|
18
32
|
const state = {
|
|
19
33
|
ready: false,
|
|
@@ -48,6 +62,8 @@ const state = {
|
|
|
48
62
|
deleting: false,
|
|
49
63
|
page: 1,
|
|
50
64
|
pageSize: 50,
|
|
65
|
+
sortColumn: null,
|
|
66
|
+
sortDirection: null,
|
|
51
67
|
searchQuery: "",
|
|
52
68
|
searchColumn: "",
|
|
53
69
|
selectedRowIndex: null,
|
|
@@ -59,10 +75,25 @@ const state = {
|
|
|
59
75
|
sqlText: "",
|
|
60
76
|
history: [],
|
|
61
77
|
historyLoading: false,
|
|
78
|
+
historyLoadingMore: false,
|
|
62
79
|
historyError: null,
|
|
80
|
+
historyTab: "recent",
|
|
81
|
+
historySearchInput: "",
|
|
82
|
+
historySearch: "",
|
|
83
|
+
historyPageSize: QUERY_HISTORY_PAGE_SIZE,
|
|
84
|
+
historyTotal: 0,
|
|
85
|
+
historyHasMore: false,
|
|
86
|
+
historyActiveId: null,
|
|
87
|
+
historySelectedId: null,
|
|
88
|
+
historyDetail: null,
|
|
89
|
+
historyRuns: [],
|
|
90
|
+
historyDetailLoading: false,
|
|
91
|
+
historyDetailError: null,
|
|
63
92
|
activeTab: "messages",
|
|
64
93
|
executing: false,
|
|
65
94
|
result: null,
|
|
95
|
+
resultSortColumn: null,
|
|
96
|
+
resultSortDirection: null,
|
|
66
97
|
error: null,
|
|
67
98
|
exportLoading: false,
|
|
68
99
|
selectedRowIndex: null,
|
|
@@ -70,6 +101,19 @@ const state = {
|
|
|
70
101
|
deleting: false,
|
|
71
102
|
saveError: null,
|
|
72
103
|
},
|
|
104
|
+
tableDesigner: {
|
|
105
|
+
tables: [],
|
|
106
|
+
selectedTableName: null,
|
|
107
|
+
draft: null,
|
|
108
|
+
pendingImportedDraft: null,
|
|
109
|
+
loading: false,
|
|
110
|
+
detailLoading: false,
|
|
111
|
+
saving: false,
|
|
112
|
+
searchQuery: "",
|
|
113
|
+
supportedTypes: [],
|
|
114
|
+
error: null,
|
|
115
|
+
saveError: null,
|
|
116
|
+
},
|
|
73
117
|
structure: {
|
|
74
118
|
data: null,
|
|
75
119
|
selectedName: null,
|
|
@@ -102,8 +146,35 @@ function normalizeError(error) {
|
|
|
102
146
|
};
|
|
103
147
|
}
|
|
104
148
|
|
|
149
|
+
function setActiveQueryHistoryItem(historyId) {
|
|
150
|
+
const normalizedId = Number(historyId);
|
|
151
|
+
|
|
152
|
+
if (!Number.isInteger(normalizedId) || normalizedId < 1) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
state.editor.historyActiveId = normalizedId;
|
|
157
|
+
return normalizedId;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function clearQueryHistoryDetailState() {
|
|
161
|
+
queryHistoryDetailLoadVersion += 1;
|
|
162
|
+
state.editor.historySelectedId = null;
|
|
163
|
+
state.editor.historyDetail = null;
|
|
164
|
+
state.editor.historyRuns = [];
|
|
165
|
+
state.editor.historyDetailLoading = false;
|
|
166
|
+
state.editor.historyDetailError = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
105
169
|
function requiresActiveDatabase(routeName) {
|
|
106
|
-
return [
|
|
170
|
+
return [
|
|
171
|
+
"overview",
|
|
172
|
+
"data",
|
|
173
|
+
"editor",
|
|
174
|
+
"editorResults",
|
|
175
|
+
"structure",
|
|
176
|
+
"tableDesigner",
|
|
177
|
+
].includes(routeName);
|
|
107
178
|
}
|
|
108
179
|
|
|
109
180
|
function normalizeDataPageSize(value, fallback = 50) {
|
|
@@ -116,6 +187,18 @@ function normalizeDataPageSize(value, fallback = 50) {
|
|
|
116
187
|
return fallback;
|
|
117
188
|
}
|
|
118
189
|
|
|
190
|
+
function normalizeSortDirection(value) {
|
|
191
|
+
return String(value ?? "").trim().toLowerCase() === "desc" ? "desc" : "asc";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getNextSortDirection(currentColumn, currentDirection, nextColumn) {
|
|
195
|
+
if (currentColumn === nextColumn) {
|
|
196
|
+
return normalizeSortDirection(currentDirection) === "asc" ? "desc" : "asc";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return "asc";
|
|
200
|
+
}
|
|
201
|
+
|
|
119
202
|
function canEditQueryResult(snapshot = state) {
|
|
120
203
|
return Boolean(snapshot.editor.result?.editing?.enabled) && !snapshot.connections.active?.readOnly;
|
|
121
204
|
}
|
|
@@ -125,6 +208,73 @@ function resetDataBrowserSearch() {
|
|
|
125
208
|
state.dataBrowser.searchColumn = "";
|
|
126
209
|
}
|
|
127
210
|
|
|
211
|
+
function resetDataBrowserSort() {
|
|
212
|
+
state.dataBrowser.sortColumn = null;
|
|
213
|
+
state.dataBrowser.sortDirection = null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function resetEditorResultSort() {
|
|
217
|
+
state.editor.resultSortColumn = null;
|
|
218
|
+
state.editor.resultSortDirection = null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getSortableEditorValue(value) {
|
|
222
|
+
if (value === null || value === undefined) {
|
|
223
|
+
return { rank: 0, value: "" };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (typeof value === "number") {
|
|
227
|
+
return { rank: 1, value };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (typeof value === "boolean") {
|
|
231
|
+
return { rank: 2, value: value ? 1 : 0 };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (typeof value === "string") {
|
|
235
|
+
return { rank: 3, value };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (value && typeof value === "object" && value.__type === "blob") {
|
|
239
|
+
return {
|
|
240
|
+
rank: 4,
|
|
241
|
+
value: `${value.sizeBytes ?? 0}:${value.hexPreview ?? ""}`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { rank: 5, value: JSON.stringify(value) };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function compareEditorValues(left, right) {
|
|
249
|
+
const leftValue = getSortableEditorValue(left);
|
|
250
|
+
const rightValue = getSortableEditorValue(right);
|
|
251
|
+
|
|
252
|
+
if (leftValue.rank !== rightValue.rank) {
|
|
253
|
+
return leftValue.rank - rightValue.rank;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (typeof leftValue.value === "number" && typeof rightValue.value === "number") {
|
|
257
|
+
return leftValue.value - rightValue.value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return String(leftValue.value).localeCompare(String(rightValue.value), undefined, {
|
|
261
|
+
numeric: true,
|
|
262
|
+
sensitivity: "base",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function sortEditorResultRows(rows, sortColumn, sortDirection) {
|
|
267
|
+
if (!sortColumn) {
|
|
268
|
+
return [...(rows ?? [])];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const directionMultiplier = normalizeSortDirection(sortDirection) === "desc" ? -1 : 1;
|
|
272
|
+
|
|
273
|
+
return [...(rows ?? [])].sort(
|
|
274
|
+
(left, right) => compareEditorValues(left?.[sortColumn], right?.[sortColumn]) * directionMultiplier
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
128
278
|
function buildUpdatedEditorResultRow(existingRow, updatedSourceRow, editableColumns) {
|
|
129
279
|
const nextRow = {
|
|
130
280
|
...existingRow,
|
|
@@ -149,6 +299,22 @@ function getCurrentStructureEntry(snapshot = state) {
|
|
|
149
299
|
return entries.find((entry) => entry.name === snapshot.structure.selectedName) ?? null;
|
|
150
300
|
}
|
|
151
301
|
|
|
302
|
+
function getTableDesignerContext(snapshot = state) {
|
|
303
|
+
return {
|
|
304
|
+
catalogTables: snapshot.tableDesigner.tables ?? [],
|
|
305
|
+
supportedTypes: snapshot.tableDesigner.supportedTypes ?? [],
|
|
306
|
+
readOnly: Boolean(snapshot.connections.active?.readOnly),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function decorateTableDesignerDraft(draft, snapshot = state) {
|
|
311
|
+
if (!draft) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return hydrateTableDesignerDraft(draft, getTableDesignerContext(snapshot));
|
|
316
|
+
}
|
|
317
|
+
|
|
152
318
|
function buildDeleteRowPreview(fields = []) {
|
|
153
319
|
return fields
|
|
154
320
|
.filter((field) => field && field.label)
|
|
@@ -175,10 +341,70 @@ function buildFallbackDeleteRowPreview(row) {
|
|
|
175
341
|
);
|
|
176
342
|
}
|
|
177
343
|
|
|
344
|
+
function normalizeQueryHistoryTab(value) {
|
|
345
|
+
return ["recent", "saved", "failed"].includes(value) ? value : "recent";
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function findQueryHistoryItem(historyId, snapshot = state) {
|
|
349
|
+
return snapshot.editor.history.find((entry) => String(entry.id) === String(historyId)) ?? null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function clearQueryHistorySearchTimer() {
|
|
353
|
+
if (!queryHistorySearchTimer) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
window.clearTimeout(queryHistorySearchTimer);
|
|
358
|
+
queryHistorySearchTimer = null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function resetQueryHistoryState({ preserveSearch = true } = {}) {
|
|
362
|
+
state.editor.history = [];
|
|
363
|
+
state.editor.historyLoading = false;
|
|
364
|
+
state.editor.historyLoadingMore = false;
|
|
365
|
+
state.editor.historyError = null;
|
|
366
|
+
state.editor.historyTotal = 0;
|
|
367
|
+
state.editor.historyHasMore = false;
|
|
368
|
+
state.editor.historyActiveId = null;
|
|
369
|
+
clearQueryHistoryDetailState();
|
|
370
|
+
|
|
371
|
+
if (!preserveSearch) {
|
|
372
|
+
clearQueryHistorySearchTimer();
|
|
373
|
+
state.editor.historySearchInput = "";
|
|
374
|
+
state.editor.historySearch = "";
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function syncQueryHistoryItem(updatedItem) {
|
|
379
|
+
if (!updatedItem) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
state.editor.history = state.editor.history.map((entry) =>
|
|
384
|
+
entry.id === updatedItem.id ? updatedItem : entry
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
if (state.editor.historyDetail?.id === updatedItem.id) {
|
|
388
|
+
state.editor.historyDetail = updatedItem;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function resolveQueryHistorySql(historyId) {
|
|
393
|
+
const historyIdAsString = String(historyId);
|
|
394
|
+
|
|
395
|
+
if (String(state.editor.historyDetail?.id ?? "") === historyIdAsString) {
|
|
396
|
+
return state.editor.historyDetail.rawSql;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return findQueryHistoryItem(historyId)?.rawSql ?? null;
|
|
400
|
+
}
|
|
401
|
+
|
|
178
402
|
function clearRouteSlices() {
|
|
179
403
|
state.overview.error = null;
|
|
180
404
|
state.dataBrowser.error = null;
|
|
181
405
|
state.dataBrowser.saveError = null;
|
|
406
|
+
state.tableDesigner.error = null;
|
|
407
|
+
state.tableDesigner.saveError = null;
|
|
182
408
|
state.structure.error = null;
|
|
183
409
|
}
|
|
184
410
|
|
|
@@ -195,6 +421,7 @@ function setMissingDatabaseState() {
|
|
|
195
421
|
state.dataBrowser.selectedTable = null;
|
|
196
422
|
state.dataBrowser.table = null;
|
|
197
423
|
state.dataBrowser.page = 1;
|
|
424
|
+
resetDataBrowserSort();
|
|
198
425
|
resetDataBrowserSearch();
|
|
199
426
|
state.dataBrowser.selectedRowIndex = null;
|
|
200
427
|
state.dataBrowser.exportLoading = false;
|
|
@@ -206,6 +433,20 @@ function setMissingDatabaseState() {
|
|
|
206
433
|
state.structure.data = null;
|
|
207
434
|
state.structure.detail = null;
|
|
208
435
|
state.structure.error = error;
|
|
436
|
+
|
|
437
|
+
state.tableDesigner.loading = false;
|
|
438
|
+
state.tableDesigner.detailLoading = false;
|
|
439
|
+
state.tableDesigner.tables = [];
|
|
440
|
+
state.tableDesigner.selectedTableName = null;
|
|
441
|
+
state.tableDesigner.draft = null;
|
|
442
|
+
state.tableDesigner.pendingImportedDraft = null;
|
|
443
|
+
state.tableDesigner.saving = false;
|
|
444
|
+
state.tableDesigner.searchQuery = "";
|
|
445
|
+
state.tableDesigner.supportedTypes = [];
|
|
446
|
+
state.tableDesigner.error = error;
|
|
447
|
+
state.tableDesigner.saveError = null;
|
|
448
|
+
|
|
449
|
+
resetQueryHistoryState({ preserveSearch: false });
|
|
209
450
|
}
|
|
210
451
|
|
|
211
452
|
function syncRouteContext() {
|
|
@@ -213,6 +454,7 @@ function syncRouteContext() {
|
|
|
213
454
|
|
|
214
455
|
if (route.name === "editorResults") {
|
|
215
456
|
state.editor.activeTab = "results";
|
|
457
|
+
clearQueryHistoryDetailState();
|
|
216
458
|
} else if (route.name === "editor" && state.editor.activeTab === "results") {
|
|
217
459
|
state.editor.activeTab = "messages";
|
|
218
460
|
}
|
|
@@ -240,6 +482,10 @@ function syncRouteContext() {
|
|
|
240
482
|
state.structure.detail = null;
|
|
241
483
|
state.structure.selectedName = null;
|
|
242
484
|
}
|
|
485
|
+
|
|
486
|
+
if (route.name !== "tableDesigner") {
|
|
487
|
+
state.tableDesigner.saveError = null;
|
|
488
|
+
}
|
|
243
489
|
}
|
|
244
490
|
|
|
245
491
|
async function refreshConnectionsState() {
|
|
@@ -284,19 +530,131 @@ async function refreshSettingsState() {
|
|
|
284
530
|
}
|
|
285
531
|
}
|
|
286
532
|
|
|
287
|
-
async function
|
|
288
|
-
|
|
289
|
-
|
|
533
|
+
async function loadQueryHistoryDetail(historyId) {
|
|
534
|
+
const normalizedId = String(historyId ?? "").trim();
|
|
535
|
+
const numericId = Number(normalizedId);
|
|
536
|
+
const requestVersion = ++queryHistoryDetailLoadVersion;
|
|
537
|
+
|
|
538
|
+
if (!normalizedId) {
|
|
539
|
+
clearQueryHistoryDetailState();
|
|
540
|
+
emitChange();
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
state.editor.historyDetailLoading = true;
|
|
545
|
+
state.editor.historyDetailError = null;
|
|
290
546
|
emitChange();
|
|
291
547
|
|
|
292
548
|
try {
|
|
293
|
-
const
|
|
294
|
-
|
|
549
|
+
const [detailResponse, runsResponse] = await Promise.all([
|
|
550
|
+
api.getQueryHistoryItem(normalizedId),
|
|
551
|
+
api.getQueryHistoryRuns(normalizedId, { limit: QUERY_HISTORY_RUN_LIMIT }),
|
|
552
|
+
]);
|
|
553
|
+
|
|
554
|
+
if (
|
|
555
|
+
requestVersion !== queryHistoryDetailLoadVersion ||
|
|
556
|
+
state.editor.historySelectedId !== numericId
|
|
557
|
+
) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
state.editor.historyDetail = detailResponse.data ?? null;
|
|
562
|
+
state.editor.historyRuns = runsResponse.data ?? [];
|
|
563
|
+
state.editor.historyDetailError = null;
|
|
564
|
+
if (detailResponse.data) {
|
|
565
|
+
syncQueryHistoryItem(detailResponse.data);
|
|
566
|
+
}
|
|
295
567
|
} catch (error) {
|
|
296
|
-
|
|
568
|
+
if (
|
|
569
|
+
requestVersion !== queryHistoryDetailLoadVersion ||
|
|
570
|
+
state.editor.historySelectedId !== numericId
|
|
571
|
+
) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
state.editor.historyDetail = null;
|
|
576
|
+
state.editor.historyRuns = [];
|
|
577
|
+
state.editor.historyDetailError = normalizeError(error);
|
|
297
578
|
} finally {
|
|
298
|
-
|
|
579
|
+
if (requestVersion === queryHistoryDetailLoadVersion) {
|
|
580
|
+
state.editor.historyDetailLoading = false;
|
|
581
|
+
emitChange();
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async function refreshQueryHistoryState({ append = false } = {}) {
|
|
587
|
+
if (!state.connections.active) {
|
|
588
|
+
resetQueryHistoryState({ preserveSearch: false });
|
|
299
589
|
emitChange();
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const requestVersion = ++queryHistoryLoadVersion;
|
|
594
|
+
const nextOffset = append ? state.editor.history.length : 0;
|
|
595
|
+
|
|
596
|
+
if (append) {
|
|
597
|
+
state.editor.historyLoadingMore = true;
|
|
598
|
+
} else {
|
|
599
|
+
state.editor.historyLoading = true;
|
|
600
|
+
state.editor.historyError = null;
|
|
601
|
+
}
|
|
602
|
+
emitChange();
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
const response = await api.getQueryHistory({
|
|
606
|
+
tab: state.editor.historyTab,
|
|
607
|
+
limit: state.editor.historyPageSize,
|
|
608
|
+
offset: nextOffset,
|
|
609
|
+
search: state.editor.historySearch,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
if (requestVersion !== queryHistoryLoadVersion) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const payload = response.data ?? {};
|
|
617
|
+
const items = payload.items ?? [];
|
|
618
|
+
state.editor.history = append ? [...state.editor.history, ...items] : items;
|
|
619
|
+
state.editor.historyTotal = payload.total ?? state.editor.history.length;
|
|
620
|
+
state.editor.historyHasMore = Boolean(payload.hasMore);
|
|
621
|
+
state.editor.historyError = null;
|
|
622
|
+
|
|
623
|
+
if (
|
|
624
|
+
state.editor.historyActiveId &&
|
|
625
|
+
!state.editor.history.some((entry) => entry.id === state.editor.historyActiveId)
|
|
626
|
+
) {
|
|
627
|
+
state.editor.historyActiveId = null;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (
|
|
631
|
+
state.editor.historySelectedId &&
|
|
632
|
+
!state.editor.history.some((entry) => entry.id === state.editor.historySelectedId)
|
|
633
|
+
) {
|
|
634
|
+
clearQueryHistoryDetailState();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (
|
|
638
|
+
state.editor.historySelectedId &&
|
|
639
|
+
!state.editor.historyDetailLoading &&
|
|
640
|
+
state.editor.historyDetail?.id !== state.editor.historySelectedId
|
|
641
|
+
) {
|
|
642
|
+
await loadQueryHistoryDetail(state.editor.historySelectedId);
|
|
643
|
+
} else {
|
|
644
|
+
emitChange();
|
|
645
|
+
}
|
|
646
|
+
} catch (error) {
|
|
647
|
+
if (requestVersion !== queryHistoryLoadVersion) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
state.editor.historyError = normalizeError(error);
|
|
652
|
+
} finally {
|
|
653
|
+
if (requestVersion === queryHistoryLoadVersion) {
|
|
654
|
+
state.editor.historyLoading = false;
|
|
655
|
+
state.editor.historyLoadingMore = false;
|
|
656
|
+
emitChange();
|
|
657
|
+
}
|
|
300
658
|
}
|
|
301
659
|
}
|
|
302
660
|
|
|
@@ -333,6 +691,8 @@ async function loadDataTable(version) {
|
|
|
333
691
|
const tableName = state.dataBrowser.selectedTable;
|
|
334
692
|
const pageSize = normalizeDataPageSize(state.dataBrowser.pageSize, 50);
|
|
335
693
|
const page = Math.max(1, Number(state.dataBrowser.page) || 1);
|
|
694
|
+
const sortColumn = state.dataBrowser.sortColumn;
|
|
695
|
+
const sortDirection = normalizeSortDirection(state.dataBrowser.sortDirection);
|
|
336
696
|
|
|
337
697
|
if (!tableName) {
|
|
338
698
|
state.dataBrowser.table = null;
|
|
@@ -348,6 +708,8 @@ async function loadDataTable(version) {
|
|
|
348
708
|
const response = await api.getDataTable(tableName, {
|
|
349
709
|
limit: pageSize,
|
|
350
710
|
offset: (page - 1) * pageSize,
|
|
711
|
+
sortColumn,
|
|
712
|
+
sortDirection,
|
|
351
713
|
});
|
|
352
714
|
|
|
353
715
|
if (version !== routeLoadVersion) {
|
|
@@ -357,6 +719,8 @@ async function loadDataTable(version) {
|
|
|
357
719
|
state.dataBrowser.table = response.data ?? null;
|
|
358
720
|
state.dataBrowser.pageSize = pageSize;
|
|
359
721
|
state.dataBrowser.page = response.data?.page ?? page;
|
|
722
|
+
state.dataBrowser.sortColumn = response.data?.sort?.column ?? null;
|
|
723
|
+
state.dataBrowser.sortDirection = response.data?.sort?.direction ?? null;
|
|
360
724
|
state.dataBrowser.searchColumn = response.data?.columns?.includes(state.dataBrowser.searchColumn)
|
|
361
725
|
? state.dataBrowser.searchColumn
|
|
362
726
|
: (response.data?.columns?.[0] ?? "");
|
|
@@ -397,6 +761,7 @@ async function loadData(version, route) {
|
|
|
397
761
|
if (requestedTableName && tables.some((table) => table.name === requestedTableName)) {
|
|
398
762
|
if (requestedTableName !== state.dataBrowser.selectedTable) {
|
|
399
763
|
state.dataBrowser.page = 1;
|
|
764
|
+
resetDataBrowserSort();
|
|
400
765
|
resetDataBrowserSearch();
|
|
401
766
|
}
|
|
402
767
|
state.dataBrowser.selectedTable = requestedTableName;
|
|
@@ -406,6 +771,7 @@ async function loadData(version, route) {
|
|
|
406
771
|
) {
|
|
407
772
|
state.dataBrowser.selectedTable = tables[0]?.name ?? null;
|
|
408
773
|
state.dataBrowser.page = 1;
|
|
774
|
+
resetDataBrowserSort();
|
|
409
775
|
resetDataBrowserSearch();
|
|
410
776
|
}
|
|
411
777
|
|
|
@@ -521,6 +887,103 @@ async function loadStructure(version) {
|
|
|
521
887
|
}
|
|
522
888
|
}
|
|
523
889
|
|
|
890
|
+
async function loadTableDesignerDetail(version, tableName) {
|
|
891
|
+
if (!tableName) {
|
|
892
|
+
state.tableDesigner.draft = null;
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
state.tableDesigner.detailLoading = true;
|
|
897
|
+
state.tableDesigner.saveError = null;
|
|
898
|
+
emitChange();
|
|
899
|
+
|
|
900
|
+
try {
|
|
901
|
+
const response = await api.getTableDesignerTable(tableName);
|
|
902
|
+
|
|
903
|
+
if (version !== routeLoadVersion) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
state.tableDesigner.selectedTableName = tableName;
|
|
908
|
+
state.tableDesigner.draft = decorateTableDesignerDraft(response.data?.draft ?? null);
|
|
909
|
+
} catch (error) {
|
|
910
|
+
if (version !== routeLoadVersion) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
state.tableDesigner.error = normalizeError(error);
|
|
915
|
+
state.tableDesigner.draft = null;
|
|
916
|
+
} finally {
|
|
917
|
+
if (version === routeLoadVersion) {
|
|
918
|
+
state.tableDesigner.detailLoading = false;
|
|
919
|
+
emitChange();
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
async function loadTableDesigner(version, route) {
|
|
925
|
+
state.tableDesigner.loading = true;
|
|
926
|
+
state.tableDesigner.error = null;
|
|
927
|
+
emitChange();
|
|
928
|
+
|
|
929
|
+
try {
|
|
930
|
+
const response = await api.getTableDesignerOverview();
|
|
931
|
+
|
|
932
|
+
if (version !== routeLoadVersion) {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
state.tableDesigner.tables = response.data?.tables ?? [];
|
|
937
|
+
state.tableDesigner.supportedTypes = response.data?.supportedTypes ?? [];
|
|
938
|
+
|
|
939
|
+
if (route.params?.isNew) {
|
|
940
|
+
const importedDraft = state.tableDesigner.pendingImportedDraft;
|
|
941
|
+
state.tableDesigner.selectedTableName = null;
|
|
942
|
+
state.tableDesigner.detailLoading = false;
|
|
943
|
+
state.tableDesigner.pendingImportedDraft = null;
|
|
944
|
+
state.tableDesigner.draft = decorateTableDesignerDraft(
|
|
945
|
+
importedDraft ?? createNewTableDesignerDraft()
|
|
946
|
+
);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const requestedTableName = route.params?.tableName ?? null;
|
|
951
|
+
const tableName =
|
|
952
|
+
requestedTableName &&
|
|
953
|
+
state.tableDesigner.tables.some((table) => table.name === requestedTableName)
|
|
954
|
+
? requestedTableName
|
|
955
|
+
: state.tableDesigner.selectedTableName &&
|
|
956
|
+
state.tableDesigner.tables.some(
|
|
957
|
+
(table) => table.name === state.tableDesigner.selectedTableName
|
|
958
|
+
)
|
|
959
|
+
? state.tableDesigner.selectedTableName
|
|
960
|
+
: state.tableDesigner.tables[0]?.name ?? null;
|
|
961
|
+
|
|
962
|
+
if (!tableName) {
|
|
963
|
+
state.tableDesigner.selectedTableName = null;
|
|
964
|
+
state.tableDesigner.detailLoading = false;
|
|
965
|
+
state.tableDesigner.draft = null;
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
await loadTableDesignerDetail(version, tableName);
|
|
970
|
+
} catch (error) {
|
|
971
|
+
if (version !== routeLoadVersion) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
state.tableDesigner.tables = [];
|
|
976
|
+
state.tableDesigner.selectedTableName = null;
|
|
977
|
+
state.tableDesigner.draft = null;
|
|
978
|
+
state.tableDesigner.error = normalizeError(error);
|
|
979
|
+
} finally {
|
|
980
|
+
if (version === routeLoadVersion) {
|
|
981
|
+
state.tableDesigner.loading = false;
|
|
982
|
+
emitChange();
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
524
987
|
function invalidateDatabaseCaches() {
|
|
525
988
|
state.overview.data = null;
|
|
526
989
|
state.dataBrowser.tables = [];
|
|
@@ -532,6 +995,15 @@ function invalidateDatabaseCaches() {
|
|
|
532
995
|
state.dataBrowser.exportLoading = false;
|
|
533
996
|
state.dataBrowser.error = null;
|
|
534
997
|
state.dataBrowser.saveError = null;
|
|
998
|
+
state.tableDesigner.tables = [];
|
|
999
|
+
state.tableDesigner.selectedTableName = null;
|
|
1000
|
+
state.tableDesigner.draft = null;
|
|
1001
|
+
state.tableDesigner.pendingImportedDraft = null;
|
|
1002
|
+
state.tableDesigner.saving = false;
|
|
1003
|
+
state.tableDesigner.searchQuery = "";
|
|
1004
|
+
state.tableDesigner.supportedTypes = [];
|
|
1005
|
+
state.tableDesigner.error = null;
|
|
1006
|
+
state.tableDesigner.saveError = null;
|
|
535
1007
|
state.structure.data = null;
|
|
536
1008
|
state.structure.detail = null;
|
|
537
1009
|
}
|
|
@@ -561,11 +1033,14 @@ async function loadRouteData(route) {
|
|
|
561
1033
|
return;
|
|
562
1034
|
case "editor":
|
|
563
1035
|
case "editorResults":
|
|
564
|
-
await
|
|
1036
|
+
await refreshQueryHistoryState();
|
|
565
1037
|
return;
|
|
566
1038
|
case "structure":
|
|
567
1039
|
await loadStructure(version);
|
|
568
1040
|
return;
|
|
1041
|
+
case "tableDesigner":
|
|
1042
|
+
await loadTableDesigner(version, route);
|
|
1043
|
+
return;
|
|
569
1044
|
case "settings":
|
|
570
1045
|
await refreshSettingsState();
|
|
571
1046
|
return;
|
|
@@ -624,7 +1099,7 @@ export async function initializeApp() {
|
|
|
624
1099
|
await Promise.all([
|
|
625
1100
|
refreshConnectionsState(),
|
|
626
1101
|
refreshSettingsState(),
|
|
627
|
-
|
|
1102
|
+
refreshQueryHistoryState(),
|
|
628
1103
|
]);
|
|
629
1104
|
|
|
630
1105
|
state.ready = true;
|
|
@@ -882,6 +1357,17 @@ export async function createActiveConnectionBackup() {
|
|
|
882
1357
|
}
|
|
883
1358
|
}
|
|
884
1359
|
|
|
1360
|
+
export async function openOverviewInFinder() {
|
|
1361
|
+
try {
|
|
1362
|
+
const response = await api.openOverviewInFinder();
|
|
1363
|
+
pushToast(response.message || "Database file revealed in Finder.", "muted");
|
|
1364
|
+
return true;
|
|
1365
|
+
} catch (error) {
|
|
1366
|
+
pushToast(normalizeError(error)?.message || "Finder could not be opened.", "alert");
|
|
1367
|
+
return false;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
885
1371
|
export function setCurrentQuery(query) {
|
|
886
1372
|
const nextQuery = String(query ?? "");
|
|
887
1373
|
const previousLineCount = Math.max(1, String(state.editor.sqlText || "").split("\n").length);
|
|
@@ -896,12 +1382,22 @@ export function setCurrentQuery(query) {
|
|
|
896
1382
|
|
|
897
1383
|
export function clearCurrentQuery() {
|
|
898
1384
|
state.editor.sqlText = "";
|
|
1385
|
+
state.editor.result = null;
|
|
1386
|
+
resetEditorResultSort();
|
|
899
1387
|
state.editor.error = null;
|
|
1388
|
+
state.editor.selectedRowIndex = null;
|
|
1389
|
+
state.editor.saving = false;
|
|
1390
|
+
state.editor.deleting = false;
|
|
1391
|
+
state.editor.saveError = null;
|
|
1392
|
+
if (state.editor.activeTab === "results" || state.editor.activeTab === "performance") {
|
|
1393
|
+
state.editor.activeTab = "messages";
|
|
1394
|
+
}
|
|
900
1395
|
emitChange();
|
|
901
1396
|
}
|
|
902
1397
|
|
|
903
1398
|
export function clearEditorResults() {
|
|
904
1399
|
state.editor.result = null;
|
|
1400
|
+
resetEditorResultSort();
|
|
905
1401
|
state.editor.error = null;
|
|
906
1402
|
state.editor.selectedRowIndex = null;
|
|
907
1403
|
state.editor.saving = false;
|
|
@@ -928,10 +1424,11 @@ export async function executeCurrentQuery() {
|
|
|
928
1424
|
try {
|
|
929
1425
|
const response = await api.executeSql(state.editor.sqlText);
|
|
930
1426
|
state.editor.result = response.data;
|
|
1427
|
+
resetEditorResultSort();
|
|
931
1428
|
state.editor.error = null;
|
|
932
1429
|
state.editor.activeTab = "results";
|
|
933
1430
|
invalidateDatabaseCaches();
|
|
934
|
-
await
|
|
1431
|
+
await refreshQueryHistoryState();
|
|
935
1432
|
pushToast(
|
|
936
1433
|
response.message || `Executed ${response.data.statementCount} SQL statement(s).`,
|
|
937
1434
|
"success"
|
|
@@ -940,7 +1437,7 @@ export async function executeCurrentQuery() {
|
|
|
940
1437
|
} catch (error) {
|
|
941
1438
|
state.editor.error = normalizeError(error);
|
|
942
1439
|
state.editor.activeTab = "messages";
|
|
943
|
-
|
|
1440
|
+
await refreshQueryHistoryState();
|
|
944
1441
|
return false;
|
|
945
1442
|
} finally {
|
|
946
1443
|
state.editor.executing = false;
|
|
@@ -948,14 +1445,14 @@ export async function executeCurrentQuery() {
|
|
|
948
1445
|
}
|
|
949
1446
|
}
|
|
950
1447
|
|
|
951
|
-
export async function
|
|
1448
|
+
export async function clearQueryHistoryStateAndData() {
|
|
952
1449
|
state.editor.historyLoading = true;
|
|
953
1450
|
emitChange();
|
|
954
1451
|
|
|
955
1452
|
try {
|
|
956
|
-
const response = await api.
|
|
957
|
-
|
|
958
|
-
pushToast(response.message || "
|
|
1453
|
+
const response = await api.clearQueryHistory();
|
|
1454
|
+
resetQueryHistoryState({ preserveSearch: false });
|
|
1455
|
+
pushToast(response.message || "Query history cleared.", "muted");
|
|
959
1456
|
return true;
|
|
960
1457
|
} catch (error) {
|
|
961
1458
|
state.editor.historyError = normalizeError(error);
|
|
@@ -967,15 +1464,156 @@ export async function clearSqlHistoryStateAndData() {
|
|
|
967
1464
|
}
|
|
968
1465
|
}
|
|
969
1466
|
|
|
970
|
-
export function
|
|
971
|
-
const
|
|
1467
|
+
export async function setQueryHistoryTab(tab) {
|
|
1468
|
+
const normalizedTab = normalizeQueryHistoryTab(String(tab ?? "").trim().toLowerCase());
|
|
1469
|
+
|
|
1470
|
+
if (state.editor.historyTab === normalizedTab) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
state.editor.historyTab = normalizedTab;
|
|
1475
|
+
state.editor.historyActiveId = null;
|
|
1476
|
+
clearQueryHistoryDetailState();
|
|
1477
|
+
emitChange();
|
|
1478
|
+
await refreshQueryHistoryState();
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
export function setQueryHistorySearchInput(query) {
|
|
1482
|
+
state.editor.historySearchInput = String(query ?? "");
|
|
1483
|
+
emitChange();
|
|
1484
|
+
|
|
1485
|
+
clearQueryHistorySearchTimer();
|
|
1486
|
+
queryHistorySearchTimer = window.setTimeout(() => {
|
|
1487
|
+
state.editor.historySearch = state.editor.historySearchInput.trim();
|
|
1488
|
+
state.editor.historyActiveId = null;
|
|
1489
|
+
clearQueryHistoryDetailState();
|
|
1490
|
+
emitChange();
|
|
1491
|
+
void refreshQueryHistoryState();
|
|
1492
|
+
}, 180);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
export async function loadMoreQueryHistory() {
|
|
1496
|
+
if (
|
|
1497
|
+
state.editor.historyLoading ||
|
|
1498
|
+
state.editor.historyLoadingMore ||
|
|
1499
|
+
!state.editor.historyHasMore
|
|
1500
|
+
) {
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
await refreshQueryHistoryState({ append: true });
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
export async function selectQueryHistoryItem(historyId) {
|
|
1508
|
+
const normalizedId = setActiveQueryHistoryItem(historyId);
|
|
1509
|
+
|
|
1510
|
+
if (normalizedId === null) {
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
state.editor.historySelectedId = normalizedId;
|
|
1515
|
+
state.editor.historyDetail = null;
|
|
1516
|
+
state.editor.historyRuns = [];
|
|
1517
|
+
state.editor.historyDetailError = null;
|
|
1518
|
+
emitChange();
|
|
1519
|
+
await loadQueryHistoryDetail(normalizedId);
|
|
1520
|
+
}
|
|
972
1521
|
|
|
973
|
-
|
|
1522
|
+
export function clearQueryHistorySelection() {
|
|
1523
|
+
if (state.editor.historySelectedId === null && !state.editor.historyDetail) {
|
|
974
1524
|
return;
|
|
975
1525
|
}
|
|
976
1526
|
|
|
977
|
-
|
|
1527
|
+
clearQueryHistoryDetailState();
|
|
1528
|
+
emitChange();
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
export function openQueryHistoryInEditor(historyId, options = {}) {
|
|
1532
|
+
const rawSql = resolveQueryHistorySql(historyId);
|
|
1533
|
+
|
|
1534
|
+
if (!rawSql) {
|
|
1535
|
+
pushToast("The selected history query could not be loaded.", "alert");
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
setActiveQueryHistoryItem(historyId);
|
|
1540
|
+
clearQueryHistoryDetailState();
|
|
1541
|
+
state.editor.sqlText = options.append
|
|
1542
|
+
? [state.editor.sqlText.trim(), rawSql].filter(Boolean).join("\n\n")
|
|
1543
|
+
: rawSql;
|
|
978
1544
|
emitChange();
|
|
1545
|
+
return true;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
export async function runQueryHistoryItem(historyId) {
|
|
1549
|
+
const loaded = openQueryHistoryInEditor(historyId);
|
|
1550
|
+
|
|
1551
|
+
if (!loaded) {
|
|
1552
|
+
return false;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
return executeCurrentQuery();
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
export async function toggleQueryHistorySavedState(historyId, nextValue) {
|
|
1559
|
+
try {
|
|
1560
|
+
const response = await api.toggleQueryHistorySaved(historyId, nextValue);
|
|
1561
|
+
syncQueryHistoryItem(response.data);
|
|
1562
|
+
pushToast(response.message || "Query save state updated.", "muted");
|
|
1563
|
+
await refreshQueryHistoryState();
|
|
1564
|
+
return true;
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
state.editor.historyDetailError = normalizeError(error);
|
|
1567
|
+
emitChange();
|
|
1568
|
+
return false;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
export async function saveQueryHistoryTitle(historyId, title) {
|
|
1573
|
+
try {
|
|
1574
|
+
const response = await api.renameQueryHistoryItem(historyId, title);
|
|
1575
|
+
syncQueryHistoryItem(response.data);
|
|
1576
|
+
pushToast(response.message || "Query title updated.", "success");
|
|
1577
|
+
await loadQueryHistoryDetail(historyId);
|
|
1578
|
+
return true;
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
state.editor.historyDetailError = normalizeError(error);
|
|
1581
|
+
emitChange();
|
|
1582
|
+
return false;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
export async function saveQueryHistoryNotes(historyId, notes) {
|
|
1587
|
+
try {
|
|
1588
|
+
const response = await api.updateQueryHistoryNotes(historyId, notes);
|
|
1589
|
+
syncQueryHistoryItem(response.data);
|
|
1590
|
+
pushToast(response.message || "Query notes updated.", "success");
|
|
1591
|
+
await loadQueryHistoryDetail(historyId);
|
|
1592
|
+
return true;
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
state.editor.historyDetailError = normalizeError(error);
|
|
1595
|
+
emitChange();
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
export async function deleteQueryHistoryStateItem(historyId) {
|
|
1601
|
+
try {
|
|
1602
|
+
const response = await api.deleteQueryHistoryItem(historyId);
|
|
1603
|
+
if (state.editor.historyActiveId === Number(historyId)) {
|
|
1604
|
+
state.editor.historyActiveId = null;
|
|
1605
|
+
}
|
|
1606
|
+
if (state.editor.historySelectedId === Number(historyId)) {
|
|
1607
|
+
clearQueryHistorySelection();
|
|
1608
|
+
}
|
|
1609
|
+
pushToast(response.message || "Query history item deleted.", "muted");
|
|
1610
|
+
await refreshQueryHistoryState();
|
|
1611
|
+
return true;
|
|
1612
|
+
} catch (error) {
|
|
1613
|
+
state.editor.historyDetailError = normalizeError(error);
|
|
1614
|
+
emitChange();
|
|
1615
|
+
return false;
|
|
1616
|
+
}
|
|
979
1617
|
}
|
|
980
1618
|
|
|
981
1619
|
export async function selectStructureEntry(name) {
|
|
@@ -984,6 +1622,125 @@ export async function selectStructureEntry(name) {
|
|
|
984
1622
|
await loadStructureDetail(++routeLoadVersion);
|
|
985
1623
|
}
|
|
986
1624
|
|
|
1625
|
+
export function setTableDesignerSearchQuery(query) {
|
|
1626
|
+
state.tableDesigner.searchQuery = String(query ?? "");
|
|
1627
|
+
emitChange();
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
export function updateCurrentTableDesignerField(field, value) {
|
|
1631
|
+
if (!state.tableDesigner.draft) {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
state.tableDesigner.draft = updateTableDesignerDraftField(
|
|
1636
|
+
state.tableDesigner.draft,
|
|
1637
|
+
field,
|
|
1638
|
+
value,
|
|
1639
|
+
getTableDesignerContext()
|
|
1640
|
+
);
|
|
1641
|
+
state.tableDesigner.saveError = null;
|
|
1642
|
+
emitChange();
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
export function updateCurrentTableDesignerColumnField(columnId, field, value) {
|
|
1646
|
+
if (!state.tableDesigner.draft) {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
state.tableDesigner.draft = updateTableDesignerColumnField(
|
|
1651
|
+
state.tableDesigner.draft,
|
|
1652
|
+
columnId,
|
|
1653
|
+
field,
|
|
1654
|
+
value,
|
|
1655
|
+
getTableDesignerContext()
|
|
1656
|
+
);
|
|
1657
|
+
state.tableDesigner.saveError = null;
|
|
1658
|
+
emitChange();
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
export function addCurrentTableDesignerColumn() {
|
|
1662
|
+
if (!state.tableDesigner.draft) {
|
|
1663
|
+
return null;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
const previousColumnIds = new Set(state.tableDesigner.draft.columns.map((column) => column.id));
|
|
1667
|
+
state.tableDesigner.draft = addTableDesignerColumn(
|
|
1668
|
+
state.tableDesigner.draft,
|
|
1669
|
+
getTableDesignerContext()
|
|
1670
|
+
);
|
|
1671
|
+
state.tableDesigner.saveError = null;
|
|
1672
|
+
emitChange();
|
|
1673
|
+
|
|
1674
|
+
const nextColumn = state.tableDesigner.draft.columns.find(
|
|
1675
|
+
(column) => !previousColumnIds.has(column.id)
|
|
1676
|
+
);
|
|
1677
|
+
|
|
1678
|
+
return nextColumn?.id ?? null;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
export function queueTableDesignerCsvImport(fileName, csvText) {
|
|
1682
|
+
try {
|
|
1683
|
+
const imported = createTableDesignerDraftFromCsvImport(
|
|
1684
|
+
{ fileName, csvText },
|
|
1685
|
+
getTableDesignerContext()
|
|
1686
|
+
);
|
|
1687
|
+
|
|
1688
|
+
state.tableDesigner.pendingImportedDraft = imported.draft;
|
|
1689
|
+
state.tableDesigner.selectedTableName = null;
|
|
1690
|
+
state.tableDesigner.saveError = null;
|
|
1691
|
+
state.tableDesigner.error = null;
|
|
1692
|
+
emitChange();
|
|
1693
|
+
return imported;
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
pushToast(error?.message || "CSV import failed.", "alert");
|
|
1696
|
+
return null;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
export function removeCurrentTableDesignerColumn(columnId) {
|
|
1701
|
+
if (!state.tableDesigner.draft) {
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
state.tableDesigner.draft = removeTableDesignerColumn(
|
|
1706
|
+
state.tableDesigner.draft,
|
|
1707
|
+
columnId,
|
|
1708
|
+
getTableDesignerContext()
|
|
1709
|
+
);
|
|
1710
|
+
state.tableDesigner.saveError = null;
|
|
1711
|
+
emitChange();
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
export async function saveCurrentTableDesignerDraft() {
|
|
1715
|
+
if (!state.tableDesigner.draft) {
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
state.tableDesigner.saving = true;
|
|
1720
|
+
state.tableDesigner.saveError = null;
|
|
1721
|
+
emitChange();
|
|
1722
|
+
|
|
1723
|
+
try {
|
|
1724
|
+
const response = await api.saveTableDesignerDraft({
|
|
1725
|
+
draft: state.tableDesigner.draft,
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
state.tableDesigner.tables = response.data?.tables ?? state.tableDesigner.tables;
|
|
1729
|
+
state.tableDesigner.selectedTableName =
|
|
1730
|
+
response.data?.savedTableName ?? state.tableDesigner.selectedTableName;
|
|
1731
|
+
state.tableDesigner.draft = decorateTableDesignerDraft(response.data?.draft ?? null);
|
|
1732
|
+
pushToast(response.message || "Table schema saved.", "success");
|
|
1733
|
+
return response.data?.savedTableName ?? null;
|
|
1734
|
+
} catch (error) {
|
|
1735
|
+
state.tableDesigner.saveError = normalizeError(error);
|
|
1736
|
+
emitChange();
|
|
1737
|
+
return null;
|
|
1738
|
+
} finally {
|
|
1739
|
+
state.tableDesigner.saving = false;
|
|
1740
|
+
emitChange();
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
987
1744
|
export function selectDataRow(index) {
|
|
988
1745
|
const numericIndex = Number(index);
|
|
989
1746
|
|
|
@@ -1063,6 +1820,32 @@ export async function setDataPage(page) {
|
|
|
1063
1820
|
}
|
|
1064
1821
|
}
|
|
1065
1822
|
|
|
1823
|
+
export async function sortDataTableByColumn(columnName) {
|
|
1824
|
+
const normalizedColumn = String(columnName ?? "").trim();
|
|
1825
|
+
|
|
1826
|
+
if (
|
|
1827
|
+
!normalizedColumn ||
|
|
1828
|
+
!state.dataBrowser.table?.columns?.includes(normalizedColumn)
|
|
1829
|
+
) {
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
state.dataBrowser.sortDirection = getNextSortDirection(
|
|
1834
|
+
state.dataBrowser.sortColumn,
|
|
1835
|
+
state.dataBrowser.sortDirection,
|
|
1836
|
+
normalizedColumn
|
|
1837
|
+
);
|
|
1838
|
+
state.dataBrowser.sortColumn = normalizedColumn;
|
|
1839
|
+
state.dataBrowser.page = 1;
|
|
1840
|
+
state.dataBrowser.selectedRowIndex = null;
|
|
1841
|
+
state.dataBrowser.saveError = null;
|
|
1842
|
+
emitChange();
|
|
1843
|
+
|
|
1844
|
+
if (state.route.name === "data" && state.dataBrowser.selectedTable) {
|
|
1845
|
+
await loadDataTable(++routeLoadVersion);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1066
1849
|
export async function setDataPageSize(pageSize) {
|
|
1067
1850
|
const normalizedPageSize = normalizeDataPageSize(pageSize, state.dataBrowser.pageSize);
|
|
1068
1851
|
|
|
@@ -1190,7 +1973,11 @@ export async function submitEditorRowUpdate(rowIndex, values) {
|
|
|
1190
1973
|
);
|
|
1191
1974
|
state.editor.result = {
|
|
1192
1975
|
...result,
|
|
1193
|
-
rows:
|
|
1976
|
+
rows: sortEditorResultRows(
|
|
1977
|
+
nextRows,
|
|
1978
|
+
state.editor.resultSortColumn,
|
|
1979
|
+
state.editor.resultSortDirection
|
|
1980
|
+
),
|
|
1194
1981
|
};
|
|
1195
1982
|
state.editor.selectedRowIndex = null;
|
|
1196
1983
|
invalidateDatabaseCaches();
|
|
@@ -1304,7 +2091,10 @@ export async function exportCurrentDataTableCsv() {
|
|
|
1304
2091
|
emitChange();
|
|
1305
2092
|
|
|
1306
2093
|
try {
|
|
1307
|
-
await api.downloadTableCsv(tableName
|
|
2094
|
+
await api.downloadTableCsv(tableName, {
|
|
2095
|
+
sortColumn: state.dataBrowser.sortColumn,
|
|
2096
|
+
sortDirection: state.dataBrowser.sortDirection,
|
|
2097
|
+
});
|
|
1308
2098
|
pushToast(`CSV export started for ${tableName}.`, "success");
|
|
1309
2099
|
return true;
|
|
1310
2100
|
} catch (error) {
|
|
@@ -1317,10 +2107,39 @@ export async function exportCurrentDataTableCsv() {
|
|
|
1317
2107
|
}
|
|
1318
2108
|
}
|
|
1319
2109
|
|
|
2110
|
+
export function sortEditorResultsByColumn(columnName) {
|
|
2111
|
+
const normalizedColumn = String(columnName ?? "").trim();
|
|
2112
|
+
const result = state.editor.result;
|
|
2113
|
+
|
|
2114
|
+
if (!normalizedColumn || !result?.columns?.includes(normalizedColumn)) {
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
const nextDirection = getNextSortDirection(
|
|
2119
|
+
state.editor.resultSortColumn,
|
|
2120
|
+
state.editor.resultSortDirection,
|
|
2121
|
+
normalizedColumn
|
|
2122
|
+
);
|
|
2123
|
+
|
|
2124
|
+
state.editor.result = {
|
|
2125
|
+
...result,
|
|
2126
|
+
rows: sortEditorResultRows(result.rows ?? [], normalizedColumn, nextDirection),
|
|
2127
|
+
};
|
|
2128
|
+
state.editor.resultSortColumn = normalizedColumn;
|
|
2129
|
+
state.editor.resultSortDirection = nextDirection;
|
|
2130
|
+
state.editor.selectedRowIndex = null;
|
|
2131
|
+
state.editor.saveError = null;
|
|
2132
|
+
emitChange();
|
|
2133
|
+
}
|
|
2134
|
+
|
|
1320
2135
|
export async function refreshCurrentRoute() {
|
|
1321
2136
|
await loadRouteData(state.route);
|
|
1322
2137
|
}
|
|
1323
2138
|
|
|
2139
|
+
export function showToast(message, tone = "muted") {
|
|
2140
|
+
pushToast(message, tone);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
1324
2143
|
export function getCurrentConnection(snapshot = state) {
|
|
1325
2144
|
return snapshot.connections.active;
|
|
1326
2145
|
}
|