sqlite-hub 0.7.0 → 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 +25 -0
- package/docs/DESIGN_GUIDELINES.md +36 -0
- package/frontend/index.html +79 -0
- package/frontend/js/api.js +67 -0
- package/frontend/js/app.js +1401 -921
- package/frontend/js/components/connectionCard.js +3 -4
- package/frontend/js/components/emptyState.js +4 -4
- package/frontend/js/components/modal.js +239 -30
- package/frontend/js/components/queryEditor.js +11 -8
- package/frontend/js/components/queryHistoryDetail.js +27 -8
- package/frontend/js/components/queryHistoryPanel.js +6 -5
- package/frontend/js/components/rowEditorPanel.js +84 -58
- package/frontend/js/components/sidebar.js +76 -33
- package/frontend/js/components/structureGraph.js +629 -715
- package/frontend/js/components/tableDesignerEditor.js +9 -8
- package/frontend/js/components/tableDesignerSidebar.js +11 -10
- package/frontend/js/components/tableDesignerSqlPreview.js +61 -30
- package/frontend/js/lib/mediaTaggingDefaults.js +27 -0
- package/frontend/js/router.js +81 -71
- package/frontend/js/store.js +3095 -2165
- package/frontend/js/views/charts.js +68 -40
- package/frontend/js/views/connections.js +5 -17
- package/frontend/js/views/data.js +40 -27
- package/frontend/js/views/editor.js +172 -177
- package/frontend/js/views/mediaTagging.js +861 -0
- package/frontend/js/views/overview.js +149 -10
- package/frontend/js/views/settings.js +2 -2
- package/frontend/js/views/structure.js +74 -70
- package/frontend/js/views/tableDesigner.js +7 -2
- package/frontend/styles/base.css +73 -1
- package/frontend/styles/components.css +105 -105
- package/frontend/styles/structure-graph.css +19 -82
- package/frontend/styles/tokens.css +2 -0
- package/frontend/styles/views.css +823 -30
- 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/sqlite/overviewService.js +68 -0
- package/server/services/storage/appStateStore.js +321 -2
package/frontend/js/app.js
CHANGED
|
@@ -1,211 +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
|
-
|
|
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');
|
|
104
126
|
|
|
105
127
|
appRoot.innerHTML = renderAppShell();
|
|
106
128
|
|
|
107
129
|
const shellRefs = {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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'),
|
|
116
138
|
};
|
|
117
139
|
let lastRenderedRoutePath = null;
|
|
140
|
+
let lastRenderedTopNavMarkup = '';
|
|
141
|
+
let lastRenderedSidebarMarkup = '';
|
|
142
|
+
let lastRenderedStatusBarMarkup = '';
|
|
143
|
+
let lastRenderedMainMarkup = '';
|
|
144
|
+
let lastRenderedPanelMarkup = '';
|
|
145
|
+
let lastRenderedModalMarkup = '';
|
|
146
|
+
let lastRenderedToastMarkup = '';
|
|
147
|
+
let lastRenderedPanelOpen = false;
|
|
148
|
+
let lastRenderedLockedRoute = false;
|
|
118
149
|
let pendingNewTableDesignerAutofocus = false;
|
|
150
|
+
let pendingQueryEditorFocus = false;
|
|
151
|
+
|
|
152
|
+
function invalidateMainRenderCache() {
|
|
153
|
+
lastRenderedMainMarkup = null;
|
|
154
|
+
}
|
|
119
155
|
|
|
120
156
|
function resetStructureGraphForDatabaseChange() {
|
|
121
|
-
|
|
157
|
+
resetPersistedStructureGraphState();
|
|
122
158
|
}
|
|
123
159
|
|
|
124
160
|
function renderQueryHighlightMarkup(query) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
161
|
+
if (query) {
|
|
162
|
+
return highlightSql(query);
|
|
163
|
+
}
|
|
128
164
|
|
|
129
|
-
|
|
165
|
+
return '<span class="text-on-surface-variant/35">SELECT name FROM sqlite_master WHERE type = \'table\';</span>';
|
|
130
166
|
}
|
|
131
167
|
|
|
132
168
|
function syncQueryEditorHighlight(textarea) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
169
|
+
if (!(textarea instanceof HTMLTextAreaElement)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
136
172
|
|
|
137
|
-
|
|
138
|
-
|
|
173
|
+
const layer = textarea.closest('.query-editor-layer');
|
|
174
|
+
const highlightNode = layer?.querySelector('[data-query-editor-highlight]');
|
|
139
175
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
176
|
+
if (!(highlightNode instanceof HTMLElement)) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
143
179
|
|
|
144
|
-
|
|
180
|
+
highlightNode.innerHTML = renderQueryHighlightMarkup(textarea.value);
|
|
145
181
|
}
|
|
146
182
|
|
|
147
183
|
function syncQueryEditorScroll(textarea) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
184
|
+
if (!(textarea instanceof HTMLTextAreaElement)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
151
187
|
|
|
152
|
-
|
|
153
|
-
|
|
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]');
|
|
154
191
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
192
|
+
if (!(highlightNode instanceof HTMLElement)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
highlightNode.style.transform = `translate(${-textarea.scrollLeft}px, ${-textarea.scrollTop}px)`;
|
|
158
197
|
|
|
159
|
-
|
|
198
|
+
if (gutterNode instanceof HTMLElement) {
|
|
199
|
+
gutterNode.style.transform = `translateY(${-textarea.scrollTop}px)`;
|
|
200
|
+
}
|
|
160
201
|
}
|
|
161
202
|
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
203
|
+
function syncMediaTagSelectionUi(input, checked) {
|
|
204
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
165
207
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
+
}
|
|
174
350
|
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
}
|
|
370
|
+
|
|
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
|
+
});
|
|
177
389
|
}
|
|
178
390
|
|
|
179
391
|
function readFileAsText(file) {
|
|
180
|
-
|
|
181
|
-
|
|
392
|
+
return new Promise((resolve, reject) => {
|
|
393
|
+
const reader = new FileReader();
|
|
182
394
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
+
};
|
|
189
401
|
|
|
190
|
-
|
|
191
|
-
|
|
402
|
+
reader.readAsText(file);
|
|
403
|
+
});
|
|
192
404
|
}
|
|
193
405
|
|
|
194
406
|
async function buildConnectionLogoUpload(file) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
};
|
|
204
416
|
}
|
|
205
417
|
|
|
206
418
|
function renderNotFoundView() {
|
|
207
|
-
|
|
208
|
-
|
|
419
|
+
return {
|
|
420
|
+
main: `
|
|
209
421
|
<section class="landing-view machined-grid px-6">
|
|
210
422
|
<div class="text-center z-10">
|
|
211
423
|
<p class="font-mono text-[10px] uppercase tracking-[0.3em] text-primary-container/40">
|
|
@@ -215,7 +427,7 @@ function renderNotFoundView() {
|
|
|
215
427
|
404_SIGNAL
|
|
216
428
|
</h1>
|
|
217
429
|
<button
|
|
218
|
-
class="mt-8
|
|
430
|
+
class="standard-button mt-8 px-6 font-headline text-sm"
|
|
219
431
|
data-action="navigate"
|
|
220
432
|
data-to="/"
|
|
221
433
|
type="button"
|
|
@@ -225,868 +437,1136 @@ function renderNotFoundView() {
|
|
|
225
437
|
</div>
|
|
226
438
|
</section>
|
|
227
439
|
`,
|
|
228
|
-
|
|
229
|
-
|
|
440
|
+
panel: '',
|
|
441
|
+
};
|
|
230
442
|
}
|
|
231
443
|
|
|
232
444
|
function resolveView(state) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
+
}
|
|
257
473
|
}
|
|
258
474
|
|
|
259
475
|
function captureFocusedInputState() {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
+
};
|
|
288
504
|
}
|
|
289
505
|
|
|
290
506
|
function buildFocusedInputSelectors(snapshot) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
507
|
+
if (!snapshot?.bind) {
|
|
508
|
+
return [];
|
|
509
|
+
}
|
|
294
510
|
|
|
295
|
-
|
|
296
|
-
|
|
511
|
+
const selectors = [];
|
|
512
|
+
const bindSelector = `[data-bind="${CSS.escape(snapshot.bind)}"]`;
|
|
297
513
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
+
}
|
|
303
519
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
520
|
+
if (snapshot.field) {
|
|
521
|
+
selectors.push(`${bindSelector}[data-field="${CSS.escape(snapshot.field)}"]`);
|
|
522
|
+
}
|
|
307
523
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
524
|
+
if (snapshot.columnId) {
|
|
525
|
+
selectors.push(`${bindSelector}[data-column-id="${CSS.escape(snapshot.columnId)}"]`);
|
|
526
|
+
}
|
|
311
527
|
|
|
312
|
-
|
|
313
|
-
|
|
528
|
+
selectors.push(bindSelector);
|
|
529
|
+
return selectors;
|
|
314
530
|
}
|
|
315
531
|
|
|
316
532
|
function restoreFocusedInputState(snapshot) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const nextElement = buildFocusedInputSelectors(snapshot)
|
|
322
|
-
.map((selector) => document.querySelector(selector))
|
|
323
|
-
.find(
|
|
324
|
-
(candidate) =>
|
|
325
|
-
candidate instanceof HTMLInputElement ||
|
|
326
|
-
candidate instanceof HTMLTextAreaElement ||
|
|
327
|
-
candidate instanceof HTMLSelectElement
|
|
328
|
-
);
|
|
533
|
+
if (!snapshot) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
329
536
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
nextElement.focus({ preventScroll: true });
|
|
342
|
-
|
|
343
|
-
if (
|
|
344
|
-
(nextElement instanceof HTMLInputElement || nextElement instanceof HTMLTextAreaElement) &&
|
|
345
|
-
typeof snapshot.selectionStart === "number" &&
|
|
346
|
-
typeof snapshot.selectionEnd === "number"
|
|
347
|
-
) {
|
|
348
|
-
nextElement.setSelectionRange(
|
|
349
|
-
snapshot.selectionStart,
|
|
350
|
-
snapshot.selectionEnd,
|
|
351
|
-
snapshot.selectionDirection || "none"
|
|
352
|
-
);
|
|
353
|
-
}
|
|
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
|
+
);
|
|
354
545
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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;
|
|
358
574
|
}
|
|
359
575
|
|
|
360
576
|
function focusNewTableDesignerNameField() {
|
|
361
|
-
|
|
362
|
-
'[data-bind="table-designer-field"][data-field="tableName"]'
|
|
363
|
-
);
|
|
577
|
+
const input = document.querySelector('[data-bind="table-designer-field"][data-field="tableName"]');
|
|
364
578
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
579
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
368
582
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
583
|
+
input.focus({ preventScroll: true });
|
|
584
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
585
|
+
return true;
|
|
372
586
|
}
|
|
373
587
|
|
|
374
588
|
function focusTableDesignerColumnNameField(columnId) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
589
|
+
if (!columnId) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
378
592
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
593
|
+
const input = document.querySelector(
|
|
594
|
+
`[data-bind="table-designer-column-field"][data-column-id="${CSS.escape(columnId)}"][data-field="name"]`,
|
|
595
|
+
);
|
|
382
596
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
597
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
386
600
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
601
|
+
input.focus({ preventScroll: true });
|
|
602
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
603
|
+
return true;
|
|
390
604
|
}
|
|
391
605
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
606
|
+
function focusQueryEditorInput() {
|
|
607
|
+
const input = document.querySelector('[data-bind="current-query"]');
|
|
396
608
|
|
|
397
|
-
|
|
609
|
+
if (!(input instanceof HTMLTextAreaElement)) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
398
612
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
613
|
+
input.focus({ preventScroll: true });
|
|
614
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
402
617
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
618
|
+
async function handleTableDesignerCsvImport(fileInput) {
|
|
619
|
+
if (!(fileInput instanceof HTMLInputElement)) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
408
622
|
|
|
409
|
-
|
|
410
|
-
const csvText = await readFileAsText(file);
|
|
411
|
-
const imported = queueTableDesignerCsvImport(file.name, csvText);
|
|
623
|
+
const file = fileInput.files?.[0];
|
|
412
624
|
|
|
413
|
-
if (!
|
|
414
|
-
|
|
625
|
+
if (!(file instanceof File)) {
|
|
626
|
+
return;
|
|
415
627
|
}
|
|
416
628
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
629
|
+
if (!file.size) {
|
|
630
|
+
showToast('The selected CSV file is empty.', 'alert');
|
|
631
|
+
fileInput.value = '';
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
const csvText = await readFileAsText(file);
|
|
637
|
+
const imported = queueTableDesignerCsvImport(file.name, csvText);
|
|
638
|
+
|
|
639
|
+
if (!imported) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
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 = '';
|
|
654
|
+
}
|
|
429
655
|
}
|
|
430
656
|
|
|
431
657
|
function renderApp(state) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
state
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
console.error("Failed to mount structure graph.", error);
|
|
479
|
-
});
|
|
480
|
-
}
|
|
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
|
+
}
|
|
481
704
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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;
|
|
780
|
+
lastRenderedToastMarkup = toastMarkup;
|
|
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
|
+
}
|
|
789
|
+
|
|
790
|
+
if (state.route.name === 'charts') {
|
|
791
|
+
mountQueryChartRenderer(state);
|
|
792
|
+
}
|
|
485
793
|
}
|
|
486
794
|
|
|
487
|
-
const router = createRouter(
|
|
488
|
-
|
|
795
|
+
const router = createRouter(route => {
|
|
796
|
+
setRoute(route);
|
|
489
797
|
});
|
|
490
798
|
|
|
491
799
|
async function executeEditorQueryAndNavigate() {
|
|
492
|
-
|
|
493
|
-
|
|
800
|
+
const success = await executeCurrentQuery();
|
|
801
|
+
router.navigate(success ? '/editor/results' : '/editor');
|
|
494
802
|
}
|
|
495
803
|
|
|
496
804
|
async function handleAction(actionNode) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (isActiveConnection) {
|
|
539
|
-
resetStructureGraphForDatabaseChange();
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const removed = await removeConnection(actionNode.dataset.connectionId);
|
|
543
|
-
if (removed) {
|
|
544
|
-
const nextState = getState();
|
|
545
|
-
if (!nextState.connections.active && nextState.route.name !== "connections") {
|
|
546
|
-
router.navigate("/connections");
|
|
547
|
-
} else {
|
|
548
|
-
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;
|
|
549
845
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
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) {
|
|
651
1156
|
return;
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
window.requestAnimationFrame(() => {
|
|
676
|
-
focusTableDesignerColumnNameField(columnId);
|
|
677
|
-
});
|
|
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();
|
|
678
1180
|
}
|
|
679
|
-
}
|
|
680
|
-
return;
|
|
681
|
-
case "remove-table-designer-column":
|
|
682
|
-
if (actionNode.dataset.columnId) {
|
|
683
|
-
removeCurrentTableDesignerColumn(actionNode.dataset.columnId);
|
|
684
|
-
}
|
|
685
|
-
return;
|
|
686
|
-
case "save-table-designer": {
|
|
687
|
-
const savedTableName = await saveCurrentTableDesignerDraft();
|
|
688
|
-
if (savedTableName) {
|
|
689
|
-
router.navigate(`/table-designer/${encodeURIComponent(savedTableName)}`);
|
|
690
|
-
}
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
case "copy-table-designer-sql": {
|
|
694
|
-
const sqlPreview = getState().tableDesigner.draft?.sqlPreview ?? "";
|
|
695
|
-
if (!sqlPreview.trim()) {
|
|
696
1181
|
return;
|
|
697
|
-
|
|
1182
|
+
}
|
|
698
1183
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
+
|
|
1200
|
+
return;
|
|
706
1201
|
}
|
|
707
|
-
case "import-table-designer-csv": {
|
|
708
|
-
const fileInput = document.querySelector('[data-bind="table-designer-import-file"]');
|
|
709
1202
|
|
|
710
|
-
|
|
1203
|
+
if (event.key !== 'Escape' || event.defaultPrevented) {
|
|
711
1204
|
return;
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
fileInput.value = "";
|
|
715
|
-
fileInput.click();
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
case "select-data-row":
|
|
719
|
-
if (actionNode.dataset.rowIndex) {
|
|
720
|
-
selectDataRow(actionNode.dataset.rowIndex);
|
|
721
|
-
}
|
|
722
|
-
return;
|
|
723
|
-
case "select-editor-row":
|
|
724
|
-
if (actionNode.dataset.rowIndex) {
|
|
725
|
-
selectEditorRow(actionNode.dataset.rowIndex);
|
|
726
|
-
}
|
|
727
|
-
return;
|
|
728
|
-
case "clear-data-row-selection":
|
|
729
|
-
clearDataRowSelection();
|
|
730
|
-
return;
|
|
731
|
-
case "clear-editor-row-selection":
|
|
732
|
-
clearEditorRowSelection();
|
|
733
|
-
return;
|
|
734
|
-
case "set-data-page":
|
|
735
|
-
if (actionNode.dataset.page) {
|
|
736
|
-
await setDataPage(actionNode.dataset.page);
|
|
737
|
-
}
|
|
738
|
-
return;
|
|
739
|
-
case "sort-data-column":
|
|
740
|
-
if (actionNode.dataset.columnName) {
|
|
741
|
-
await sortDataTableByColumn(actionNode.dataset.columnName);
|
|
742
|
-
}
|
|
743
|
-
return;
|
|
744
|
-
case "sort-editor-results-column":
|
|
745
|
-
if (actionNode.dataset.columnName) {
|
|
746
|
-
sortEditorResultsByColumn(actionNode.dataset.columnName);
|
|
747
|
-
}
|
|
748
|
-
return;
|
|
749
|
-
case "set-data-page-size":
|
|
750
|
-
if (actionNode.dataset.pageSize) {
|
|
751
|
-
await setDataPageSize(actionNode.dataset.pageSize);
|
|
752
|
-
}
|
|
753
|
-
return;
|
|
754
|
-
case "reload-data-route":
|
|
755
|
-
await refreshCurrentRoute();
|
|
756
|
-
return;
|
|
757
|
-
default:
|
|
758
|
-
}
|
|
759
|
-
}
|
|
1205
|
+
}
|
|
760
1206
|
|
|
761
|
-
|
|
762
|
-
const actionNode = event.target.closest("[data-action]");
|
|
1207
|
+
const state = getState();
|
|
763
1208
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
+
}
|
|
767
1223
|
|
|
768
|
-
|
|
1224
|
+
if (state.route.name === 'editorResults' && typeof state.editor.selectedRowIndex === 'number') {
|
|
1225
|
+
event.preventDefault();
|
|
1226
|
+
clearEditorRowSelection();
|
|
1227
|
+
}
|
|
769
1228
|
});
|
|
770
1229
|
|
|
771
|
-
document.addEventListener(
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
if (
|
|
775
|
-
event.key === "Enter" &&
|
|
776
|
-
event.shiftKey &&
|
|
777
|
-
!event.altKey &&
|
|
778
|
-
!event.ctrlKey &&
|
|
779
|
-
!event.metaKey &&
|
|
780
|
-
!event.defaultPrevented &&
|
|
781
|
-
target instanceof HTMLTextAreaElement &&
|
|
782
|
-
target.dataset.bind === "current-query"
|
|
783
|
-
) {
|
|
784
|
-
event.preventDefault();
|
|
1230
|
+
document.addEventListener('input', event => {
|
|
1231
|
+
const bindNode = event.target.closest('[data-bind]');
|
|
785
1232
|
|
|
786
|
-
if (!
|
|
787
|
-
|
|
1233
|
+
if (!bindNode) {
|
|
1234
|
+
return;
|
|
788
1235
|
}
|
|
789
1236
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1237
|
+
if (bindNode.dataset.bind === 'current-query') {
|
|
1238
|
+
invalidateMainRenderCache();
|
|
1239
|
+
syncQueryEditorHighlight(bindNode);
|
|
1240
|
+
syncQueryEditorScroll(bindNode);
|
|
1241
|
+
setCurrentQuery(bindNode.value);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
796
1244
|
|
|
797
|
-
|
|
1245
|
+
if (bindNode.dataset.bind === 'data-search-query') {
|
|
1246
|
+
setDataSearchQuery(bindNode.value);
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
798
1249
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
1250
|
+
if (bindNode.dataset.bind === 'table-designer-search') {
|
|
1251
|
+
setTableDesignerSearchQuery(bindNode.value);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
804
1254
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
}
|
|
1255
|
+
if (bindNode.dataset.bind === 'media-tagging-tag-search') {
|
|
1256
|
+
syncMediaTaggingTagSearchUi(bindNode);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
810
1259
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
});
|
|
1260
|
+
if (bindNode.dataset.bind === 'table-designer-field') {
|
|
1261
|
+
if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
816
1264
|
|
|
817
|
-
|
|
818
|
-
|
|
1265
|
+
updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.value);
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
819
1268
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1269
|
+
if (bindNode.dataset.bind === 'table-designer-column-field') {
|
|
1270
|
+
updateCurrentTableDesignerColumnField(bindNode.dataset.columnId, bindNode.dataset.field, bindNode.value);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
823
1273
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
1274
|
+
if (bindNode.dataset.bind === 'media-tagging-field') {
|
|
1275
|
+
if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
830
1278
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1279
|
+
// Skip select elements - they're handled in the change event with preview refresh
|
|
1280
|
+
if (bindNode instanceof HTMLSelectElement) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
835
1283
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1284
|
+
if (bindNode instanceof HTMLTextAreaElement && bindNode.dataset.sqlHighlight === 'true') {
|
|
1285
|
+
syncQueryEditorHighlight(bindNode);
|
|
1286
|
+
syncQueryEditorScroll(bindNode);
|
|
1287
|
+
}
|
|
840
1288
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
return;
|
|
1289
|
+
updateCurrentMediaTaggingField(bindNode.dataset.field, bindNode.value);
|
|
1290
|
+
return;
|
|
844
1291
|
}
|
|
845
1292
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1293
|
+
if (bindNode.dataset.bind === 'media-tagging-tag-form-field') {
|
|
1294
|
+
if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
849
1297
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
bindNode.dataset.field,
|
|
854
|
-
bindNode.value
|
|
855
|
-
);
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
1298
|
+
updateCurrentMediaTaggingTagFormField(bindNode.dataset.field, bindNode.value);
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
858
1301
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1302
|
+
if (bindNode.dataset.bind === 'query-history-search') {
|
|
1303
|
+
setQueryHistorySearchInput(bindNode.value);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
863
1306
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1307
|
+
if (bindNode.dataset.bind === 'query-chart-draft:name') {
|
|
1308
|
+
updateCurrentQueryChartDraftField('name', bindNode.value);
|
|
1309
|
+
}
|
|
867
1310
|
});
|
|
868
1311
|
|
|
869
1312
|
document.addEventListener(
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1313
|
+
'scroll',
|
|
1314
|
+
event => {
|
|
1315
|
+
const target = event.target;
|
|
873
1316
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1317
|
+
if (!(target instanceof HTMLTextAreaElement)) {
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
877
1320
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1321
|
+
if (target.dataset.bind !== 'current-query' && target.dataset.sqlHighlight !== 'true') {
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
syncQueryEditorScroll(target);
|
|
1326
|
+
},
|
|
1327
|
+
true,
|
|
881
1328
|
);
|
|
882
1329
|
|
|
883
|
-
document.addEventListener(
|
|
884
|
-
|
|
1330
|
+
document.addEventListener('change', event => {
|
|
1331
|
+
const bindNode = event.target.closest('[data-bind]');
|
|
885
1332
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1333
|
+
if (!bindNode) {
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
889
1336
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1337
|
+
if (bindNode.dataset.bind === 'data-search-column') {
|
|
1338
|
+
setDataSearchColumn(bindNode.value);
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
894
1341
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1342
|
+
if (bindNode.dataset.bind === 'media-tagging-field') {
|
|
1343
|
+
updateCurrentMediaTaggingField(bindNode.dataset.field, bindNode.value);
|
|
1344
|
+
void refreshMediaTaggingPreview();
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
899
1347
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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;
|
|
904
1353
|
}
|
|
905
1354
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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
|
+
}
|
|
909
1362
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
bindNode.value
|
|
915
|
-
);
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
if (bindNode.dataset.bind === "table-designer-column-flag") {
|
|
920
|
-
updateCurrentTableDesignerColumnField(
|
|
921
|
-
bindNode.dataset.columnId,
|
|
922
|
-
bindNode.dataset.field,
|
|
923
|
-
bindNode.checked
|
|
924
|
-
);
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
if (bindNode.dataset.bind === "query-chart-draft:chartType") {
|
|
929
|
-
updateCurrentQueryChartDraftField("chartType", bindNode.value);
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
if (bindNode.dataset.bind === "query-chart-draft:tableVisible") {
|
|
934
|
-
updateCurrentQueryChartDraftField("tableVisible", bindNode.checked);
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
if (bindNode.dataset.bind?.startsWith("query-chart-draft-config:")) {
|
|
939
|
-
const field = bindNode.dataset.bind.slice("query-chart-draft-config:".length);
|
|
940
|
-
const value =
|
|
941
|
-
bindNode instanceof HTMLInputElement && bindNode.type === "checkbox"
|
|
942
|
-
? bindNode.checked
|
|
943
|
-
: bindNode.value === ""
|
|
944
|
-
? null
|
|
945
|
-
: bindNode.value;
|
|
946
|
-
updateCurrentQueryChartDraftConfigField(field, value);
|
|
947
|
-
}
|
|
948
|
-
});
|
|
1363
|
+
if (bindNode.dataset.bind === 'table-designer-import-file') {
|
|
1364
|
+
void handleTableDesignerCsvImport(bindNode);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
949
1367
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
event.preventDefault();
|
|
958
|
-
const formData = new FormData(form);
|
|
959
|
-
|
|
960
|
-
switch (form.dataset.form) {
|
|
961
|
-
case "open-connection": {
|
|
962
|
-
resetStructureGraphForDatabaseChange();
|
|
963
|
-
const connection = await submitOpenConnection({
|
|
964
|
-
path: String(formData.get("path") ?? ""),
|
|
965
|
-
label: String(formData.get("label") ?? ""),
|
|
966
|
-
readOnly: formData.get("readOnly") === "on",
|
|
967
|
-
});
|
|
968
|
-
|
|
969
|
-
if (connection) {
|
|
970
|
-
router.navigate("/overview");
|
|
971
|
-
}
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
|
-
case "create-connection": {
|
|
975
|
-
resetStructureGraphForDatabaseChange();
|
|
976
|
-
const connection = await submitCreateConnection({
|
|
977
|
-
path: String(formData.get("path") ?? ""),
|
|
978
|
-
label: String(formData.get("label") ?? ""),
|
|
979
|
-
});
|
|
980
|
-
|
|
981
|
-
if (connection) {
|
|
982
|
-
router.navigate("/overview");
|
|
983
|
-
}
|
|
984
|
-
return;
|
|
985
|
-
}
|
|
986
|
-
case "import-sql": {
|
|
987
|
-
resetStructureGraphForDatabaseChange();
|
|
988
|
-
const targetMode = String(formData.get("targetMode") ?? "active");
|
|
989
|
-
const payload = {
|
|
990
|
-
sqlFilePath: String(formData.get("sqlFilePath") ?? ""),
|
|
991
|
-
label: String(formData.get("label") ?? ""),
|
|
992
|
-
};
|
|
993
|
-
|
|
994
|
-
if (targetMode === "recent") {
|
|
995
|
-
payload.targetConnectionId = String(formData.get("targetConnectionId") ?? "");
|
|
996
|
-
} else if (targetMode === "create") {
|
|
997
|
-
payload.createNew = true;
|
|
998
|
-
payload.targetPath = String(formData.get("targetPath") ?? "");
|
|
999
|
-
} else if (targetMode === "path") {
|
|
1000
|
-
payload.targetPath = String(formData.get("targetPath") ?? "");
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
const result = await submitImportSql(payload);
|
|
1004
|
-
if (result) {
|
|
1005
|
-
router.navigate("/overview");
|
|
1006
|
-
}
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
case "edit-connection": {
|
|
1010
|
-
const connectionId = String(formData.get("connectionId") ?? "");
|
|
1011
|
-
const isActiveConnection = getState().connections.active?.id === connectionId;
|
|
1012
|
-
const logoFile = formData.get("logoFile");
|
|
1013
|
-
const logoUpload = await buildConnectionLogoUpload(logoFile);
|
|
1014
|
-
|
|
1015
|
-
if (isActiveConnection) {
|
|
1016
|
-
resetStructureGraphForDatabaseChange();
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
await submitEditConnection(connectionId, {
|
|
1020
|
-
path: String(formData.get("path") ?? ""),
|
|
1021
|
-
label: String(formData.get("label") ?? ""),
|
|
1022
|
-
readOnly: formData.get("readOnly") === "on",
|
|
1023
|
-
clearLogo: formData.get("clearLogo") === "on" && !logoUpload,
|
|
1024
|
-
logoUpload,
|
|
1025
|
-
});
|
|
1026
|
-
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
case "delete-row-confirm":
|
|
1030
|
-
await submitDeleteRowConfirmation();
|
|
1031
|
-
return;
|
|
1032
|
-
case "save-query-chart":
|
|
1033
|
-
await saveCurrentQueryChartDraft();
|
|
1034
|
-
return;
|
|
1035
|
-
case "delete-query-chart":
|
|
1036
|
-
await submitDeleteChartConfirmation();
|
|
1037
|
-
return;
|
|
1038
|
-
case "save-data-row": {
|
|
1039
|
-
const values = {};
|
|
1040
|
-
|
|
1041
|
-
for (const [key, value] of formData.entries()) {
|
|
1042
|
-
if (!key.startsWith("field:")) {
|
|
1043
|
-
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;
|
|
1044
1372
|
}
|
|
1045
1373
|
|
|
1046
|
-
|
|
1047
|
-
|
|
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
|
+
}
|
|
1387
|
+
|
|
1388
|
+
if (bindNode.dataset.bind === 'query-chart-draft:chartType') {
|
|
1389
|
+
updateCurrentQueryChartDraftField('chartType', bindNode.value);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
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]');
|
|
1048
1412
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
values
|
|
1052
|
-
);
|
|
1053
|
-
return;
|
|
1413
|
+
if (!form) {
|
|
1414
|
+
return;
|
|
1054
1415
|
}
|
|
1055
|
-
case "save-editor-row": {
|
|
1056
|
-
const values = {};
|
|
1057
1416
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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;
|
|
1061
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
|
+
}
|
|
1062
1542
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
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
|
+
}
|
|
1086
1566
|
});
|
|
1087
1567
|
|
|
1088
1568
|
subscribe(renderApp);
|
|
1089
1569
|
renderApp(getState());
|
|
1090
1570
|
initializeApp().then(() => {
|
|
1091
|
-
|
|
1571
|
+
router.start();
|
|
1092
1572
|
});
|