sqlite-hub 0.11.1 → 0.16.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 +52 -13
- package/database.sqlite +0 -0
- package/frontend/js/api.js +64 -12
- package/frontend/js/app.js +723 -37
- package/frontend/js/components/emptyState.js +42 -46
- package/frontend/js/components/modal.js +526 -2
- package/frontend/js/components/queryEditor.js +12 -3
- package/frontend/js/components/queryResults.js +79 -17
- package/frontend/js/components/rowEditorPanel.js +345 -12
- package/frontend/js/components/sidebar.js +106 -23
- package/frontend/js/components/tableDesignerEditor.js +69 -11
- package/frontend/js/store.js +437 -26
- package/frontend/js/utils/copyColumnExport.js +117 -0
- package/frontend/js/utils/exportFilenames.js +32 -0
- package/frontend/js/utils/filePathPreview.js +315 -0
- package/frontend/js/utils/format.js +1 -1
- package/frontend/js/utils/rowEditorJson.js +65 -0
- package/frontend/js/utils/sqlFormatter.js +691 -0
- package/frontend/js/utils/tableDesigner.js +178 -6
- package/frontend/js/utils/textCellStats.js +20 -0
- package/frontend/js/utils/timestampPreview.js +264 -0
- package/frontend/js/views/charts.js +3 -1
- package/frontend/js/views/data.js +39 -4
- package/frontend/js/views/editor.js +50 -1
- package/frontend/js/views/settings.js +1 -4
- package/frontend/js/views/structure.js +154 -212
- package/frontend/styles/base.css +6 -0
- package/frontend/styles/components.css +463 -2
- package/frontend/styles/structure-graph.css +0 -3
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/tokens.css +95 -95
- package/package.json +2 -3
- package/server/routes/export.js +97 -12
- package/server/services/sqlite/dataBrowserService.js +2 -68
- package/server/services/sqlite/exportService.js +74 -15
- package/server/services/sqlite/introspection.js +209 -1
- package/server/services/sqlite/sqlExecutor.js +31 -0
- package/server/services/sqlite/tableDesigner/changeAnalysis.js +25 -1
- package/server/services/sqlite/tableDesigner/schemaMapping.js +105 -10
- package/server/services/sqlite/tableDesigner/validation.js +60 -2
- package/server/services/sqlite/tableDesignerService.js +1 -1
- package/server/services/sqlite/tableFilter.js +75 -0
- package/server/utils/csv.js +30 -4
- package/tests/check-constraint-options.test.js +90 -0
- package/tests/export-filenames.test.js +34 -0
- package/tests/file-path-preview.test.js +165 -0
- package/tests/row-editor-json.test.js +82 -0
- package/tests/row-editor-timestamp-preview.test.js +192 -0
- package/tests/sql-formatter.test.js +173 -0
- package/tests/sql-highlight.test.js +38 -0
- package/tests/table-designer-v2-unique-constraints.test.js +78 -0
- package/tests/text-cell-stats.test.js +38 -0
package/frontend/js/store.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
hydrateTableDesignerDraft,
|
|
8
8
|
removeTableDesignerColumn,
|
|
9
9
|
updateTableDesignerColumnField,
|
|
10
|
+
updateTableDesignerConstraintField,
|
|
10
11
|
updateTableDesignerDraftField,
|
|
11
12
|
} from './utils/tableDesigner.js';
|
|
12
13
|
import {
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
MEDIA_TAGGING_DEFAULT_MAPPING_TABLE,
|
|
23
24
|
MEDIA_TAGGING_DEFAULT_TAG_TABLE,
|
|
24
25
|
} from './lib/mediaTaggingDefaults.js';
|
|
26
|
+
import { buildTextExportFilename } from './utils/exportFilenames.js';
|
|
25
27
|
|
|
26
28
|
const listeners = new Set();
|
|
27
29
|
const DEFAULT_SETTINGS = {
|
|
@@ -35,6 +37,9 @@ const DATA_FILTER_OPERATORS = new Set(['=', '!=', '<', '>', '<=', '>=', 'equals'
|
|
|
35
37
|
const DATA_ROW_SIZE_STORAGE_KEY = 'data_row_size';
|
|
36
38
|
const CHARTS_HISTORY_TAB_STORAGE_KEY = 'charts_history_tab';
|
|
37
39
|
const QUERY_HISTORY_TAB_STORAGE_KEY = 'query_history_tab';
|
|
40
|
+
const COPY_COLUMN_SEPARATOR_STORAGE_KEY = 'sqlitehub.copyColumn.separator';
|
|
41
|
+
const COPY_COLUMN_WRAPPER_STORAGE_KEY = 'sqlitehub.copyColumn.wrapper';
|
|
42
|
+
const COPY_COLUMN_LINE_BREAKS_STORAGE_KEY = 'sqlitehub.copyColumn.lineBreaks';
|
|
38
43
|
const UI_PREFERENCE_STORAGE_KEYS = {
|
|
39
44
|
sqlEditorHistoryVisible: 'sqlite_hub_sql_editor_history_visible',
|
|
40
45
|
sqlEditorEditorVisible: 'sqlite_hub_sql_editor_editor_visible',
|
|
@@ -50,6 +55,12 @@ const QUERY_HISTORY_PAGE_SIZE = 30;
|
|
|
50
55
|
const QUERY_HISTORY_RUN_LIMIT = 8;
|
|
51
56
|
const CHART_HEIGHT_PRESETS = new Set(['small', 'medium', 'large']);
|
|
52
57
|
const EDITOR_RESULT_TABS = new Set(['results', 'performance', 'messages']);
|
|
58
|
+
const COPY_COLUMN_MODES = new Set(['column', 'column-with-header', 'first-10', 'markdown-todo']);
|
|
59
|
+
const TEXT_EXPORT_FORMAT_LABELS = {
|
|
60
|
+
csv: 'CSV',
|
|
61
|
+
tsv: 'TSV',
|
|
62
|
+
md: 'Markdown',
|
|
63
|
+
};
|
|
53
64
|
const MISSING_DATABASE_ERROR = {
|
|
54
65
|
code: 'ACTIVE_DATABASE_REQUIRED',
|
|
55
66
|
message: 'No active SQLite database selected.',
|
|
@@ -79,6 +90,34 @@ function storeDataPageSize(pageSize) {
|
|
|
79
90
|
}
|
|
80
91
|
}
|
|
81
92
|
|
|
93
|
+
function findQueryHistoryItemBySql(sql, snapshot = state) {
|
|
94
|
+
const normalizedSql = String(sql ?? '').trim();
|
|
95
|
+
|
|
96
|
+
if (!normalizedSql) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (String(snapshot.editor.historyDetail?.rawSql ?? '').trim() === normalizedSql) {
|
|
101
|
+
return snapshot.editor.historyDetail;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return snapshot.editor.history.find(entry => String(entry.rawSql ?? '').trim() === normalizedSql) ?? null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getCurrentQueryExportFilename(format = 'csv', filename = '') {
|
|
108
|
+
const queryText = String(state.editor.sqlText ?? '');
|
|
109
|
+
const historyItem = findQueryHistoryItemBySql(queryText);
|
|
110
|
+
const fallback = historyItem?.displayTitle || 'query-results';
|
|
111
|
+
|
|
112
|
+
return buildTextExportFilename(filename || fallback, { format, fallback });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getCurrentDataTableExportFilename(format = 'csv', filename = '') {
|
|
116
|
+
const fallback = state.dataBrowser.selectedTable || 'table';
|
|
117
|
+
|
|
118
|
+
return buildTextExportFilename(filename || fallback, { format, fallback });
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
function readStoredBoolean(key, fallback) {
|
|
83
122
|
try {
|
|
84
123
|
const value = globalThis.localStorage?.getItem(key);
|
|
@@ -122,6 +161,27 @@ function storeString(key, value) {
|
|
|
122
161
|
}
|
|
123
162
|
}
|
|
124
163
|
|
|
164
|
+
function readCopyColumnPreferences() {
|
|
165
|
+
return {
|
|
166
|
+
separator: readStoredString(COPY_COLUMN_SEPARATOR_STORAGE_KEY, ','),
|
|
167
|
+
wrapper: readStoredString(COPY_COLUMN_WRAPPER_STORAGE_KEY, '"'),
|
|
168
|
+
lineBreaks: readStoredBoolean(COPY_COLUMN_LINE_BREAKS_STORAGE_KEY, false),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function normalizeCopyColumnMode(mode) {
|
|
173
|
+
const normalizedMode = String(mode ?? '').trim();
|
|
174
|
+
return COPY_COLUMN_MODES.has(normalizedMode) ? normalizedMode : 'column';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function normalizeCopyColumnScope(scope) {
|
|
178
|
+
return scope === 'charts' ? 'charts' : 'editor';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getResultByCopyColumnScope(scope, snapshot = state) {
|
|
182
|
+
return normalizeCopyColumnScope(scope) === 'charts' ? snapshot.charts.result : snapshot.editor.result;
|
|
183
|
+
}
|
|
184
|
+
|
|
125
185
|
function readStoredEditorActiveTab(fallback = 'messages') {
|
|
126
186
|
try {
|
|
127
187
|
const value = globalThis.localStorage?.getItem(UI_PREFERENCE_STORAGE_KEYS.sqlEditorActiveTab);
|
|
@@ -1475,6 +1535,16 @@ async function resolvePendingDataBrowserRow(version) {
|
|
|
1475
1535
|
return;
|
|
1476
1536
|
}
|
|
1477
1537
|
|
|
1538
|
+
if (!pendingTarget.identity && Number.isInteger(pendingTarget.rowIndex)) {
|
|
1539
|
+
if (table.rows?.[pendingTarget.rowIndex]) {
|
|
1540
|
+
state.dataBrowser.selectedRowIndex = pendingTarget.rowIndex;
|
|
1541
|
+
state.dataBrowser.selectedRow = null;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
state.dataBrowser.pendingOpenRow = null;
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1478
1548
|
const matchingRowIndex = findDataBrowserRowIndexByIdentity(table.rows ?? [], pendingTarget.identity);
|
|
1479
1549
|
|
|
1480
1550
|
if (matchingRowIndex >= 0) {
|
|
@@ -1978,14 +2048,20 @@ async function previewMediaTaggingDraft(options = {}) {
|
|
|
1978
2048
|
}
|
|
1979
2049
|
}
|
|
1980
2050
|
|
|
1981
|
-
function invalidateDatabaseCaches() {
|
|
2051
|
+
function invalidateDatabaseCaches(options = {}) {
|
|
2052
|
+
const preserveDataBrowserState = options.preserveDataBrowserState === true;
|
|
2053
|
+
|
|
1982
2054
|
state.overview.data = null;
|
|
1983
2055
|
state.dataBrowser.tables = [];
|
|
1984
|
-
|
|
2056
|
+
if (!preserveDataBrowserState) {
|
|
2057
|
+
state.dataBrowser.selectedTable = null;
|
|
2058
|
+
}
|
|
1985
2059
|
state.dataBrowser.table = null;
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2060
|
+
if (!preserveDataBrowserState) {
|
|
2061
|
+
state.dataBrowser.page = 1;
|
|
2062
|
+
resetDataBrowserTableSearch();
|
|
2063
|
+
resetDataBrowserSearch();
|
|
2064
|
+
}
|
|
1989
2065
|
clearDataBrowserRowSelectionState();
|
|
1990
2066
|
state.dataBrowser.pendingOpenRow = null;
|
|
1991
2067
|
state.dataBrowser.exportLoading = false;
|
|
@@ -2244,15 +2320,131 @@ export async function setRoute(route) {
|
|
|
2244
2320
|
await loadRouteData(route);
|
|
2245
2321
|
}
|
|
2246
2322
|
|
|
2247
|
-
export function openModal(kind) {
|
|
2323
|
+
export function openModal(kind, options = {}) {
|
|
2248
2324
|
state.modal = {
|
|
2249
2325
|
kind,
|
|
2250
2326
|
error: null,
|
|
2251
2327
|
submitting: false,
|
|
2328
|
+
...options,
|
|
2329
|
+
};
|
|
2330
|
+
emitChange();
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
export function openQueryExportModal() {
|
|
2334
|
+
if (!String(state.editor.sqlText ?? '').trim()) {
|
|
2335
|
+
pushToast('Enter a query before exporting.', 'alert');
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
state.modal = {
|
|
2340
|
+
kind: 'query-export',
|
|
2341
|
+
filename: getCurrentQueryExportFilename('csv'),
|
|
2342
|
+
error: null,
|
|
2343
|
+
submitting: false,
|
|
2344
|
+
};
|
|
2345
|
+
emitChange();
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
export function openDataExportModal() {
|
|
2349
|
+
if (!state.dataBrowser.selectedTable) {
|
|
2350
|
+
pushToast('No table selected for export.', 'alert');
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
state.modal = {
|
|
2355
|
+
kind: 'data-export',
|
|
2356
|
+
filename: getCurrentDataTableExportFilename('csv'),
|
|
2357
|
+
error: null,
|
|
2358
|
+
submitting: false,
|
|
2252
2359
|
};
|
|
2253
2360
|
emitChange();
|
|
2254
2361
|
}
|
|
2255
2362
|
|
|
2363
|
+
export function openCopyColumnModal({ scope = 'editor', columnName = '', mode = 'column' } = {}) {
|
|
2364
|
+
const resultScope = normalizeCopyColumnScope(scope);
|
|
2365
|
+
const normalizedColumnName = String(columnName ?? '');
|
|
2366
|
+
const result = getResultByCopyColumnScope(resultScope);
|
|
2367
|
+
const hasColumn = (result?.columns ?? []).some(column => String(column) === normalizedColumnName);
|
|
2368
|
+
|
|
2369
|
+
if (!hasColumn) {
|
|
2370
|
+
pushToast('Column could not be found in the current result set.', 'alert');
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
const preferences = readCopyColumnPreferences();
|
|
2375
|
+
|
|
2376
|
+
state.modal = {
|
|
2377
|
+
kind: 'copy-column',
|
|
2378
|
+
scope: resultScope,
|
|
2379
|
+
columnName: normalizedColumnName,
|
|
2380
|
+
copyMode: normalizeCopyColumnMode(mode),
|
|
2381
|
+
separator: preferences.separator,
|
|
2382
|
+
wrapper: preferences.wrapper,
|
|
2383
|
+
lineBreaks: preferences.lineBreaks,
|
|
2384
|
+
error: null,
|
|
2385
|
+
submitting: false,
|
|
2386
|
+
};
|
|
2387
|
+
emitChange();
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
export function storeCopyColumnPreferences({ separator = ',', wrapper = '"', lineBreaks = false } = {}) {
|
|
2391
|
+
storeString(COPY_COLUMN_SEPARATOR_STORAGE_KEY, separator);
|
|
2392
|
+
storeString(COPY_COLUMN_WRAPPER_STORAGE_KEY, wrapper);
|
|
2393
|
+
storeBoolean(COPY_COLUMN_LINE_BREAKS_STORAGE_KEY, lineBreaks);
|
|
2394
|
+
|
|
2395
|
+
if (state.modal?.kind === 'copy-column') {
|
|
2396
|
+
state.modal.separator = String(separator ?? '');
|
|
2397
|
+
state.modal.wrapper = String(wrapper ?? '');
|
|
2398
|
+
state.modal.lineBreaks = Boolean(lineBreaks);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
export function updateCopyColumnModalFormatField(field, value) {
|
|
2403
|
+
if (state.modal?.kind !== 'copy-column') {
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
const normalizedField = String(field ?? '').trim();
|
|
2408
|
+
|
|
2409
|
+
if (normalizedField !== 'separator' && normalizedField !== 'wrapper' && normalizedField !== 'lineBreaks') {
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
const normalizedValue = normalizedField === 'lineBreaks' ? Boolean(value) : String(value ?? '');
|
|
2414
|
+
state.modal[normalizedField] = normalizedValue;
|
|
2415
|
+
state.modal.error = null;
|
|
2416
|
+
|
|
2417
|
+
if (normalizedField === 'lineBreaks') {
|
|
2418
|
+
storeBoolean(COPY_COLUMN_LINE_BREAKS_STORAGE_KEY, normalizedValue);
|
|
2419
|
+
} else {
|
|
2420
|
+
storeString(
|
|
2421
|
+
normalizedField === 'separator' ? COPY_COLUMN_SEPARATOR_STORAGE_KEY : COPY_COLUMN_WRAPPER_STORAGE_KEY,
|
|
2422
|
+
normalizedValue,
|
|
2423
|
+
);
|
|
2424
|
+
}
|
|
2425
|
+
emitChange();
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
export function setCopyColumnModalSubmitting(submitting) {
|
|
2429
|
+
if (state.modal?.kind !== 'copy-column') {
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
state.modal.submitting = Boolean(submitting);
|
|
2434
|
+
if (submitting) {
|
|
2435
|
+
state.modal.error = null;
|
|
2436
|
+
}
|
|
2437
|
+
emitChange();
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
export function setCopyColumnModalError(error) {
|
|
2441
|
+
if (state.modal?.kind !== 'copy-column') {
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
withModalError(error);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2256
2448
|
export function openEditConnectionModal(id) {
|
|
2257
2449
|
const connection = state.connections.recent.find(entry => entry.id === id);
|
|
2258
2450
|
|
|
@@ -2845,7 +3037,7 @@ export async function executeCurrentQuery() {
|
|
|
2845
3037
|
state.editor.result = response.data;
|
|
2846
3038
|
resetEditorResultSort();
|
|
2847
3039
|
state.editor.error = null;
|
|
2848
|
-
invalidateDatabaseCaches();
|
|
3040
|
+
invalidateDatabaseCaches({ preserveDataBrowserState: true });
|
|
2849
3041
|
await refreshQueryHistoryState();
|
|
2850
3042
|
pushToast(response.message || `Executed ${response.data.statementCount} SQL statement(s).`, 'success');
|
|
2851
3043
|
return true;
|
|
@@ -3143,6 +3335,26 @@ export function updateCurrentTableDesignerColumnField(columnId, field, value, op
|
|
|
3143
3335
|
}
|
|
3144
3336
|
}
|
|
3145
3337
|
|
|
3338
|
+
export function updateCurrentTableDesignerConstraintField(constraintKind, constraintId, field, value, options = {}) {
|
|
3339
|
+
if (!state.tableDesigner.draft) {
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
state.tableDesigner.draft = updateTableDesignerConstraintField(
|
|
3344
|
+
state.tableDesigner.draft,
|
|
3345
|
+
constraintKind,
|
|
3346
|
+
constraintId,
|
|
3347
|
+
field,
|
|
3348
|
+
value,
|
|
3349
|
+
getTableDesignerContext(),
|
|
3350
|
+
);
|
|
3351
|
+
state.tableDesigner.saveError = null;
|
|
3352
|
+
|
|
3353
|
+
if (options.notify !== false) {
|
|
3354
|
+
emitChange();
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3146
3358
|
export function addCurrentTableDesignerColumn() {
|
|
3147
3359
|
if (!state.tableDesigner.draft) {
|
|
3148
3360
|
return null;
|
|
@@ -3158,9 +3370,15 @@ export function addCurrentTableDesignerColumn() {
|
|
|
3158
3370
|
return nextColumn?.id ?? null;
|
|
3159
3371
|
}
|
|
3160
3372
|
|
|
3161
|
-
export function queueTableDesignerCsvImport(fileName, csvText) {
|
|
3373
|
+
export function queueTableDesignerCsvImport(fileName, csvText, options = {}) {
|
|
3162
3374
|
try {
|
|
3163
|
-
const imported = createTableDesignerDraftFromCsvImport(
|
|
3375
|
+
const imported = createTableDesignerDraftFromCsvImport(
|
|
3376
|
+
{ fileName, csvText },
|
|
3377
|
+
{
|
|
3378
|
+
...getTableDesignerContext(),
|
|
3379
|
+
...(options.context ?? {}),
|
|
3380
|
+
},
|
|
3381
|
+
);
|
|
3164
3382
|
|
|
3165
3383
|
state.tableDesigner.pendingImportedDraft = imported.draft;
|
|
3166
3384
|
state.tableDesigner.selectedTableName = null;
|
|
@@ -3169,6 +3387,10 @@ export function queueTableDesignerCsvImport(fileName, csvText) {
|
|
|
3169
3387
|
emitChange();
|
|
3170
3388
|
return imported;
|
|
3171
3389
|
} catch (error) {
|
|
3390
|
+
if (options.throwOnError) {
|
|
3391
|
+
throw error;
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3172
3394
|
pushToast(error?.message || 'CSV import failed.', 'alert');
|
|
3173
3395
|
return null;
|
|
3174
3396
|
}
|
|
@@ -3627,6 +3849,26 @@ export function openDataRowByIdentity(tableName, identity) {
|
|
|
3627
3849
|
return true;
|
|
3628
3850
|
}
|
|
3629
3851
|
|
|
3852
|
+
export function preserveCurrentDataRowSelectionForReload() {
|
|
3853
|
+
const tableName = state.dataBrowser.selectedTable ?? state.dataBrowser.table?.name ?? '';
|
|
3854
|
+
const row = getSelectedDataBrowserRow();
|
|
3855
|
+
const rowIndex =
|
|
3856
|
+
typeof state.dataBrowser.selectedRowIndex === 'number' ? state.dataBrowser.selectedRowIndex : null;
|
|
3857
|
+
|
|
3858
|
+
if (!tableName || !row) {
|
|
3859
|
+
return false;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
state.dataBrowser.pendingOpenRow = {
|
|
3863
|
+
tableName,
|
|
3864
|
+
identity: row.__identity ?? null,
|
|
3865
|
+
rowIndex,
|
|
3866
|
+
};
|
|
3867
|
+
clearDataBrowserRowSelectionState();
|
|
3868
|
+
state.dataBrowser.saveError = null;
|
|
3869
|
+
return true;
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3630
3872
|
export function selectEditorRow(index) {
|
|
3631
3873
|
const numericIndex = Number(index);
|
|
3632
3874
|
|
|
@@ -4132,25 +4374,138 @@ export async function submitDeleteQueryHistoryConfirmation() {
|
|
|
4132
4374
|
return deleted;
|
|
4133
4375
|
}
|
|
4134
4376
|
|
|
4135
|
-
|
|
4377
|
+
function beginCurrentQueryExport() {
|
|
4136
4378
|
state.editor.exportLoading = true;
|
|
4379
|
+
if (state.modal?.kind === 'query-export') {
|
|
4380
|
+
state.modal.submitting = true;
|
|
4381
|
+
state.modal.error = null;
|
|
4382
|
+
}
|
|
4137
4383
|
emitChange();
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
function reportCurrentQueryExportError(error) {
|
|
4387
|
+
if (state.modal?.kind === 'query-export') {
|
|
4388
|
+
withModalError(error);
|
|
4389
|
+
return;
|
|
4390
|
+
}
|
|
4391
|
+
|
|
4392
|
+
state.editor.error = normalizeError(error);
|
|
4393
|
+
emitChange();
|
|
4394
|
+
}
|
|
4395
|
+
|
|
4396
|
+
function finishCurrentQueryExport() {
|
|
4397
|
+
state.editor.exportLoading = false;
|
|
4398
|
+
if (state.modal?.kind === 'query-export') {
|
|
4399
|
+
state.modal.submitting = false;
|
|
4400
|
+
}
|
|
4401
|
+
emitChange();
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
export async function exportCurrentQueryFormat(format = 'csv', filename = '') {
|
|
4405
|
+
const normalizedFormat = String(format ?? 'csv').toLowerCase();
|
|
4406
|
+
const label = TEXT_EXPORT_FORMAT_LABELS[normalizedFormat] ?? TEXT_EXPORT_FORMAT_LABELS.csv;
|
|
4407
|
+
const exportFilename = getCurrentQueryExportFilename(normalizedFormat, filename || state.modal?.filename);
|
|
4408
|
+
|
|
4409
|
+
if (state.modal?.kind === 'query-export') {
|
|
4410
|
+
state.modal.filename = exportFilename;
|
|
4411
|
+
}
|
|
4412
|
+
|
|
4413
|
+
beginCurrentQueryExport();
|
|
4138
4414
|
|
|
4139
4415
|
try {
|
|
4140
|
-
await api.
|
|
4141
|
-
|
|
4416
|
+
await api.downloadQueryExport(state.editor.sqlText, normalizedFormat, { filename: exportFilename });
|
|
4417
|
+
closeModalInternal();
|
|
4418
|
+
pushToast(`${label} export started.`, 'success');
|
|
4142
4419
|
return true;
|
|
4143
4420
|
} catch (error) {
|
|
4144
|
-
|
|
4145
|
-
emitChange();
|
|
4421
|
+
reportCurrentQueryExportError(error);
|
|
4146
4422
|
return false;
|
|
4147
4423
|
} finally {
|
|
4148
|
-
|
|
4424
|
+
finishCurrentQueryExport();
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
export async function duplicateCurrentQueryAsTable(filename = '') {
|
|
4429
|
+
const exportFilename = getCurrentQueryExportFilename('csv', filename || state.modal?.filename);
|
|
4430
|
+
|
|
4431
|
+
if (state.modal?.kind === 'query-export') {
|
|
4432
|
+
state.modal.filename = exportFilename;
|
|
4433
|
+
}
|
|
4434
|
+
|
|
4435
|
+
beginCurrentQueryExport();
|
|
4436
|
+
|
|
4437
|
+
try {
|
|
4438
|
+
const response = await api.getQueryExport(state.editor.sqlText, 'csv');
|
|
4439
|
+
const exportData = response?.data ?? {};
|
|
4440
|
+
const imported = queueTableDesignerCsvImport(
|
|
4441
|
+
exportFilename || getCurrentQueryExportFilename('csv', exportData.filename),
|
|
4442
|
+
exportData.content || '',
|
|
4443
|
+
{ throwOnError: true },
|
|
4444
|
+
);
|
|
4445
|
+
|
|
4446
|
+
closeModalInternal();
|
|
4447
|
+
pushToast(
|
|
4448
|
+
`Table draft created from ${imported.importedRowCount} row${imported.importedRowCount === 1 ? '' : 's'}.`,
|
|
4449
|
+
'success',
|
|
4450
|
+
);
|
|
4451
|
+
return imported;
|
|
4452
|
+
} catch (error) {
|
|
4453
|
+
reportCurrentQueryExportError(error);
|
|
4454
|
+
return null;
|
|
4455
|
+
} finally {
|
|
4456
|
+
finishCurrentQueryExport();
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
|
|
4460
|
+
export async function exportCurrentQueryCsv() {
|
|
4461
|
+
try {
|
|
4462
|
+
return await exportCurrentQueryFormat('csv');
|
|
4463
|
+
} catch (error) {
|
|
4464
|
+
state.editor.error = normalizeError(error);
|
|
4149
4465
|
emitChange();
|
|
4466
|
+
return false;
|
|
4150
4467
|
}
|
|
4151
4468
|
}
|
|
4152
4469
|
|
|
4153
|
-
|
|
4470
|
+
function getCurrentDataTableExportOptions(format = 'csv') {
|
|
4471
|
+
return {
|
|
4472
|
+
sortColumn: state.dataBrowser.sortColumn,
|
|
4473
|
+
sortDirection: state.dataBrowser.sortDirection,
|
|
4474
|
+
filterColumn: state.dataBrowser.searchColumn,
|
|
4475
|
+
filterOperator: state.dataBrowser.filterOperator,
|
|
4476
|
+
filterValue: state.dataBrowser.searchQuery,
|
|
4477
|
+
format,
|
|
4478
|
+
};
|
|
4479
|
+
}
|
|
4480
|
+
|
|
4481
|
+
function beginCurrentDataTableExport() {
|
|
4482
|
+
state.dataBrowser.exportLoading = true;
|
|
4483
|
+
if (state.modal?.kind === 'data-export') {
|
|
4484
|
+
state.modal.submitting = true;
|
|
4485
|
+
state.modal.error = null;
|
|
4486
|
+
}
|
|
4487
|
+
emitChange();
|
|
4488
|
+
}
|
|
4489
|
+
|
|
4490
|
+
function reportCurrentDataTableExportError(error) {
|
|
4491
|
+
if (state.modal?.kind === 'data-export') {
|
|
4492
|
+
withModalError(error);
|
|
4493
|
+
return;
|
|
4494
|
+
}
|
|
4495
|
+
|
|
4496
|
+
state.dataBrowser.error = normalizeError(error);
|
|
4497
|
+
emitChange();
|
|
4498
|
+
}
|
|
4499
|
+
|
|
4500
|
+
function finishCurrentDataTableExport() {
|
|
4501
|
+
state.dataBrowser.exportLoading = false;
|
|
4502
|
+
if (state.modal?.kind === 'data-export') {
|
|
4503
|
+
state.modal.submitting = false;
|
|
4504
|
+
}
|
|
4505
|
+
emitChange();
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4508
|
+
export async function exportCurrentDataTableFormat(format = 'csv', filename = '') {
|
|
4154
4509
|
const tableName = state.dataBrowser.selectedTable;
|
|
4155
4510
|
|
|
4156
4511
|
if (!tableName) {
|
|
@@ -4158,26 +4513,82 @@ export async function exportCurrentDataTableCsv() {
|
|
|
4158
4513
|
return false;
|
|
4159
4514
|
}
|
|
4160
4515
|
|
|
4161
|
-
|
|
4162
|
-
|
|
4516
|
+
const normalizedFormat = String(format ?? 'csv').toLowerCase();
|
|
4517
|
+
const label = TEXT_EXPORT_FORMAT_LABELS[normalizedFormat] ?? TEXT_EXPORT_FORMAT_LABELS.csv;
|
|
4518
|
+
const exportFilename = getCurrentDataTableExportFilename(normalizedFormat, filename || state.modal?.filename);
|
|
4519
|
+
|
|
4520
|
+
if (state.modal?.kind === 'data-export') {
|
|
4521
|
+
state.modal.filename = exportFilename;
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4524
|
+
beginCurrentDataTableExport();
|
|
4163
4525
|
|
|
4164
4526
|
try {
|
|
4165
|
-
await api.
|
|
4166
|
-
|
|
4167
|
-
|
|
4527
|
+
await api.downloadTableExport(tableName, {
|
|
4528
|
+
...getCurrentDataTableExportOptions(normalizedFormat),
|
|
4529
|
+
filename: exportFilename,
|
|
4168
4530
|
});
|
|
4169
|
-
|
|
4531
|
+
closeModalInternal();
|
|
4532
|
+
pushToast(`${label} export started for ${tableName}.`, 'success');
|
|
4170
4533
|
return true;
|
|
4171
4534
|
} catch (error) {
|
|
4172
|
-
|
|
4173
|
-
emitChange();
|
|
4535
|
+
reportCurrentDataTableExportError(error);
|
|
4174
4536
|
return false;
|
|
4175
4537
|
} finally {
|
|
4176
|
-
|
|
4177
|
-
|
|
4538
|
+
finishCurrentDataTableExport();
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
|
|
4542
|
+
export async function duplicateCurrentDataTableAsTable(filename = '') {
|
|
4543
|
+
const tableName = state.dataBrowser.selectedTable;
|
|
4544
|
+
|
|
4545
|
+
if (!tableName) {
|
|
4546
|
+
pushToast('No table selected for export.', 'alert');
|
|
4547
|
+
return null;
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4550
|
+
const exportFilename = getCurrentDataTableExportFilename('csv', filename || state.modal?.filename);
|
|
4551
|
+
|
|
4552
|
+
if (state.modal?.kind === 'data-export') {
|
|
4553
|
+
state.modal.filename = exportFilename;
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4556
|
+
beginCurrentDataTableExport();
|
|
4557
|
+
|
|
4558
|
+
try {
|
|
4559
|
+
const response = await api.getTableExport(tableName, getCurrentDataTableExportOptions('csv'));
|
|
4560
|
+
const exportData = response?.data ?? {};
|
|
4561
|
+
const imported = queueTableDesignerCsvImport(
|
|
4562
|
+
exportFilename || getCurrentDataTableExportFilename('csv', exportData.filename),
|
|
4563
|
+
exportData.content || '',
|
|
4564
|
+
{
|
|
4565
|
+
throwOnError: true,
|
|
4566
|
+
context: {
|
|
4567
|
+
catalogTables: state.tableDesigner.tables?.length
|
|
4568
|
+
? state.tableDesigner.tables
|
|
4569
|
+
: state.dataBrowser.tables,
|
|
4570
|
+
},
|
|
4571
|
+
},
|
|
4572
|
+
);
|
|
4573
|
+
|
|
4574
|
+
closeModalInternal();
|
|
4575
|
+
pushToast(
|
|
4576
|
+
`Table draft created from ${imported.importedRowCount} row${imported.importedRowCount === 1 ? '' : 's'}.`,
|
|
4577
|
+
'success',
|
|
4578
|
+
);
|
|
4579
|
+
return imported;
|
|
4580
|
+
} catch (error) {
|
|
4581
|
+
reportCurrentDataTableExportError(error);
|
|
4582
|
+
return null;
|
|
4583
|
+
} finally {
|
|
4584
|
+
finishCurrentDataTableExport();
|
|
4178
4585
|
}
|
|
4179
4586
|
}
|
|
4180
4587
|
|
|
4588
|
+
export async function exportCurrentDataTableCsv() {
|
|
4589
|
+
return exportCurrentDataTableFormat('csv');
|
|
4590
|
+
}
|
|
4591
|
+
|
|
4181
4592
|
export function sortEditorResultsByColumn(columnName) {
|
|
4182
4593
|
const normalizedColumn = String(columnName ?? '').trim();
|
|
4183
4594
|
const result = state.editor.result;
|