sqlite-hub 0.9.3 → 0.9.4
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 +5 -1
- 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/tableDesignerEditor.js +91 -116
- package/frontend/js/store.js +39 -3
- 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 +81 -0
- package/frontend/styles/tailwind.generated.css +1 -0
- package/frontend/styles/tokens.css +3 -3
- package/package.json +16 -3
- 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
|
@@ -367,7 +367,11 @@ function syncQueryHistoryUi(historyId) {
|
|
|
367
367
|
|
|
368
368
|
const historyItem = state.editor.history.find(entry => Number(entry.id) === numericId) ?? state.editor.historyDetail ?? null;
|
|
369
369
|
const listItemNode = shellRefs.view.querySelector(
|
|
370
|
-
|
|
370
|
+
[
|
|
371
|
+
'[data-action="select-query-history-item"][data-history-id="',
|
|
372
|
+
String(numericId),
|
|
373
|
+
'"]',
|
|
374
|
+
].join(''),
|
|
371
375
|
)?.closest('.query-history-item');
|
|
372
376
|
|
|
373
377
|
if (historyItem && listItemNode instanceof HTMLElement) {
|
|
@@ -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
|
`;
|