@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.
Files changed (25) hide show
  1. package/build/_app/immutable/chunks/BjFADNkA.js +992 -0
  2. package/build/_app/immutable/chunks/{BHBXmd1Y.js → C9rXzYLu.js} +1 -1
  3. package/build/_app/immutable/chunks/pDJnDxXM.js +1 -0
  4. package/build/_app/immutable/entry/{app.CGfmRH-N.js → app.DdaMXHAv.js} +2 -2
  5. package/build/_app/immutable/entry/start.-MLBtQXB.js +1 -0
  6. package/build/_app/immutable/nodes/{0.BYhY6O5-.js → 0.Ysoz3Y3h.js} +1 -1
  7. package/build/_app/immutable/nodes/{1.4XYGRMul.js → 1.C2jbGTOJ.js} +1 -1
  8. package/build/_app/immutable/nodes/{12.DXZfNPNK.js → 12.DQG5zpEx.js} +1 -1
  9. package/build/_app/immutable/nodes/{8.C7OOsMOJ.js → 8.CrHI4F9p.js} +1 -1
  10. package/build/_app/version.json +1 -1
  11. package/build/index.html +9 -9
  12. package/dist/embed/{RedUiEmbedRoot-Dia9lxfb.js → RedUiEmbedRoot-BwFUA5X-.js} +7441 -7387
  13. package/dist/embed/RedUiEmbedRoot-BwFUA5X-.js.map +1 -0
  14. package/dist/embed/{index-Bq0TT7Ao.js → index-DGhgtnaK.js} +4 -4
  15. package/dist/embed/{index-Bq0TT7Ao.js.map → index-DGhgtnaK.js.map} +1 -1
  16. package/dist/embed/index.js +1 -1
  17. package/dist/embed/{sigma.esm-C2pqg5JH.js → sigma.esm-D5yoExsH.js} +2 -2
  18. package/dist/embed/{sigma.esm-C2pqg5JH.js.map → sigma.esm-D5yoExsH.js.map} +1 -1
  19. package/package.json +3 -3
  20. package/src/lib/SettingsView.svelte +73 -19
  21. package/src/lib/settings-sections.ts +18 -0
  22. package/build/_app/immutable/chunks/BMc_qoms.js +0 -973
  23. package/build/_app/immutable/chunks/CwxFwvow.js +0 -1
  24. package/build/_app/immutable/entry/start._LhFJimm.js +0 -1
  25. 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.2.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.2.0",
65
- "@reddb-io/ui-mcp": "0.2.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
- // Reuse the global command palette as the search surface — the same ⌘K idiom
190
- // the topbar uses, so Esc-to-close comes for free from the palette overlay.
191
- function openPalette() {
192
- window.dispatchEvent(
193
- new KeyboardEvent('keydown', { key: 'k', metaKey: true, ctrlKey: true, bubbles: true }),
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
- <button
216
- type="button"
217
- onclick={openPalette}
218
- class="flex h-7 w-full items-center gap-2 rounded-md border border-line-2 bg-bg-2 px-2.5 text-xs text-fg-2 transition-colors hover:border-line-3 hover:text-fg-1"
219
- >
220
- <Search class="size-3.5 text-fg-3" />
221
- <span class="flex-1 text-left">Search settings</span>
222
- <span class="inline-flex gap-0.5"><Kbd>⌘</Kbd><Kbd>K</Kbd></span>
223
- </button>
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 text-[11px] text-fg-3">
243
- Live from <span class="font-mono text-fg-2">{activeUrl}</span>.
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
+ }