sqlite-hub 0.8.7 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +11 -0
- package/frontend/index.html +1 -1
- package/frontend/js/api.js +67 -0
- package/frontend/js/app.js +1398 -999
- package/frontend/js/components/modal.js +208 -3
- package/frontend/js/components/queryEditor.js +9 -4
- package/frontend/js/components/queryHistoryDetail.js +20 -10
- package/frontend/js/components/queryHistoryPanel.js +4 -3
- package/frontend/js/components/rowEditorPanel.js +26 -5
- package/frontend/js/components/sidebar.js +49 -5
- package/frontend/js/components/structureGraph.js +614 -632
- package/frontend/js/components/tableDesignerSqlPreview.js +24 -25
- package/frontend/js/lib/mediaTaggingDefaults.js +27 -0
- package/frontend/js/router.js +81 -71
- package/frontend/js/store.js +3091 -2184
- package/frontend/js/views/charts.js +2 -2
- package/frontend/js/views/data.js +28 -14
- package/frontend/js/views/editor.js +172 -177
- package/frontend/js/views/mediaTagging.js +861 -0
- package/frontend/styles/base.css +11 -1
- package/frontend/styles/components.css +48 -6
- package/frontend/styles/views.css +796 -0
- package/package.json +1 -1
- package/server/routes/charts.js +4 -1
- package/server/routes/data.js +14 -0
- package/server/routes/mediaTagging.js +166 -0
- package/server/routes/sql.js +1 -0
- package/server/server.js +4 -0
- package/server/services/sqlite/dataBrowserService.js +25 -0
- package/server/services/sqlite/exportService.js +31 -1
- package/server/services/sqlite/mediaTaggingService.js +1689 -0
- package/server/services/storage/appStateStore.js +321 -2
package/frontend/js/app.js
CHANGED
|
@@ -1,227 +1,423 @@
|
|
|
1
|
-
import { renderAppShell } from
|
|
2
|
-
import { renderModal } from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { renderAppShell } from './components/appShell.js';
|
|
2
|
+
import { renderModal } from './components/modal.js';
|
|
3
|
+
import { renderQueryHistoryDetail } from './components/queryHistoryDetail.js';
|
|
4
|
+
import { renderQueryHistoryListItem } from './components/queryHistoryPanel.js';
|
|
5
|
+
import { renderSidebar } from './components/sidebar.js';
|
|
6
|
+
import { renderStatusBar } from './components/statusBar.js';
|
|
5
7
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from
|
|
8
|
+
mountStructureGraph,
|
|
9
|
+
teardownStructureGraph,
|
|
10
|
+
resetPersistedStructureGraphState,
|
|
11
|
+
} from './components/structureGraph.js';
|
|
10
12
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from
|
|
15
|
-
import { renderToasts } from
|
|
16
|
-
import { renderTopNav } from
|
|
17
|
-
import { createRouter } from
|
|
13
|
+
exportQueryChartAsPng,
|
|
14
|
+
mountQueryChartRenderer,
|
|
15
|
+
teardownQueryChartRenderer,
|
|
16
|
+
} from './components/queryChartRenderer.js';
|
|
17
|
+
import { renderToasts } from './components/toast.js';
|
|
18
|
+
import { renderTopNav } from './components/topNav.js';
|
|
19
|
+
import { createRouter } from './router.js';
|
|
18
20
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
21
|
+
createActiveConnectionBackup,
|
|
22
|
+
clearCurrentQuery,
|
|
23
|
+
clearDataRowSelection,
|
|
24
|
+
clearEditorRowSelection,
|
|
25
|
+
clearEditorResults,
|
|
26
|
+
clearQueryHistorySelection,
|
|
27
|
+
closeModal,
|
|
28
|
+
dismissMediaTaggingIssue,
|
|
29
|
+
dismissToast,
|
|
30
|
+
executeCurrentQuery,
|
|
31
|
+
exportCurrentDataTableCsv,
|
|
32
|
+
exportCurrentQueryCsv,
|
|
33
|
+
getState,
|
|
34
|
+
initializeApp,
|
|
35
|
+
loadMoreQueryHistory,
|
|
36
|
+
openModal,
|
|
37
|
+
openOverviewInFinder,
|
|
38
|
+
openQueryHistoryInEditor,
|
|
39
|
+
openDeleteDataRowModal,
|
|
40
|
+
openDeleteEditorRowModal,
|
|
41
|
+
openDeleteQueryHistoryModal,
|
|
42
|
+
openDeleteQueryChartModal,
|
|
43
|
+
openDataRowByIdentity,
|
|
44
|
+
openEditConnectionModal,
|
|
45
|
+
openCreateQueryChartModal,
|
|
46
|
+
openEditQueryChartModal,
|
|
47
|
+
refreshCurrentRoute,
|
|
48
|
+
refreshMediaTaggingPreview,
|
|
49
|
+
removeConnection,
|
|
50
|
+
removeCurrentMediaTag,
|
|
51
|
+
resetMediaTaggingQueriesToDefault,
|
|
52
|
+
resetSkippedMediaTaggingItems,
|
|
53
|
+
runQueryHistoryItem,
|
|
54
|
+
skipCurrentMediaTaggingItem,
|
|
55
|
+
saveCurrentQueryChartDraft,
|
|
56
|
+
saveCurrentMediaTaggingConfig,
|
|
57
|
+
selectDataRow,
|
|
58
|
+
selectEditorRow,
|
|
59
|
+
selectConnection,
|
|
60
|
+
selectQueryHistoryItem,
|
|
61
|
+
selectStructureEntry,
|
|
62
|
+
setTableDesignerSearchQuery,
|
|
63
|
+
setTableDesignerSqlPreviewVisibility,
|
|
64
|
+
toggleStructureTablesPanel,
|
|
65
|
+
setDataPage,
|
|
66
|
+
setDataPageSize,
|
|
67
|
+
setDataSearchColumn,
|
|
68
|
+
setDataSearchQuery,
|
|
69
|
+
toggleDataTablesPanel,
|
|
70
|
+
setCurrentQuery,
|
|
71
|
+
setChartsHeightPreset,
|
|
72
|
+
setEditorPanelVisibility,
|
|
73
|
+
setEditorTab,
|
|
74
|
+
submitDeleteChartConfirmation,
|
|
75
|
+
submitCreateMediaTaggingTagTable,
|
|
76
|
+
submitCreateMediaTaggingMappingTable,
|
|
77
|
+
submitDeleteQueryHistoryConfirmation,
|
|
78
|
+
setQueryHistoryPanelVisibility,
|
|
79
|
+
sortDataTableByColumn,
|
|
80
|
+
sortEditorResultsByColumn,
|
|
81
|
+
setQueryHistorySearchInput,
|
|
82
|
+
setQueryHistoryTab,
|
|
83
|
+
setRoute,
|
|
84
|
+
saveQueryHistoryNotes,
|
|
85
|
+
saveQueryHistoryTitle,
|
|
86
|
+
saveCurrentTableDesignerDraft,
|
|
87
|
+
toggleChartsResultsPanel,
|
|
88
|
+
toggleChartsSqlPanel,
|
|
89
|
+
setMediaTaggingWorkflowMediaDetailsVisible,
|
|
90
|
+
queueTableDesignerCsvImport,
|
|
91
|
+
showToast,
|
|
92
|
+
submitCreateConnection,
|
|
93
|
+
createCurrentMediaTag,
|
|
94
|
+
submitDeleteRowConfirmation,
|
|
95
|
+
submitDataRowUpdate,
|
|
96
|
+
submitEditorRowUpdate,
|
|
97
|
+
submitEditConnection,
|
|
98
|
+
submitImportSql,
|
|
99
|
+
submitOpenConnection,
|
|
100
|
+
subscribe,
|
|
101
|
+
toggleCurrentMediaTagSelection,
|
|
102
|
+
toggleQueryHistorySavedState,
|
|
103
|
+
updateCurrentMediaTaggingField,
|
|
104
|
+
updateCurrentMediaTaggingTagFormField,
|
|
105
|
+
updateCurrentQueryChartDraftConfigField,
|
|
106
|
+
updateCurrentQueryChartDraftField,
|
|
107
|
+
updateCurrentTableDesignerColumnField,
|
|
108
|
+
updateCurrentTableDesignerField,
|
|
109
|
+
addCurrentTableDesignerColumn,
|
|
110
|
+
applyCurrentMediaTaggingSelection,
|
|
111
|
+
removeCurrentTableDesignerColumn,
|
|
112
|
+
} from './store.js';
|
|
113
|
+
import { renderChartsView } from './views/charts.js';
|
|
114
|
+
import { renderConnectionsView } from './views/connections.js';
|
|
115
|
+
import { renderDataRowEditorPanel, renderDataView } from './views/data.js';
|
|
116
|
+
import { renderEditorView } from './views/editor.js';
|
|
117
|
+
import { renderLandingView } from './views/landing.js';
|
|
118
|
+
import { renderMediaTaggingView } from './views/mediaTagging.js';
|
|
119
|
+
import { renderOverviewView } from './views/overview.js';
|
|
120
|
+
import { renderSettingsView } from './views/settings.js';
|
|
121
|
+
import { renderStructureView } from './views/structure.js';
|
|
122
|
+
import { renderTableDesignerView } from './views/tableDesigner.js';
|
|
123
|
+
import { highlightSql } from './utils/format.js';
|
|
124
|
+
|
|
125
|
+
const appRoot = document.querySelector('#app');
|
|
106
126
|
|
|
107
127
|
appRoot.innerHTML = renderAppShell();
|
|
108
128
|
|
|
109
129
|
const shellRefs = {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
shell: document.querySelector('.app-shell'),
|
|
131
|
+
topNav: document.querySelector('#top-nav'),
|
|
132
|
+
sidebar: document.querySelector('#sidebar'),
|
|
133
|
+
view: document.querySelector('#app-view'),
|
|
134
|
+
panel: document.querySelector('#app-panel'),
|
|
135
|
+
statusBar: document.querySelector('#status-bar'),
|
|
136
|
+
modal: document.querySelector('#modal-root'),
|
|
137
|
+
toast: document.querySelector('#toast-root'),
|
|
118
138
|
};
|
|
119
139
|
let lastRenderedRoutePath = null;
|
|
120
|
-
let lastRenderedTopNavMarkup =
|
|
121
|
-
let lastRenderedSidebarMarkup =
|
|
122
|
-
let lastRenderedStatusBarMarkup =
|
|
123
|
-
let lastRenderedMainMarkup =
|
|
124
|
-
let lastRenderedPanelMarkup =
|
|
125
|
-
let lastRenderedModalMarkup =
|
|
126
|
-
let lastRenderedToastMarkup =
|
|
140
|
+
let lastRenderedTopNavMarkup = '';
|
|
141
|
+
let lastRenderedSidebarMarkup = '';
|
|
142
|
+
let lastRenderedStatusBarMarkup = '';
|
|
143
|
+
let lastRenderedMainMarkup = '';
|
|
144
|
+
let lastRenderedPanelMarkup = '';
|
|
145
|
+
let lastRenderedModalMarkup = '';
|
|
146
|
+
let lastRenderedToastMarkup = '';
|
|
127
147
|
let lastRenderedPanelOpen = false;
|
|
128
148
|
let lastRenderedLockedRoute = false;
|
|
129
149
|
let pendingNewTableDesignerAutofocus = false;
|
|
130
150
|
let pendingQueryEditorFocus = false;
|
|
131
151
|
|
|
132
152
|
function invalidateMainRenderCache() {
|
|
133
|
-
|
|
153
|
+
lastRenderedMainMarkup = null;
|
|
134
154
|
}
|
|
135
155
|
|
|
136
156
|
function resetStructureGraphForDatabaseChange() {
|
|
137
|
-
|
|
157
|
+
resetPersistedStructureGraphState();
|
|
138
158
|
}
|
|
139
159
|
|
|
140
160
|
function renderQueryHighlightMarkup(query) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
if (query) {
|
|
162
|
+
return highlightSql(query);
|
|
163
|
+
}
|
|
144
164
|
|
|
145
|
-
|
|
165
|
+
return '<span class="text-on-surface-variant/35">SELECT name FROM sqlite_master WHERE type = \'table\';</span>';
|
|
146
166
|
}
|
|
147
167
|
|
|
148
168
|
function syncQueryEditorHighlight(textarea) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
169
|
+
if (!(textarea instanceof HTMLTextAreaElement)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
152
172
|
|
|
153
|
-
|
|
154
|
-
|
|
173
|
+
const layer = textarea.closest('.query-editor-layer');
|
|
174
|
+
const highlightNode = layer?.querySelector('[data-query-editor-highlight]');
|
|
155
175
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
176
|
+
if (!(highlightNode instanceof HTMLElement)) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
159
179
|
|
|
160
|
-
|
|
180
|
+
highlightNode.innerHTML = renderQueryHighlightMarkup(textarea.value);
|
|
161
181
|
}
|
|
162
182
|
|
|
163
183
|
function syncQueryEditorScroll(textarea) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
184
|
+
if (!(textarea instanceof HTMLTextAreaElement)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
167
187
|
|
|
168
|
-
|
|
169
|
-
|
|
188
|
+
const layer = textarea.closest('.query-editor-layer');
|
|
189
|
+
const highlightNode = layer?.querySelector('[data-query-editor-highlight]');
|
|
190
|
+
const gutterNode = textarea.closest('.query-editor-shell')?.querySelector('[data-query-editor-gutter]');
|
|
191
|
+
|
|
192
|
+
if (!(highlightNode instanceof HTMLElement)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
170
195
|
|
|
171
|
-
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
196
|
+
highlightNode.style.transform = `translate(${-textarea.scrollLeft}px, ${-textarea.scrollTop}px)`;
|
|
174
197
|
|
|
175
|
-
|
|
198
|
+
if (gutterNode instanceof HTMLElement) {
|
|
199
|
+
gutterNode.style.transform = `translateY(${-textarea.scrollTop}px)`;
|
|
200
|
+
}
|
|
176
201
|
}
|
|
177
202
|
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
203
|
+
function syncMediaTagSelectionUi(input, checked) {
|
|
204
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
181
207
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
208
|
+
const tagOption = input.closest('.media-tagging-tag-option');
|
|
209
|
+
input.checked = Boolean(checked);
|
|
210
|
+
tagOption?.classList.toggle('is-selected', Boolean(checked));
|
|
211
|
+
syncMediaTaggingApplyButtonUi(input);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function syncMediaTaggingApplyButtonUi(node) {
|
|
215
|
+
if (!(node instanceof HTMLElement)) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const tagPanel = node.closest('.media-tagging-tag-panel');
|
|
220
|
+
|
|
221
|
+
if (!(tagPanel instanceof HTMLElement)) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const applyButton = tagPanel.querySelector('[data-action="apply-media-tagging"]');
|
|
226
|
+
|
|
227
|
+
if (!(applyButton instanceof HTMLButtonElement)) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const selectedCount = tagPanel.querySelectorAll('.media-tagging-tag-option__checkbox:checked').length;
|
|
232
|
+
const canApply = applyButton.dataset.canApply === 'true';
|
|
233
|
+
applyButton.textContent = `${selectedCount} tagged & next`;
|
|
234
|
+
applyButton.disabled = !canApply || selectedCount < 1;
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function syncMediaTaggingCurrentMediaUi(button, detailsVisible) {
|
|
239
|
+
if (!(button instanceof HTMLButtonElement)) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const preview = button.closest('.media-tagging-preview');
|
|
244
|
+
|
|
245
|
+
if (!(preview instanceof HTMLElement)) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const nextVisible = Boolean(detailsVisible);
|
|
250
|
+
const expandedLabel = button.dataset.expandedLabel || 'Shrink Media Viewer';
|
|
251
|
+
const collapsedLabel = button.dataset.collapsedLabel || 'Show Media Viewer';
|
|
252
|
+
const expandedMarkup = `<span class="material-symbols-outlined">visibility_off</span> ${expandedLabel}`;
|
|
253
|
+
|
|
254
|
+
preview.classList.toggle('media-tagging-preview--meta-hidden', !nextVisible);
|
|
255
|
+
button.dataset.nextValue = nextVisible ? 'false' : 'true';
|
|
256
|
+
button.setAttribute('aria-expanded', nextVisible ? 'true' : 'false');
|
|
257
|
+
button.innerHTML = nextVisible ? expandedMarkup : collapsedLabel;
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function syncMediaTaggingTagSearchUi(input) {
|
|
262
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const tagPanel = input.closest('.media-tagging-tag-panel');
|
|
267
|
+
|
|
268
|
+
if (!(tagPanel instanceof HTMLElement)) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const normalizedQuery = input.value.trim().toLowerCase();
|
|
273
|
+
const tagOptions = tagPanel.querySelectorAll('.media-tagging-tag-option');
|
|
274
|
+
|
|
275
|
+
for (const tagOption of tagOptions) {
|
|
276
|
+
if (!(tagOption instanceof HTMLElement)) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const searchText = String(tagOption.dataset.tagSearchText ?? '').trim().toLowerCase();
|
|
281
|
+
tagOption.hidden = Boolean(normalizedQuery) && !searchText.includes(normalizedQuery);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function syncDataRowSelectionUi(selectedRowIndex = null) {
|
|
288
|
+
if (getState().route.name !== 'data') {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const rowNodes = shellRefs.view.querySelectorAll('[data-action="select-data-row"][data-row-index]');
|
|
293
|
+
|
|
294
|
+
for (const rowNode of rowNodes) {
|
|
295
|
+
if (!(rowNode instanceof HTMLElement)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
rowNode.classList.toggle('is-selected', rowNode.dataset.rowIndex === String(selectedRowIndex));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const panelMarkup = renderDataRowEditorPanel(getState());
|
|
303
|
+
const panelOpen = Boolean(panelMarkup);
|
|
304
|
+
|
|
305
|
+
shellRefs.panel.innerHTML = panelMarkup;
|
|
306
|
+
shellRefs.shell.classList.toggle('panel-open', panelOpen);
|
|
307
|
+
lastRenderedPanelMarkup = panelMarkup;
|
|
308
|
+
lastRenderedPanelOpen = panelOpen;
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function syncQueryHistoryUi(historyId) {
|
|
313
|
+
const state = getState();
|
|
314
|
+
const numericId = Number(historyId);
|
|
315
|
+
|
|
316
|
+
if (!Number.isInteger(numericId) || numericId < 1) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const historyItem = state.editor.history.find(entry => Number(entry.id) === numericId) ?? state.editor.historyDetail ?? null;
|
|
321
|
+
const listItemNode = shellRefs.view.querySelector(
|
|
322
|
+
`[data-action="select-query-history-item"][data-history-id="${String(numericId)}"]`,
|
|
323
|
+
)?.closest('.query-history-item');
|
|
324
|
+
|
|
325
|
+
if (historyItem && listItemNode instanceof HTMLElement) {
|
|
326
|
+
listItemNode.outerHTML = renderQueryHistoryListItem(
|
|
327
|
+
historyItem,
|
|
328
|
+
state.editor.historyActiveId,
|
|
329
|
+
state.editor.historySelectedId,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (state.editor.historySelectedId === numericId) {
|
|
334
|
+
const panelMarkup = renderQueryHistoryDetail({
|
|
335
|
+
item: state.editor.historyDetail,
|
|
336
|
+
runs: state.editor.historyRuns,
|
|
337
|
+
loading: state.editor.historyDetailLoading,
|
|
338
|
+
error: state.editor.historyDetailError,
|
|
339
|
+
});
|
|
340
|
+
const panelOpen = Boolean(panelMarkup);
|
|
341
|
+
|
|
342
|
+
shellRefs.panel.innerHTML = panelMarkup;
|
|
343
|
+
shellRefs.shell.classList.toggle('panel-open', panelOpen);
|
|
344
|
+
lastRenderedPanelMarkup = panelMarkup;
|
|
345
|
+
lastRenderedPanelOpen = panelOpen;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function syncQueryHistorySelectionUi(selectedHistoryId = null) {
|
|
352
|
+
const historyButtons = shellRefs.view.querySelectorAll(
|
|
353
|
+
'[data-action="select-query-history-item"][data-history-id]',
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
for (const button of historyButtons) {
|
|
357
|
+
if (!(button instanceof HTMLElement) || !button.classList.contains('query-history-icon-button')) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
button.classList.toggle('is-active', button.dataset.historyId === String(selectedHistoryId));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (selectedHistoryId === null) {
|
|
365
|
+
shellRefs.panel.innerHTML = '';
|
|
366
|
+
shellRefs.shell.classList.remove('panel-open');
|
|
367
|
+
lastRenderedPanelMarkup = '';
|
|
368
|
+
lastRenderedPanelOpen = false;
|
|
369
|
+
}
|
|
190
370
|
|
|
191
|
-
|
|
192
|
-
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function readFileAsBase64(file) {
|
|
375
|
+
return new Promise((resolve, reject) => {
|
|
376
|
+
const reader = new FileReader();
|
|
377
|
+
|
|
378
|
+
reader.onload = () => {
|
|
379
|
+
const result = String(reader.result ?? '');
|
|
380
|
+
const markerIndex = result.indexOf(',');
|
|
381
|
+
resolve(markerIndex >= 0 ? result.slice(markerIndex + 1) : result);
|
|
382
|
+
};
|
|
383
|
+
reader.onerror = () => {
|
|
384
|
+
reject(reader.error ?? new Error('The selected logo could not be read.'));
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
reader.readAsDataURL(file);
|
|
388
|
+
});
|
|
193
389
|
}
|
|
194
390
|
|
|
195
391
|
function readFileAsText(file) {
|
|
196
|
-
|
|
197
|
-
|
|
392
|
+
return new Promise((resolve, reject) => {
|
|
393
|
+
const reader = new FileReader();
|
|
198
394
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
395
|
+
reader.onload = () => {
|
|
396
|
+
resolve(String(reader.result ?? ''));
|
|
397
|
+
};
|
|
398
|
+
reader.onerror = () => {
|
|
399
|
+
reject(reader.error ?? new Error('The selected CSV file could not be read.'));
|
|
400
|
+
};
|
|
205
401
|
|
|
206
|
-
|
|
207
|
-
|
|
402
|
+
reader.readAsText(file);
|
|
403
|
+
});
|
|
208
404
|
}
|
|
209
405
|
|
|
210
406
|
async function buildConnectionLogoUpload(file) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
407
|
+
if (!(file instanceof File) || !file.size) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
fileName: file.name,
|
|
413
|
+
mimeType: file.type,
|
|
414
|
+
base64: await readFileAsBase64(file),
|
|
415
|
+
};
|
|
220
416
|
}
|
|
221
417
|
|
|
222
418
|
function renderNotFoundView() {
|
|
223
|
-
|
|
224
|
-
|
|
419
|
+
return {
|
|
420
|
+
main: `
|
|
225
421
|
<section class="landing-view machined-grid px-6">
|
|
226
422
|
<div class="text-center z-10">
|
|
227
423
|
<p class="font-mono text-[10px] uppercase tracking-[0.3em] text-primary-container/40">
|
|
@@ -241,933 +437,1136 @@ function renderNotFoundView() {
|
|
|
241
437
|
</div>
|
|
242
438
|
</section>
|
|
243
439
|
`,
|
|
244
|
-
|
|
245
|
-
|
|
440
|
+
panel: '',
|
|
441
|
+
};
|
|
246
442
|
}
|
|
247
443
|
|
|
248
444
|
function resolveView(state) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
445
|
+
switch (state.route.name) {
|
|
446
|
+
case 'landing':
|
|
447
|
+
return renderLandingView(state);
|
|
448
|
+
case 'connections':
|
|
449
|
+
return renderConnectionsView(state);
|
|
450
|
+
case 'overview':
|
|
451
|
+
return renderOverviewView(state);
|
|
452
|
+
case 'charts':
|
|
453
|
+
return renderChartsView(state);
|
|
454
|
+
case 'data':
|
|
455
|
+
return renderDataView(state);
|
|
456
|
+
case 'editor':
|
|
457
|
+
return renderEditorView(state, { isResultsRoute: false });
|
|
458
|
+
case 'editorResults':
|
|
459
|
+
return renderEditorView(state, { isResultsRoute: true });
|
|
460
|
+
case 'structure':
|
|
461
|
+
return renderStructureView(state);
|
|
462
|
+
case 'tableDesigner':
|
|
463
|
+
return renderTableDesignerView(state);
|
|
464
|
+
case 'mediaTaggingSetup':
|
|
465
|
+
return renderMediaTaggingView(state, { subView: 'setup' });
|
|
466
|
+
case 'mediaTaggingQueue':
|
|
467
|
+
return renderMediaTaggingView(state, { subView: 'queue' });
|
|
468
|
+
case 'settings':
|
|
469
|
+
return renderSettingsView(state);
|
|
470
|
+
default:
|
|
471
|
+
return renderNotFoundView();
|
|
472
|
+
}
|
|
273
473
|
}
|
|
274
474
|
|
|
275
475
|
function captureFocusedInputState() {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
476
|
+
const activeElement = document.activeElement;
|
|
477
|
+
|
|
478
|
+
if (
|
|
479
|
+
!activeElement ||
|
|
480
|
+
!(
|
|
481
|
+
activeElement instanceof HTMLInputElement ||
|
|
482
|
+
activeElement instanceof HTMLTextAreaElement ||
|
|
483
|
+
activeElement instanceof HTMLSelectElement
|
|
484
|
+
)
|
|
485
|
+
) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const { bind } = activeElement.dataset;
|
|
490
|
+
if (!bind) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
bind,
|
|
496
|
+
field: activeElement.dataset.field ?? null,
|
|
497
|
+
columnId: activeElement.dataset.columnId ?? null,
|
|
498
|
+
selectionStart: activeElement.selectionStart,
|
|
499
|
+
selectionEnd: activeElement.selectionEnd,
|
|
500
|
+
selectionDirection: activeElement.selectionDirection,
|
|
501
|
+
scrollTop: activeElement.scrollTop,
|
|
502
|
+
scrollLeft: activeElement.scrollLeft,
|
|
503
|
+
};
|
|
304
504
|
}
|
|
305
505
|
|
|
306
506
|
function buildFocusedInputSelectors(snapshot) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
507
|
+
if (!snapshot?.bind) {
|
|
508
|
+
return [];
|
|
509
|
+
}
|
|
310
510
|
|
|
311
|
-
|
|
312
|
-
|
|
511
|
+
const selectors = [];
|
|
512
|
+
const bindSelector = `[data-bind="${CSS.escape(snapshot.bind)}"]`;
|
|
313
513
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
514
|
+
if (snapshot.columnId && snapshot.field) {
|
|
515
|
+
selectors.push(
|
|
516
|
+
`${bindSelector}[data-column-id="${CSS.escape(snapshot.columnId)}"][data-field="${CSS.escape(snapshot.field)}"]`,
|
|
517
|
+
);
|
|
518
|
+
}
|
|
319
519
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
520
|
+
if (snapshot.field) {
|
|
521
|
+
selectors.push(`${bindSelector}[data-field="${CSS.escape(snapshot.field)}"]`);
|
|
522
|
+
}
|
|
323
523
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
524
|
+
if (snapshot.columnId) {
|
|
525
|
+
selectors.push(`${bindSelector}[data-column-id="${CSS.escape(snapshot.columnId)}"]`);
|
|
526
|
+
}
|
|
327
527
|
|
|
328
|
-
|
|
329
|
-
|
|
528
|
+
selectors.push(bindSelector);
|
|
529
|
+
return selectors;
|
|
330
530
|
}
|
|
331
531
|
|
|
332
532
|
function restoreFocusedInputState(snapshot) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const nextElement = buildFocusedInputSelectors(snapshot)
|
|
338
|
-
.map((selector) => document.querySelector(selector))
|
|
339
|
-
.find(
|
|
340
|
-
(candidate) =>
|
|
341
|
-
candidate instanceof HTMLInputElement ||
|
|
342
|
-
candidate instanceof HTMLTextAreaElement ||
|
|
343
|
-
candidate instanceof HTMLSelectElement
|
|
344
|
-
);
|
|
533
|
+
if (!snapshot) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
345
536
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
nextElement.focus({ preventScroll: true });
|
|
358
|
-
|
|
359
|
-
if (
|
|
360
|
-
(nextElement instanceof HTMLInputElement || nextElement instanceof HTMLTextAreaElement) &&
|
|
361
|
-
typeof snapshot.selectionStart === "number" &&
|
|
362
|
-
typeof snapshot.selectionEnd === "number"
|
|
363
|
-
) {
|
|
364
|
-
nextElement.setSelectionRange(
|
|
365
|
-
snapshot.selectionStart,
|
|
366
|
-
snapshot.selectionEnd,
|
|
367
|
-
snapshot.selectionDirection || "none"
|
|
368
|
-
);
|
|
369
|
-
}
|
|
537
|
+
const nextElement = buildFocusedInputSelectors(snapshot)
|
|
538
|
+
.map(selector => document.querySelector(selector))
|
|
539
|
+
.find(
|
|
540
|
+
candidate =>
|
|
541
|
+
candidate instanceof HTMLInputElement ||
|
|
542
|
+
candidate instanceof HTMLTextAreaElement ||
|
|
543
|
+
candidate instanceof HTMLSelectElement,
|
|
544
|
+
);
|
|
370
545
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
546
|
+
if (
|
|
547
|
+
!nextElement ||
|
|
548
|
+
!(
|
|
549
|
+
nextElement instanceof HTMLInputElement ||
|
|
550
|
+
nextElement instanceof HTMLTextAreaElement ||
|
|
551
|
+
nextElement instanceof HTMLSelectElement
|
|
552
|
+
)
|
|
553
|
+
) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
nextElement.focus({ preventScroll: true });
|
|
558
|
+
|
|
559
|
+
if (
|
|
560
|
+
(nextElement instanceof HTMLInputElement || nextElement instanceof HTMLTextAreaElement) &&
|
|
561
|
+
typeof snapshot.selectionStart === 'number' &&
|
|
562
|
+
typeof snapshot.selectionEnd === 'number'
|
|
563
|
+
) {
|
|
564
|
+
nextElement.setSelectionRange(
|
|
565
|
+
snapshot.selectionStart,
|
|
566
|
+
snapshot.selectionEnd,
|
|
567
|
+
snapshot.selectionDirection || 'none',
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
nextElement.scrollTop = snapshot.scrollTop;
|
|
572
|
+
nextElement.scrollLeft = snapshot.scrollLeft;
|
|
573
|
+
return true;
|
|
374
574
|
}
|
|
375
575
|
|
|
376
576
|
function focusNewTableDesignerNameField() {
|
|
377
|
-
|
|
378
|
-
'[data-bind="table-designer-field"][data-field="tableName"]'
|
|
379
|
-
);
|
|
577
|
+
const input = document.querySelector('[data-bind="table-designer-field"][data-field="tableName"]');
|
|
380
578
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
579
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
384
582
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
583
|
+
input.focus({ preventScroll: true });
|
|
584
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
585
|
+
return true;
|
|
388
586
|
}
|
|
389
587
|
|
|
390
588
|
function focusTableDesignerColumnNameField(columnId) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
589
|
+
if (!columnId) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
394
592
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
593
|
+
const input = document.querySelector(
|
|
594
|
+
`[data-bind="table-designer-column-field"][data-column-id="${CSS.escape(columnId)}"][data-field="name"]`,
|
|
595
|
+
);
|
|
398
596
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
597
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
402
600
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
601
|
+
input.focus({ preventScroll: true });
|
|
602
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
603
|
+
return true;
|
|
406
604
|
}
|
|
407
605
|
|
|
408
606
|
function focusQueryEditorInput() {
|
|
409
|
-
|
|
607
|
+
const input = document.querySelector('[data-bind="current-query"]');
|
|
410
608
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
609
|
+
if (!(input instanceof HTMLTextAreaElement)) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
414
612
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
613
|
+
input.focus({ preventScroll: true });
|
|
614
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
615
|
+
return true;
|
|
418
616
|
}
|
|
419
617
|
|
|
420
618
|
async function handleTableDesignerCsvImport(fileInput) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
619
|
+
if (!(fileInput instanceof HTMLInputElement)) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const file = fileInput.files?.[0];
|
|
424
624
|
|
|
425
|
-
|
|
625
|
+
if (!(file instanceof File)) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
426
628
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
629
|
+
if (!file.size) {
|
|
630
|
+
showToast('The selected CSV file is empty.', 'alert');
|
|
631
|
+
fileInput.value = '';
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
430
634
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
635
|
+
try {
|
|
636
|
+
const csvText = await readFileAsText(file);
|
|
637
|
+
const imported = queueTableDesignerCsvImport(file.name, csvText);
|
|
436
638
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
639
|
+
if (!imported) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
440
642
|
|
|
441
|
-
|
|
442
|
-
|
|
643
|
+
router.navigate('/table-designer/new');
|
|
644
|
+
showToast(
|
|
645
|
+
`Imported ${imported.columnCount} columns and ${imported.importedRowCount} row${
|
|
646
|
+
imported.importedRowCount === 1 ? '' : 's'
|
|
647
|
+
} from ${file.name}.`,
|
|
648
|
+
'success',
|
|
649
|
+
);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
showToast(error?.message || 'CSV import failed.', 'alert');
|
|
652
|
+
} finally {
|
|
653
|
+
fileInput.value = '';
|
|
443
654
|
}
|
|
444
|
-
|
|
445
|
-
router.navigate("/table-designer/new");
|
|
446
|
-
showToast(
|
|
447
|
-
`Imported ${imported.columnCount} columns and ${imported.importedRowCount} row${
|
|
448
|
-
imported.importedRowCount === 1 ? "" : "s"
|
|
449
|
-
} from ${file.name}.`,
|
|
450
|
-
"success"
|
|
451
|
-
);
|
|
452
|
-
} catch (error) {
|
|
453
|
-
showToast(error?.message || "CSV import failed.", "alert");
|
|
454
|
-
} finally {
|
|
455
|
-
fileInput.value = "";
|
|
456
|
-
}
|
|
457
655
|
}
|
|
458
656
|
|
|
459
657
|
function renderApp(state) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
658
|
+
const previousRoutePath = lastRenderedRoutePath;
|
|
659
|
+
const { main, panel } = resolveView(state);
|
|
660
|
+
const topNavMarkup = renderTopNav(state);
|
|
661
|
+
const sidebarMarkup = renderSidebar(state);
|
|
662
|
+
const statusBarMarkup = renderStatusBar(state);
|
|
663
|
+
const modalMarkup = renderModal(state);
|
|
664
|
+
const toastMarkup = renderToasts(state.toasts);
|
|
665
|
+
const isLockedRoute = [
|
|
666
|
+
'editor',
|
|
667
|
+
'editorResults',
|
|
668
|
+
'data',
|
|
669
|
+
'charts',
|
|
670
|
+
'structure',
|
|
671
|
+
'tableDesigner',
|
|
672
|
+
'mediaTaggingSetup',
|
|
673
|
+
'mediaTaggingQueue',
|
|
674
|
+
].includes(state.route.name);
|
|
675
|
+
const panelOpen = Boolean(panel);
|
|
676
|
+
const topNavChanged = topNavMarkup !== lastRenderedTopNavMarkup;
|
|
677
|
+
const sidebarChanged = sidebarMarkup !== lastRenderedSidebarMarkup;
|
|
678
|
+
const statusBarChanged = statusBarMarkup !== lastRenderedStatusBarMarkup;
|
|
679
|
+
const mainChanged = main !== lastRenderedMainMarkup;
|
|
680
|
+
const panelChanged = panel !== lastRenderedPanelMarkup;
|
|
681
|
+
const modalChanged = modalMarkup !== lastRenderedModalMarkup;
|
|
682
|
+
const panelOpenChanged = panelOpen !== lastRenderedPanelOpen;
|
|
683
|
+
const lockedRouteChanged = isLockedRoute !== lastRenderedLockedRoute;
|
|
684
|
+
const shellMarkupUnchanged =
|
|
685
|
+
state.route.path === lastRenderedRoutePath &&
|
|
686
|
+
!topNavChanged &&
|
|
687
|
+
!sidebarChanged &&
|
|
688
|
+
!statusBarChanged &&
|
|
689
|
+
!mainChanged &&
|
|
690
|
+
!panelChanged &&
|
|
691
|
+
!modalChanged &&
|
|
692
|
+
!panelOpenChanged &&
|
|
693
|
+
!lockedRouteChanged;
|
|
694
|
+
|
|
695
|
+
if (shellMarkupUnchanged && toastMarkup !== lastRenderedToastMarkup) {
|
|
696
|
+
shellRefs.toast.innerHTML = toastMarkup;
|
|
697
|
+
lastRenderedToastMarkup = toastMarkup;
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (shellMarkupUnchanged && toastMarkup === lastRenderedToastMarkup) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const focusedInput = captureFocusedInputState();
|
|
706
|
+
const isEnteringNewTableDesignerRoute =
|
|
707
|
+
state.route.name === 'tableDesigner' && state.route.params?.isNew && previousRoutePath !== state.route.path;
|
|
708
|
+
|
|
709
|
+
if (isEnteringNewTableDesignerRoute) {
|
|
710
|
+
pendingNewTableDesignerAutofocus = true;
|
|
711
|
+
} else if (state.route.name !== 'tableDesigner' || !state.route.params?.isNew) {
|
|
712
|
+
pendingNewTableDesignerAutofocus = false;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (mainChanged) {
|
|
716
|
+
teardownStructureGraph();
|
|
717
|
+
teardownQueryChartRenderer();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (topNavChanged) {
|
|
721
|
+
shellRefs.topNav.innerHTML = topNavMarkup;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (sidebarChanged) {
|
|
725
|
+
shellRefs.sidebar.innerHTML = sidebarMarkup;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (statusBarChanged) {
|
|
729
|
+
shellRefs.statusBar.innerHTML = statusBarMarkup;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (mainChanged) {
|
|
733
|
+
shellRefs.view.innerHTML = main;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (mainChanged || lockedRouteChanged) {
|
|
737
|
+
shellRefs.view.classList.toggle('app-main-scroll--locked', isLockedRoute);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (panelChanged) {
|
|
741
|
+
shellRefs.panel.innerHTML = panel;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (modalChanged) {
|
|
745
|
+
shellRefs.modal.innerHTML = modalMarkup;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (toastMarkup !== lastRenderedToastMarkup) {
|
|
749
|
+
shellRefs.toast.innerHTML = toastMarkup;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (panelChanged || panelOpenChanged) {
|
|
753
|
+
shellRefs.shell.classList.toggle('panel-open', panelOpen);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (pendingQueryEditorFocus && (state.route.name === 'editor' || state.route.name === 'editorResults')) {
|
|
757
|
+
if (focusQueryEditorInput()) {
|
|
758
|
+
pendingQueryEditorFocus = false;
|
|
759
|
+
}
|
|
760
|
+
} else if (
|
|
761
|
+
pendingNewTableDesignerAutofocus &&
|
|
762
|
+
state.route.name === 'tableDesigner' &&
|
|
763
|
+
state.route.params?.isNew &&
|
|
764
|
+
state.tableDesigner.draft?.mode === 'create'
|
|
765
|
+
) {
|
|
766
|
+
if (focusNewTableDesignerNameField()) {
|
|
767
|
+
pendingNewTableDesignerAutofocus = false;
|
|
768
|
+
}
|
|
769
|
+
} else {
|
|
770
|
+
restoreFocusedInputState(focusedInput);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
lastRenderedRoutePath = state.route.path;
|
|
774
|
+
lastRenderedTopNavMarkup = topNavMarkup;
|
|
775
|
+
lastRenderedSidebarMarkup = sidebarMarkup;
|
|
776
|
+
lastRenderedStatusBarMarkup = statusBarMarkup;
|
|
777
|
+
lastRenderedMainMarkup = main;
|
|
778
|
+
lastRenderedPanelMarkup = panel;
|
|
779
|
+
lastRenderedModalMarkup = modalMarkup;
|
|
484
780
|
lastRenderedToastMarkup = toastMarkup;
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const isEnteringNewTableDesignerRoute =
|
|
494
|
-
state.route.name === "tableDesigner" &&
|
|
495
|
-
state.route.params?.isNew &&
|
|
496
|
-
previousRoutePath !== state.route.path;
|
|
497
|
-
|
|
498
|
-
if (isEnteringNewTableDesignerRoute) {
|
|
499
|
-
pendingNewTableDesignerAutofocus = true;
|
|
500
|
-
} else if (state.route.name !== "tableDesigner" || !state.route.params?.isNew) {
|
|
501
|
-
pendingNewTableDesignerAutofocus = false;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
teardownStructureGraph();
|
|
505
|
-
teardownQueryChartRenderer();
|
|
506
|
-
shellRefs.topNav.innerHTML = topNavMarkup;
|
|
507
|
-
shellRefs.sidebar.innerHTML = sidebarMarkup;
|
|
508
|
-
shellRefs.statusBar.innerHTML = statusBarMarkup;
|
|
509
|
-
shellRefs.view.innerHTML = main;
|
|
510
|
-
shellRefs.view.classList.toggle("app-main-scroll--locked", isLockedRoute);
|
|
511
|
-
shellRefs.panel.innerHTML = panel;
|
|
512
|
-
shellRefs.modal.innerHTML = modalMarkup;
|
|
513
|
-
shellRefs.toast.innerHTML = toastMarkup;
|
|
514
|
-
shellRefs.shell.classList.toggle("panel-open", panelOpen);
|
|
515
|
-
|
|
516
|
-
if (
|
|
517
|
-
pendingQueryEditorFocus &&
|
|
518
|
-
(state.route.name === "editor" || state.route.name === "editorResults")
|
|
519
|
-
) {
|
|
520
|
-
if (focusQueryEditorInput()) {
|
|
521
|
-
pendingQueryEditorFocus = false;
|
|
522
|
-
}
|
|
523
|
-
} else if (
|
|
524
|
-
pendingNewTableDesignerAutofocus &&
|
|
525
|
-
state.route.name === "tableDesigner" &&
|
|
526
|
-
state.route.params?.isNew &&
|
|
527
|
-
state.tableDesigner.draft?.mode === "create"
|
|
528
|
-
) {
|
|
529
|
-
if (focusNewTableDesignerNameField()) {
|
|
530
|
-
pendingNewTableDesignerAutofocus = false;
|
|
531
|
-
}
|
|
532
|
-
} else {
|
|
533
|
-
restoreFocusedInputState(focusedInput);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
lastRenderedRoutePath = state.route.path;
|
|
537
|
-
lastRenderedTopNavMarkup = topNavMarkup;
|
|
538
|
-
lastRenderedSidebarMarkup = sidebarMarkup;
|
|
539
|
-
lastRenderedStatusBarMarkup = statusBarMarkup;
|
|
540
|
-
lastRenderedMainMarkup = main;
|
|
541
|
-
lastRenderedPanelMarkup = panel;
|
|
542
|
-
lastRenderedModalMarkup = modalMarkup;
|
|
543
|
-
lastRenderedToastMarkup = toastMarkup;
|
|
544
|
-
lastRenderedPanelOpen = panelOpen;
|
|
545
|
-
lastRenderedLockedRoute = isLockedRoute;
|
|
546
|
-
|
|
547
|
-
if (state.route.name === "structure") {
|
|
548
|
-
mountStructureGraph(state).catch((error) => {
|
|
549
|
-
console.error("Failed to mount structure graph.", error);
|
|
550
|
-
});
|
|
551
|
-
}
|
|
781
|
+
lastRenderedPanelOpen = panelOpen;
|
|
782
|
+
lastRenderedLockedRoute = isLockedRoute;
|
|
783
|
+
|
|
784
|
+
if (state.route.name === 'structure') {
|
|
785
|
+
mountStructureGraph(state).catch(error => {
|
|
786
|
+
console.error('Failed to mount structure graph.', error);
|
|
787
|
+
});
|
|
788
|
+
}
|
|
552
789
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
790
|
+
if (state.route.name === 'charts') {
|
|
791
|
+
mountQueryChartRenderer(state);
|
|
792
|
+
}
|
|
556
793
|
}
|
|
557
794
|
|
|
558
|
-
const router = createRouter(
|
|
559
|
-
|
|
795
|
+
const router = createRouter(route => {
|
|
796
|
+
setRoute(route);
|
|
560
797
|
});
|
|
561
798
|
|
|
562
799
|
async function executeEditorQueryAndNavigate() {
|
|
563
|
-
|
|
564
|
-
|
|
800
|
+
const success = await executeCurrentQuery();
|
|
801
|
+
router.navigate(success ? '/editor/results' : '/editor');
|
|
565
802
|
}
|
|
566
803
|
|
|
567
804
|
async function handleAction(actionNode) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if (isActiveConnection) {
|
|
610
|
-
resetStructureGraphForDatabaseChange();
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const removed = await removeConnection(actionNode.dataset.connectionId);
|
|
614
|
-
if (removed) {
|
|
615
|
-
const nextState = getState();
|
|
616
|
-
if (!nextState.connections.active && nextState.route.name !== "connections") {
|
|
617
|
-
router.navigate("/connections");
|
|
618
|
-
} else {
|
|
619
|
-
await refreshCurrentRoute();
|
|
805
|
+
const { action } = actionNode.dataset;
|
|
806
|
+
|
|
807
|
+
switch (action) {
|
|
808
|
+
case 'navigate':
|
|
809
|
+
router.navigate(actionNode.dataset.to ?? '/');
|
|
810
|
+
return;
|
|
811
|
+
case 'refresh-view':
|
|
812
|
+
await refreshCurrentRoute();
|
|
813
|
+
return;
|
|
814
|
+
case 'open-modal':
|
|
815
|
+
openModal(actionNode.dataset.modal);
|
|
816
|
+
return;
|
|
817
|
+
case 'open-create-query-chart-modal':
|
|
818
|
+
openCreateQueryChartModal();
|
|
819
|
+
return;
|
|
820
|
+
case 'open-edit-query-chart-modal':
|
|
821
|
+
openEditQueryChartModal(actionNode.dataset.chartId);
|
|
822
|
+
return;
|
|
823
|
+
case 'open-delete-query-chart-modal':
|
|
824
|
+
openDeleteQueryChartModal(actionNode.dataset.chartId);
|
|
825
|
+
return;
|
|
826
|
+
case 'open-delete-query-history-modal':
|
|
827
|
+
openDeleteQueryHistoryModal(actionNode.dataset.historyId);
|
|
828
|
+
return;
|
|
829
|
+
case 'edit-connection':
|
|
830
|
+
openEditConnectionModal(actionNode.dataset.connectionId);
|
|
831
|
+
return;
|
|
832
|
+
case 'close-modal':
|
|
833
|
+
closeModal();
|
|
834
|
+
return;
|
|
835
|
+
case 'dismiss-toast':
|
|
836
|
+
dismissToast(actionNode.dataset.toastId);
|
|
837
|
+
return;
|
|
838
|
+
case 'select-connection': {
|
|
839
|
+
resetStructureGraphForDatabaseChange();
|
|
840
|
+
const next = await selectConnection(actionNode.dataset.connectionId);
|
|
841
|
+
if (next) {
|
|
842
|
+
router.navigate('/overview');
|
|
843
|
+
}
|
|
844
|
+
return;
|
|
620
845
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
846
|
+
case 'remove-connection': {
|
|
847
|
+
const isActiveConnection = getState().connections.active?.id === actionNode.dataset.connectionId;
|
|
848
|
+
|
|
849
|
+
if (isActiveConnection) {
|
|
850
|
+
resetStructureGraphForDatabaseChange();
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const removed = await removeConnection(actionNode.dataset.connectionId);
|
|
854
|
+
if (removed) {
|
|
855
|
+
const nextState = getState();
|
|
856
|
+
if (!nextState.connections.active && nextState.route.name !== 'connections') {
|
|
857
|
+
router.navigate('/connections');
|
|
858
|
+
} else {
|
|
859
|
+
await refreshCurrentRoute();
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
case 'create-backup':
|
|
865
|
+
await createActiveConnectionBackup();
|
|
866
|
+
return;
|
|
867
|
+
case 'copy-media-tags': {
|
|
868
|
+
const tags = getState().mediaTagging.tags ?? [];
|
|
869
|
+
const formattedTags = tags
|
|
870
|
+
.map(tag => `${String(tag.label ?? '').trim()}${tag.isParentTag ? ' (parent)' : ''}`)
|
|
871
|
+
.filter(Boolean)
|
|
872
|
+
.join(', ');
|
|
873
|
+
|
|
874
|
+
if (!formattedTags) {
|
|
875
|
+
showToast('No tags available to copy.', 'alert');
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
try {
|
|
880
|
+
await navigator.clipboard.writeText(formattedTags);
|
|
881
|
+
showToast('Tags copied.', 'success');
|
|
882
|
+
} catch (error) {
|
|
883
|
+
showToast('Tags could not be copied.', 'alert');
|
|
884
|
+
}
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
case 'open-overview-in-finder':
|
|
888
|
+
await openOverviewInFinder();
|
|
889
|
+
return;
|
|
890
|
+
case 'execute-query': {
|
|
891
|
+
await executeEditorQueryAndNavigate();
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
case 'delete-data-row':
|
|
895
|
+
openDeleteDataRowModal(actionNode.dataset.rowIndex);
|
|
896
|
+
return;
|
|
897
|
+
case 'delete-editor-row':
|
|
898
|
+
openDeleteEditorRowModal(actionNode.dataset.rowIndex);
|
|
899
|
+
return;
|
|
900
|
+
case 'clear-query':
|
|
901
|
+
pendingQueryEditorFocus = true;
|
|
902
|
+
clearCurrentQuery();
|
|
903
|
+
if (getState().route.name === 'editorResults') {
|
|
904
|
+
router.navigate('/editor');
|
|
905
|
+
}
|
|
906
|
+
return;
|
|
907
|
+
case 'clear-results':
|
|
908
|
+
clearEditorResults();
|
|
909
|
+
router.navigate('/editor');
|
|
910
|
+
return;
|
|
911
|
+
case 'select-query-history-item':
|
|
912
|
+
if (actionNode.dataset.historyId) {
|
|
913
|
+
const pendingSelection = selectQueryHistoryItem(actionNode.dataset.historyId, { notify: false });
|
|
914
|
+
syncQueryHistorySelectionUi(actionNode.dataset.historyId);
|
|
915
|
+
syncQueryHistoryUi(actionNode.dataset.historyId);
|
|
916
|
+
await pendingSelection;
|
|
917
|
+
syncQueryHistoryUi(actionNode.dataset.historyId);
|
|
918
|
+
}
|
|
919
|
+
return;
|
|
920
|
+
case 'clear-query-history-selection':
|
|
921
|
+
clearQueryHistorySelection({ notify: false });
|
|
922
|
+
syncQueryHistorySelectionUi(null);
|
|
923
|
+
return;
|
|
924
|
+
case 'set-query-history-tab':
|
|
925
|
+
if (actionNode.dataset.tab) {
|
|
926
|
+
await setQueryHistoryTab(actionNode.dataset.tab);
|
|
927
|
+
}
|
|
928
|
+
return;
|
|
929
|
+
case 'toggle-query-history-panel':
|
|
930
|
+
setQueryHistoryPanelVisibility(
|
|
931
|
+
actionNode.dataset.nextValue ? actionNode.dataset.nextValue === 'true' : undefined,
|
|
932
|
+
);
|
|
933
|
+
return;
|
|
934
|
+
case 'toggle-editor-panel':
|
|
935
|
+
setEditorPanelVisibility(
|
|
936
|
+
actionNode.dataset.nextValue ? actionNode.dataset.nextValue === 'true' : undefined,
|
|
937
|
+
);
|
|
938
|
+
return;
|
|
939
|
+
case 'load-more-query-history':
|
|
940
|
+
await loadMoreQueryHistory();
|
|
941
|
+
return;
|
|
942
|
+
case 'open-query-history':
|
|
943
|
+
if (actionNode.dataset.historyId && openQueryHistoryInEditor(actionNode.dataset.historyId)) {
|
|
944
|
+
router.navigate('/editor');
|
|
945
|
+
}
|
|
946
|
+
return;
|
|
947
|
+
case 'run-query-history':
|
|
948
|
+
if (actionNode.dataset.historyId) {
|
|
949
|
+
const success = await runQueryHistoryItem(actionNode.dataset.historyId);
|
|
950
|
+
router.navigate(success ? '/editor/results' : '/editor');
|
|
951
|
+
}
|
|
952
|
+
return;
|
|
953
|
+
case 'toggle-query-history-saved':
|
|
954
|
+
if (actionNode.dataset.historyId) {
|
|
955
|
+
await toggleQueryHistorySavedState(
|
|
956
|
+
actionNode.dataset.historyId,
|
|
957
|
+
actionNode.dataset.nextValue === 'true',
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
return;
|
|
961
|
+
case 'toggle-charts-sql-panel':
|
|
962
|
+
toggleChartsSqlPanel();
|
|
963
|
+
return;
|
|
964
|
+
case 'toggle-charts-results-panel':
|
|
965
|
+
toggleChartsResultsPanel();
|
|
966
|
+
return;
|
|
967
|
+
case 'set-charts-height-preset':
|
|
968
|
+
if (actionNode.dataset.preset) {
|
|
969
|
+
setChartsHeightPreset(actionNode.dataset.preset);
|
|
970
|
+
}
|
|
971
|
+
return;
|
|
972
|
+
case 'export-query-chart-png':
|
|
973
|
+
if (actionNode.dataset.chartId && !exportQueryChartAsPng(actionNode.dataset.chartId)) {
|
|
974
|
+
showToast('The selected chart is not ready for PNG export.', 'alert');
|
|
975
|
+
}
|
|
976
|
+
return;
|
|
977
|
+
case 'set-editor-tab': {
|
|
978
|
+
const tab = actionNode.dataset.tab;
|
|
979
|
+
if (!tab) {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
setEditorTab(tab);
|
|
983
|
+
router.navigate(tab === 'results' ? '/editor/results' : '/editor');
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
case 'export-query-csv':
|
|
987
|
+
await exportCurrentQueryCsv();
|
|
988
|
+
return;
|
|
989
|
+
case 'export-data-csv':
|
|
990
|
+
await exportCurrentDataTableCsv();
|
|
991
|
+
return;
|
|
992
|
+
case 'toggle-data-tables':
|
|
993
|
+
toggleDataTablesPanel();
|
|
994
|
+
return;
|
|
995
|
+
case 'toggle-structure-tables':
|
|
996
|
+
toggleStructureTablesPanel();
|
|
997
|
+
return;
|
|
998
|
+
case 'select-structure-entry':
|
|
999
|
+
if (actionNode.dataset.entryName) {
|
|
1000
|
+
await selectStructureEntry(actionNode.dataset.entryName);
|
|
1001
|
+
}
|
|
1002
|
+
return;
|
|
1003
|
+
case 'add-table-designer-column':
|
|
1004
|
+
{
|
|
1005
|
+
const columnId = addCurrentTableDesignerColumn();
|
|
1006
|
+
if (columnId) {
|
|
1007
|
+
window.requestAnimationFrame(() => {
|
|
1008
|
+
focusTableDesignerColumnNameField(columnId);
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return;
|
|
1013
|
+
case 'remove-table-designer-column':
|
|
1014
|
+
if (actionNode.dataset.columnId) {
|
|
1015
|
+
removeCurrentTableDesignerColumn(actionNode.dataset.columnId);
|
|
1016
|
+
}
|
|
1017
|
+
return;
|
|
1018
|
+
case 'save-table-designer': {
|
|
1019
|
+
const savedTableName = await saveCurrentTableDesignerDraft();
|
|
1020
|
+
if (savedTableName) {
|
|
1021
|
+
router.navigate(`/table-designer/${encodeURIComponent(savedTableName)}`);
|
|
1022
|
+
}
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
case 'copy-table-designer-sql': {
|
|
1026
|
+
const sqlPreview = getState().tableDesigner.draft?.sqlPreview ?? '';
|
|
1027
|
+
if (!sqlPreview.trim()) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
try {
|
|
1032
|
+
await navigator.clipboard.writeText(sqlPreview);
|
|
1033
|
+
showToast('SQL preview copied.', 'success');
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
showToast('Clipboard access failed.', 'alert');
|
|
1036
|
+
}
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
case 'toggle-table-designer-sql-preview':
|
|
1040
|
+
setTableDesignerSqlPreviewVisibility(
|
|
1041
|
+
actionNode.dataset.nextValue ? actionNode.dataset.nextValue === 'true' : undefined,
|
|
1042
|
+
);
|
|
1043
|
+
return;
|
|
1044
|
+
case 'refresh-media-tagging-preview':
|
|
1045
|
+
await refreshMediaTaggingPreview();
|
|
1046
|
+
return;
|
|
1047
|
+
case 'dismiss-media-tagging-issue':
|
|
1048
|
+
dismissMediaTaggingIssue(actionNode.dataset.issueKey);
|
|
1049
|
+
return;
|
|
1050
|
+
case 'reset-media-tagging-queries':
|
|
1051
|
+
await resetMediaTaggingQueriesToDefault();
|
|
1052
|
+
return;
|
|
1053
|
+
case 'save-media-tagging':
|
|
1054
|
+
await saveCurrentMediaTaggingConfig();
|
|
1055
|
+
return;
|
|
1056
|
+
case 'create-media-tag':
|
|
1057
|
+
await createCurrentMediaTag();
|
|
1058
|
+
// Refocus the name field after creating a tag
|
|
1059
|
+
const nameInput = document.querySelector('[data-bind="media-tagging-tag-form-field"][data-field="name"]');
|
|
1060
|
+
if (nameInput instanceof HTMLInputElement) {
|
|
1061
|
+
nameInput.focus();
|
|
1062
|
+
}
|
|
1063
|
+
return;
|
|
1064
|
+
case 'remove-media-tag':
|
|
1065
|
+
await removeCurrentMediaTag(actionNode.dataset.tagKey);
|
|
1066
|
+
return;
|
|
1067
|
+
case 'skip-media-tagging-item':
|
|
1068
|
+
await skipCurrentMediaTaggingItem();
|
|
1069
|
+
return;
|
|
1070
|
+
case 'reset-skipped-media-tagging':
|
|
1071
|
+
await resetSkippedMediaTaggingItems();
|
|
1072
|
+
return;
|
|
1073
|
+
case 'apply-media-tagging':
|
|
1074
|
+
await applyCurrentMediaTaggingSelection();
|
|
1075
|
+
return;
|
|
1076
|
+
case 'toggle-media-tagging-current-media':
|
|
1077
|
+
if (actionNode instanceof HTMLButtonElement) {
|
|
1078
|
+
const nextValue = actionNode.dataset.nextValue !== 'false';
|
|
1079
|
+
syncMediaTaggingCurrentMediaUi(actionNode, nextValue);
|
|
1080
|
+
setMediaTaggingWorkflowMediaDetailsVisible(nextValue, { notify: false });
|
|
1081
|
+
}
|
|
1082
|
+
return;
|
|
1083
|
+
case 'open-media-tagging-current-in-data': {
|
|
1084
|
+
const currentState = getState();
|
|
1085
|
+
const mediaTableName = currentState.mediaTagging.draft?.mediaTable ?? '';
|
|
1086
|
+
const identity = currentState.mediaTagging.workflow?.currentItem?.identity ?? null;
|
|
1087
|
+
|
|
1088
|
+
if (!openDataRowByIdentity(mediaTableName, identity)) {
|
|
1089
|
+
showToast('The current media row could not be opened in Data.', 'alert');
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
router.navigate(`/data/${encodeURIComponent(mediaTableName)}`);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
case 'import-table-designer-csv': {
|
|
1097
|
+
const fileInput = document.querySelector('[data-bind="table-designer-import-file"]');
|
|
1098
|
+
|
|
1099
|
+
if (!(fileInput instanceof HTMLInputElement)) {
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
fileInput.value = '';
|
|
1104
|
+
fileInput.click();
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
case 'select-data-row':
|
|
1108
|
+
if (actionNode.dataset.rowIndex) {
|
|
1109
|
+
selectDataRow(actionNode.dataset.rowIndex, { notify: false });
|
|
1110
|
+
syncDataRowSelectionUi(actionNode.dataset.rowIndex);
|
|
1111
|
+
}
|
|
1112
|
+
return;
|
|
1113
|
+
case 'select-editor-row':
|
|
1114
|
+
if (actionNode.dataset.rowIndex) {
|
|
1115
|
+
selectEditorRow(actionNode.dataset.rowIndex);
|
|
1116
|
+
}
|
|
1117
|
+
return;
|
|
1118
|
+
case 'clear-data-row-selection':
|
|
1119
|
+
clearDataRowSelection({ notify: false });
|
|
1120
|
+
syncDataRowSelectionUi(null);
|
|
1121
|
+
return;
|
|
1122
|
+
case 'clear-editor-row-selection':
|
|
1123
|
+
clearEditorRowSelection();
|
|
1124
|
+
return;
|
|
1125
|
+
case 'set-data-page':
|
|
1126
|
+
if (actionNode.dataset.page) {
|
|
1127
|
+
await setDataPage(actionNode.dataset.page);
|
|
1128
|
+
}
|
|
1129
|
+
return;
|
|
1130
|
+
case 'sort-data-column':
|
|
1131
|
+
if (actionNode.dataset.columnName) {
|
|
1132
|
+
await sortDataTableByColumn(actionNode.dataset.columnName);
|
|
1133
|
+
}
|
|
1134
|
+
return;
|
|
1135
|
+
case 'sort-editor-results-column':
|
|
1136
|
+
if (actionNode.dataset.columnName) {
|
|
1137
|
+
sortEditorResultsByColumn(actionNode.dataset.columnName);
|
|
1138
|
+
}
|
|
1139
|
+
return;
|
|
1140
|
+
case 'set-data-page-size':
|
|
1141
|
+
if (actionNode.dataset.pageSize) {
|
|
1142
|
+
await setDataPageSize(actionNode.dataset.pageSize);
|
|
1143
|
+
}
|
|
1144
|
+
return;
|
|
1145
|
+
case 'reload-data-route':
|
|
1146
|
+
await refreshCurrentRoute();
|
|
1147
|
+
return;
|
|
1148
|
+
default:
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
document.addEventListener('click', event => {
|
|
1153
|
+
const actionNode = event.target.closest('[data-action]');
|
|
1154
|
+
|
|
1155
|
+
if (!actionNode) {
|
|
723
1156
|
return;
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
{
|
|
748
|
-
const columnId = addCurrentTableDesignerColumn();
|
|
749
|
-
if (columnId) {
|
|
750
|
-
window.requestAnimationFrame(() => {
|
|
751
|
-
focusTableDesignerColumnNameField(columnId);
|
|
752
|
-
});
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
handleAction(actionNode);
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
document.addEventListener('keydown', event => {
|
|
1163
|
+
const target = event.target;
|
|
1164
|
+
|
|
1165
|
+
// Handle Enter key in tag form fields to trigger create tag
|
|
1166
|
+
if (
|
|
1167
|
+
event.key === 'Enter' &&
|
|
1168
|
+
!event.shiftKey &&
|
|
1169
|
+
!event.altKey &&
|
|
1170
|
+
!event.ctrlKey &&
|
|
1171
|
+
!event.metaKey &&
|
|
1172
|
+
!event.defaultPrevented &&
|
|
1173
|
+
target instanceof HTMLElement &&
|
|
1174
|
+
target.dataset.bind === 'media-tagging-tag-form-field'
|
|
1175
|
+
) {
|
|
1176
|
+
event.preventDefault();
|
|
1177
|
+
const createButton = document.querySelector('[data-action="create-media-tag"]');
|
|
1178
|
+
if (createButton && !(createButton instanceof HTMLButtonElement && createButton.disabled)) {
|
|
1179
|
+
createButton?.click();
|
|
753
1180
|
}
|
|
754
|
-
}
|
|
755
|
-
return;
|
|
756
|
-
case "remove-table-designer-column":
|
|
757
|
-
if (actionNode.dataset.columnId) {
|
|
758
|
-
removeCurrentTableDesignerColumn(actionNode.dataset.columnId);
|
|
759
|
-
}
|
|
760
|
-
return;
|
|
761
|
-
case "save-table-designer": {
|
|
762
|
-
const savedTableName = await saveCurrentTableDesignerDraft();
|
|
763
|
-
if (savedTableName) {
|
|
764
|
-
router.navigate(`/table-designer/${encodeURIComponent(savedTableName)}`);
|
|
765
|
-
}
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
case "copy-table-designer-sql": {
|
|
769
|
-
const sqlPreview = getState().tableDesigner.draft?.sqlPreview ?? "";
|
|
770
|
-
if (!sqlPreview.trim()) {
|
|
771
1181
|
return;
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if (!(fileInput instanceof HTMLInputElement)) {
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (
|
|
1185
|
+
event.key === 'Enter' &&
|
|
1186
|
+
event.shiftKey &&
|
|
1187
|
+
!event.altKey &&
|
|
1188
|
+
!event.ctrlKey &&
|
|
1189
|
+
!event.metaKey &&
|
|
1190
|
+
!event.defaultPrevented &&
|
|
1191
|
+
target instanceof HTMLTextAreaElement &&
|
|
1192
|
+
target.dataset.bind === 'current-query'
|
|
1193
|
+
) {
|
|
1194
|
+
event.preventDefault();
|
|
1195
|
+
|
|
1196
|
+
if (!getState().editor.executing) {
|
|
1197
|
+
void executeEditorQueryAndNavigate();
|
|
1198
|
+
}
|
|
1199
|
+
|
|
791
1200
|
return;
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
fileInput.value = "";
|
|
795
|
-
fileInput.click();
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
case "select-data-row":
|
|
799
|
-
if (actionNode.dataset.rowIndex) {
|
|
800
|
-
selectDataRow(actionNode.dataset.rowIndex);
|
|
801
|
-
}
|
|
802
|
-
return;
|
|
803
|
-
case "select-editor-row":
|
|
804
|
-
if (actionNode.dataset.rowIndex) {
|
|
805
|
-
selectEditorRow(actionNode.dataset.rowIndex);
|
|
806
|
-
}
|
|
807
|
-
return;
|
|
808
|
-
case "clear-data-row-selection":
|
|
809
|
-
clearDataRowSelection();
|
|
810
|
-
return;
|
|
811
|
-
case "clear-editor-row-selection":
|
|
812
|
-
clearEditorRowSelection();
|
|
813
|
-
return;
|
|
814
|
-
case "set-data-page":
|
|
815
|
-
if (actionNode.dataset.page) {
|
|
816
|
-
await setDataPage(actionNode.dataset.page);
|
|
817
|
-
}
|
|
818
|
-
return;
|
|
819
|
-
case "sort-data-column":
|
|
820
|
-
if (actionNode.dataset.columnName) {
|
|
821
|
-
await sortDataTableByColumn(actionNode.dataset.columnName);
|
|
822
|
-
}
|
|
823
|
-
return;
|
|
824
|
-
case "sort-editor-results-column":
|
|
825
|
-
if (actionNode.dataset.columnName) {
|
|
826
|
-
sortEditorResultsByColumn(actionNode.dataset.columnName);
|
|
827
|
-
}
|
|
828
|
-
return;
|
|
829
|
-
case "set-data-page-size":
|
|
830
|
-
if (actionNode.dataset.pageSize) {
|
|
831
|
-
await setDataPageSize(actionNode.dataset.pageSize);
|
|
832
|
-
}
|
|
833
|
-
return;
|
|
834
|
-
case "reload-data-route":
|
|
835
|
-
await refreshCurrentRoute();
|
|
836
|
-
return;
|
|
837
|
-
default:
|
|
838
|
-
}
|
|
839
|
-
}
|
|
1201
|
+
}
|
|
840
1202
|
|
|
841
|
-
|
|
842
|
-
|
|
1203
|
+
if (event.key !== 'Escape' || event.defaultPrevented) {
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
843
1206
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1207
|
+
const state = getState();
|
|
1208
|
+
|
|
1209
|
+
if (state.modal) {
|
|
1210
|
+
event.preventDefault();
|
|
1211
|
+
closeModal();
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (
|
|
1216
|
+
state.route.name === 'data' &&
|
|
1217
|
+
(typeof state.dataBrowser.selectedRowIndex === 'number' || Boolean(state.dataBrowser.selectedRow))
|
|
1218
|
+
) {
|
|
1219
|
+
event.preventDefault();
|
|
1220
|
+
clearDataRowSelection();
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
847
1223
|
|
|
848
|
-
|
|
1224
|
+
if (state.route.name === 'editorResults' && typeof state.editor.selectedRowIndex === 'number') {
|
|
1225
|
+
event.preventDefault();
|
|
1226
|
+
clearEditorRowSelection();
|
|
1227
|
+
}
|
|
849
1228
|
});
|
|
850
1229
|
|
|
851
|
-
document.addEventListener(
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
if (
|
|
855
|
-
event.key === "Enter" &&
|
|
856
|
-
event.shiftKey &&
|
|
857
|
-
!event.altKey &&
|
|
858
|
-
!event.ctrlKey &&
|
|
859
|
-
!event.metaKey &&
|
|
860
|
-
!event.defaultPrevented &&
|
|
861
|
-
target instanceof HTMLTextAreaElement &&
|
|
862
|
-
target.dataset.bind === "current-query"
|
|
863
|
-
) {
|
|
864
|
-
event.preventDefault();
|
|
1230
|
+
document.addEventListener('input', event => {
|
|
1231
|
+
const bindNode = event.target.closest('[data-bind]');
|
|
865
1232
|
|
|
866
|
-
if (!
|
|
867
|
-
|
|
1233
|
+
if (!bindNode) {
|
|
1234
|
+
return;
|
|
868
1235
|
}
|
|
869
1236
|
|
|
870
|
-
|
|
871
|
-
|
|
1237
|
+
if (bindNode.dataset.bind === 'current-query') {
|
|
1238
|
+
invalidateMainRenderCache();
|
|
1239
|
+
syncQueryEditorHighlight(bindNode);
|
|
1240
|
+
syncQueryEditorScroll(bindNode);
|
|
1241
|
+
setCurrentQuery(bindNode.value);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
872
1244
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1245
|
+
if (bindNode.dataset.bind === 'data-search-query') {
|
|
1246
|
+
setDataSearchQuery(bindNode.value);
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
876
1249
|
|
|
877
|
-
|
|
1250
|
+
if (bindNode.dataset.bind === 'table-designer-search') {
|
|
1251
|
+
setTableDesignerSearchQuery(bindNode.value);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
878
1254
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
1255
|
+
if (bindNode.dataset.bind === 'media-tagging-tag-search') {
|
|
1256
|
+
syncMediaTaggingTagSearchUi(bindNode);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
884
1259
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
}
|
|
1260
|
+
if (bindNode.dataset.bind === 'table-designer-field') {
|
|
1261
|
+
if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
890
1264
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
895
|
-
});
|
|
1265
|
+
updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.value);
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
896
1268
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
if (bindNode.dataset.bind === "table-designer-search") {
|
|
918
|
-
setTableDesignerSearchQuery(bindNode.value);
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
if (bindNode.dataset.bind === "table-designer-field") {
|
|
923
|
-
if (bindNode instanceof HTMLInputElement && bindNode.type === "checkbox") {
|
|
924
|
-
return;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.value);
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (bindNode.dataset.bind === "table-designer-column-field") {
|
|
932
|
-
updateCurrentTableDesignerColumnField(
|
|
933
|
-
bindNode.dataset.columnId,
|
|
934
|
-
bindNode.dataset.field,
|
|
935
|
-
bindNode.value
|
|
936
|
-
);
|
|
937
|
-
return;
|
|
938
|
-
}
|
|
1269
|
+
if (bindNode.dataset.bind === 'table-designer-column-field') {
|
|
1270
|
+
updateCurrentTableDesignerColumnField(bindNode.dataset.columnId, bindNode.dataset.field, bindNode.value);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (bindNode.dataset.bind === 'media-tagging-field') {
|
|
1275
|
+
if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Skip select elements - they're handled in the change event with preview refresh
|
|
1280
|
+
if (bindNode instanceof HTMLSelectElement) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
if (bindNode instanceof HTMLTextAreaElement && bindNode.dataset.sqlHighlight === 'true') {
|
|
1285
|
+
syncQueryEditorHighlight(bindNode);
|
|
1286
|
+
syncQueryEditorScroll(bindNode);
|
|
1287
|
+
}
|
|
939
1288
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1289
|
+
updateCurrentMediaTaggingField(bindNode.dataset.field, bindNode.value);
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
if (bindNode.dataset.bind === 'media-tagging-tag-form-field') {
|
|
1294
|
+
if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
944
1297
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1298
|
+
updateCurrentMediaTaggingTagFormField(bindNode.dataset.field, bindNode.value);
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
if (bindNode.dataset.bind === 'query-history-search') {
|
|
1303
|
+
setQueryHistorySearchInput(bindNode.value);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
if (bindNode.dataset.bind === 'query-chart-draft:name') {
|
|
1308
|
+
updateCurrentQueryChartDraftField('name', bindNode.value);
|
|
1309
|
+
}
|
|
948
1310
|
});
|
|
949
1311
|
|
|
950
1312
|
document.addEventListener(
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1313
|
+
'scroll',
|
|
1314
|
+
event => {
|
|
1315
|
+
const target = event.target;
|
|
954
1316
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1317
|
+
if (!(target instanceof HTMLTextAreaElement)) {
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (target.dataset.bind !== 'current-query' && target.dataset.sqlHighlight !== 'true') {
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
958
1324
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1325
|
+
syncQueryEditorScroll(target);
|
|
1326
|
+
},
|
|
1327
|
+
true,
|
|
962
1328
|
);
|
|
963
1329
|
|
|
964
|
-
document.addEventListener(
|
|
965
|
-
|
|
1330
|
+
document.addEventListener('change', event => {
|
|
1331
|
+
const bindNode = event.target.closest('[data-bind]');
|
|
966
1332
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1333
|
+
if (!bindNode) {
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
970
1336
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1337
|
+
if (bindNode.dataset.bind === 'data-search-column') {
|
|
1338
|
+
setDataSearchColumn(bindNode.value);
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
975
1341
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1342
|
+
if (bindNode.dataset.bind === 'media-tagging-field') {
|
|
1343
|
+
updateCurrentMediaTaggingField(bindNode.dataset.field, bindNode.value);
|
|
1344
|
+
void refreshMediaTaggingPreview();
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
980
1347
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1348
|
+
if (bindNode.dataset.bind === 'media-tagging-tag-form-field') {
|
|
1349
|
+
const nextValue =
|
|
1350
|
+
bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox' ? bindNode.checked : bindNode.value;
|
|
1351
|
+
updateCurrentMediaTaggingTagFormField(bindNode.dataset.field, nextValue);
|
|
1352
|
+
return;
|
|
985
1353
|
}
|
|
986
1354
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1355
|
+
if (bindNode.dataset.bind === 'media-tagging-tag-selection') {
|
|
1356
|
+
const nextValue =
|
|
1357
|
+
bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox' ? bindNode.checked : false;
|
|
1358
|
+
toggleCurrentMediaTagSelection(bindNode.dataset.tagKey, nextValue, { notify: false });
|
|
1359
|
+
syncMediaTagSelectionUi(bindNode, nextValue);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
990
1362
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
bindNode.value
|
|
996
|
-
);
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
if (bindNode.dataset.bind === "table-designer-column-flag") {
|
|
1001
|
-
updateCurrentTableDesignerColumnField(
|
|
1002
|
-
bindNode.dataset.columnId,
|
|
1003
|
-
bindNode.dataset.field,
|
|
1004
|
-
bindNode.checked
|
|
1005
|
-
);
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
if (bindNode.dataset.bind === "query-chart-draft:chartType") {
|
|
1010
|
-
updateCurrentQueryChartDraftField("chartType", bindNode.value);
|
|
1011
|
-
return;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
if (bindNode.dataset.bind === "query-chart-draft:tableVisible") {
|
|
1015
|
-
updateCurrentQueryChartDraftField("tableVisible", bindNode.checked);
|
|
1016
|
-
return;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
if (bindNode.dataset.bind?.startsWith("query-chart-draft-config:")) {
|
|
1020
|
-
const field = bindNode.dataset.bind.slice("query-chart-draft-config:".length);
|
|
1021
|
-
const value =
|
|
1022
|
-
bindNode instanceof HTMLInputElement && bindNode.type === "checkbox"
|
|
1023
|
-
? bindNode.checked
|
|
1024
|
-
: bindNode.value === ""
|
|
1025
|
-
? null
|
|
1026
|
-
: bindNode.value;
|
|
1027
|
-
updateCurrentQueryChartDraftConfigField(field, value);
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1363
|
+
if (bindNode.dataset.bind === 'table-designer-import-file') {
|
|
1364
|
+
void handleTableDesignerCsvImport(bindNode);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1030
1367
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
event.preventDefault();
|
|
1039
|
-
const formData = new FormData(form);
|
|
1040
|
-
|
|
1041
|
-
switch (form.dataset.form) {
|
|
1042
|
-
case "open-connection": {
|
|
1043
|
-
resetStructureGraphForDatabaseChange();
|
|
1044
|
-
const connection = await submitOpenConnection({
|
|
1045
|
-
path: String(formData.get("path") ?? ""),
|
|
1046
|
-
label: String(formData.get("label") ?? ""),
|
|
1047
|
-
readOnly: formData.get("readOnly") === "on",
|
|
1048
|
-
});
|
|
1049
|
-
|
|
1050
|
-
if (connection) {
|
|
1051
|
-
router.navigate("/overview");
|
|
1052
|
-
}
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
case "create-connection": {
|
|
1056
|
-
resetStructureGraphForDatabaseChange();
|
|
1057
|
-
const connection = await submitCreateConnection({
|
|
1058
|
-
path: String(formData.get("path") ?? ""),
|
|
1059
|
-
label: String(formData.get("label") ?? ""),
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
if (connection) {
|
|
1063
|
-
router.navigate("/overview");
|
|
1064
|
-
}
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
|
-
case "import-sql": {
|
|
1068
|
-
resetStructureGraphForDatabaseChange();
|
|
1069
|
-
const targetMode = String(formData.get("targetMode") ?? "active");
|
|
1070
|
-
const payload = {
|
|
1071
|
-
sqlFilePath: String(formData.get("sqlFilePath") ?? ""),
|
|
1072
|
-
label: String(formData.get("label") ?? ""),
|
|
1073
|
-
};
|
|
1074
|
-
|
|
1075
|
-
if (targetMode === "recent") {
|
|
1076
|
-
payload.targetConnectionId = String(formData.get("targetConnectionId") ?? "");
|
|
1077
|
-
} else if (targetMode === "create") {
|
|
1078
|
-
payload.createNew = true;
|
|
1079
|
-
payload.targetPath = String(formData.get("targetPath") ?? "");
|
|
1080
|
-
} else if (targetMode === "path") {
|
|
1081
|
-
payload.targetPath = String(formData.get("targetPath") ?? "");
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
const result = await submitImportSql(payload);
|
|
1085
|
-
if (result) {
|
|
1086
|
-
router.navigate("/overview");
|
|
1087
|
-
}
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
case "edit-connection": {
|
|
1091
|
-
const connectionId = String(formData.get("connectionId") ?? "");
|
|
1092
|
-
const isActiveConnection = getState().connections.active?.id === connectionId;
|
|
1093
|
-
const logoFile = formData.get("logoFile");
|
|
1094
|
-
const logoUpload = await buildConnectionLogoUpload(logoFile);
|
|
1095
|
-
|
|
1096
|
-
if (isActiveConnection) {
|
|
1097
|
-
resetStructureGraphForDatabaseChange();
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
await submitEditConnection(connectionId, {
|
|
1101
|
-
path: String(formData.get("path") ?? ""),
|
|
1102
|
-
label: String(formData.get("label") ?? ""),
|
|
1103
|
-
readOnly: formData.get("readOnly") === "on",
|
|
1104
|
-
clearLogo: formData.get("clearLogo") === "on" && !logoUpload,
|
|
1105
|
-
logoUpload,
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
case "delete-row-confirm":
|
|
1111
|
-
await submitDeleteRowConfirmation();
|
|
1112
|
-
return;
|
|
1113
|
-
case "save-query-chart":
|
|
1114
|
-
await saveCurrentQueryChartDraft();
|
|
1115
|
-
return;
|
|
1116
|
-
case "delete-query-chart":
|
|
1117
|
-
await submitDeleteChartConfirmation();
|
|
1118
|
-
return;
|
|
1119
|
-
case "save-data-row": {
|
|
1120
|
-
const values = {};
|
|
1121
|
-
|
|
1122
|
-
for (const [key, value] of formData.entries()) {
|
|
1123
|
-
if (!key.startsWith("field:")) {
|
|
1124
|
-
continue;
|
|
1368
|
+
if (bindNode.dataset.bind === 'table-designer-field') {
|
|
1369
|
+
if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
|
|
1370
|
+
updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.checked);
|
|
1371
|
+
return;
|
|
1125
1372
|
}
|
|
1126
1373
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1374
|
+
updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.value);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (bindNode.dataset.bind === 'table-designer-column-field') {
|
|
1379
|
+
updateCurrentTableDesignerColumnField(bindNode.dataset.columnId, bindNode.dataset.field, bindNode.value);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
if (bindNode.dataset.bind === 'table-designer-column-flag') {
|
|
1384
|
+
updateCurrentTableDesignerColumnField(bindNode.dataset.columnId, bindNode.dataset.field, bindNode.checked);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1129
1387
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
);
|
|
1134
|
-
return;
|
|
1388
|
+
if (bindNode.dataset.bind === 'query-chart-draft:chartType') {
|
|
1389
|
+
updateCurrentQueryChartDraftField('chartType', bindNode.value);
|
|
1390
|
+
return;
|
|
1135
1391
|
}
|
|
1136
|
-
case "save-editor-row": {
|
|
1137
|
-
const values = {};
|
|
1138
1392
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1393
|
+
if (bindNode.dataset.bind === 'query-chart-draft:tableVisible') {
|
|
1394
|
+
updateCurrentQueryChartDraftField('tableVisible', bindNode.checked);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (bindNode.dataset.bind?.startsWith('query-chart-draft-config:')) {
|
|
1399
|
+
const field = bindNode.dataset.bind.slice('query-chart-draft-config:'.length);
|
|
1400
|
+
const value =
|
|
1401
|
+
bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox'
|
|
1402
|
+
? bindNode.checked
|
|
1403
|
+
: bindNode.value === ''
|
|
1404
|
+
? null
|
|
1405
|
+
: bindNode.value;
|
|
1406
|
+
updateCurrentQueryChartDraftConfigField(field, value);
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
document.addEventListener('submit', async event => {
|
|
1411
|
+
const form = event.target.closest('[data-form]');
|
|
1412
|
+
|
|
1413
|
+
if (!form) {
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
event.preventDefault();
|
|
1418
|
+
const formData = new FormData(form);
|
|
1419
|
+
|
|
1420
|
+
switch (form.dataset.form) {
|
|
1421
|
+
case 'open-connection': {
|
|
1422
|
+
resetStructureGraphForDatabaseChange();
|
|
1423
|
+
const connection = await submitOpenConnection({
|
|
1424
|
+
path: String(formData.get('path') ?? ''),
|
|
1425
|
+
label: String(formData.get('label') ?? ''),
|
|
1426
|
+
readOnly: formData.get('readOnly') === 'on',
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
if (connection) {
|
|
1430
|
+
router.navigate('/overview');
|
|
1431
|
+
}
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
case 'create-connection': {
|
|
1435
|
+
resetStructureGraphForDatabaseChange();
|
|
1436
|
+
const connection = await submitCreateConnection({
|
|
1437
|
+
path: String(formData.get('path') ?? ''),
|
|
1438
|
+
label: String(formData.get('label') ?? ''),
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
if (connection) {
|
|
1442
|
+
router.navigate('/overview');
|
|
1443
|
+
}
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
case 'import-sql': {
|
|
1447
|
+
resetStructureGraphForDatabaseChange();
|
|
1448
|
+
const targetMode = String(formData.get('targetMode') ?? 'active');
|
|
1449
|
+
const payload = {
|
|
1450
|
+
sqlFilePath: String(formData.get('sqlFilePath') ?? ''),
|
|
1451
|
+
label: String(formData.get('label') ?? ''),
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
if (targetMode === 'recent') {
|
|
1455
|
+
payload.targetConnectionId = String(formData.get('targetConnectionId') ?? '');
|
|
1456
|
+
} else if (targetMode === 'create') {
|
|
1457
|
+
payload.createNew = true;
|
|
1458
|
+
payload.targetPath = String(formData.get('targetPath') ?? '');
|
|
1459
|
+
} else if (targetMode === 'path') {
|
|
1460
|
+
payload.targetPath = String(formData.get('targetPath') ?? '');
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const result = await submitImportSql(payload);
|
|
1464
|
+
if (result) {
|
|
1465
|
+
router.navigate('/overview');
|
|
1466
|
+
}
|
|
1467
|
+
return;
|
|
1142
1468
|
}
|
|
1469
|
+
case 'edit-connection': {
|
|
1470
|
+
const connectionId = String(formData.get('connectionId') ?? '');
|
|
1471
|
+
const isActiveConnection = getState().connections.active?.id === connectionId;
|
|
1472
|
+
const logoFile = formData.get('logoFile');
|
|
1473
|
+
const logoUpload = await buildConnectionLogoUpload(logoFile);
|
|
1474
|
+
|
|
1475
|
+
if (isActiveConnection) {
|
|
1476
|
+
resetStructureGraphForDatabaseChange();
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
await submitEditConnection(connectionId, {
|
|
1480
|
+
path: String(formData.get('path') ?? ''),
|
|
1481
|
+
label: String(formData.get('label') ?? ''),
|
|
1482
|
+
readOnly: formData.get('readOnly') === 'on',
|
|
1483
|
+
clearLogo: formData.get('clearLogo') === 'on' && !logoUpload,
|
|
1484
|
+
logoUpload,
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
case 'delete-row-confirm':
|
|
1490
|
+
await submitDeleteRowConfirmation();
|
|
1491
|
+
return;
|
|
1492
|
+
case 'save-query-chart':
|
|
1493
|
+
await saveCurrentQueryChartDraft();
|
|
1494
|
+
return;
|
|
1495
|
+
case 'delete-query-chart':
|
|
1496
|
+
await submitDeleteChartConfirmation();
|
|
1497
|
+
return;
|
|
1498
|
+
case 'delete-query-history-confirm':
|
|
1499
|
+
await submitDeleteQueryHistoryConfirmation();
|
|
1500
|
+
return;
|
|
1501
|
+
case 'create-media-tagging-tag-table':
|
|
1502
|
+
await submitCreateMediaTaggingTagTable();
|
|
1503
|
+
return;
|
|
1504
|
+
case 'create-media-tagging-mapping-table':
|
|
1505
|
+
await submitCreateMediaTaggingMappingTable();
|
|
1506
|
+
return;
|
|
1507
|
+
case 'save-data-row': {
|
|
1508
|
+
const values = {};
|
|
1509
|
+
|
|
1510
|
+
for (const [key, value] of formData.entries()) {
|
|
1511
|
+
if (!key.startsWith('field:')) {
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
values[key.slice('field:'.length)] = String(value ?? '');
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
let rowIdentity = null;
|
|
1519
|
+
const rawRowIdentity = String(formData.get('rowIdentity') ?? '').trim();
|
|
1520
|
+
|
|
1521
|
+
if (rawRowIdentity) {
|
|
1522
|
+
try {
|
|
1523
|
+
rowIdentity = JSON.parse(rawRowIdentity);
|
|
1524
|
+
} catch (error) {
|
|
1525
|
+
rowIdentity = null;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
await submitDataRowUpdate(String(formData.get('rowIndex') ?? ''), values, rowIdentity);
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
case 'save-editor-row': {
|
|
1533
|
+
const values = {};
|
|
1534
|
+
|
|
1535
|
+
for (const [key, value] of formData.entries()) {
|
|
1536
|
+
if (!key.startsWith('field:')) {
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
values[key.slice('field:'.length)] = String(value ?? '');
|
|
1541
|
+
}
|
|
1143
1542
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1543
|
+
await submitEditorRowUpdate(String(formData.get('rowIndex') ?? ''), values);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
case 'save-query-history-title': {
|
|
1547
|
+
const historyId = String(formData.get('historyId') ?? '');
|
|
1548
|
+
const updatedItem = await saveQueryHistoryTitle(historyId, String(formData.get('title') ?? ''));
|
|
1549
|
+
|
|
1550
|
+
if (updatedItem) {
|
|
1551
|
+
syncQueryHistoryUi(historyId);
|
|
1552
|
+
}
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
case 'save-query-history-notes': {
|
|
1556
|
+
const historyId = String(formData.get('historyId') ?? '');
|
|
1557
|
+
const updatedItem = await saveQueryHistoryNotes(historyId, String(formData.get('notes') ?? ''));
|
|
1558
|
+
|
|
1559
|
+
if (updatedItem) {
|
|
1560
|
+
syncQueryHistoryUi(historyId);
|
|
1561
|
+
}
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
default:
|
|
1565
|
+
}
|
|
1167
1566
|
});
|
|
1168
1567
|
|
|
1169
1568
|
subscribe(renderApp);
|
|
1170
1569
|
renderApp(getState());
|
|
1171
1570
|
initializeApp().then(() => {
|
|
1172
|
-
|
|
1571
|
+
router.start();
|
|
1173
1572
|
});
|