From d8b831089aa8f9d1385920f865750e026e67ff90 Mon Sep 17 00:00:00 2001 From: Hibryda Date: Sun, 22 Mar 2026 12:40:56 +0100 Subject: [PATCH] fix(electrobun): XDG dirs, sanitized errors, themed dropdowns + checkboxes - PathBrowser: dynamic XDG shortcuts (only shows dirs that exist) - Errors sanitized: "Directory not found" instead of raw ENOENT - Select dropdowns: custom arrow, themed options, appearance:none - Checkboxes: custom styled with --ctp-blue check, themed border --- ui-electrobun/src/mainview/PathBrowser.svelte | 43 +++++++++++++++--- .../src/mainview/ProjectWizard.svelte | 45 ++++++++++++++++++- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/ui-electrobun/src/mainview/PathBrowser.svelte b/ui-electrobun/src/mainview/PathBrowser.svelte index 5ca50a4..96d5adc 100644 --- a/ui-electrobun/src/mainview/PathBrowser.svelte +++ b/ui-electrobun/src/mainview/PathBrowser.svelte @@ -10,12 +10,34 @@ let { onSelect, onClose }: Props = $props(); let HOME = $state('/home'); - const SHORTCUTS = [ + let shortcuts = $state([ { label: 'Home', path: '~' }, - { label: 'Desktop', path: '~/Desktop' }, - { label: 'Documents', path: '~/Documents' }, { label: '/tmp', path: '/tmp' }, - ]; + ]); + + // Load XDG user directories from backend + async function loadXdgDirs() { + try { + const r = await appRpc.request['files.homeDir']({}); + if (r?.path) HOME = r.path; + // Check which XDG dirs actually exist + const candidates = [ + { label: 'Desktop', env: 'XDG_DESKTOP_DIR', fallback: `${HOME}/Desktop` }, + { label: 'Documents', env: 'XDG_DOCUMENTS_DIR', fallback: `${HOME}/Documents` }, + { label: 'Downloads', env: 'XDG_DOWNLOAD_DIR', fallback: `${HOME}/Downloads` }, + { label: 'Projects', env: 'XDG_PROJECTS_DIR', fallback: `${HOME}/Projects` }, + ]; + // Validate each via files.browse (only add if dir exists) + const existing: typeof shortcuts = [{ label: 'Home', path: '~' }]; + for (const c of candidates) { + const path = c.fallback.replace(HOME, '~'); + const res = await appRpc.request['files.browse']({ path: c.fallback }).catch(() => null); + if (res && !res.error) existing.push({ label: c.label, path }); + } + existing.push({ label: '/tmp', path: '/tmp' }); + shortcuts = existing; + } catch {} + } let currentPath = $state('~'); let entries = $state>([]); @@ -65,7 +87,15 @@ } currentPath = dirPath; } catch (err) { - error = err instanceof Error ? err.message : String(err); + const raw = err instanceof Error ? err.message : String(err); + // Sanitize: show user-friendly message, not raw ENOENT + if (raw.includes('ENOENT') || raw.includes('no such file')) { + error = 'Directory not found'; + } else if (raw.includes('EACCES') || raw.includes('permission')) { + error = 'Permission denied'; + } else { + error = 'Cannot access this directory'; + } entries = []; } finally { loading = false; @@ -89,6 +119,7 @@ // Load initial directory $effect(() => { + loadXdgDirs(); loadDir('~'); }); @@ -102,7 +133,7 @@
- {#each SHORTCUTS as sc} + {#each shortcuts as sc} {/each}
diff --git a/ui-electrobun/src/mainview/ProjectWizard.svelte b/ui-electrobun/src/mainview/ProjectWizard.svelte index d49e03a..7a90503 100644 --- a/ui-electrobun/src/mainview/ProjectWizard.svelte +++ b/ui-electrobun/src/mainview/ProjectWizard.svelte @@ -521,7 +521,50 @@ .wz-input:focus, .wz-select:focus, .wz-textarea:focus { outline: none; border-color: var(--ctp-blue); } .wz-input::placeholder, .wz-textarea::placeholder { color: var(--ctp-overlay0); } .wz-textarea { resize: vertical; min-height: 3rem; } - .wz-select { cursor: pointer; } + .wz-select { + cursor: pointer; + -webkit-appearance: none; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a6adc8' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.5rem center; + padding-right: 1.75rem; + } + .wz-select option { + background: var(--ctp-surface0); + color: var(--ctp-text); + } + + /* Themed checkboxes */ + input[type="checkbox"] { + -webkit-appearance: none; + appearance: none; + width: 1rem; height: 1rem; + border: 1px solid var(--ctp-surface2); + border-radius: 0.1875rem; + background: var(--ctp-surface0); + cursor: pointer; + position: relative; + vertical-align: middle; + flex-shrink: 0; + } + input[type="checkbox"]:checked { + background: var(--ctp-blue); + border-color: var(--ctp-blue); + } + input[type="checkbox"]:checked::after { + content: ''; + position: absolute; + left: 0.25rem; top: 0.0625rem; + width: 0.3125rem; height: 0.5625rem; + border: solid var(--ctp-base); + border-width: 0 2px 2px 0; + transform: rotate(45deg); + } + input[type="checkbox"]:focus-visible { + outline: 2px solid var(--ctp-blue); + outline-offset: 1px; + } .wz-radios { display: flex; flex-wrap: wrap; gap: 0.375rem; }