@reddb-io/ui 0.2.0 → 0.3.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/build/_app/immutable/chunks/BjFADNkA.js +992 -0
- package/build/_app/immutable/chunks/{BHBXmd1Y.js → C9rXzYLu.js} +1 -1
- package/build/_app/immutable/chunks/pDJnDxXM.js +1 -0
- package/build/_app/immutable/entry/{app.CGfmRH-N.js → app.DdaMXHAv.js} +2 -2
- package/build/_app/immutable/entry/start.-MLBtQXB.js +1 -0
- package/build/_app/immutable/nodes/{0.BYhY6O5-.js → 0.Ysoz3Y3h.js} +1 -1
- package/build/_app/immutable/nodes/{1.4XYGRMul.js → 1.C2jbGTOJ.js} +1 -1
- package/build/_app/immutable/nodes/{12.DXZfNPNK.js → 12.DQG5zpEx.js} +1 -1
- package/build/_app/immutable/nodes/{8.C7OOsMOJ.js → 8.CrHI4F9p.js} +1 -1
- package/build/_app/version.json +1 -1
- package/build/index.html +9 -9
- package/dist/embed/{RedUiEmbedRoot-Dia9lxfb.js → RedUiEmbedRoot-BwFUA5X-.js} +7441 -7387
- package/dist/embed/RedUiEmbedRoot-BwFUA5X-.js.map +1 -0
- package/dist/embed/{index-Bq0TT7Ao.js → index-DGhgtnaK.js} +4 -4
- package/dist/embed/{index-Bq0TT7Ao.js.map → index-DGhgtnaK.js.map} +1 -1
- package/dist/embed/index.js +1 -1
- package/dist/embed/{sigma.esm-C2pqg5JH.js → sigma.esm-D5yoExsH.js} +2 -2
- package/dist/embed/{sigma.esm-C2pqg5JH.js.map → sigma.esm-D5yoExsH.js.map} +1 -1
- package/package.json +3 -3
- package/src/lib/SettingsView.svelte +73 -19
- package/src/lib/settings-sections.ts +18 -0
- package/build/_app/immutable/chunks/BMc_qoms.js +0 -973
- package/build/_app/immutable/chunks/CwxFwvow.js +0 -1
- package/build/_app/immutable/entry/start._LhFJimm.js +0 -1
- package/dist/embed/RedUiEmbedRoot-Dia9lxfb.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reddb-io/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "RedDB UI components and standalone web application.",
|
|
6
6
|
"type": "module",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"sigma": "^3.0.3",
|
|
62
62
|
"tailwind-merge": "^3.6.0",
|
|
63
63
|
"tailwind-variants": "^2.1.0",
|
|
64
|
-
"@reddb-io/ui-kit": "0.
|
|
65
|
-
"@reddb-io/ui-mcp": "0.
|
|
64
|
+
"@reddb-io/ui-kit": "0.3.0",
|
|
65
|
+
"@reddb-io/ui-mcp": "0.3.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@sveltejs/adapter-static": "^3.0.8",
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
SectionHeading,
|
|
17
17
|
Pill,
|
|
18
18
|
ListRow,
|
|
19
|
-
Kbd,
|
|
20
19
|
} from '@reddb-io/ui-kit'
|
|
21
20
|
import {
|
|
22
21
|
Settings2,
|
|
@@ -26,6 +25,7 @@
|
|
|
26
25
|
Shield,
|
|
27
26
|
Search,
|
|
28
27
|
RefreshCw,
|
|
28
|
+
Download,
|
|
29
29
|
Lock,
|
|
30
30
|
Unplug,
|
|
31
31
|
type Icon,
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
resolveControl,
|
|
41
41
|
sourceForKey,
|
|
42
42
|
flattenConfig,
|
|
43
|
+
filterKeys,
|
|
43
44
|
type ControlDescriptor,
|
|
44
45
|
type SettingsSource,
|
|
45
46
|
} from '$lib/settings-sections'
|
|
@@ -63,6 +64,24 @@
|
|
|
63
64
|
let activeId = $state(SECTIONS[0].id)
|
|
64
65
|
const active = $derived(resolveSection(activeId))
|
|
65
66
|
|
|
67
|
+
// Per-section search query, keyed by section id, so switching tabs preserves
|
|
68
|
+
// each section's typed query. The input is focusable via the ⌘K idiom.
|
|
69
|
+
let queries = $state<Record<string, string>>({})
|
|
70
|
+
let searchEl = $state<HTMLInputElement | null>(null)
|
|
71
|
+
const query = $derived(queries[activeId] ?? '')
|
|
72
|
+
|
|
73
|
+
function setQuery(v: string) {
|
|
74
|
+
queries = { ...queries, [activeId]: v }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function onWindowKey(e: KeyboardEvent) {
|
|
78
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
|
79
|
+
e.preventDefault()
|
|
80
|
+
searchEl?.focus()
|
|
81
|
+
searchEl?.select()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
66
85
|
// Flattened live config and, per source, the reason its grant is absent
|
|
67
86
|
// (null ⇒ available). Both are rebuilt atomically on every refresh.
|
|
68
87
|
let values = $state<Record<string, unknown>>({})
|
|
@@ -148,7 +167,7 @@
|
|
|
148
167
|
// not report is dropped (graceful degradation — no empty placeholder).
|
|
149
168
|
const rows = $derived.by<RenderRow[]>(() => {
|
|
150
169
|
const out: RenderRow[] = []
|
|
151
|
-
for (const key of active.keys) {
|
|
170
|
+
for (const key of filterKeys(active.keys, query)) {
|
|
152
171
|
const src = sourceForKey(key)
|
|
153
172
|
const reason = denied[src]
|
|
154
173
|
const label = resolveControl(key).label
|
|
@@ -186,15 +205,29 @@
|
|
|
186
205
|
return String(value)
|
|
187
206
|
}
|
|
188
207
|
|
|
189
|
-
//
|
|
190
|
-
// the
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
// Export the live config snapshot (what's currently inspected) as JSON. This
|
|
209
|
+
// is the honest, real action: the snapshot is read from connection.client, so
|
|
210
|
+
// it can be downloaded but NOT written back (the client exposes no config
|
|
211
|
+
// write/reset). Import/reset would have no real target here, so they are
|
|
212
|
+
// intentionally not offered rather than faked.
|
|
213
|
+
function exportConfig() {
|
|
214
|
+
const payload = {
|
|
215
|
+
url: activeUrl,
|
|
216
|
+
exportedAt: new Date().toISOString(),
|
|
217
|
+
config: values,
|
|
218
|
+
}
|
|
219
|
+
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' })
|
|
220
|
+
const url = URL.createObjectURL(blob)
|
|
221
|
+
const a = document.createElement('a')
|
|
222
|
+
a.href = url
|
|
223
|
+
a.download = `reddb-config-${new Date().toISOString().slice(0, 10)}.json`
|
|
224
|
+
a.click()
|
|
225
|
+
URL.revokeObjectURL(url)
|
|
195
226
|
}
|
|
196
227
|
</script>
|
|
197
228
|
|
|
229
|
+
<svelte:window onkeydown={onWindowKey} />
|
|
230
|
+
|
|
198
231
|
{#if !connected}
|
|
199
232
|
<div class="h-full overflow-auto bg-bg-0 p-6">
|
|
200
233
|
<PageHeader
|
|
@@ -212,15 +245,25 @@
|
|
|
212
245
|
{:else}
|
|
213
246
|
<SplitView searchShortcut={null}>
|
|
214
247
|
{#snippet search()}
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
248
|
+
<div class="relative">
|
|
249
|
+
<Search
|
|
250
|
+
class="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-fg-3"
|
|
251
|
+
/>
|
|
252
|
+
<input
|
|
253
|
+
bind:this={searchEl}
|
|
254
|
+
type="text"
|
|
255
|
+
placeholder="Search this section ⌘K"
|
|
256
|
+
value={query}
|
|
257
|
+
oninput={(e) => setQuery(e.currentTarget.value)}
|
|
258
|
+
onkeydown={(e) => {
|
|
259
|
+
if (e.key === 'Escape') {
|
|
260
|
+
setQuery('')
|
|
261
|
+
e.currentTarget.blur()
|
|
262
|
+
}
|
|
263
|
+
}}
|
|
264
|
+
class="h-7 w-full rounded-md border border-line-2 bg-bg-2 pl-8 pr-2.5 text-xs text-fg-1 placeholder:text-fg-3 outline-none transition-colors focus:border-line-3"
|
|
265
|
+
/>
|
|
266
|
+
</div>
|
|
224
267
|
{/snippet}
|
|
225
268
|
|
|
226
269
|
{#snippet nav()}
|
|
@@ -239,8 +282,19 @@
|
|
|
239
282
|
{/snippet}
|
|
240
283
|
|
|
241
284
|
{#snippet footer()}
|
|
242
|
-
<div class="px-3 py-2.5
|
|
243
|
-
|
|
285
|
+
<div class="grid gap-2 px-3 py-2.5">
|
|
286
|
+
<button
|
|
287
|
+
type="button"
|
|
288
|
+
onclick={exportConfig}
|
|
289
|
+
disabled={loading}
|
|
290
|
+
class="inline-flex h-7 items-center justify-center gap-1.5 rounded-md border border-line-2 bg-bg-2 px-2.5 text-[11px] font-mono text-fg-1 transition-colors hover:border-line-3 hover:text-fg-0 disabled:cursor-not-allowed disabled:opacity-40"
|
|
291
|
+
>
|
|
292
|
+
<Download class="size-3.5" />
|
|
293
|
+
export config
|
|
294
|
+
</button>
|
|
295
|
+
<div class="text-[11px] text-fg-3">
|
|
296
|
+
Live from <span class="font-mono text-fg-2">{activeUrl}</span>.
|
|
297
|
+
</div>
|
|
244
298
|
</div>
|
|
245
299
|
{/snippet}
|
|
246
300
|
|
|
@@ -247,3 +247,21 @@ export function flattenConfig(
|
|
|
247
247
|
export function resolveSection(id: string | null): SettingsSection {
|
|
248
248
|
return SECTIONS.find((s) => s.id === id) ?? SECTIONS[0];
|
|
249
249
|
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Filter a section's curated keys by a free-text query, matching against both
|
|
253
|
+
* the config path AND the resolved human label (case-insensitive). An empty or
|
|
254
|
+
* whitespace-only query returns the keys unchanged. Pure + framework-free so
|
|
255
|
+
* the settings search is unit-testable without rendering. The settings view
|
|
256
|
+
* keeps one query string per section id, so switching sections preserves each
|
|
257
|
+
* section's typed query.
|
|
258
|
+
*/
|
|
259
|
+
export function filterKeys(keys: readonly string[], query: string): string[] {
|
|
260
|
+
const q = query.trim().toLowerCase();
|
|
261
|
+
if (!q) return [...keys];
|
|
262
|
+
return keys.filter(
|
|
263
|
+
(key) =>
|
|
264
|
+
key.toLowerCase().includes(q) ||
|
|
265
|
+
resolveControl(key).label.toLowerCase().includes(q)
|
|
266
|
+
);
|
|
267
|
+
}
|