sqlite-hub 0.8.7 → 0.9.1
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 +16 -0
- package/frontend/index.html +1 -1
- package/frontend/js/api.js +67 -0
- package/frontend/js/app.js +1554 -999
- package/frontend/js/components/modal.js +208 -3
- package/frontend/js/components/queryEditor.js +9 -4
- package/frontend/js/components/queryHistoryDetail.js +34 -12
- package/frontend/js/components/queryHistoryPanel.js +8 -11
- package/frontend/js/components/rowEditorPanel.js +26 -5
- package/frontend/js/components/sidebar.js +49 -5
- package/frontend/js/components/structureGraph.js +614 -632
- package/frontend/js/components/tableDesignerSqlPreview.js +24 -25
- package/frontend/js/lib/mediaTaggingDefaults.js +27 -0
- package/frontend/js/router.js +81 -71
- package/frontend/js/store.js +3140 -2179
- package/frontend/js/views/charts.js +2 -2
- package/frontend/js/views/data.js +28 -14
- package/frontend/js/views/editor.js +172 -177
- package/frontend/js/views/mediaTagging.js +931 -0
- package/frontend/styles/base.css +11 -1
- package/frontend/styles/components.css +54 -6
- package/frontend/styles/views.css +910 -0
- package/package.json +1 -1
- package/server/routes/charts.js +4 -1
- package/server/routes/data.js +14 -0
- package/server/routes/mediaTagging.js +166 -0
- package/server/routes/sql.js +1 -0
- package/server/server.js +4 -0
- package/server/services/sqlite/dataBrowserService.js +25 -0
- package/server/services/sqlite/exportService.js +31 -1
- package/server/services/sqlite/mediaTaggingService.js +1689 -0
- package/server/services/storage/appStateStore.js +325 -3
- package/server/services/storage/queryHistoryUtils.js +165 -2
- package/shortkeys.md +5 -0
|
@@ -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 `
|
|
@@ -25,7 +33,7 @@ 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="
|
|
36
|
+
<label class="flex flex-col gap-2">
|
|
29
37
|
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
30
38
|
${escapeHtml(label)}
|
|
31
39
|
</span>
|
|
@@ -41,6 +49,27 @@ function renderCheckboxField({ label, name, checked = false, text }) {
|
|
|
41
49
|
`;
|
|
42
50
|
}
|
|
43
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
|
+
|
|
44
73
|
function renderFileField({
|
|
45
74
|
label,
|
|
46
75
|
name,
|
|
@@ -70,7 +99,7 @@ function renderFileField({
|
|
|
70
99
|
function renderSelectField({ label, name, value = "", options = [], bind = "" }) {
|
|
71
100
|
return `
|
|
72
101
|
<label class="block space-y-2">
|
|
73
|
-
<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">
|
|
74
103
|
${escapeHtml(label)}
|
|
75
104
|
</span>
|
|
76
105
|
<select
|
|
@@ -687,6 +716,167 @@ function renderDeleteChartForm(modal) {
|
|
|
687
716
|
`;
|
|
688
717
|
}
|
|
689
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
|
+
|
|
690
880
|
export function renderModal(state) {
|
|
691
881
|
const modal = state.modal;
|
|
692
882
|
|
|
@@ -730,6 +920,21 @@ export function renderModal(state) {
|
|
|
730
920
|
title: "Delete Chart",
|
|
731
921
|
body: renderDeleteChartForm(modal),
|
|
732
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
|
+
},
|
|
733
938
|
};
|
|
734
939
|
|
|
735
940
|
const config = contentByKind[modal.kind];
|
|
@@ -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>
|
|
@@ -19,6 +19,22 @@ function renderDetailMetaItem(label, value) {
|
|
|
19
19
|
`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function canOpenQueryHistoryInCharts(item) {
|
|
23
|
+
return Boolean(item?.chartsEligible);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getQueryTypeTone(queryType) {
|
|
27
|
+
if (queryType === "select" || queryType === "update") {
|
|
28
|
+
return "success";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (queryType === "pragma") {
|
|
32
|
+
return "primary";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return "muted";
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
function renderRunItem(run) {
|
|
23
39
|
return `
|
|
24
40
|
<div class="border border-outline-variant/10 bg-surface-container px-3 py-3">
|
|
@@ -131,9 +147,9 @@ export function renderQueryHistoryDetail({
|
|
|
131
147
|
</button>
|
|
132
148
|
</div>
|
|
133
149
|
<div class="mt-4 flex flex-wrap gap-2">
|
|
134
|
-
${renderStatusBadge(item.queryType, item.
|
|
150
|
+
${renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType))}
|
|
135
151
|
${item.isSaved ? renderStatusBadge("saved", "primary") : ""}
|
|
136
|
-
${item.isDestructive ? renderStatusBadge("destructive", "
|
|
152
|
+
${item.isDestructive ? renderStatusBadge("destructive", "warning") : ""}
|
|
137
153
|
${item.lastRun ? renderStatusBadge(item.lastRun.status, item.lastRun.status === "error" ? "alert" : "success") : ""}
|
|
138
154
|
</div>
|
|
139
155
|
</div>
|
|
@@ -182,15 +198,21 @@ export function renderQueryHistoryDetail({
|
|
|
182
198
|
>
|
|
183
199
|
Open In Editor
|
|
184
200
|
</button>
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
201
|
+
${
|
|
202
|
+
canOpenQueryHistoryInCharts(item)
|
|
203
|
+
? `
|
|
204
|
+
<button
|
|
205
|
+
class="standard-button"
|
|
206
|
+
data-action="navigate"
|
|
207
|
+
data-to="/charts/${encodeURIComponent(item.id)}"
|
|
208
|
+
type="button"
|
|
209
|
+
>
|
|
210
|
+
<span class="material-symbols-outlined text-sm">bar_chart</span>
|
|
211
|
+
Open In Charts
|
|
212
|
+
</button>
|
|
213
|
+
`
|
|
214
|
+
: ""
|
|
215
|
+
}
|
|
194
216
|
<button
|
|
195
217
|
class="standard-button"
|
|
196
218
|
data-action="run-query-history"
|
|
@@ -210,7 +232,7 @@ export function renderQueryHistoryDetail({
|
|
|
210
232
|
</button>
|
|
211
233
|
<button
|
|
212
234
|
class="delete-button"
|
|
213
|
-
data-action="delete-query-history"
|
|
235
|
+
data-action="open-delete-query-history-modal"
|
|
214
236
|
data-history-id="${escapeHtml(item.id)}"
|
|
215
237
|
type="button"
|
|
216
238
|
>
|
|
@@ -4,12 +4,8 @@ import {
|
|
|
4
4
|
} from "../utils/format.js";
|
|
5
5
|
import { renderStatusBadge } from "./badges.js";
|
|
6
6
|
|
|
7
|
-
function getQueryTypeTone(queryType
|
|
8
|
-
if (
|
|
9
|
-
return "alert";
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (queryType === "select") {
|
|
7
|
+
function getQueryTypeTone(queryType) {
|
|
8
|
+
if (queryType === "select" || queryType === "update") {
|
|
13
9
|
return "success";
|
|
14
10
|
}
|
|
15
11
|
|
|
@@ -20,7 +16,7 @@ function getQueryTypeTone(queryType, isDestructive) {
|
|
|
20
16
|
return "muted";
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
function
|
|
19
|
+
export function renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId) {
|
|
24
20
|
const isActive = Number(activeHistoryId) === Number(item.id);
|
|
25
21
|
const isSelected = Number(selectedHistoryId) === Number(item.id);
|
|
26
22
|
const visibleTables = (item.tablesDetected ?? []).slice(0, 3);
|
|
@@ -39,9 +35,9 @@ function renderHistoryItem(item, activeHistoryId, selectedHistoryId) {
|
|
|
39
35
|
<span class="truncate font-headline text-sm font-bold uppercase tracking-tight text-on-surface">
|
|
40
36
|
${escapeHtml(item.displayTitle)}
|
|
41
37
|
</span>
|
|
42
|
-
${renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType
|
|
38
|
+
${renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType))}
|
|
43
39
|
${item.isSaved ? renderStatusBadge("saved", "primary") : ""}
|
|
44
|
-
${item.isDestructive ? renderStatusBadge("destructive", "
|
|
40
|
+
${item.isDestructive ? renderStatusBadge("destructive", "warning") : ""}
|
|
45
41
|
</div>
|
|
46
42
|
<p class="query-history-sql-preview mt-2 text-left font-mono text-xs leading-5 text-on-surface-variant/75">
|
|
47
43
|
${escapeHtml(item.previewSql)}
|
|
@@ -109,6 +105,7 @@ function renderQueryHistoryTabs(activeTab, historyTotal) {
|
|
|
109
105
|
const tabs = [
|
|
110
106
|
{ id: "recent", label: "Recent" },
|
|
111
107
|
{ id: "saved", label: "Saved" },
|
|
108
|
+
{ id: "unsaved", label: "Unsaved" },
|
|
112
109
|
{ id: "failed", label: "Failed" },
|
|
113
110
|
];
|
|
114
111
|
|
|
@@ -204,9 +201,9 @@ export function renderQueryHistoryPanel({
|
|
|
204
201
|
`
|
|
205
202
|
: ""
|
|
206
203
|
}
|
|
207
|
-
|
|
204
|
+
<div class="space-y-3">
|
|
208
205
|
${items
|
|
209
|
-
.map((item) =>
|
|
206
|
+
.map((item) => renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId))
|
|
210
207
|
.join("")}
|
|
211
208
|
</div>
|
|
212
209
|
${
|
|
@@ -42,12 +42,23 @@ function renderJsonViewer(prettyJson, title = "JSON Viewer") {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function renderReadonlyField(label, value) {
|
|
45
|
+
const badges = Array.isArray(label?.badges) ? label.badges : [];
|
|
46
|
+
const displayLabel = typeof label === "object" ? label.label : label;
|
|
45
47
|
const jsonPreview = getJsonPreview(value);
|
|
46
48
|
|
|
47
49
|
return `
|
|
48
50
|
<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3">
|
|
49
|
-
<div class="text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
50
|
-
|
|
51
|
+
<div class="flex flex-wrap items-center gap-2 text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
52
|
+
<span>${escapeHtml(displayLabel)}</span>
|
|
53
|
+
${badges
|
|
54
|
+
.map(
|
|
55
|
+
(badge) => `
|
|
56
|
+
<span class="border border-outline-variant/20 bg-surface-container px-2 py-1 text-[9px] text-primary-container">
|
|
57
|
+
${escapeHtml(badge)}
|
|
58
|
+
</span>
|
|
59
|
+
`
|
|
60
|
+
)
|
|
61
|
+
.join("")}
|
|
51
62
|
</div>
|
|
52
63
|
${
|
|
53
64
|
jsonPreview
|
|
@@ -59,12 +70,22 @@ function renderReadonlyField(label, value) {
|
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
function renderEditableField(field) {
|
|
73
|
+
const badges = Array.isArray(field.badges) ? field.badges : [];
|
|
62
74
|
const jsonPreview = getJsonPreview(field.value);
|
|
63
75
|
|
|
64
76
|
return `
|
|
65
77
|
<label class="block space-y-2">
|
|
66
|
-
<span class="text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
67
|
-
|
|
78
|
+
<span class="flex flex-wrap items-center gap-2 text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
|
|
79
|
+
<span>${escapeHtml(field.label ?? field.name)}</span>
|
|
80
|
+
${badges
|
|
81
|
+
.map(
|
|
82
|
+
(badge) => `
|
|
83
|
+
<span class="border border-outline-variant/20 bg-surface-container px-2 py-1 text-[9px] text-primary-container">
|
|
84
|
+
${escapeHtml(badge)}
|
|
85
|
+
</span>
|
|
86
|
+
`
|
|
87
|
+
)
|
|
88
|
+
.join("")}
|
|
68
89
|
</span>
|
|
69
90
|
${
|
|
70
91
|
jsonPreview
|
|
@@ -135,7 +156,7 @@ export function renderRowEditorPanel({
|
|
|
135
156
|
<button
|
|
136
157
|
class="delete-button"
|
|
137
158
|
data-action="${escapeHtml(deleteAction)}"
|
|
138
|
-
data-row-index="${escapeHtml(String(deleteRowIndex
|
|
159
|
+
${deleteRowIndex === null ? "" : `data-row-index="${escapeHtml(String(deleteRowIndex))}"`}
|
|
139
160
|
type="button"
|
|
140
161
|
${saving || deleting ? "disabled" : ""}
|
|
141
162
|
>
|
|
@@ -5,10 +5,19 @@ const sidebarItems = [
|
|
|
5
5
|
{ label: 'Connections', href: '#/connections', key: 'connections', icon: 'database' },
|
|
6
6
|
{ label: 'Overview', href: '#/overview', key: 'overview', icon: 'dashboard' },
|
|
7
7
|
{ label: 'Data', href: '#/data', key: 'data', icon: 'table_rows' },
|
|
8
|
-
{ label: 'SQL Editor', href: '#/editor', key: 'editor', icon: 'terminal' },
|
|
9
8
|
{ label: 'Structure', href: '#/structure', key: 'structure', icon: 'account_tree' },
|
|
9
|
+
{ label: 'SQL Editor', href: '#/editor', key: 'editor', icon: 'terminal' },
|
|
10
10
|
{ label: 'Charts', href: '#/charts', key: 'charts', icon: 'bar_chart' },
|
|
11
11
|
{ label: 'Table Designer', href: '#/table-designer', key: 'tableDesigner', icon: 'table_chart' },
|
|
12
|
+
{
|
|
13
|
+
label: 'Media Tagging',
|
|
14
|
+
key: 'mediaTagging',
|
|
15
|
+
icon: 'sell',
|
|
16
|
+
children: [
|
|
17
|
+
{ label: 'Setup', href: '#/media-tagging', key: 'mediaTaggingSetup' },
|
|
18
|
+
{ label: 'Tagging Queue', href: '#/media-tagging/queue', key: 'mediaTaggingQueue' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
12
21
|
{ label: 'Settings', href: '#/settings', key: 'settings', icon: 'settings' },
|
|
13
22
|
];
|
|
14
23
|
|
|
@@ -21,24 +30,59 @@ function getActiveSidebarKey(routeName) {
|
|
|
21
30
|
return 'editor';
|
|
22
31
|
}
|
|
23
32
|
|
|
33
|
+
if (routeName === 'mediaTaggingSetup' || routeName === 'mediaTaggingQueue') {
|
|
34
|
+
return 'mediaTagging';
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
return routeName;
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
export function renderSidebar(state) {
|
|
28
41
|
const activeKey = getActiveSidebarKey(state.route.name);
|
|
29
42
|
const activeConnection = state.connections.active;
|
|
43
|
+
const expandedKey = activeKey === 'mediaTagging' ? 'mediaTagging' : null;
|
|
30
44
|
|
|
31
45
|
return `
|
|
32
46
|
<nav class="sidebar-links">
|
|
33
47
|
${sidebarItems
|
|
34
|
-
.map(
|
|
35
|
-
item
|
|
48
|
+
.map(item => {
|
|
49
|
+
if (item.children) {
|
|
50
|
+
const isExpanded = expandedKey === item.key;
|
|
51
|
+
const isActive = activeKey === item.key;
|
|
52
|
+
return `
|
|
53
|
+
<div class="sidebar-group">
|
|
54
|
+
<a class="sidebar-link ${isActive ? 'is-active' : ''}" href="${item.children[0].href}" data-group="${item.key}">
|
|
55
|
+
<span class="material-symbols-outlined">${item.icon}</span>
|
|
56
|
+
<span>${item.label}</span>
|
|
57
|
+
<span class="material-symbols-outlined ml-auto text-[14px] ${isExpanded ? 'rotate-180' : ''}">expand_more</span>
|
|
58
|
+
</a>
|
|
59
|
+
${
|
|
60
|
+
isExpanded
|
|
61
|
+
? `
|
|
62
|
+
<div class="sidebar-sublinks">
|
|
63
|
+
${item.children
|
|
64
|
+
.map(
|
|
65
|
+
child => `
|
|
66
|
+
<a class="sidebar-sublink ${state.route.name === child.key ? 'is-active' : ''}" href="${child.href}">
|
|
67
|
+
${child.label}
|
|
68
|
+
</a>
|
|
69
|
+
`,
|
|
70
|
+
)
|
|
71
|
+
.join('')}
|
|
72
|
+
</div>
|
|
73
|
+
`
|
|
74
|
+
: ''
|
|
75
|
+
}
|
|
76
|
+
</div>
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
return `
|
|
36
80
|
<a class="sidebar-link ${item.key === activeKey ? 'is-active' : ''}" href="${item.href}">
|
|
37
81
|
<span class="material-symbols-outlined">${item.icon}</span>
|
|
38
82
|
<span>${item.label}</span>
|
|
39
83
|
</a>
|
|
40
|
-
|
|
41
|
-
)
|
|
84
|
+
`;
|
|
85
|
+
})
|
|
42
86
|
.join('')}
|
|
43
87
|
</nav>
|
|
44
88
|
<div class="sidebar-footer">
|