sqlite-hub 0.9.3 → 0.9.6
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/.github/workflows/ci.yml +36 -0
- package/README.md +2 -2
- package/bin/sqlite-hub.js +1 -1
- package/frontend/index.html +2 -158
- package/frontend/js/app.js +41 -22
- package/frontend/js/components/connectionCard.js +62 -87
- package/frontend/js/components/emptyState.js +20 -23
- package/frontend/js/components/modal.js +145 -195
- package/frontend/js/components/pageHeader.js +1 -1
- package/frontend/js/components/queryEditor.js +16 -30
- package/frontend/js/components/queryHistoryDetail.js +93 -164
- package/frontend/js/components/queryHistoryPanel.js +81 -99
- package/frontend/js/components/queryResults.js +3 -1
- package/frontend/js/components/rowEditorPanel.js +28 -31
- package/frontend/js/components/structureGraph.js +10 -9
- package/frontend/js/components/tableDesignerEditor.js +91 -116
- package/frontend/js/store.js +39 -3
- package/frontend/js/utils/dom.js +28 -0
- package/frontend/js/utils/tableDesigner.js +8 -2
- package/frontend/js/views/charts.js +23 -43
- package/frontend/js/views/data.js +116 -132
- package/frontend/js/views/mediaTagging.js +131 -164
- package/frontend/js/views/structure.js +52 -48
- package/frontend/styles/tailwind.css +80 -0
- package/frontend/styles/tailwind.generated.css +2 -0
- package/frontend/styles/tokens.css +3 -3
- package/package.json +19 -5
- package/server/routes/mediaTagging.js +2 -10
- package/server/routes/sql.js +35 -10
- package/server/server.js +24 -0
- package/server/services/sqlite/dataBrowserService.js +25 -5
- package/server/services/sqlite/exportService.js +4 -2
- package/server/services/sqlite/introspection.js +2 -2
- package/server/services/sqlite/mediaTaggingService.js +166 -53
- package/server/services/sqlite/structureService.js +2 -2
- package/server/services/sqlite/tableDesigner/sql.js +19 -3
- package/server/services/storage/appStateStore.js +227 -87
- package/server/utils/appPaths.js +55 -19
- package/server/utils/fileValidation.js +94 -8
- package/tailwind.config.cjs +73 -0
- package/tests/security-paths.test.js +84 -0
- package/tests/sql-identifier-safety.test.js +66 -0
- package/.npmingnore +0 -4
- package/changelog.md +0 -84
- package/docs/DESIGN_GUIDELINES.md +0 -36
- package/scripts/publish_brew.sh +0 -466
- package/scripts/publish_npm.sh +0 -241
- package/shortkeys.md +0 -5
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build-test-audit:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Setup Node
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: 26
|
|
24
|
+
cache: npm
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: npm ci
|
|
28
|
+
|
|
29
|
+
- name: Build
|
|
30
|
+
run: npm run build
|
|
31
|
+
|
|
32
|
+
- name: Test
|
|
33
|
+
run: npm test
|
|
34
|
+
|
|
35
|
+
- name: Dependency audit
|
|
36
|
+
run: npm run audit
|
package/README.md
CHANGED
package/bin/sqlite-hub.js
CHANGED
|
@@ -334,7 +334,7 @@ async function main() {
|
|
|
334
334
|
} else if (stmt.kind === 'mutation') {
|
|
335
335
|
console.log(`Changes: ${stmt.changes}`);
|
|
336
336
|
if (stmt.lastInsertRowid) {
|
|
337
|
-
console.log(
|
|
337
|
+
console.log('Last insert rowid:', stmt.lastInsertRowid);
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
});
|
package/frontend/index.html
CHANGED
|
@@ -6,164 +6,8 @@
|
|
|
6
6
|
<title>SQLite Hub</title>
|
|
7
7
|
<link href="/assets/images/logo_raw.png" rel="icon" type="image/png" />
|
|
8
8
|
<link href="/assets/images/logo_raw.png" rel="shortcut icon" type="image/png" />
|
|
9
|
-
<
|
|
10
|
-
<link
|
|
11
|
-
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700;800;900&family=Inter:wght@300;400;500;600;700;800&family=Roboto+Mono:wght@400;500;700&display=swap"
|
|
12
|
-
rel="stylesheet"
|
|
13
|
-
/>
|
|
14
|
-
<link
|
|
15
|
-
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
|
|
16
|
-
rel="stylesheet"
|
|
17
|
-
/>
|
|
18
|
-
<script id="tailwind-config">
|
|
19
|
-
tailwind.config = {
|
|
20
|
-
darkMode: "class",
|
|
21
|
-
theme: {
|
|
22
|
-
extend: {
|
|
23
|
-
colors: {
|
|
24
|
-
"on-secondary-fixed": "#1c1b1b",
|
|
25
|
-
background: "rgb(var(--rgb-background) / <alpha-value>)",
|
|
26
|
-
"inverse-primary": "#6a5f00",
|
|
27
|
-
"primary-fixed": "#fde403",
|
|
28
|
-
tertiary: "#fbfffe",
|
|
29
|
-
outline: "#979177",
|
|
30
|
-
"on-secondary": "#313030",
|
|
31
|
-
"error-container": "#93000a",
|
|
32
|
-
"on-primary-fixed-variant": "#504700",
|
|
33
|
-
"on-tertiary-container": "#006f72",
|
|
34
|
-
"on-tertiary-fixed-variant": "#004f51",
|
|
35
|
-
"secondary-fixed-dim": "#c8c6c5",
|
|
36
|
-
"tertiary-fixed-dim": "#00dce1",
|
|
37
|
-
"on-background": "rgb(var(--rgb-on-surface) / <alpha-value>)",
|
|
38
|
-
"surface-tint": "#dec800",
|
|
39
|
-
"primary-fixed-dim": "#dec800",
|
|
40
|
-
"on-surface": "rgb(var(--rgb-on-surface) / <alpha-value>)",
|
|
41
|
-
"surface-container-low": "rgb(var(--rgb-surface-low) / <alpha-value>)",
|
|
42
|
-
"on-tertiary": "#003738",
|
|
43
|
-
"surface-container": "rgb(var(--rgb-surface-container) / <alpha-value>)",
|
|
44
|
-
"on-surface-variant": "rgb(var(--rgb-on-surface-variant) / <alpha-value>)",
|
|
45
|
-
"on-primary-container": "#706400",
|
|
46
|
-
"on-error": "#690005",
|
|
47
|
-
"surface-container-high": "rgb(var(--rgb-surface-high) / <alpha-value>)",
|
|
48
|
-
"surface-container-highest": "rgb(var(--rgb-surface-highest) / <alpha-value>)",
|
|
49
|
-
error: "rgb(var(--rgb-error) / <alpha-value>)",
|
|
50
|
-
primary: "#fffeff",
|
|
51
|
-
"outline-variant": "rgb(var(--rgb-outline) / <alpha-value>)",
|
|
52
|
-
"on-error-container": "#ffdad6",
|
|
53
|
-
"on-secondary-fixed-variant": "#474746",
|
|
54
|
-
"surface-container-lowest": "rgb(var(--rgb-surface-lowest) / <alpha-value>)",
|
|
55
|
-
"inverse-surface": "#e5e2e1",
|
|
56
|
-
surface: "rgb(var(--rgb-surface) / <alpha-value>)",
|
|
57
|
-
"tertiary-container": "#04faff",
|
|
58
|
-
"on-tertiary-fixed": "#002021",
|
|
59
|
-
"tertiary-fixed": "#2dfaff",
|
|
60
|
-
"surface-variant": "#353534",
|
|
61
|
-
"on-primary": "#373100",
|
|
62
|
-
"primary-container": "rgb(var(--rgb-primary) / <alpha-value>)",
|
|
63
|
-
"inverse-on-surface": "#313030",
|
|
64
|
-
secondary: "#c8c6c5",
|
|
65
|
-
"on-secondary-container": "#b7b5b4",
|
|
66
|
-
"on-primary-fixed": "#201c00",
|
|
67
|
-
"secondary-container": "#474746",
|
|
68
|
-
"surface-dim": "rgb(var(--rgb-surface) / <alpha-value>)",
|
|
69
|
-
"surface-bright": "rgb(var(--rgb-surface-bright) / <alpha-value>)",
|
|
70
|
-
"secondary-fixed": "#e5e2e1",
|
|
71
|
-
},
|
|
72
|
-
fontFamily: {
|
|
73
|
-
headline: ["Space Grotesk"],
|
|
74
|
-
body: ["Inter"],
|
|
75
|
-
label: ["Inter"],
|
|
76
|
-
mono: ["Roboto Mono"],
|
|
77
|
-
},
|
|
78
|
-
borderRadius: {
|
|
79
|
-
DEFAULT: "0px",
|
|
80
|
-
lg: "0px",
|
|
81
|
-
xl: "0px",
|
|
82
|
-
full: "9999px",
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
</script>
|
|
88
|
-
<style type="text/tailwindcss">
|
|
89
|
-
@layer components {
|
|
90
|
-
.signature-button {
|
|
91
|
-
@apply inline-flex items-center justify-center gap-2 h-[var(--control-height)] min-h-[var(--control-height)] border border-primary-container bg-primary-container px-[var(--control-padding-inline)] font-headline text-xs font-bold uppercase tracking-[0.16em] text-on-primary transition-all;
|
|
92
|
-
box-shadow: 0 0 18px -10px rgb(var(--rgb-primary) / 0.7);
|
|
93
|
-
clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.signature-button:hover {
|
|
97
|
-
@apply -translate-y-px bg-primary-fixed text-on-primary;
|
|
98
|
-
border-color: rgb(var(--rgb-primary) / 0.55);
|
|
99
|
-
box-shadow: 0 0 24px -8px rgb(var(--rgb-primary) / 0.92);
|
|
100
|
-
filter: brightness(1.03);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.signature-button:disabled,
|
|
104
|
-
.signature-button[aria-disabled="true"] {
|
|
105
|
-
@apply cursor-not-allowed opacity-45;
|
|
106
|
-
box-shadow: none;
|
|
107
|
-
filter: none;
|
|
108
|
-
transform: none;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.signature-button .material-symbols-outlined {
|
|
112
|
-
@apply text-base;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.standard-button {
|
|
116
|
-
@apply inline-flex items-center justify-center gap-2 h-[var(--control-height)] min-h-[var(--control-height)] border border-outline-variant/20 bg-surface-container px-[var(--control-padding-inline)] font-mono text-[10px] font-bold uppercase tracking-[0.16em] text-on-surface transition-colors;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.standard-button:hover {
|
|
120
|
-
@apply bg-surface-container-highest text-primary-container;
|
|
121
|
-
border-color: var(--primary-alpha-24);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.standard-button.is-active {
|
|
125
|
-
@apply bg-surface-container-high text-primary-container;
|
|
126
|
-
border-color: var(--primary-alpha-30);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.standard-button:disabled,
|
|
130
|
-
.standard-button[aria-disabled="true"] {
|
|
131
|
-
@apply cursor-not-allowed opacity-40;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.standard-button .material-symbols-outlined {
|
|
135
|
-
@apply text-base;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.standard-checkbox {
|
|
139
|
-
@apply inline-flex w-full items-center gap-3 min-h-[var(--control-height)] border border-outline-variant/10 bg-surface-container-lowest px-[var(--control-padding-inline)] text-sm text-on-surface;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.standard-checkbox input[type="checkbox"] {
|
|
143
|
-
@apply m-0 rounded-none border-outline bg-surface-container-lowest text-primary-container focus:ring-primary-container;
|
|
144
|
-
accent-color: var(--color-primary-container);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.standard-checkbox.is-disabled,
|
|
148
|
-
.standard-checkbox:has(input:disabled) {
|
|
149
|
-
@apply opacity-45;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.delete-button {
|
|
153
|
-
@apply inline-flex items-center justify-center gap-2 h-[var(--control-height)] min-h-[var(--control-height)] border border-error/25 bg-error-container/10 px-[var(--control-padding-inline)] font-mono text-[10px] font-bold uppercase tracking-[0.16em] text-error transition-all;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.delete-button:hover {
|
|
157
|
-
@apply -translate-y-px border-error bg-error-container/20 text-error;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.delete-button:disabled,
|
|
161
|
-
.delete-button[aria-disabled="true"] {
|
|
162
|
-
@apply cursor-not-allowed opacity-45;
|
|
163
|
-
transform: none;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
</style>
|
|
9
|
+
<link href="/vendor/material-symbols/outlined.css" rel="stylesheet" />
|
|
10
|
+
<link href="styles/tailwind.generated.css" rel="stylesheet" />
|
|
167
11
|
<link href="styles/tokens.css" rel="stylesheet" />
|
|
168
12
|
<link href="styles/base.css" rel="stylesheet" />
|
|
169
13
|
<link href="styles/utilities.css" rel="stylesheet" />
|
package/frontend/js/app.js
CHANGED
|
@@ -122,11 +122,15 @@ import { renderOverviewView } from './views/overview.js';
|
|
|
122
122
|
import { renderSettingsView } from './views/settings.js';
|
|
123
123
|
import { renderStructureView } from './views/structure.js';
|
|
124
124
|
import { renderTableDesignerView } from './views/tableDesigner.js';
|
|
125
|
+
import {
|
|
126
|
+
replaceChildrenFromRenderedMarkup,
|
|
127
|
+
replaceElementFromRenderedMarkup,
|
|
128
|
+
} from './utils/dom.js';
|
|
125
129
|
import { highlightSql } from './utils/format.js';
|
|
126
130
|
|
|
127
131
|
const appRoot = document.querySelector('#app');
|
|
128
132
|
|
|
129
|
-
appRoot
|
|
133
|
+
replaceChildrenFromRenderedMarkup(appRoot, renderAppShell());
|
|
130
134
|
|
|
131
135
|
const shellRefs = {
|
|
132
136
|
shell: document.querySelector('.app-shell'),
|
|
@@ -186,7 +190,7 @@ function syncQueryEditorHighlight(textarea) {
|
|
|
186
190
|
return;
|
|
187
191
|
}
|
|
188
192
|
|
|
189
|
-
highlightNode
|
|
193
|
+
replaceChildrenFromRenderedMarkup(highlightNode, renderQueryHighlightMarkup(textarea.value));
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
function syncQueryEditorScroll(textarea) {
|
|
@@ -258,12 +262,20 @@ function syncMediaTaggingCurrentMediaUi(button, detailsVisible) {
|
|
|
258
262
|
const nextVisible = Boolean(detailsVisible);
|
|
259
263
|
const expandedLabel = button.dataset.expandedLabel || 'Shrink Media Viewer';
|
|
260
264
|
const collapsedLabel = button.dataset.collapsedLabel || 'Show Media Viewer';
|
|
261
|
-
const expandedMarkup = `<span class="material-symbols-outlined">visibility_off</span> ${expandedLabel}`;
|
|
262
|
-
|
|
263
265
|
preview.classList.toggle('media-tagging-preview--meta-hidden', !nextVisible);
|
|
264
266
|
button.dataset.nextValue = nextVisible ? 'false' : 'true';
|
|
265
267
|
button.setAttribute('aria-expanded', nextVisible ? 'true' : 'false');
|
|
266
|
-
|
|
268
|
+
|
|
269
|
+
if (nextVisible) {
|
|
270
|
+
const icon = document.createElement('span');
|
|
271
|
+
|
|
272
|
+
icon.className = 'material-symbols-outlined';
|
|
273
|
+
icon.textContent = 'visibility_off';
|
|
274
|
+
button.replaceChildren(icon, document.createTextNode(` ${expandedLabel}`));
|
|
275
|
+
} else {
|
|
276
|
+
button.textContent = collapsedLabel;
|
|
277
|
+
}
|
|
278
|
+
|
|
267
279
|
return true;
|
|
268
280
|
}
|
|
269
281
|
|
|
@@ -350,7 +362,7 @@ function syncDataRowSelectionUi(selectedRowIndex = null) {
|
|
|
350
362
|
const panelMarkup = renderDataRowEditorPanel(getState());
|
|
351
363
|
const panelOpen = Boolean(panelMarkup);
|
|
352
364
|
|
|
353
|
-
shellRefs.panel
|
|
365
|
+
replaceChildrenFromRenderedMarkup(shellRefs.panel, panelMarkup);
|
|
354
366
|
shellRefs.shell.classList.toggle('panel-open', panelOpen);
|
|
355
367
|
lastRenderedPanelMarkup = panelMarkup;
|
|
356
368
|
lastRenderedPanelOpen = panelOpen;
|
|
@@ -367,14 +379,21 @@ function syncQueryHistoryUi(historyId) {
|
|
|
367
379
|
|
|
368
380
|
const historyItem = state.editor.history.find(entry => Number(entry.id) === numericId) ?? state.editor.historyDetail ?? null;
|
|
369
381
|
const listItemNode = shellRefs.view.querySelector(
|
|
370
|
-
|
|
382
|
+
[
|
|
383
|
+
'[data-action="select-query-history-item"][data-history-id="',
|
|
384
|
+
String(numericId),
|
|
385
|
+
'"]',
|
|
386
|
+
].join(''),
|
|
371
387
|
)?.closest('.query-history-item');
|
|
372
388
|
|
|
373
389
|
if (historyItem && listItemNode instanceof HTMLElement) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
390
|
+
replaceElementFromRenderedMarkup(
|
|
391
|
+
listItemNode,
|
|
392
|
+
renderQueryHistoryListItem(
|
|
393
|
+
historyItem,
|
|
394
|
+
state.editor.historyActiveId,
|
|
395
|
+
state.editor.historySelectedId,
|
|
396
|
+
),
|
|
378
397
|
);
|
|
379
398
|
}
|
|
380
399
|
|
|
@@ -387,7 +406,7 @@ function syncQueryHistoryUi(historyId) {
|
|
|
387
406
|
});
|
|
388
407
|
const panelOpen = Boolean(panelMarkup);
|
|
389
408
|
|
|
390
|
-
shellRefs.panel
|
|
409
|
+
replaceChildrenFromRenderedMarkup(shellRefs.panel, panelMarkup);
|
|
391
410
|
shellRefs.shell.classList.toggle('panel-open', panelOpen);
|
|
392
411
|
lastRenderedPanelMarkup = panelMarkup;
|
|
393
412
|
lastRenderedPanelOpen = panelOpen;
|
|
@@ -410,7 +429,7 @@ function syncQueryHistorySelectionUi(selectedHistoryId = null) {
|
|
|
410
429
|
}
|
|
411
430
|
|
|
412
431
|
if (selectedHistoryId === null) {
|
|
413
|
-
shellRefs.panel.
|
|
432
|
+
shellRefs.panel.replaceChildren();
|
|
414
433
|
shellRefs.shell.classList.remove('panel-open');
|
|
415
434
|
lastRenderedPanelMarkup = '';
|
|
416
435
|
lastRenderedPanelOpen = false;
|
|
@@ -596,7 +615,7 @@ function patchChartsDetailUi(state) {
|
|
|
596
615
|
return false;
|
|
597
616
|
}
|
|
598
617
|
|
|
599
|
-
detailNode
|
|
618
|
+
replaceChildrenFromRenderedMarkup(detailNode, renderChartsDetail(state));
|
|
600
619
|
syncChartsHistorySelectionUi(state);
|
|
601
620
|
return true;
|
|
602
621
|
}
|
|
@@ -972,7 +991,7 @@ function renderApp(state) {
|
|
|
972
991
|
!lockedRouteChanged;
|
|
973
992
|
|
|
974
993
|
if (shellMarkupUnchanged && toastMarkup !== lastRenderedToastMarkup) {
|
|
975
|
-
shellRefs.toast
|
|
994
|
+
replaceChildrenFromRenderedMarkup(shellRefs.toast, toastMarkup);
|
|
976
995
|
lastRenderedToastMarkup = toastMarkup;
|
|
977
996
|
return;
|
|
978
997
|
}
|
|
@@ -1009,7 +1028,7 @@ function renderApp(state) {
|
|
|
1009
1028
|
}
|
|
1010
1029
|
|
|
1011
1030
|
if (topNavChanged) {
|
|
1012
|
-
shellRefs.topNav
|
|
1031
|
+
replaceChildrenFromRenderedMarkup(shellRefs.topNav, topNavMarkup);
|
|
1013
1032
|
}
|
|
1014
1033
|
|
|
1015
1034
|
if (sidebarChanged) {
|
|
@@ -1019,16 +1038,16 @@ function renderApp(state) {
|
|
|
1019
1038
|
syncSidebarActiveRoute(state.route.name);
|
|
1020
1039
|
|
|
1021
1040
|
if (!sidebarSynced) {
|
|
1022
|
-
shellRefs.sidebar
|
|
1041
|
+
replaceChildrenFromRenderedMarkup(shellRefs.sidebar, sidebarMarkup);
|
|
1023
1042
|
}
|
|
1024
1043
|
}
|
|
1025
1044
|
|
|
1026
1045
|
if (statusBarChanged) {
|
|
1027
|
-
shellRefs.statusBar
|
|
1046
|
+
replaceChildrenFromRenderedMarkup(shellRefs.statusBar, statusBarMarkup);
|
|
1028
1047
|
}
|
|
1029
1048
|
|
|
1030
1049
|
if (mainChanged && !mainPatched) {
|
|
1031
|
-
shellRefs.view
|
|
1050
|
+
replaceChildrenFromRenderedMarkup(shellRefs.view, main);
|
|
1032
1051
|
}
|
|
1033
1052
|
|
|
1034
1053
|
if (mainChanged || lockedRouteChanged) {
|
|
@@ -1036,15 +1055,15 @@ function renderApp(state) {
|
|
|
1036
1055
|
}
|
|
1037
1056
|
|
|
1038
1057
|
if (panelChanged) {
|
|
1039
|
-
shellRefs.panel
|
|
1058
|
+
replaceChildrenFromRenderedMarkup(shellRefs.panel, panel);
|
|
1040
1059
|
}
|
|
1041
1060
|
|
|
1042
1061
|
if (modalChanged) {
|
|
1043
|
-
shellRefs.modal
|
|
1062
|
+
replaceChildrenFromRenderedMarkup(shellRefs.modal, modalMarkup);
|
|
1044
1063
|
}
|
|
1045
1064
|
|
|
1046
1065
|
if (toastMarkup !== lastRenderedToastMarkup) {
|
|
1047
|
-
shellRefs.toast
|
|
1066
|
+
replaceChildrenFromRenderedMarkup(shellRefs.toast, toastMarkup);
|
|
1048
1067
|
}
|
|
1049
1068
|
|
|
1050
1069
|
if (panelChanged || panelOpenChanged) {
|
|
@@ -13,92 +13,67 @@ export function renderConnectionCard(connection, activeConnectionId) {
|
|
|
13
13
|
: Boolean(connection.isActive);
|
|
14
14
|
const clipPath = "polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%)";
|
|
15
15
|
const primaryActionLabel = isActive ? "Open Overview" : "Set Active";
|
|
16
|
+
const connectionId = escapeHtml(connection.id);
|
|
17
|
+
const logoMarkup = renderConnectionLogo(connection, {
|
|
18
|
+
containerClass: [
|
|
19
|
+
"clipped-corner flex h-10 w-10 items-center justify-center overflow-hidden transition-colors",
|
|
20
|
+
isActive ? "bg-primary-container" : "bg-surface-container-highest",
|
|
21
|
+
].join(" "),
|
|
22
|
+
containerAttributes: ['style="--clip-path: ', clipPath, ';"'].join(""),
|
|
23
|
+
imageClassName: "h-full w-full object-cover",
|
|
24
|
+
iconClassName: isActive ? "text-on-primary" : "text-outline-variant",
|
|
25
|
+
});
|
|
16
26
|
|
|
17
|
-
return
|
|
18
|
-
<article
|
|
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
|
-
<div class="text-xs font-bold text-on-surface">${connection.readOnly ? "Read only" : "Read / Write"}</div>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
<div class="border-t border-outline-variant/10 bg-surface-container-low px-4 py-3">
|
|
74
|
-
<div class="grid grid-cols-[minmax(0,1fr)_5.1rem_5.8rem] gap-2">
|
|
75
|
-
<button
|
|
76
|
-
class="signature-button"
|
|
77
|
-
data-action="select-connection"
|
|
78
|
-
data-connection-id="${escapeHtml(connection.id)}"
|
|
79
|
-
type="button"
|
|
80
|
-
title="${primaryActionLabel}"
|
|
81
|
-
>
|
|
82
|
-
${primaryActionLabel}
|
|
83
|
-
</button>
|
|
84
|
-
<button
|
|
85
|
-
class="standard-button"
|
|
86
|
-
data-action="edit-connection"
|
|
87
|
-
data-connection-id="${escapeHtml(connection.id)}"
|
|
88
|
-
type="button"
|
|
89
|
-
>
|
|
90
|
-
Edit
|
|
91
|
-
</button>
|
|
92
|
-
<button
|
|
93
|
-
class="delete-button"
|
|
94
|
-
data-action="remove-connection"
|
|
95
|
-
data-connection-id="${escapeHtml(connection.id)}"
|
|
96
|
-
type="button"
|
|
97
|
-
>
|
|
98
|
-
Remove
|
|
99
|
-
</button>
|
|
100
|
-
</div>
|
|
101
|
-
</div>
|
|
102
|
-
</article>
|
|
103
|
-
`;
|
|
27
|
+
return [
|
|
28
|
+
'<article class="connection-card clipped-corner ',
|
|
29
|
+
isActive ? "is-active" : "",
|
|
30
|
+
'" style="--clip-path: ',
|
|
31
|
+
clipPath,
|
|
32
|
+
';">',
|
|
33
|
+
'<div class="flex-1 p-6"><div class="mb-6 flex items-start justify-between">',
|
|
34
|
+
logoMarkup,
|
|
35
|
+
'<div class="flex items-center gap-2">',
|
|
36
|
+
renderStatusBadge(isActive ? "ACTIVE" : "RECENT", isActive ? "primary" : "muted"),
|
|
37
|
+
connection.readOnly ? renderStatusBadge("READ_ONLY", "alert") : "",
|
|
38
|
+
"</div></div>",
|
|
39
|
+
'<h3 class="mb-1 font-headline text-xl font-bold uppercase ',
|
|
40
|
+
isActive ? "text-[#FCE300]" : "text-on-surface",
|
|
41
|
+
'">',
|
|
42
|
+
escapeHtml(connection.label),
|
|
43
|
+
"</h3>",
|
|
44
|
+
'<p class="block overflow-hidden text-ellipsis whitespace-nowrap font-mono text-[10px] text-outline-variant" title="',
|
|
45
|
+
escapeHtml(connection.path),
|
|
46
|
+
'">',
|
|
47
|
+
escapeHtml(truncateMiddle(connection.path, 68)),
|
|
48
|
+
"</p>",
|
|
49
|
+
'<div class="mt-8 grid grid-cols-2 gap-4">',
|
|
50
|
+
'<div><div class="mb-1 text-[9px] font-mono uppercase text-outline-variant">Allocation</div><div class="text-xs font-bold text-on-surface">',
|
|
51
|
+
escapeHtml(formatBytes(connection.sizeBytes)),
|
|
52
|
+
"</div></div>",
|
|
53
|
+
'<div><div class="mb-1 text-[9px] font-mono uppercase text-outline-variant">Last Modified</div><div class="text-xs font-bold text-on-surface">',
|
|
54
|
+
escapeHtml(formatDateTime(connection.lastModifiedAt)),
|
|
55
|
+
"</div></div>",
|
|
56
|
+
'<div><div class="mb-1 text-[9px] font-mono uppercase text-outline-variant">Last Opened</div><div class="text-xs font-bold text-on-surface">',
|
|
57
|
+
escapeHtml(formatDateTime(connection.lastOpenedAt)),
|
|
58
|
+
"</div></div>",
|
|
59
|
+
'<div><div class="mb-1 text-[9px] font-mono uppercase text-outline-variant">Mode</div><div class="text-xs font-bold text-on-surface">',
|
|
60
|
+
connection.readOnly ? "Read only" : "Read / Write",
|
|
61
|
+
"</div></div></div></div>",
|
|
62
|
+
'<div class="border-t border-outline-variant/10 bg-surface-container-low px-4 py-3">',
|
|
63
|
+
'<div class="grid grid-cols-[minmax(0,1fr)_5.1rem_5.8rem] gap-2">',
|
|
64
|
+
'<button class="signature-button" data-action="select-connection" data-connection-id="',
|
|
65
|
+
connectionId,
|
|
66
|
+
'" type="button" title="',
|
|
67
|
+
primaryActionLabel,
|
|
68
|
+
'">',
|
|
69
|
+
primaryActionLabel,
|
|
70
|
+
"</button>",
|
|
71
|
+
'<button class="standard-button" data-action="edit-connection" data-connection-id="',
|
|
72
|
+
connectionId,
|
|
73
|
+
'" type="button">Edit</button>',
|
|
74
|
+
'<button class="delete-button" data-action="remove-connection" data-connection-id="',
|
|
75
|
+
connectionId,
|
|
76
|
+
'" type="button">Remove</button>',
|
|
77
|
+
"</div></div></article>",
|
|
78
|
+
].join("");
|
|
104
79
|
}
|
|
@@ -6,6 +6,25 @@ import {
|
|
|
6
6
|
} from "../utils/format.js";
|
|
7
7
|
import { renderConnectionLogo } from "./connectionLogo.js";
|
|
8
8
|
|
|
9
|
+
function renderRecentConnectionButton(connection) {
|
|
10
|
+
return [
|
|
11
|
+
'<button 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" data-action="select-connection" data-connection-id="',
|
|
12
|
+
escapeHtml(connection.id),
|
|
13
|
+
'" type="button">',
|
|
14
|
+
renderConnectionLogo(connection, {
|
|
15
|
+
containerClass:
|
|
16
|
+
"flex h-8 w-8 items-center justify-center overflow-hidden bg-surface-container-highest",
|
|
17
|
+
imageClassName: "h-full w-full object-cover",
|
|
18
|
+
iconClassName: "text-sm text-primary-container",
|
|
19
|
+
}),
|
|
20
|
+
'<span class="min-w-0"><span class="block truncate font-mono text-xs">',
|
|
21
|
+
escapeHtml(connection.label),
|
|
22
|
+
'</span><span class="block truncate text-[10px] text-on-surface-variant/45">',
|
|
23
|
+
escapeHtml(truncateMiddle(connection.path, 34)),
|
|
24
|
+
"</span></span></button>",
|
|
25
|
+
].join("");
|
|
26
|
+
}
|
|
27
|
+
|
|
9
28
|
function renderRecentConnections(recentConnections = []) {
|
|
10
29
|
if (!recentConnections.length) {
|
|
11
30
|
return `
|
|
@@ -19,29 +38,7 @@ function renderRecentConnections(recentConnections = []) {
|
|
|
19
38
|
<div class="flex flex-wrap justify-center gap-4">
|
|
20
39
|
${recentConnections
|
|
21
40
|
.slice(0, 4)
|
|
22
|
-
.map(
|
|
23
|
-
(connection) => `
|
|
24
|
-
<button
|
|
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
|
-
data-action="select-connection"
|
|
27
|
-
data-connection-id="${escapeHtml(connection.id)}"
|
|
28
|
-
type="button"
|
|
29
|
-
>
|
|
30
|
-
${renderConnectionLogo(connection, {
|
|
31
|
-
containerClass:
|
|
32
|
-
"flex h-8 w-8 items-center justify-center overflow-hidden bg-surface-container-highest",
|
|
33
|
-
imageClassName: "h-full w-full object-cover",
|
|
34
|
-
iconClassName: "text-sm text-primary-container",
|
|
35
|
-
})}
|
|
36
|
-
<span class="min-w-0">
|
|
37
|
-
<span class="block truncate font-mono text-xs">${escapeHtml(connection.label)}</span>
|
|
38
|
-
<span class="block truncate text-[10px] text-on-surface-variant/45">${escapeHtml(
|
|
39
|
-
truncateMiddle(connection.path, 34)
|
|
40
|
-
)}</span>
|
|
41
|
-
</span>
|
|
42
|
-
</button>
|
|
43
|
-
`
|
|
44
|
-
)
|
|
41
|
+
.map((connection) => renderRecentConnectionButton(connection))
|
|
45
42
|
.join("")}
|
|
46
43
|
</div>
|
|
47
44
|
`;
|