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
|
@@ -73,17 +73,16 @@ export function renderConnectionCard(connection, activeConnectionId) {
|
|
|
73
73
|
<div class="border-t border-outline-variant/10 bg-surface-container-low px-4 py-3">
|
|
74
74
|
<div class="grid grid-cols-[minmax(0,1fr)_5.1rem_5.8rem] gap-2">
|
|
75
75
|
<button
|
|
76
|
-
class="
|
|
76
|
+
class="signature-button"
|
|
77
77
|
data-action="select-connection"
|
|
78
78
|
data-connection-id="${escapeHtml(connection.id)}"
|
|
79
|
-
style="--clip-path: ${clipPath};"
|
|
80
79
|
type="button"
|
|
81
80
|
title="${primaryActionLabel}"
|
|
82
81
|
>
|
|
83
82
|
${primaryActionLabel}
|
|
84
83
|
</button>
|
|
85
84
|
<button
|
|
86
|
-
class="
|
|
85
|
+
class="standard-button"
|
|
87
86
|
data-action="edit-connection"
|
|
88
87
|
data-connection-id="${escapeHtml(connection.id)}"
|
|
89
88
|
type="button"
|
|
@@ -91,7 +90,7 @@ export function renderConnectionCard(connection, activeConnectionId) {
|
|
|
91
90
|
Edit
|
|
92
91
|
</button>
|
|
93
92
|
<button
|
|
94
|
-
class="
|
|
93
|
+
class="delete-button"
|
|
95
94
|
data-action="remove-connection"
|
|
96
95
|
data-connection-id="${escapeHtml(connection.id)}"
|
|
97
96
|
type="button"
|
|
@@ -22,7 +22,7 @@ function renderRecentConnections(recentConnections = []) {
|
|
|
22
22
|
.map(
|
|
23
23
|
(connection) => `
|
|
24
24
|
<button
|
|
25
|
-
class="flex items-center gap-2 border border-outline-variant/15 bg-surface-container-low px-4
|
|
25
|
+
class="control-button flex items-center gap-2 border border-outline-variant/15 bg-surface-container-low px-4 text-left text-on-surface transition-colors hover:border-primary-container/30 hover:bg-surface-container-high"
|
|
26
26
|
data-action="select-connection"
|
|
27
27
|
data-connection-id="${escapeHtml(connection.id)}"
|
|
28
28
|
type="button"
|
|
@@ -135,7 +135,7 @@ export function renderEmptyState({ activeConnection, recentConnections = [] }) {
|
|
|
135
135
|
? `
|
|
136
136
|
<div class="mx-auto mt-8 grid w-full max-w-3xl grid-cols-1 gap-4 px-6 md:grid-cols-3">
|
|
137
137
|
<button
|
|
138
|
-
class="
|
|
138
|
+
class="standard-button"
|
|
139
139
|
data-action="navigate"
|
|
140
140
|
data-to="/overview"
|
|
141
141
|
type="button"
|
|
@@ -143,7 +143,7 @@ export function renderEmptyState({ activeConnection, recentConnections = [] }) {
|
|
|
143
143
|
Overview
|
|
144
144
|
</button>
|
|
145
145
|
<button
|
|
146
|
-
class="
|
|
146
|
+
class="standard-button"
|
|
147
147
|
data-action="navigate"
|
|
148
148
|
data-to="/structure"
|
|
149
149
|
type="button"
|
|
@@ -151,7 +151,7 @@ export function renderEmptyState({ activeConnection, recentConnections = [] }) {
|
|
|
151
151
|
Structure
|
|
152
152
|
</button>
|
|
153
153
|
<button
|
|
154
|
-
class="
|
|
154
|
+
class="standard-button"
|
|
155
155
|
data-action="navigate"
|
|
156
156
|
data-to="/editor"
|
|
157
157
|
type="button"
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import { escapeHtml, truncateMiddle } from "../utils/format.js";
|
|
1
|
+
import { escapeHtml, highlightSql, truncateMiddle } from "../utils/format.js";
|
|
2
2
|
import { renderConnectionLogo } from "./connectionLogo.js";
|
|
3
3
|
import {
|
|
4
4
|
analyzeQueryChartResult,
|
|
5
5
|
getQueryChartTypeLabel,
|
|
6
6
|
QUERY_CHART_TYPES,
|
|
7
7
|
} from "../lib/queryCharts.js";
|
|
8
|
+
import {
|
|
9
|
+
hasDefaultMediaTaggingTagTable,
|
|
10
|
+
hasDefaultMediaTaggingMappingTable,
|
|
11
|
+
MEDIA_TAGGING_DEFAULT_MAPPING_TABLE,
|
|
12
|
+
MEDIA_TAGGING_DEFAULT_MAPPING_TABLE_SQL,
|
|
13
|
+
MEDIA_TAGGING_DEFAULT_TAG_TABLE,
|
|
14
|
+
MEDIA_TAGGING_DEFAULT_TAG_TABLE_SQL,
|
|
15
|
+
} from "../lib/mediaTaggingDefaults.js";
|
|
8
16
|
|
|
9
17
|
function renderField({ label, name, type = "text", placeholder = "", value = "" }) {
|
|
10
18
|
return `
|
|
@@ -13,7 +21,7 @@ function renderField({ label, name, type = "text", placeholder = "", value = ""
|
|
|
13
21
|
${escapeHtml(label)}
|
|
14
22
|
</span>
|
|
15
23
|
<input
|
|
16
|
-
class="w-full border border-outline-variant/20 bg-surface-container-lowest
|
|
24
|
+
class="control-input w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
17
25
|
name="${escapeHtml(name)}"
|
|
18
26
|
placeholder="${escapeHtml(placeholder)}"
|
|
19
27
|
type="${escapeHtml(type)}"
|
|
@@ -25,18 +33,43 @@ function renderField({ label, name, type = "text", placeholder = "", value = ""
|
|
|
25
33
|
|
|
26
34
|
function renderCheckboxField({ label, name, checked = false, text }) {
|
|
27
35
|
return `
|
|
28
|
-
<label class="flex
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
<label class="flex flex-col gap-2">
|
|
37
|
+
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
38
|
+
${escapeHtml(label)}
|
|
39
|
+
</span>
|
|
40
|
+
<span class="standard-checkbox">
|
|
41
|
+
<input
|
|
42
|
+
${checked ? "checked" : ""}
|
|
43
|
+
name="${escapeHtml(name)}"
|
|
44
|
+
type="checkbox"
|
|
45
|
+
/>
|
|
46
|
+
<span>${escapeHtml(text || label)}</span>
|
|
47
|
+
</span>
|
|
36
48
|
</label>
|
|
37
49
|
`;
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
function renderSqlPreviewField(value, minHeightClass = "sql-highlight-shell--tall") {
|
|
53
|
+
return `
|
|
54
|
+
<div class="sql-highlight-shell ${minHeightClass}">
|
|
55
|
+
<div class="query-editor-layer sql-highlight-layer">
|
|
56
|
+
<div
|
|
57
|
+
aria-hidden="true"
|
|
58
|
+
class="query-editor-highlight sql-highlight-content"
|
|
59
|
+
data-query-editor-highlight
|
|
60
|
+
>${value ? highlightSql(value) : ""}</div>
|
|
61
|
+
<textarea
|
|
62
|
+
class="query-editor-input sql-highlight-input custom-scrollbar"
|
|
63
|
+
data-sql-highlight="true"
|
|
64
|
+
readonly
|
|
65
|
+
spellcheck="false"
|
|
66
|
+
wrap="off"
|
|
67
|
+
>${escapeHtml(value)}</textarea>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
40
73
|
function renderFileField({
|
|
41
74
|
label,
|
|
42
75
|
name,
|
|
@@ -50,7 +83,7 @@ function renderFileField({
|
|
|
50
83
|
</span>
|
|
51
84
|
<input
|
|
52
85
|
accept="${escapeHtml(accept)}"
|
|
53
|
-
class="block w-full border border-outline-variant/20 bg-surface-container-lowest
|
|
86
|
+
class="control-input block w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface file:mr-4 file:border-0 file:bg-primary-container file:px-3 file:py-2 file:text-xs file:font-bold file:text-on-primary"
|
|
54
87
|
name="${escapeHtml(name)}"
|
|
55
88
|
type="file"
|
|
56
89
|
/>
|
|
@@ -66,11 +99,11 @@ function renderFileField({
|
|
|
66
99
|
function renderSelectField({ label, name, value = "", options = [], bind = "" }) {
|
|
67
100
|
return `
|
|
68
101
|
<label class="block space-y-2">
|
|
69
|
-
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
102
|
+
<span class="block text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
70
103
|
${escapeHtml(label)}
|
|
71
104
|
</span>
|
|
72
105
|
<select
|
|
73
|
-
class="w-full border border-outline-variant/20 bg-surface-container-lowest
|
|
106
|
+
class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
74
107
|
${bind ? `data-bind="${escapeHtml(bind)}"` : ""}
|
|
75
108
|
${name ? `name="${escapeHtml(name)}"` : ""}
|
|
76
109
|
>
|
|
@@ -124,14 +157,14 @@ function renderOpenConnectionForm(modal) {
|
|
|
124
157
|
${renderError(modal.error)}
|
|
125
158
|
<div class="flex items-center justify-end gap-3 pt-2">
|
|
126
159
|
<button
|
|
127
|
-
class="
|
|
160
|
+
class="standard-button"
|
|
128
161
|
data-action="close-modal"
|
|
129
162
|
type="button"
|
|
130
163
|
>
|
|
131
164
|
Cancel
|
|
132
165
|
</button>
|
|
133
166
|
<button
|
|
134
|
-
class="
|
|
167
|
+
class="standard-button"
|
|
135
168
|
type="submit"
|
|
136
169
|
>
|
|
137
170
|
${modal.submitting ? "Opening..." : "Open Database"}
|
|
@@ -198,14 +231,14 @@ function renderEditConnectionForm(modal) {
|
|
|
198
231
|
${renderError(modal.error)}
|
|
199
232
|
<div class="flex items-center justify-end gap-3 pt-2">
|
|
200
233
|
<button
|
|
201
|
-
class="
|
|
234
|
+
class="standard-button"
|
|
202
235
|
data-action="close-modal"
|
|
203
236
|
type="button"
|
|
204
237
|
>
|
|
205
238
|
Cancel
|
|
206
239
|
</button>
|
|
207
240
|
<button
|
|
208
|
-
class="
|
|
241
|
+
class="standard-button"
|
|
209
242
|
type="submit"
|
|
210
243
|
>
|
|
211
244
|
${modal.submitting ? "Saving..." : "Save Changes"}
|
|
@@ -231,14 +264,14 @@ function renderCreateDatabaseForm(modal) {
|
|
|
231
264
|
${renderError(modal.error)}
|
|
232
265
|
<div class="flex items-center justify-end gap-3 pt-2">
|
|
233
266
|
<button
|
|
234
|
-
class="
|
|
267
|
+
class="standard-button"
|
|
235
268
|
data-action="close-modal"
|
|
236
269
|
type="button"
|
|
237
270
|
>
|
|
238
271
|
Cancel
|
|
239
272
|
</button>
|
|
240
273
|
<button
|
|
241
|
-
class="
|
|
274
|
+
class="standard-button"
|
|
242
275
|
type="submit"
|
|
243
276
|
>
|
|
244
277
|
${modal.submitting ? "Creating..." : "Create Database"}
|
|
@@ -265,7 +298,7 @@ function renderImportTargetOptions(state) {
|
|
|
265
298
|
Import Target
|
|
266
299
|
</span>
|
|
267
300
|
<select
|
|
268
|
-
class="w-full border border-outline-variant/20 bg-surface-container-lowest
|
|
301
|
+
class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
269
302
|
name="targetMode"
|
|
270
303
|
>
|
|
271
304
|
${
|
|
@@ -286,7 +319,7 @@ function renderImportTargetOptions(state) {
|
|
|
286
319
|
Recent Connection
|
|
287
320
|
</span>
|
|
288
321
|
<select
|
|
289
|
-
class="w-full border border-outline-variant/20 bg-surface-container-lowest
|
|
322
|
+
class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
290
323
|
name="targetConnectionId"
|
|
291
324
|
>
|
|
292
325
|
${recentOptions}
|
|
@@ -324,14 +357,14 @@ function renderImportSqlForm(modal, state) {
|
|
|
324
357
|
${renderError(modal.error)}
|
|
325
358
|
<div class="flex items-center justify-end gap-3 pt-2">
|
|
326
359
|
<button
|
|
327
|
-
class="
|
|
360
|
+
class="standard-button"
|
|
328
361
|
data-action="close-modal"
|
|
329
362
|
type="button"
|
|
330
363
|
>
|
|
331
364
|
Cancel
|
|
332
365
|
</button>
|
|
333
366
|
<button
|
|
334
|
-
class="
|
|
367
|
+
class="standard-button"
|
|
335
368
|
type="submit"
|
|
336
369
|
>
|
|
337
370
|
${modal.submitting ? "Importing..." : "Import SQL Dump"}
|
|
@@ -393,14 +426,14 @@ function renderDeleteRowConfirmForm(modal) {
|
|
|
393
426
|
${renderError(modal.error)}
|
|
394
427
|
<div class="flex items-center justify-end gap-3 pt-2">
|
|
395
428
|
<button
|
|
396
|
-
class="
|
|
429
|
+
class="standard-button"
|
|
397
430
|
data-action="close-modal"
|
|
398
431
|
type="button"
|
|
399
432
|
>
|
|
400
433
|
Cancel
|
|
401
434
|
</button>
|
|
402
435
|
<button
|
|
403
|
-
class="
|
|
436
|
+
class="delete-button"
|
|
404
437
|
type="submit"
|
|
405
438
|
>
|
|
406
439
|
${modal.submitting ? "Deleting..." : "Delete Row"}
|
|
@@ -633,14 +666,14 @@ function renderChartEditorForm(modal, state) {
|
|
|
633
666
|
${renderError(modal.error)}
|
|
634
667
|
<div class="flex items-center justify-end gap-3 pt-2">
|
|
635
668
|
<button
|
|
636
|
-
class="
|
|
669
|
+
class="standard-button"
|
|
637
670
|
data-action="close-modal"
|
|
638
671
|
type="button"
|
|
639
672
|
>
|
|
640
673
|
Cancel
|
|
641
674
|
</button>
|
|
642
675
|
<button
|
|
643
|
-
class="
|
|
676
|
+
class="standard-button"
|
|
644
677
|
type="submit"
|
|
645
678
|
>
|
|
646
679
|
${modal.submitting ? "Saving..." : draft.mode === "edit" ? "Save Chart" : "Create Chart"}
|
|
@@ -666,14 +699,14 @@ function renderDeleteChartForm(modal) {
|
|
|
666
699
|
${renderError(modal.error)}
|
|
667
700
|
<div class="flex items-center justify-end gap-3 pt-2">
|
|
668
701
|
<button
|
|
669
|
-
class="
|
|
702
|
+
class="standard-button"
|
|
670
703
|
data-action="close-modal"
|
|
671
704
|
type="button"
|
|
672
705
|
>
|
|
673
706
|
Cancel
|
|
674
707
|
</button>
|
|
675
708
|
<button
|
|
676
|
-
class="
|
|
709
|
+
class="delete-button"
|
|
677
710
|
type="submit"
|
|
678
711
|
>
|
|
679
712
|
${modal.submitting ? "Deleting..." : "Delete Chart"}
|
|
@@ -683,6 +716,167 @@ function renderDeleteChartForm(modal) {
|
|
|
683
716
|
`;
|
|
684
717
|
}
|
|
685
718
|
|
|
719
|
+
function renderDeleteQueryHistoryForm(modal) {
|
|
720
|
+
return `
|
|
721
|
+
<form class="space-y-5" data-form="delete-query-history-confirm">
|
|
722
|
+
<div class="space-y-3">
|
|
723
|
+
<p class="text-sm leading-7 text-on-surface">
|
|
724
|
+
Delete query <span class="font-bold text-primary-container">${escapeHtml(
|
|
725
|
+
modal.queryTitle ?? "SQL query"
|
|
726
|
+
)}</span>?
|
|
727
|
+
</p>
|
|
728
|
+
<p class="text-sm leading-7 text-on-surface-variant/65">
|
|
729
|
+
This removes the query-history entry and all recorded runs linked to it.
|
|
730
|
+
</p>
|
|
731
|
+
</div>
|
|
732
|
+
${renderError(modal.error)}
|
|
733
|
+
<div class="flex items-center justify-end gap-3 pt-2">
|
|
734
|
+
<button
|
|
735
|
+
class="standard-button"
|
|
736
|
+
data-action="close-modal"
|
|
737
|
+
type="button"
|
|
738
|
+
>
|
|
739
|
+
Cancel
|
|
740
|
+
</button>
|
|
741
|
+
<button
|
|
742
|
+
class="delete-button"
|
|
743
|
+
type="submit"
|
|
744
|
+
>
|
|
745
|
+
${modal.submitting ? "Deleting..." : "Delete Query"}
|
|
746
|
+
</button>
|
|
747
|
+
</div>
|
|
748
|
+
</form>
|
|
749
|
+
`;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function renderCreateMediaTaggingMappingTableForm(modal, state) {
|
|
753
|
+
const mappingExists = hasDefaultMediaTaggingMappingTable(state.mediaTagging.schemaTables ?? []);
|
|
754
|
+
const readOnly = Boolean(state.mediaTagging.connection?.readOnly);
|
|
755
|
+
|
|
756
|
+
return `
|
|
757
|
+
<form class="space-y-5" data-form="create-media-tagging-mapping-table">
|
|
758
|
+
<div class="space-y-3">
|
|
759
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
760
|
+
Mapping Table
|
|
761
|
+
</div>
|
|
762
|
+
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 font-mono text-sm text-on-surface">
|
|
763
|
+
${escapeHtml(MEDIA_TAGGING_DEFAULT_MAPPING_TABLE)}
|
|
764
|
+
</div>
|
|
765
|
+
</div>
|
|
766
|
+
<div class="space-y-3">
|
|
767
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
768
|
+
Status
|
|
769
|
+
</div>
|
|
770
|
+
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface">
|
|
771
|
+
${
|
|
772
|
+
mappingExists
|
|
773
|
+
? `${escapeHtml(MEDIA_TAGGING_DEFAULT_MAPPING_TABLE)} already exists in the active database.`
|
|
774
|
+
: `${escapeHtml(MEDIA_TAGGING_DEFAULT_MAPPING_TABLE)} does not exist yet.`
|
|
775
|
+
}
|
|
776
|
+
${
|
|
777
|
+
readOnly && !mappingExists
|
|
778
|
+
? `<div class="mt-2 text-on-surface-variant/60">The active connection is read-only, so the table cannot be created here.</div>`
|
|
779
|
+
: ""
|
|
780
|
+
}
|
|
781
|
+
</div>
|
|
782
|
+
</div>
|
|
783
|
+
<div class="space-y-3">
|
|
784
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
785
|
+
SQL
|
|
786
|
+
</div>
|
|
787
|
+
${renderSqlPreviewField(MEDIA_TAGGING_DEFAULT_MAPPING_TABLE_SQL)}
|
|
788
|
+
</div>
|
|
789
|
+
${renderError(modal.error)}
|
|
790
|
+
<div class="flex items-center justify-end gap-3 pt-2">
|
|
791
|
+
<button
|
|
792
|
+
class="standard-button"
|
|
793
|
+
data-action="close-modal"
|
|
794
|
+
type="button"
|
|
795
|
+
>
|
|
796
|
+
Close
|
|
797
|
+
</button>
|
|
798
|
+
${
|
|
799
|
+
!mappingExists
|
|
800
|
+
? `
|
|
801
|
+
<button
|
|
802
|
+
class="standard-button"
|
|
803
|
+
type="submit"
|
|
804
|
+
${readOnly ? "disabled" : ""}
|
|
805
|
+
>
|
|
806
|
+
${modal.submitting ? "Creating..." : "Create Table"}
|
|
807
|
+
</button>
|
|
808
|
+
`
|
|
809
|
+
: ""
|
|
810
|
+
}
|
|
811
|
+
</div>
|
|
812
|
+
</form>
|
|
813
|
+
`;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function renderCreateMediaTaggingTagTableForm(modal, state) {
|
|
817
|
+
const tagTableExists = hasDefaultMediaTaggingTagTable(state.mediaTagging.schemaTables ?? []);
|
|
818
|
+
const readOnly = Boolean(state.mediaTagging.connection?.readOnly);
|
|
819
|
+
|
|
820
|
+
return `
|
|
821
|
+
<form class="space-y-5" data-form="create-media-tagging-tag-table">
|
|
822
|
+
<div class="space-y-3">
|
|
823
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
824
|
+
Tag Table
|
|
825
|
+
</div>
|
|
826
|
+
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 font-mono text-sm text-on-surface">
|
|
827
|
+
${escapeHtml(MEDIA_TAGGING_DEFAULT_TAG_TABLE)}
|
|
828
|
+
</div>
|
|
829
|
+
</div>
|
|
830
|
+
<div class="space-y-3">
|
|
831
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
832
|
+
Status
|
|
833
|
+
</div>
|
|
834
|
+
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface">
|
|
835
|
+
${
|
|
836
|
+
tagTableExists
|
|
837
|
+
? `${escapeHtml(MEDIA_TAGGING_DEFAULT_TAG_TABLE)} already exists in the active database.`
|
|
838
|
+
: `${escapeHtml(MEDIA_TAGGING_DEFAULT_TAG_TABLE)} does not exist yet.`
|
|
839
|
+
}
|
|
840
|
+
${
|
|
841
|
+
readOnly && !tagTableExists
|
|
842
|
+
? `<div class="mt-2 text-on-surface-variant/60">The active connection is read-only, so the table cannot be created here.</div>`
|
|
843
|
+
: ""
|
|
844
|
+
}
|
|
845
|
+
</div>
|
|
846
|
+
</div>
|
|
847
|
+
<div class="space-y-3">
|
|
848
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
849
|
+
SQL
|
|
850
|
+
</div>
|
|
851
|
+
${renderSqlPreviewField(MEDIA_TAGGING_DEFAULT_TAG_TABLE_SQL)}
|
|
852
|
+
</div>
|
|
853
|
+
${renderError(modal.error)}
|
|
854
|
+
<div class="flex items-center justify-end gap-3 pt-2">
|
|
855
|
+
<button
|
|
856
|
+
class="standard-button"
|
|
857
|
+
data-action="close-modal"
|
|
858
|
+
type="button"
|
|
859
|
+
>
|
|
860
|
+
Close
|
|
861
|
+
</button>
|
|
862
|
+
${
|
|
863
|
+
!tagTableExists
|
|
864
|
+
? `
|
|
865
|
+
<button
|
|
866
|
+
class="standard-button"
|
|
867
|
+
type="submit"
|
|
868
|
+
${readOnly ? "disabled" : ""}
|
|
869
|
+
>
|
|
870
|
+
${modal.submitting ? "Creating..." : "Create Table"}
|
|
871
|
+
</button>
|
|
872
|
+
`
|
|
873
|
+
: ""
|
|
874
|
+
}
|
|
875
|
+
</div>
|
|
876
|
+
</form>
|
|
877
|
+
`;
|
|
878
|
+
}
|
|
879
|
+
|
|
686
880
|
export function renderModal(state) {
|
|
687
881
|
const modal = state.modal;
|
|
688
882
|
|
|
@@ -726,6 +920,21 @@ export function renderModal(state) {
|
|
|
726
920
|
title: "Delete Chart",
|
|
727
921
|
body: renderDeleteChartForm(modal),
|
|
728
922
|
},
|
|
923
|
+
"delete-query-history": {
|
|
924
|
+
eyebrow: "History // Confirm query deletion",
|
|
925
|
+
title: "Delete Query",
|
|
926
|
+
body: renderDeleteQueryHistoryForm(modal),
|
|
927
|
+
},
|
|
928
|
+
"create-media-tagging-tag-table": {
|
|
929
|
+
eyebrow: "Media Tagging // Create default tag table",
|
|
930
|
+
title: "Create Tag Table",
|
|
931
|
+
body: renderCreateMediaTaggingTagTableForm(modal, state),
|
|
932
|
+
},
|
|
933
|
+
"create-media-tagging-mapping-table": {
|
|
934
|
+
eyebrow: "Media Tagging // Create default join table",
|
|
935
|
+
title: "Create Mapping Table",
|
|
936
|
+
body: renderCreateMediaTaggingMappingTableForm(modal, state),
|
|
937
|
+
},
|
|
729
938
|
};
|
|
730
939
|
|
|
731
940
|
const config = contentByKind[modal.kind];
|
|
@@ -747,7 +956,7 @@ export function renderModal(state) {
|
|
|
747
956
|
</h2>
|
|
748
957
|
</div>
|
|
749
958
|
<button
|
|
750
|
-
class="
|
|
959
|
+
class="control-icon-button border border-outline-variant/20 text-on-surface-variant hover:bg-surface-container-highest hover:text-primary-container"
|
|
751
960
|
data-action="close-modal"
|
|
752
961
|
type="button"
|
|
753
962
|
>
|
|
@@ -19,11 +19,16 @@ function renderHighlightedQuery(query) {
|
|
|
19
19
|
|
|
20
20
|
function renderEditorSurface({ query }) {
|
|
21
21
|
return `
|
|
22
|
-
<div class="flex min-h-0 flex-1 overflow-hidden">
|
|
23
|
-
<div class="flex w-12
|
|
24
|
-
|
|
22
|
+
<div class="query-editor-shell flex min-h-0 flex-1 overflow-hidden">
|
|
23
|
+
<div class="flex w-12 min-h-0 overflow-hidden bg-surface-container-lowest py-4 font-mono text-xs select-none text-outline-variant/30">
|
|
24
|
+
<div
|
|
25
|
+
class="query-editor-gutter-track flex w-full flex-col items-center"
|
|
26
|
+
data-query-editor-gutter
|
|
27
|
+
>
|
|
28
|
+
${renderLineNumbers(query)}
|
|
29
|
+
</div>
|
|
25
30
|
</div>
|
|
26
|
-
<div class="relative flex-1 overflow-hidden bg-surface-container p-6 font-mono text-sm leading-relaxed">
|
|
31
|
+
<div class="relative min-h-0 flex-1 overflow-hidden bg-surface-container p-6 font-mono text-sm leading-relaxed">
|
|
27
32
|
<div class="pointer-events-none absolute right-0 top-0 p-4 opacity-5">
|
|
28
33
|
<span class="material-symbols-outlined text-[120px] font-thin">terminal</span>
|
|
29
34
|
</div>
|
|
@@ -55,8 +60,7 @@ export function renderQueryEditor({
|
|
|
55
60
|
editorVisible = true,
|
|
56
61
|
historyVisible = true,
|
|
57
62
|
}) {
|
|
58
|
-
const secondaryButtonClass =
|
|
59
|
-
"toolbar-button border border-outline-variant/20 bg-surface-container px-4 py-2 text-[10px] font-bold uppercase tracking-widest text-on-surface transition-colors hover:border-primary-container hover:text-primary-container";
|
|
63
|
+
const secondaryButtonClass = "standard-button";
|
|
60
64
|
const left = `
|
|
61
65
|
<div class="flex items-center gap-2 bg-surface-container-lowest px-3 py-1">
|
|
62
66
|
<span class="material-symbols-outlined text-xs text-[#FCE300]">database</span>
|
|
@@ -104,9 +108,8 @@ export function renderQueryEditor({
|
|
|
104
108
|
${exporting ? "Exporting..." : "Export CSV"}
|
|
105
109
|
</button>
|
|
106
110
|
<button
|
|
107
|
-
class="
|
|
111
|
+
class="signature-button"
|
|
108
112
|
data-action="execute-query"
|
|
109
|
-
style="--clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%);"
|
|
110
113
|
type="button"
|
|
111
114
|
>
|
|
112
115
|
${executing ? "RUNNING..." : "EXECUTE"}
|
|
@@ -19,6 +19,10 @@ function renderDetailMetaItem(label, value) {
|
|
|
19
19
|
`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function canOpenQueryHistoryInCharts(item) {
|
|
23
|
+
return Boolean(item?.chartsEligible);
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
function renderRunItem(run) {
|
|
23
27
|
return `
|
|
24
28
|
<div class="border border-outline-variant/10 bg-surface-container px-3 py-3">
|
|
@@ -155,14 +159,14 @@ export function renderQueryHistoryDetail({
|
|
|
155
159
|
</label>
|
|
156
160
|
<div class="mt-2 flex gap-2">
|
|
157
161
|
<input
|
|
158
|
-
class="flex-1 border border-outline-variant/20 bg-surface-container
|
|
162
|
+
class="control-input flex-1 border border-outline-variant/20 bg-surface-container text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
|
|
159
163
|
name="title"
|
|
160
164
|
placeholder="${escapeHtml(item.displayTitle)}"
|
|
161
165
|
type="text"
|
|
162
166
|
value="${escapeHtml(item.title ?? "")}"
|
|
163
167
|
/>
|
|
164
168
|
<button
|
|
165
|
-
class="
|
|
169
|
+
class="standard-button"
|
|
166
170
|
type="submit"
|
|
167
171
|
>
|
|
168
172
|
Save
|
|
@@ -175,15 +179,30 @@ export function renderQueryHistoryDetail({
|
|
|
175
179
|
|
|
176
180
|
<div class="mt-5 flex flex-wrap gap-2">
|
|
177
181
|
<button
|
|
178
|
-
class="
|
|
182
|
+
class="standard-button"
|
|
179
183
|
data-action="open-query-history"
|
|
180
184
|
data-history-id="${escapeHtml(item.id)}"
|
|
181
185
|
type="button"
|
|
182
186
|
>
|
|
183
187
|
Open In Editor
|
|
184
188
|
</button>
|
|
189
|
+
${
|
|
190
|
+
canOpenQueryHistoryInCharts(item)
|
|
191
|
+
? `
|
|
192
|
+
<button
|
|
193
|
+
class="standard-button"
|
|
194
|
+
data-action="navigate"
|
|
195
|
+
data-to="/charts/${encodeURIComponent(item.id)}"
|
|
196
|
+
type="button"
|
|
197
|
+
>
|
|
198
|
+
<span class="material-symbols-outlined text-sm">bar_chart</span>
|
|
199
|
+
Open In Charts
|
|
200
|
+
</button>
|
|
201
|
+
`
|
|
202
|
+
: ""
|
|
203
|
+
}
|
|
185
204
|
<button
|
|
186
|
-
class="
|
|
205
|
+
class="standard-button"
|
|
187
206
|
data-action="run-query-history"
|
|
188
207
|
data-history-id="${escapeHtml(item.id)}"
|
|
189
208
|
type="button"
|
|
@@ -191,7 +210,7 @@ export function renderQueryHistoryDetail({
|
|
|
191
210
|
Run Now
|
|
192
211
|
</button>
|
|
193
212
|
<button
|
|
194
|
-
class="
|
|
213
|
+
class="standard-button"
|
|
195
214
|
data-action="toggle-query-history-saved"
|
|
196
215
|
data-history-id="${escapeHtml(item.id)}"
|
|
197
216
|
data-next-value="${item.isSaved ? "false" : "true"}"
|
|
@@ -200,8 +219,8 @@ export function renderQueryHistoryDetail({
|
|
|
200
219
|
${item.isSaved ? "Unsave" : "Save"}
|
|
201
220
|
</button>
|
|
202
221
|
<button
|
|
203
|
-
class="
|
|
204
|
-
data-action="delete-query-history"
|
|
222
|
+
class="delete-button"
|
|
223
|
+
data-action="open-delete-query-history-modal"
|
|
205
224
|
data-history-id="${escapeHtml(item.id)}"
|
|
206
225
|
type="button"
|
|
207
226
|
>
|
|
@@ -228,7 +247,7 @@ export function renderQueryHistoryDetail({
|
|
|
228
247
|
)}</textarea>
|
|
229
248
|
<div class="mt-2 flex justify-end">
|
|
230
249
|
<button
|
|
231
|
-
class="
|
|
250
|
+
class="standard-button"
|
|
232
251
|
type="submit"
|
|
233
252
|
>
|
|
234
253
|
Save Notes
|