sqlite-hub 0.4.0 → 0.5.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 +7 -0
- package/frontend/js/api.js +99 -5
- package/frontend/js/app.js +90 -11
- 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/store.js +545 -21
- package/frontend/js/utils/format.js +23 -0
- package/frontend/js/views/data.js +34 -1
- package/frontend/js/views/editor.js +34 -10
- package/frontend/js/views/overview.js +15 -0
- package/frontend/styles/components.css +106 -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/server.js +1 -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/tableSort.js +63 -0
- package/server/services/storage/appStateStore.js +674 -1
- package/server/services/storage/queryHistoryUtils.js +169 -0
package/frontend/js/store.js
CHANGED
|
@@ -8,12 +8,17 @@ const DEFAULT_SETTINGS = {
|
|
|
8
8
|
csvDelimiter: ",",
|
|
9
9
|
};
|
|
10
10
|
const DATA_PAGE_SIZES = [25, 50, 100];
|
|
11
|
+
const QUERY_HISTORY_PAGE_SIZE = 30;
|
|
12
|
+
const QUERY_HISTORY_RUN_LIMIT = 8;
|
|
11
13
|
const MISSING_DATABASE_ERROR = {
|
|
12
14
|
code: "ACTIVE_DATABASE_REQUIRED",
|
|
13
15
|
message: "No active SQLite database selected.",
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
let routeLoadVersion = 0;
|
|
19
|
+
let queryHistoryLoadVersion = 0;
|
|
20
|
+
let queryHistoryDetailLoadVersion = 0;
|
|
21
|
+
let queryHistorySearchTimer = null;
|
|
17
22
|
|
|
18
23
|
const state = {
|
|
19
24
|
ready: false,
|
|
@@ -48,6 +53,8 @@ const state = {
|
|
|
48
53
|
deleting: false,
|
|
49
54
|
page: 1,
|
|
50
55
|
pageSize: 50,
|
|
56
|
+
sortColumn: null,
|
|
57
|
+
sortDirection: null,
|
|
51
58
|
searchQuery: "",
|
|
52
59
|
searchColumn: "",
|
|
53
60
|
selectedRowIndex: null,
|
|
@@ -59,10 +66,25 @@ const state = {
|
|
|
59
66
|
sqlText: "",
|
|
60
67
|
history: [],
|
|
61
68
|
historyLoading: false,
|
|
69
|
+
historyLoadingMore: false,
|
|
62
70
|
historyError: null,
|
|
71
|
+
historyTab: "recent",
|
|
72
|
+
historySearchInput: "",
|
|
73
|
+
historySearch: "",
|
|
74
|
+
historyPageSize: QUERY_HISTORY_PAGE_SIZE,
|
|
75
|
+
historyTotal: 0,
|
|
76
|
+
historyHasMore: false,
|
|
77
|
+
historyActiveId: null,
|
|
78
|
+
historySelectedId: null,
|
|
79
|
+
historyDetail: null,
|
|
80
|
+
historyRuns: [],
|
|
81
|
+
historyDetailLoading: false,
|
|
82
|
+
historyDetailError: null,
|
|
63
83
|
activeTab: "messages",
|
|
64
84
|
executing: false,
|
|
65
85
|
result: null,
|
|
86
|
+
resultSortColumn: null,
|
|
87
|
+
resultSortDirection: null,
|
|
66
88
|
error: null,
|
|
67
89
|
exportLoading: false,
|
|
68
90
|
selectedRowIndex: null,
|
|
@@ -102,6 +124,26 @@ function normalizeError(error) {
|
|
|
102
124
|
};
|
|
103
125
|
}
|
|
104
126
|
|
|
127
|
+
function setActiveQueryHistoryItem(historyId) {
|
|
128
|
+
const normalizedId = Number(historyId);
|
|
129
|
+
|
|
130
|
+
if (!Number.isInteger(normalizedId) || normalizedId < 1) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
state.editor.historyActiveId = normalizedId;
|
|
135
|
+
return normalizedId;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function clearQueryHistoryDetailState() {
|
|
139
|
+
queryHistoryDetailLoadVersion += 1;
|
|
140
|
+
state.editor.historySelectedId = null;
|
|
141
|
+
state.editor.historyDetail = null;
|
|
142
|
+
state.editor.historyRuns = [];
|
|
143
|
+
state.editor.historyDetailLoading = false;
|
|
144
|
+
state.editor.historyDetailError = null;
|
|
145
|
+
}
|
|
146
|
+
|
|
105
147
|
function requiresActiveDatabase(routeName) {
|
|
106
148
|
return ["overview", "data", "editor", "editorResults", "structure"].includes(routeName);
|
|
107
149
|
}
|
|
@@ -116,6 +158,18 @@ function normalizeDataPageSize(value, fallback = 50) {
|
|
|
116
158
|
return fallback;
|
|
117
159
|
}
|
|
118
160
|
|
|
161
|
+
function normalizeSortDirection(value) {
|
|
162
|
+
return String(value ?? "").trim().toLowerCase() === "desc" ? "desc" : "asc";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getNextSortDirection(currentColumn, currentDirection, nextColumn) {
|
|
166
|
+
if (currentColumn === nextColumn) {
|
|
167
|
+
return normalizeSortDirection(currentDirection) === "asc" ? "desc" : "asc";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return "asc";
|
|
171
|
+
}
|
|
172
|
+
|
|
119
173
|
function canEditQueryResult(snapshot = state) {
|
|
120
174
|
return Boolean(snapshot.editor.result?.editing?.enabled) && !snapshot.connections.active?.readOnly;
|
|
121
175
|
}
|
|
@@ -125,6 +179,73 @@ function resetDataBrowserSearch() {
|
|
|
125
179
|
state.dataBrowser.searchColumn = "";
|
|
126
180
|
}
|
|
127
181
|
|
|
182
|
+
function resetDataBrowserSort() {
|
|
183
|
+
state.dataBrowser.sortColumn = null;
|
|
184
|
+
state.dataBrowser.sortDirection = null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function resetEditorResultSort() {
|
|
188
|
+
state.editor.resultSortColumn = null;
|
|
189
|
+
state.editor.resultSortDirection = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getSortableEditorValue(value) {
|
|
193
|
+
if (value === null || value === undefined) {
|
|
194
|
+
return { rank: 0, value: "" };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (typeof value === "number") {
|
|
198
|
+
return { rank: 1, value };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (typeof value === "boolean") {
|
|
202
|
+
return { rank: 2, value: value ? 1 : 0 };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (typeof value === "string") {
|
|
206
|
+
return { rank: 3, value };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (value && typeof value === "object" && value.__type === "blob") {
|
|
210
|
+
return {
|
|
211
|
+
rank: 4,
|
|
212
|
+
value: `${value.sizeBytes ?? 0}:${value.hexPreview ?? ""}`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { rank: 5, value: JSON.stringify(value) };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function compareEditorValues(left, right) {
|
|
220
|
+
const leftValue = getSortableEditorValue(left);
|
|
221
|
+
const rightValue = getSortableEditorValue(right);
|
|
222
|
+
|
|
223
|
+
if (leftValue.rank !== rightValue.rank) {
|
|
224
|
+
return leftValue.rank - rightValue.rank;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (typeof leftValue.value === "number" && typeof rightValue.value === "number") {
|
|
228
|
+
return leftValue.value - rightValue.value;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return String(leftValue.value).localeCompare(String(rightValue.value), undefined, {
|
|
232
|
+
numeric: true,
|
|
233
|
+
sensitivity: "base",
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function sortEditorResultRows(rows, sortColumn, sortDirection) {
|
|
238
|
+
if (!sortColumn) {
|
|
239
|
+
return [...(rows ?? [])];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const directionMultiplier = normalizeSortDirection(sortDirection) === "desc" ? -1 : 1;
|
|
243
|
+
|
|
244
|
+
return [...(rows ?? [])].sort(
|
|
245
|
+
(left, right) => compareEditorValues(left?.[sortColumn], right?.[sortColumn]) * directionMultiplier
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
128
249
|
function buildUpdatedEditorResultRow(existingRow, updatedSourceRow, editableColumns) {
|
|
129
250
|
const nextRow = {
|
|
130
251
|
...existingRow,
|
|
@@ -175,6 +296,64 @@ function buildFallbackDeleteRowPreview(row) {
|
|
|
175
296
|
);
|
|
176
297
|
}
|
|
177
298
|
|
|
299
|
+
function normalizeQueryHistoryTab(value) {
|
|
300
|
+
return ["recent", "saved", "failed"].includes(value) ? value : "recent";
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function findQueryHistoryItem(historyId, snapshot = state) {
|
|
304
|
+
return snapshot.editor.history.find((entry) => String(entry.id) === String(historyId)) ?? null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function clearQueryHistorySearchTimer() {
|
|
308
|
+
if (!queryHistorySearchTimer) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
window.clearTimeout(queryHistorySearchTimer);
|
|
313
|
+
queryHistorySearchTimer = null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function resetQueryHistoryState({ preserveSearch = true } = {}) {
|
|
317
|
+
state.editor.history = [];
|
|
318
|
+
state.editor.historyLoading = false;
|
|
319
|
+
state.editor.historyLoadingMore = false;
|
|
320
|
+
state.editor.historyError = null;
|
|
321
|
+
state.editor.historyTotal = 0;
|
|
322
|
+
state.editor.historyHasMore = false;
|
|
323
|
+
state.editor.historyActiveId = null;
|
|
324
|
+
clearQueryHistoryDetailState();
|
|
325
|
+
|
|
326
|
+
if (!preserveSearch) {
|
|
327
|
+
clearQueryHistorySearchTimer();
|
|
328
|
+
state.editor.historySearchInput = "";
|
|
329
|
+
state.editor.historySearch = "";
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function syncQueryHistoryItem(updatedItem) {
|
|
334
|
+
if (!updatedItem) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
state.editor.history = state.editor.history.map((entry) =>
|
|
339
|
+
entry.id === updatedItem.id ? updatedItem : entry
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
if (state.editor.historyDetail?.id === updatedItem.id) {
|
|
343
|
+
state.editor.historyDetail = updatedItem;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function resolveQueryHistorySql(historyId) {
|
|
348
|
+
const historyIdAsString = String(historyId);
|
|
349
|
+
|
|
350
|
+
if (String(state.editor.historyDetail?.id ?? "") === historyIdAsString) {
|
|
351
|
+
return state.editor.historyDetail.rawSql;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return findQueryHistoryItem(historyId)?.rawSql ?? null;
|
|
355
|
+
}
|
|
356
|
+
|
|
178
357
|
function clearRouteSlices() {
|
|
179
358
|
state.overview.error = null;
|
|
180
359
|
state.dataBrowser.error = null;
|
|
@@ -195,6 +374,7 @@ function setMissingDatabaseState() {
|
|
|
195
374
|
state.dataBrowser.selectedTable = null;
|
|
196
375
|
state.dataBrowser.table = null;
|
|
197
376
|
state.dataBrowser.page = 1;
|
|
377
|
+
resetDataBrowserSort();
|
|
198
378
|
resetDataBrowserSearch();
|
|
199
379
|
state.dataBrowser.selectedRowIndex = null;
|
|
200
380
|
state.dataBrowser.exportLoading = false;
|
|
@@ -206,6 +386,8 @@ function setMissingDatabaseState() {
|
|
|
206
386
|
state.structure.data = null;
|
|
207
387
|
state.structure.detail = null;
|
|
208
388
|
state.structure.error = error;
|
|
389
|
+
|
|
390
|
+
resetQueryHistoryState({ preserveSearch: false });
|
|
209
391
|
}
|
|
210
392
|
|
|
211
393
|
function syncRouteContext() {
|
|
@@ -213,6 +395,7 @@ function syncRouteContext() {
|
|
|
213
395
|
|
|
214
396
|
if (route.name === "editorResults") {
|
|
215
397
|
state.editor.activeTab = "results";
|
|
398
|
+
clearQueryHistoryDetailState();
|
|
216
399
|
} else if (route.name === "editor" && state.editor.activeTab === "results") {
|
|
217
400
|
state.editor.activeTab = "messages";
|
|
218
401
|
}
|
|
@@ -284,19 +467,131 @@ async function refreshSettingsState() {
|
|
|
284
467
|
}
|
|
285
468
|
}
|
|
286
469
|
|
|
287
|
-
async function
|
|
288
|
-
|
|
289
|
-
|
|
470
|
+
async function loadQueryHistoryDetail(historyId) {
|
|
471
|
+
const normalizedId = String(historyId ?? "").trim();
|
|
472
|
+
const numericId = Number(normalizedId);
|
|
473
|
+
const requestVersion = ++queryHistoryDetailLoadVersion;
|
|
474
|
+
|
|
475
|
+
if (!normalizedId) {
|
|
476
|
+
clearQueryHistoryDetailState();
|
|
477
|
+
emitChange();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
state.editor.historyDetailLoading = true;
|
|
482
|
+
state.editor.historyDetailError = null;
|
|
290
483
|
emitChange();
|
|
291
484
|
|
|
292
485
|
try {
|
|
293
|
-
const
|
|
294
|
-
|
|
486
|
+
const [detailResponse, runsResponse] = await Promise.all([
|
|
487
|
+
api.getQueryHistoryItem(normalizedId),
|
|
488
|
+
api.getQueryHistoryRuns(normalizedId, { limit: QUERY_HISTORY_RUN_LIMIT }),
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
if (
|
|
492
|
+
requestVersion !== queryHistoryDetailLoadVersion ||
|
|
493
|
+
state.editor.historySelectedId !== numericId
|
|
494
|
+
) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
state.editor.historyDetail = detailResponse.data ?? null;
|
|
499
|
+
state.editor.historyRuns = runsResponse.data ?? [];
|
|
500
|
+
state.editor.historyDetailError = null;
|
|
501
|
+
if (detailResponse.data) {
|
|
502
|
+
syncQueryHistoryItem(detailResponse.data);
|
|
503
|
+
}
|
|
295
504
|
} catch (error) {
|
|
296
|
-
|
|
505
|
+
if (
|
|
506
|
+
requestVersion !== queryHistoryDetailLoadVersion ||
|
|
507
|
+
state.editor.historySelectedId !== numericId
|
|
508
|
+
) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
state.editor.historyDetail = null;
|
|
513
|
+
state.editor.historyRuns = [];
|
|
514
|
+
state.editor.historyDetailError = normalizeError(error);
|
|
297
515
|
} finally {
|
|
298
|
-
|
|
516
|
+
if (requestVersion === queryHistoryDetailLoadVersion) {
|
|
517
|
+
state.editor.historyDetailLoading = false;
|
|
518
|
+
emitChange();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function refreshQueryHistoryState({ append = false } = {}) {
|
|
524
|
+
if (!state.connections.active) {
|
|
525
|
+
resetQueryHistoryState({ preserveSearch: false });
|
|
299
526
|
emitChange();
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const requestVersion = ++queryHistoryLoadVersion;
|
|
531
|
+
const nextOffset = append ? state.editor.history.length : 0;
|
|
532
|
+
|
|
533
|
+
if (append) {
|
|
534
|
+
state.editor.historyLoadingMore = true;
|
|
535
|
+
} else {
|
|
536
|
+
state.editor.historyLoading = true;
|
|
537
|
+
state.editor.historyError = null;
|
|
538
|
+
}
|
|
539
|
+
emitChange();
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
const response = await api.getQueryHistory({
|
|
543
|
+
tab: state.editor.historyTab,
|
|
544
|
+
limit: state.editor.historyPageSize,
|
|
545
|
+
offset: nextOffset,
|
|
546
|
+
search: state.editor.historySearch,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
if (requestVersion !== queryHistoryLoadVersion) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const payload = response.data ?? {};
|
|
554
|
+
const items = payload.items ?? [];
|
|
555
|
+
state.editor.history = append ? [...state.editor.history, ...items] : items;
|
|
556
|
+
state.editor.historyTotal = payload.total ?? state.editor.history.length;
|
|
557
|
+
state.editor.historyHasMore = Boolean(payload.hasMore);
|
|
558
|
+
state.editor.historyError = null;
|
|
559
|
+
|
|
560
|
+
if (
|
|
561
|
+
state.editor.historyActiveId &&
|
|
562
|
+
!state.editor.history.some((entry) => entry.id === state.editor.historyActiveId)
|
|
563
|
+
) {
|
|
564
|
+
state.editor.historyActiveId = null;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (
|
|
568
|
+
state.editor.historySelectedId &&
|
|
569
|
+
!state.editor.history.some((entry) => entry.id === state.editor.historySelectedId)
|
|
570
|
+
) {
|
|
571
|
+
clearQueryHistoryDetailState();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (
|
|
575
|
+
state.editor.historySelectedId &&
|
|
576
|
+
!state.editor.historyDetailLoading &&
|
|
577
|
+
state.editor.historyDetail?.id !== state.editor.historySelectedId
|
|
578
|
+
) {
|
|
579
|
+
await loadQueryHistoryDetail(state.editor.historySelectedId);
|
|
580
|
+
} else {
|
|
581
|
+
emitChange();
|
|
582
|
+
}
|
|
583
|
+
} catch (error) {
|
|
584
|
+
if (requestVersion !== queryHistoryLoadVersion) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
state.editor.historyError = normalizeError(error);
|
|
589
|
+
} finally {
|
|
590
|
+
if (requestVersion === queryHistoryLoadVersion) {
|
|
591
|
+
state.editor.historyLoading = false;
|
|
592
|
+
state.editor.historyLoadingMore = false;
|
|
593
|
+
emitChange();
|
|
594
|
+
}
|
|
300
595
|
}
|
|
301
596
|
}
|
|
302
597
|
|
|
@@ -333,6 +628,8 @@ async function loadDataTable(version) {
|
|
|
333
628
|
const tableName = state.dataBrowser.selectedTable;
|
|
334
629
|
const pageSize = normalizeDataPageSize(state.dataBrowser.pageSize, 50);
|
|
335
630
|
const page = Math.max(1, Number(state.dataBrowser.page) || 1);
|
|
631
|
+
const sortColumn = state.dataBrowser.sortColumn;
|
|
632
|
+
const sortDirection = normalizeSortDirection(state.dataBrowser.sortDirection);
|
|
336
633
|
|
|
337
634
|
if (!tableName) {
|
|
338
635
|
state.dataBrowser.table = null;
|
|
@@ -348,6 +645,8 @@ async function loadDataTable(version) {
|
|
|
348
645
|
const response = await api.getDataTable(tableName, {
|
|
349
646
|
limit: pageSize,
|
|
350
647
|
offset: (page - 1) * pageSize,
|
|
648
|
+
sortColumn,
|
|
649
|
+
sortDirection,
|
|
351
650
|
});
|
|
352
651
|
|
|
353
652
|
if (version !== routeLoadVersion) {
|
|
@@ -357,6 +656,8 @@ async function loadDataTable(version) {
|
|
|
357
656
|
state.dataBrowser.table = response.data ?? null;
|
|
358
657
|
state.dataBrowser.pageSize = pageSize;
|
|
359
658
|
state.dataBrowser.page = response.data?.page ?? page;
|
|
659
|
+
state.dataBrowser.sortColumn = response.data?.sort?.column ?? null;
|
|
660
|
+
state.dataBrowser.sortDirection = response.data?.sort?.direction ?? null;
|
|
360
661
|
state.dataBrowser.searchColumn = response.data?.columns?.includes(state.dataBrowser.searchColumn)
|
|
361
662
|
? state.dataBrowser.searchColumn
|
|
362
663
|
: (response.data?.columns?.[0] ?? "");
|
|
@@ -397,6 +698,7 @@ async function loadData(version, route) {
|
|
|
397
698
|
if (requestedTableName && tables.some((table) => table.name === requestedTableName)) {
|
|
398
699
|
if (requestedTableName !== state.dataBrowser.selectedTable) {
|
|
399
700
|
state.dataBrowser.page = 1;
|
|
701
|
+
resetDataBrowserSort();
|
|
400
702
|
resetDataBrowserSearch();
|
|
401
703
|
}
|
|
402
704
|
state.dataBrowser.selectedTable = requestedTableName;
|
|
@@ -406,6 +708,7 @@ async function loadData(version, route) {
|
|
|
406
708
|
) {
|
|
407
709
|
state.dataBrowser.selectedTable = tables[0]?.name ?? null;
|
|
408
710
|
state.dataBrowser.page = 1;
|
|
711
|
+
resetDataBrowserSort();
|
|
409
712
|
resetDataBrowserSearch();
|
|
410
713
|
}
|
|
411
714
|
|
|
@@ -561,7 +864,7 @@ async function loadRouteData(route) {
|
|
|
561
864
|
return;
|
|
562
865
|
case "editor":
|
|
563
866
|
case "editorResults":
|
|
564
|
-
await
|
|
867
|
+
await refreshQueryHistoryState();
|
|
565
868
|
return;
|
|
566
869
|
case "structure":
|
|
567
870
|
await loadStructure(version);
|
|
@@ -624,7 +927,7 @@ export async function initializeApp() {
|
|
|
624
927
|
await Promise.all([
|
|
625
928
|
refreshConnectionsState(),
|
|
626
929
|
refreshSettingsState(),
|
|
627
|
-
|
|
930
|
+
refreshQueryHistoryState(),
|
|
628
931
|
]);
|
|
629
932
|
|
|
630
933
|
state.ready = true;
|
|
@@ -882,6 +1185,17 @@ export async function createActiveConnectionBackup() {
|
|
|
882
1185
|
}
|
|
883
1186
|
}
|
|
884
1187
|
|
|
1188
|
+
export async function openOverviewInFinder() {
|
|
1189
|
+
try {
|
|
1190
|
+
const response = await api.openOverviewInFinder();
|
|
1191
|
+
pushToast(response.message || "Database file revealed in Finder.", "muted");
|
|
1192
|
+
return true;
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
pushToast(normalizeError(error)?.message || "Finder could not be opened.", "alert");
|
|
1195
|
+
return false;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
885
1199
|
export function setCurrentQuery(query) {
|
|
886
1200
|
const nextQuery = String(query ?? "");
|
|
887
1201
|
const previousLineCount = Math.max(1, String(state.editor.sqlText || "").split("\n").length);
|
|
@@ -896,12 +1210,22 @@ export function setCurrentQuery(query) {
|
|
|
896
1210
|
|
|
897
1211
|
export function clearCurrentQuery() {
|
|
898
1212
|
state.editor.sqlText = "";
|
|
1213
|
+
state.editor.result = null;
|
|
1214
|
+
resetEditorResultSort();
|
|
899
1215
|
state.editor.error = null;
|
|
1216
|
+
state.editor.selectedRowIndex = null;
|
|
1217
|
+
state.editor.saving = false;
|
|
1218
|
+
state.editor.deleting = false;
|
|
1219
|
+
state.editor.saveError = null;
|
|
1220
|
+
if (state.editor.activeTab === "results" || state.editor.activeTab === "performance") {
|
|
1221
|
+
state.editor.activeTab = "messages";
|
|
1222
|
+
}
|
|
900
1223
|
emitChange();
|
|
901
1224
|
}
|
|
902
1225
|
|
|
903
1226
|
export function clearEditorResults() {
|
|
904
1227
|
state.editor.result = null;
|
|
1228
|
+
resetEditorResultSort();
|
|
905
1229
|
state.editor.error = null;
|
|
906
1230
|
state.editor.selectedRowIndex = null;
|
|
907
1231
|
state.editor.saving = false;
|
|
@@ -928,10 +1252,11 @@ export async function executeCurrentQuery() {
|
|
|
928
1252
|
try {
|
|
929
1253
|
const response = await api.executeSql(state.editor.sqlText);
|
|
930
1254
|
state.editor.result = response.data;
|
|
1255
|
+
resetEditorResultSort();
|
|
931
1256
|
state.editor.error = null;
|
|
932
1257
|
state.editor.activeTab = "results";
|
|
933
1258
|
invalidateDatabaseCaches();
|
|
934
|
-
await
|
|
1259
|
+
await refreshQueryHistoryState();
|
|
935
1260
|
pushToast(
|
|
936
1261
|
response.message || `Executed ${response.data.statementCount} SQL statement(s).`,
|
|
937
1262
|
"success"
|
|
@@ -940,7 +1265,7 @@ export async function executeCurrentQuery() {
|
|
|
940
1265
|
} catch (error) {
|
|
941
1266
|
state.editor.error = normalizeError(error);
|
|
942
1267
|
state.editor.activeTab = "messages";
|
|
943
|
-
|
|
1268
|
+
await refreshQueryHistoryState();
|
|
944
1269
|
return false;
|
|
945
1270
|
} finally {
|
|
946
1271
|
state.editor.executing = false;
|
|
@@ -948,14 +1273,14 @@ export async function executeCurrentQuery() {
|
|
|
948
1273
|
}
|
|
949
1274
|
}
|
|
950
1275
|
|
|
951
|
-
export async function
|
|
1276
|
+
export async function clearQueryHistoryStateAndData() {
|
|
952
1277
|
state.editor.historyLoading = true;
|
|
953
1278
|
emitChange();
|
|
954
1279
|
|
|
955
1280
|
try {
|
|
956
|
-
const response = await api.
|
|
957
|
-
|
|
958
|
-
pushToast(response.message || "
|
|
1281
|
+
const response = await api.clearQueryHistory();
|
|
1282
|
+
resetQueryHistoryState({ preserveSearch: false });
|
|
1283
|
+
pushToast(response.message || "Query history cleared.", "muted");
|
|
959
1284
|
return true;
|
|
960
1285
|
} catch (error) {
|
|
961
1286
|
state.editor.historyError = normalizeError(error);
|
|
@@ -967,15 +1292,156 @@ export async function clearSqlHistoryStateAndData() {
|
|
|
967
1292
|
}
|
|
968
1293
|
}
|
|
969
1294
|
|
|
970
|
-
export function
|
|
971
|
-
const
|
|
1295
|
+
export async function setQueryHistoryTab(tab) {
|
|
1296
|
+
const normalizedTab = normalizeQueryHistoryTab(String(tab ?? "").trim().toLowerCase());
|
|
972
1297
|
|
|
973
|
-
if (
|
|
1298
|
+
if (state.editor.historyTab === normalizedTab) {
|
|
974
1299
|
return;
|
|
975
1300
|
}
|
|
976
1301
|
|
|
977
|
-
state.editor.
|
|
1302
|
+
state.editor.historyTab = normalizedTab;
|
|
1303
|
+
state.editor.historyActiveId = null;
|
|
1304
|
+
clearQueryHistoryDetailState();
|
|
1305
|
+
emitChange();
|
|
1306
|
+
await refreshQueryHistoryState();
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
export function setQueryHistorySearchInput(query) {
|
|
1310
|
+
state.editor.historySearchInput = String(query ?? "");
|
|
978
1311
|
emitChange();
|
|
1312
|
+
|
|
1313
|
+
clearQueryHistorySearchTimer();
|
|
1314
|
+
queryHistorySearchTimer = window.setTimeout(() => {
|
|
1315
|
+
state.editor.historySearch = state.editor.historySearchInput.trim();
|
|
1316
|
+
state.editor.historyActiveId = null;
|
|
1317
|
+
clearQueryHistoryDetailState();
|
|
1318
|
+
emitChange();
|
|
1319
|
+
void refreshQueryHistoryState();
|
|
1320
|
+
}, 180);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
export async function loadMoreQueryHistory() {
|
|
1324
|
+
if (
|
|
1325
|
+
state.editor.historyLoading ||
|
|
1326
|
+
state.editor.historyLoadingMore ||
|
|
1327
|
+
!state.editor.historyHasMore
|
|
1328
|
+
) {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
await refreshQueryHistoryState({ append: true });
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
export async function selectQueryHistoryItem(historyId) {
|
|
1336
|
+
const normalizedId = setActiveQueryHistoryItem(historyId);
|
|
1337
|
+
|
|
1338
|
+
if (normalizedId === null) {
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
state.editor.historySelectedId = normalizedId;
|
|
1343
|
+
state.editor.historyDetail = null;
|
|
1344
|
+
state.editor.historyRuns = [];
|
|
1345
|
+
state.editor.historyDetailError = null;
|
|
1346
|
+
emitChange();
|
|
1347
|
+
await loadQueryHistoryDetail(normalizedId);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
export function clearQueryHistorySelection() {
|
|
1351
|
+
if (state.editor.historySelectedId === null && !state.editor.historyDetail) {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
clearQueryHistoryDetailState();
|
|
1356
|
+
emitChange();
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
export function openQueryHistoryInEditor(historyId, options = {}) {
|
|
1360
|
+
const rawSql = resolveQueryHistorySql(historyId);
|
|
1361
|
+
|
|
1362
|
+
if (!rawSql) {
|
|
1363
|
+
pushToast("The selected history query could not be loaded.", "alert");
|
|
1364
|
+
return false;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
setActiveQueryHistoryItem(historyId);
|
|
1368
|
+
clearQueryHistoryDetailState();
|
|
1369
|
+
state.editor.sqlText = options.append
|
|
1370
|
+
? [state.editor.sqlText.trim(), rawSql].filter(Boolean).join("\n\n")
|
|
1371
|
+
: rawSql;
|
|
1372
|
+
emitChange();
|
|
1373
|
+
return true;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
export async function runQueryHistoryItem(historyId) {
|
|
1377
|
+
const loaded = openQueryHistoryInEditor(historyId);
|
|
1378
|
+
|
|
1379
|
+
if (!loaded) {
|
|
1380
|
+
return false;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
return executeCurrentQuery();
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
export async function toggleQueryHistorySavedState(historyId, nextValue) {
|
|
1387
|
+
try {
|
|
1388
|
+
const response = await api.toggleQueryHistorySaved(historyId, nextValue);
|
|
1389
|
+
syncQueryHistoryItem(response.data);
|
|
1390
|
+
pushToast(response.message || "Query save state updated.", "muted");
|
|
1391
|
+
await refreshQueryHistoryState();
|
|
1392
|
+
return true;
|
|
1393
|
+
} catch (error) {
|
|
1394
|
+
state.editor.historyDetailError = normalizeError(error);
|
|
1395
|
+
emitChange();
|
|
1396
|
+
return false;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
export async function saveQueryHistoryTitle(historyId, title) {
|
|
1401
|
+
try {
|
|
1402
|
+
const response = await api.renameQueryHistoryItem(historyId, title);
|
|
1403
|
+
syncQueryHistoryItem(response.data);
|
|
1404
|
+
pushToast(response.message || "Query title updated.", "success");
|
|
1405
|
+
await loadQueryHistoryDetail(historyId);
|
|
1406
|
+
return true;
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
state.editor.historyDetailError = normalizeError(error);
|
|
1409
|
+
emitChange();
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
export async function saveQueryHistoryNotes(historyId, notes) {
|
|
1415
|
+
try {
|
|
1416
|
+
const response = await api.updateQueryHistoryNotes(historyId, notes);
|
|
1417
|
+
syncQueryHistoryItem(response.data);
|
|
1418
|
+
pushToast(response.message || "Query notes updated.", "success");
|
|
1419
|
+
await loadQueryHistoryDetail(historyId);
|
|
1420
|
+
return true;
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
state.editor.historyDetailError = normalizeError(error);
|
|
1423
|
+
emitChange();
|
|
1424
|
+
return false;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
export async function deleteQueryHistoryStateItem(historyId) {
|
|
1429
|
+
try {
|
|
1430
|
+
const response = await api.deleteQueryHistoryItem(historyId);
|
|
1431
|
+
if (state.editor.historyActiveId === Number(historyId)) {
|
|
1432
|
+
state.editor.historyActiveId = null;
|
|
1433
|
+
}
|
|
1434
|
+
if (state.editor.historySelectedId === Number(historyId)) {
|
|
1435
|
+
clearQueryHistorySelection();
|
|
1436
|
+
}
|
|
1437
|
+
pushToast(response.message || "Query history item deleted.", "muted");
|
|
1438
|
+
await refreshQueryHistoryState();
|
|
1439
|
+
return true;
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
state.editor.historyDetailError = normalizeError(error);
|
|
1442
|
+
emitChange();
|
|
1443
|
+
return false;
|
|
1444
|
+
}
|
|
979
1445
|
}
|
|
980
1446
|
|
|
981
1447
|
export async function selectStructureEntry(name) {
|
|
@@ -1063,6 +1529,32 @@ export async function setDataPage(page) {
|
|
|
1063
1529
|
}
|
|
1064
1530
|
}
|
|
1065
1531
|
|
|
1532
|
+
export async function sortDataTableByColumn(columnName) {
|
|
1533
|
+
const normalizedColumn = String(columnName ?? "").trim();
|
|
1534
|
+
|
|
1535
|
+
if (
|
|
1536
|
+
!normalizedColumn ||
|
|
1537
|
+
!state.dataBrowser.table?.columns?.includes(normalizedColumn)
|
|
1538
|
+
) {
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
state.dataBrowser.sortDirection = getNextSortDirection(
|
|
1543
|
+
state.dataBrowser.sortColumn,
|
|
1544
|
+
state.dataBrowser.sortDirection,
|
|
1545
|
+
normalizedColumn
|
|
1546
|
+
);
|
|
1547
|
+
state.dataBrowser.sortColumn = normalizedColumn;
|
|
1548
|
+
state.dataBrowser.page = 1;
|
|
1549
|
+
state.dataBrowser.selectedRowIndex = null;
|
|
1550
|
+
state.dataBrowser.saveError = null;
|
|
1551
|
+
emitChange();
|
|
1552
|
+
|
|
1553
|
+
if (state.route.name === "data" && state.dataBrowser.selectedTable) {
|
|
1554
|
+
await loadDataTable(++routeLoadVersion);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1066
1558
|
export async function setDataPageSize(pageSize) {
|
|
1067
1559
|
const normalizedPageSize = normalizeDataPageSize(pageSize, state.dataBrowser.pageSize);
|
|
1068
1560
|
|
|
@@ -1190,7 +1682,11 @@ export async function submitEditorRowUpdate(rowIndex, values) {
|
|
|
1190
1682
|
);
|
|
1191
1683
|
state.editor.result = {
|
|
1192
1684
|
...result,
|
|
1193
|
-
rows:
|
|
1685
|
+
rows: sortEditorResultRows(
|
|
1686
|
+
nextRows,
|
|
1687
|
+
state.editor.resultSortColumn,
|
|
1688
|
+
state.editor.resultSortDirection
|
|
1689
|
+
),
|
|
1194
1690
|
};
|
|
1195
1691
|
state.editor.selectedRowIndex = null;
|
|
1196
1692
|
invalidateDatabaseCaches();
|
|
@@ -1304,7 +1800,10 @@ export async function exportCurrentDataTableCsv() {
|
|
|
1304
1800
|
emitChange();
|
|
1305
1801
|
|
|
1306
1802
|
try {
|
|
1307
|
-
await api.downloadTableCsv(tableName
|
|
1803
|
+
await api.downloadTableCsv(tableName, {
|
|
1804
|
+
sortColumn: state.dataBrowser.sortColumn,
|
|
1805
|
+
sortDirection: state.dataBrowser.sortDirection,
|
|
1806
|
+
});
|
|
1308
1807
|
pushToast(`CSV export started for ${tableName}.`, "success");
|
|
1309
1808
|
return true;
|
|
1310
1809
|
} catch (error) {
|
|
@@ -1317,6 +1816,31 @@ export async function exportCurrentDataTableCsv() {
|
|
|
1317
1816
|
}
|
|
1318
1817
|
}
|
|
1319
1818
|
|
|
1819
|
+
export function sortEditorResultsByColumn(columnName) {
|
|
1820
|
+
const normalizedColumn = String(columnName ?? "").trim();
|
|
1821
|
+
const result = state.editor.result;
|
|
1822
|
+
|
|
1823
|
+
if (!normalizedColumn || !result?.columns?.includes(normalizedColumn)) {
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
const nextDirection = getNextSortDirection(
|
|
1828
|
+
state.editor.resultSortColumn,
|
|
1829
|
+
state.editor.resultSortDirection,
|
|
1830
|
+
normalizedColumn
|
|
1831
|
+
);
|
|
1832
|
+
|
|
1833
|
+
state.editor.result = {
|
|
1834
|
+
...result,
|
|
1835
|
+
rows: sortEditorResultRows(result.rows ?? [], normalizedColumn, nextDirection),
|
|
1836
|
+
};
|
|
1837
|
+
state.editor.resultSortColumn = normalizedColumn;
|
|
1838
|
+
state.editor.resultSortDirection = nextDirection;
|
|
1839
|
+
state.editor.selectedRowIndex = null;
|
|
1840
|
+
state.editor.saveError = null;
|
|
1841
|
+
emitChange();
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1320
1844
|
export async function refreshCurrentRoute() {
|
|
1321
1845
|
await loadRouteData(state.route);
|
|
1322
1846
|
}
|