sqlite-hub 0.3.2 → 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/README.md +5 -5
- package/changelog.md +14 -0
- package/{js → frontend/js}/api.js +99 -5
- package/{js → frontend/js}/app.js +162 -11
- package/{js → frontend/js}/components/connectionCard.js +8 -9
- package/frontend/js/components/connectionLogo.js +33 -0
- package/{js → frontend/js}/components/dataGrid.js +3 -3
- package/{js → frontend/js}/components/emptyState.js +25 -11
- package/{js → frontend/js}/components/modal.js +57 -0
- package/{js → frontend/js}/components/queryEditor.js +33 -55
- package/frontend/js/components/queryHistoryDetail.js +263 -0
- package/frontend/js/components/queryHistoryPanel.js +228 -0
- package/{js → frontend/js}/components/queryResults.js +32 -46
- package/{js → frontend/js}/components/rowEditorPanel.js +73 -14
- package/{js → frontend/js}/components/sidebar.js +8 -3
- package/{js → frontend/js}/store.js +577 -21
- package/{js → frontend/js}/utils/format.js +23 -0
- package/{js → frontend/js}/views/data.js +136 -10
- package/{js → frontend/js}/views/editor.js +34 -10
- package/{js → frontend/js}/views/overview.js +15 -0
- package/{js → frontend/js}/views/structure.js +10 -12
- package/{styles → frontend/styles}/components.css +106 -0
- package/{styles → frontend/styles}/structure-graph.css +5 -10
- package/package.json +2 -2
- package/{publish_brew.sh → scripts/publish_brew.sh} +2 -2
- package/{publish_npm.sh → scripts/publish_npm.sh} +2 -2
- package/server/data/db_logos/.gitkeep +0 -0
- package/server/routes/connections.js +2 -0
- 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 +8 -6
- package/server/services/sqlite/connectionManager.js +68 -33
- 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 +832 -20
- package/server/services/storage/queryHistoryUtils.js +169 -0
- package/server/utils/appPaths.js +42 -18
- package/{assets → frontend/assets}/images/logo.webp +0 -0
- package/{assets → frontend/assets}/images/logo_extrasmall.webp +0 -0
- package/{assets → frontend/assets}/images/logo_raw.png +0 -0
- package/{assets → frontend/assets}/images/logo_small.webp +0 -0
- package/{assets → frontend/assets}/mockups/connections.png +0 -0
- package/{assets → frontend/assets}/mockups/data.png +0 -0
- package/{assets → frontend/assets}/mockups/data_edit.png +0 -0
- package/{assets → frontend/assets}/mockups/graph_visualize.png +0 -0
- package/{assets → frontend/assets}/mockups/home.png +0 -0
- package/{assets → frontend/assets}/mockups/overview.png +0 -0
- package/{assets → frontend/assets}/mockups/sql_editor.png +0 -0
- package/{assets → frontend/assets}/mockups/structure.png +0 -0
- package/{index.html → frontend/index.html} +0 -0
- package/{js → frontend/js}/components/actionBar.js +0 -0
- package/{js → frontend/js}/components/appShell.js +0 -0
- package/{js → frontend/js}/components/badges.js +0 -0
- package/{js → frontend/js}/components/bottomTabs.js +1 -1
- /package/{js → frontend/js}/components/metricCard.js +0 -0
- /package/{js → frontend/js}/components/pageHeader.js +0 -0
- /package/{js → frontend/js}/components/statusBar.js +0 -0
- /package/{js → frontend/js}/components/structureGraph.js +0 -0
- /package/{js → frontend/js}/components/toast.js +0 -0
- /package/{js → frontend/js}/components/topNav.js +0 -0
- /package/{js → frontend/js}/lib/cytoscapeRuntime.js +0 -0
- /package/{js → frontend/js}/router.js +0 -0
- /package/{js → frontend/js}/views/connections.js +0 -0
- /package/{js → frontend/js}/views/landing.js +0 -0
- /package/{js → frontend/js}/views/settings.js +0 -0
- /package/{styles → frontend/styles}/base.css +0 -0
- /package/{styles → frontend/styles}/layout.css +0 -0
- /package/{styles → frontend/styles}/tokens.css +0 -0
- /package/{styles → frontend/styles}/views.css +0 -0
- /package/{data → server/data}/.gitkeep +0 -0
|
@@ -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,10 @@ const state = {
|
|
|
48
53
|
deleting: false,
|
|
49
54
|
page: 1,
|
|
50
55
|
pageSize: 50,
|
|
56
|
+
sortColumn: null,
|
|
57
|
+
sortDirection: null,
|
|
58
|
+
searchQuery: "",
|
|
59
|
+
searchColumn: "",
|
|
51
60
|
selectedRowIndex: null,
|
|
52
61
|
exportLoading: false,
|
|
53
62
|
error: null,
|
|
@@ -57,10 +66,25 @@ const state = {
|
|
|
57
66
|
sqlText: "",
|
|
58
67
|
history: [],
|
|
59
68
|
historyLoading: false,
|
|
69
|
+
historyLoadingMore: false,
|
|
60
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,
|
|
61
83
|
activeTab: "messages",
|
|
62
84
|
executing: false,
|
|
63
85
|
result: null,
|
|
86
|
+
resultSortColumn: null,
|
|
87
|
+
resultSortDirection: null,
|
|
64
88
|
error: null,
|
|
65
89
|
exportLoading: false,
|
|
66
90
|
selectedRowIndex: null,
|
|
@@ -100,6 +124,26 @@ function normalizeError(error) {
|
|
|
100
124
|
};
|
|
101
125
|
}
|
|
102
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
|
+
|
|
103
147
|
function requiresActiveDatabase(routeName) {
|
|
104
148
|
return ["overview", "data", "editor", "editorResults", "structure"].includes(routeName);
|
|
105
149
|
}
|
|
@@ -114,10 +158,94 @@ function normalizeDataPageSize(value, fallback = 50) {
|
|
|
114
158
|
return fallback;
|
|
115
159
|
}
|
|
116
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
|
+
|
|
117
173
|
function canEditQueryResult(snapshot = state) {
|
|
118
174
|
return Boolean(snapshot.editor.result?.editing?.enabled) && !snapshot.connections.active?.readOnly;
|
|
119
175
|
}
|
|
120
176
|
|
|
177
|
+
function resetDataBrowserSearch() {
|
|
178
|
+
state.dataBrowser.searchQuery = "";
|
|
179
|
+
state.dataBrowser.searchColumn = "";
|
|
180
|
+
}
|
|
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
|
+
|
|
121
249
|
function buildUpdatedEditorResultRow(existingRow, updatedSourceRow, editableColumns) {
|
|
122
250
|
const nextRow = {
|
|
123
251
|
...existingRow,
|
|
@@ -168,6 +296,64 @@ function buildFallbackDeleteRowPreview(row) {
|
|
|
168
296
|
);
|
|
169
297
|
}
|
|
170
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
|
+
|
|
171
357
|
function clearRouteSlices() {
|
|
172
358
|
state.overview.error = null;
|
|
173
359
|
state.dataBrowser.error = null;
|
|
@@ -188,6 +374,8 @@ function setMissingDatabaseState() {
|
|
|
188
374
|
state.dataBrowser.selectedTable = null;
|
|
189
375
|
state.dataBrowser.table = null;
|
|
190
376
|
state.dataBrowser.page = 1;
|
|
377
|
+
resetDataBrowserSort();
|
|
378
|
+
resetDataBrowserSearch();
|
|
191
379
|
state.dataBrowser.selectedRowIndex = null;
|
|
192
380
|
state.dataBrowser.exportLoading = false;
|
|
193
381
|
state.dataBrowser.error = error;
|
|
@@ -198,6 +386,8 @@ function setMissingDatabaseState() {
|
|
|
198
386
|
state.structure.data = null;
|
|
199
387
|
state.structure.detail = null;
|
|
200
388
|
state.structure.error = error;
|
|
389
|
+
|
|
390
|
+
resetQueryHistoryState({ preserveSearch: false });
|
|
201
391
|
}
|
|
202
392
|
|
|
203
393
|
function syncRouteContext() {
|
|
@@ -205,6 +395,7 @@ function syncRouteContext() {
|
|
|
205
395
|
|
|
206
396
|
if (route.name === "editorResults") {
|
|
207
397
|
state.editor.activeTab = "results";
|
|
398
|
+
clearQueryHistoryDetailState();
|
|
208
399
|
} else if (route.name === "editor" && state.editor.activeTab === "results") {
|
|
209
400
|
state.editor.activeTab = "messages";
|
|
210
401
|
}
|
|
@@ -221,6 +412,9 @@ function syncRouteContext() {
|
|
|
221
412
|
if (route.name !== "data" || route.params?.tableName !== state.dataBrowser.selectedTable) {
|
|
222
413
|
state.dataBrowser.page = 1;
|
|
223
414
|
}
|
|
415
|
+
if (route.params?.tableName !== state.dataBrowser.selectedTable) {
|
|
416
|
+
resetDataBrowserSearch();
|
|
417
|
+
}
|
|
224
418
|
state.dataBrowser.selectedRowIndex = null;
|
|
225
419
|
state.dataBrowser.saveError = null;
|
|
226
420
|
}
|
|
@@ -273,19 +467,131 @@ async function refreshSettingsState() {
|
|
|
273
467
|
}
|
|
274
468
|
}
|
|
275
469
|
|
|
276
|
-
async function
|
|
277
|
-
|
|
278
|
-
|
|
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;
|
|
279
483
|
emitChange();
|
|
280
484
|
|
|
281
485
|
try {
|
|
282
|
-
const
|
|
283
|
-
|
|
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
|
+
}
|
|
284
504
|
} catch (error) {
|
|
285
|
-
|
|
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);
|
|
286
515
|
} finally {
|
|
287
|
-
|
|
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 });
|
|
288
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
|
+
}
|
|
289
595
|
}
|
|
290
596
|
}
|
|
291
597
|
|
|
@@ -322,6 +628,8 @@ async function loadDataTable(version) {
|
|
|
322
628
|
const tableName = state.dataBrowser.selectedTable;
|
|
323
629
|
const pageSize = normalizeDataPageSize(state.dataBrowser.pageSize, 50);
|
|
324
630
|
const page = Math.max(1, Number(state.dataBrowser.page) || 1);
|
|
631
|
+
const sortColumn = state.dataBrowser.sortColumn;
|
|
632
|
+
const sortDirection = normalizeSortDirection(state.dataBrowser.sortDirection);
|
|
325
633
|
|
|
326
634
|
if (!tableName) {
|
|
327
635
|
state.dataBrowser.table = null;
|
|
@@ -337,6 +645,8 @@ async function loadDataTable(version) {
|
|
|
337
645
|
const response = await api.getDataTable(tableName, {
|
|
338
646
|
limit: pageSize,
|
|
339
647
|
offset: (page - 1) * pageSize,
|
|
648
|
+
sortColumn,
|
|
649
|
+
sortDirection,
|
|
340
650
|
});
|
|
341
651
|
|
|
342
652
|
if (version !== routeLoadVersion) {
|
|
@@ -346,6 +656,11 @@ async function loadDataTable(version) {
|
|
|
346
656
|
state.dataBrowser.table = response.data ?? null;
|
|
347
657
|
state.dataBrowser.pageSize = pageSize;
|
|
348
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;
|
|
661
|
+
state.dataBrowser.searchColumn = response.data?.columns?.includes(state.dataBrowser.searchColumn)
|
|
662
|
+
? state.dataBrowser.searchColumn
|
|
663
|
+
: (response.data?.columns?.[0] ?? "");
|
|
349
664
|
state.dataBrowser.selectedRowIndex = null;
|
|
350
665
|
} catch (error) {
|
|
351
666
|
if (version !== routeLoadVersion) {
|
|
@@ -383,6 +698,8 @@ async function loadData(version, route) {
|
|
|
383
698
|
if (requestedTableName && tables.some((table) => table.name === requestedTableName)) {
|
|
384
699
|
if (requestedTableName !== state.dataBrowser.selectedTable) {
|
|
385
700
|
state.dataBrowser.page = 1;
|
|
701
|
+
resetDataBrowserSort();
|
|
702
|
+
resetDataBrowserSearch();
|
|
386
703
|
}
|
|
387
704
|
state.dataBrowser.selectedTable = requestedTableName;
|
|
388
705
|
} else if (
|
|
@@ -391,10 +708,13 @@ async function loadData(version, route) {
|
|
|
391
708
|
) {
|
|
392
709
|
state.dataBrowser.selectedTable = tables[0]?.name ?? null;
|
|
393
710
|
state.dataBrowser.page = 1;
|
|
711
|
+
resetDataBrowserSort();
|
|
712
|
+
resetDataBrowserSearch();
|
|
394
713
|
}
|
|
395
714
|
|
|
396
715
|
if (!state.dataBrowser.selectedTable) {
|
|
397
716
|
state.dataBrowser.table = null;
|
|
717
|
+
resetDataBrowserSearch();
|
|
398
718
|
state.dataBrowser.selectedRowIndex = null;
|
|
399
719
|
return;
|
|
400
720
|
}
|
|
@@ -510,6 +830,7 @@ function invalidateDatabaseCaches() {
|
|
|
510
830
|
state.dataBrowser.selectedTable = null;
|
|
511
831
|
state.dataBrowser.table = null;
|
|
512
832
|
state.dataBrowser.page = 1;
|
|
833
|
+
resetDataBrowserSearch();
|
|
513
834
|
state.dataBrowser.selectedRowIndex = null;
|
|
514
835
|
state.dataBrowser.exportLoading = false;
|
|
515
836
|
state.dataBrowser.error = null;
|
|
@@ -543,7 +864,7 @@ async function loadRouteData(route) {
|
|
|
543
864
|
return;
|
|
544
865
|
case "editor":
|
|
545
866
|
case "editorResults":
|
|
546
|
-
await
|
|
867
|
+
await refreshQueryHistoryState();
|
|
547
868
|
return;
|
|
548
869
|
case "structure":
|
|
549
870
|
await loadStructure(version);
|
|
@@ -606,7 +927,7 @@ export async function initializeApp() {
|
|
|
606
927
|
await Promise.all([
|
|
607
928
|
refreshConnectionsState(),
|
|
608
929
|
refreshSettingsState(),
|
|
609
|
-
|
|
930
|
+
refreshQueryHistoryState(),
|
|
610
931
|
]);
|
|
611
932
|
|
|
612
933
|
state.ready = true;
|
|
@@ -864,6 +1185,17 @@ export async function createActiveConnectionBackup() {
|
|
|
864
1185
|
}
|
|
865
1186
|
}
|
|
866
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
|
+
|
|
867
1199
|
export function setCurrentQuery(query) {
|
|
868
1200
|
const nextQuery = String(query ?? "");
|
|
869
1201
|
const previousLineCount = Math.max(1, String(state.editor.sqlText || "").split("\n").length);
|
|
@@ -878,12 +1210,22 @@ export function setCurrentQuery(query) {
|
|
|
878
1210
|
|
|
879
1211
|
export function clearCurrentQuery() {
|
|
880
1212
|
state.editor.sqlText = "";
|
|
1213
|
+
state.editor.result = null;
|
|
1214
|
+
resetEditorResultSort();
|
|
881
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
|
+
}
|
|
882
1223
|
emitChange();
|
|
883
1224
|
}
|
|
884
1225
|
|
|
885
1226
|
export function clearEditorResults() {
|
|
886
1227
|
state.editor.result = null;
|
|
1228
|
+
resetEditorResultSort();
|
|
887
1229
|
state.editor.error = null;
|
|
888
1230
|
state.editor.selectedRowIndex = null;
|
|
889
1231
|
state.editor.saving = false;
|
|
@@ -910,10 +1252,11 @@ export async function executeCurrentQuery() {
|
|
|
910
1252
|
try {
|
|
911
1253
|
const response = await api.executeSql(state.editor.sqlText);
|
|
912
1254
|
state.editor.result = response.data;
|
|
1255
|
+
resetEditorResultSort();
|
|
913
1256
|
state.editor.error = null;
|
|
914
1257
|
state.editor.activeTab = "results";
|
|
915
1258
|
invalidateDatabaseCaches();
|
|
916
|
-
await
|
|
1259
|
+
await refreshQueryHistoryState();
|
|
917
1260
|
pushToast(
|
|
918
1261
|
response.message || `Executed ${response.data.statementCount} SQL statement(s).`,
|
|
919
1262
|
"success"
|
|
@@ -922,7 +1265,7 @@ export async function executeCurrentQuery() {
|
|
|
922
1265
|
} catch (error) {
|
|
923
1266
|
state.editor.error = normalizeError(error);
|
|
924
1267
|
state.editor.activeTab = "messages";
|
|
925
|
-
|
|
1268
|
+
await refreshQueryHistoryState();
|
|
926
1269
|
return false;
|
|
927
1270
|
} finally {
|
|
928
1271
|
state.editor.executing = false;
|
|
@@ -930,14 +1273,14 @@ export async function executeCurrentQuery() {
|
|
|
930
1273
|
}
|
|
931
1274
|
}
|
|
932
1275
|
|
|
933
|
-
export async function
|
|
1276
|
+
export async function clearQueryHistoryStateAndData() {
|
|
934
1277
|
state.editor.historyLoading = true;
|
|
935
1278
|
emitChange();
|
|
936
1279
|
|
|
937
1280
|
try {
|
|
938
|
-
const response = await api.
|
|
939
|
-
|
|
940
|
-
pushToast(response.message || "
|
|
1281
|
+
const response = await api.clearQueryHistory();
|
|
1282
|
+
resetQueryHistoryState({ preserveSearch: false });
|
|
1283
|
+
pushToast(response.message || "Query history cleared.", "muted");
|
|
941
1284
|
return true;
|
|
942
1285
|
} catch (error) {
|
|
943
1286
|
state.editor.historyError = normalizeError(error);
|
|
@@ -949,17 +1292,158 @@ export async function clearSqlHistoryStateAndData() {
|
|
|
949
1292
|
}
|
|
950
1293
|
}
|
|
951
1294
|
|
|
952
|
-
export function
|
|
953
|
-
const
|
|
1295
|
+
export async function setQueryHistoryTab(tab) {
|
|
1296
|
+
const normalizedTab = normalizeQueryHistoryTab(String(tab ?? "").trim().toLowerCase());
|
|
1297
|
+
|
|
1298
|
+
if (state.editor.historyTab === normalizedTab) {
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
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 ?? "");
|
|
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
|
+
}
|
|
954
1349
|
|
|
955
|
-
|
|
1350
|
+
export function clearQueryHistorySelection() {
|
|
1351
|
+
if (state.editor.historySelectedId === null && !state.editor.historyDetail) {
|
|
956
1352
|
return;
|
|
957
1353
|
}
|
|
958
1354
|
|
|
959
|
-
|
|
1355
|
+
clearQueryHistoryDetailState();
|
|
960
1356
|
emitChange();
|
|
961
1357
|
}
|
|
962
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
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
963
1447
|
export async function selectStructureEntry(name) {
|
|
964
1448
|
state.structure.selectedName = name;
|
|
965
1449
|
emitChange();
|
|
@@ -1010,6 +1494,20 @@ export function clearDataRowSelection() {
|
|
|
1010
1494
|
emitChange();
|
|
1011
1495
|
}
|
|
1012
1496
|
|
|
1497
|
+
export function setDataSearchQuery(query) {
|
|
1498
|
+
state.dataBrowser.searchQuery = String(query ?? "");
|
|
1499
|
+
state.dataBrowser.selectedRowIndex = null;
|
|
1500
|
+
state.dataBrowser.saveError = null;
|
|
1501
|
+
emitChange();
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
export function setDataSearchColumn(columnName) {
|
|
1505
|
+
state.dataBrowser.searchColumn = String(columnName ?? "");
|
|
1506
|
+
state.dataBrowser.selectedRowIndex = null;
|
|
1507
|
+
state.dataBrowser.saveError = null;
|
|
1508
|
+
emitChange();
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1013
1511
|
export async function setDataPage(page) {
|
|
1014
1512
|
const numericPage = Number(page);
|
|
1015
1513
|
|
|
@@ -1031,6 +1529,32 @@ export async function setDataPage(page) {
|
|
|
1031
1529
|
}
|
|
1032
1530
|
}
|
|
1033
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
|
+
|
|
1034
1558
|
export async function setDataPageSize(pageSize) {
|
|
1035
1559
|
const normalizedPageSize = normalizeDataPageSize(pageSize, state.dataBrowser.pageSize);
|
|
1036
1560
|
|
|
@@ -1158,7 +1682,11 @@ export async function submitEditorRowUpdate(rowIndex, values) {
|
|
|
1158
1682
|
);
|
|
1159
1683
|
state.editor.result = {
|
|
1160
1684
|
...result,
|
|
1161
|
-
rows:
|
|
1685
|
+
rows: sortEditorResultRows(
|
|
1686
|
+
nextRows,
|
|
1687
|
+
state.editor.resultSortColumn,
|
|
1688
|
+
state.editor.resultSortDirection
|
|
1689
|
+
),
|
|
1162
1690
|
};
|
|
1163
1691
|
state.editor.selectedRowIndex = null;
|
|
1164
1692
|
invalidateDatabaseCaches();
|
|
@@ -1272,7 +1800,10 @@ export async function exportCurrentDataTableCsv() {
|
|
|
1272
1800
|
emitChange();
|
|
1273
1801
|
|
|
1274
1802
|
try {
|
|
1275
|
-
await api.downloadTableCsv(tableName
|
|
1803
|
+
await api.downloadTableCsv(tableName, {
|
|
1804
|
+
sortColumn: state.dataBrowser.sortColumn,
|
|
1805
|
+
sortDirection: state.dataBrowser.sortDirection,
|
|
1806
|
+
});
|
|
1276
1807
|
pushToast(`CSV export started for ${tableName}.`, "success");
|
|
1277
1808
|
return true;
|
|
1278
1809
|
} catch (error) {
|
|
@@ -1285,6 +1816,31 @@ export async function exportCurrentDataTableCsv() {
|
|
|
1285
1816
|
}
|
|
1286
1817
|
}
|
|
1287
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
|
+
|
|
1288
1844
|
export async function refreshCurrentRoute() {
|
|
1289
1845
|
await loadRouteData(state.route);
|
|
1290
1846
|
}
|