/* =================================================================== KabelFlux v5 — Command palette (⌘K) Adds a power-user navigation surface so users don't hunt across 11 tabs. =================================================================== */ const Palette = ({ open, onClose, onNav, project, projects = [], isAdmin = false, isSuper = false }) => { const [q, setQ] = useState(''); const [sel, setSel] = useState(0); const ref = useRef(); useEffect(() => { if (open) { setTimeout(() => { ref.current && ref.current.focus(); }, 30); setQ(''); setSel(0); } }, [open]); const items = useMemo(() => { const all = [ { group: 'Navigate', icon: 'layout', label: 'Go to Layout', hint: 'Canvas · diagram + formboard', kbd: 'G 1', kind: 'goto:layout' }, { group: 'Navigate', icon: 'wires', label: 'Go to Wires', hint: 'Inline-editable wire table', kbd: 'G 2', kind: 'goto:wires' }, { group: 'Navigate', icon: 'connector', label: 'Go to Connectors', hint: 'Connector inventory + pinouts', kbd: 'G 3', kind: 'goto:connectors' }, { group: 'Navigate', icon: 'bom', label: 'Go to BOM', hint: 'Bill of materials + charts', kbd: 'G 4', kind: 'goto:bom' }, { group: 'Navigate', icon: 'nodes', label: 'Go to Nodes', hint: 'Splices, fuses, grounds', kbd: 'G 5', kind: 'goto:nodes' }, { group: 'Navigate', icon: 'notes', label: 'Go to Notes & Revisions', kbd: 'G 6', kind: 'goto:notes' }, { group: 'Navigate', icon: 'title', label: 'Go to Title Block', kbd: 'G 7', kind: 'goto:title' }, { group: 'Navigate', icon: 'library', label: 'Go to Symbol Library', kbd: 'G 8', kind: 'goto:library' }, { group: 'Navigate', icon: 'settings', label: 'Go to Settings', kbd: 'G 9', kind: 'goto:settings' }, { group: 'Actions', icon: 'plus', label: 'New wire', hint: 'Add a wire to the harness', kbd: 'N', kind: 'action:new-wire' }, { group: 'Actions', icon: 'bolt', label: 'Toggle Wire mode', hint: 'Click-pin-A then click-pin-B', kbd: 'W', kind: 'action:wire-mode' }, { group: 'Actions', icon: 'connector', label: 'New connector', hint: 'Pick from symbol library', kind: 'action:new-conn' }, { group: 'Actions', icon: 'nodes', label: 'New splice / fuse / ground node', kind: 'action:new-node' }, { group: 'Actions', icon: 'import_', label: 'Import CSV / Komax',hint: 'Wire list into current project', kind: 'action:import' }, { group: 'Export', icon: 'download', label: 'Production Drawing (PDF)', hint: 'Combined formboard + wire table + breakout', kbd: '⌘⇧E', kind: 'export:prod' }, { group: 'Export', icon: 'flask', label: 'Continuity Test Plan', hint: 'QA-ready PDF · sign-off block', kind: 'export:cont' }, { group: 'Export', icon: 'bom', label: 'BOM (XLSX)', kind: 'export:bom-xlsx' }, { group: 'Export', icon: 'ruler', label: 'Formboard DXF', hint: 'AutoCAD / Fusion 360 layers', kind: 'export:dxf' }, { group: 'Export', icon: 'file', label: 'Komax / Schleuniger CSV', hint: 'For wire-prep machines', kind: 'export:komax' }, { group: 'Export', icon: 'ok', label: 'AS50881 compliance audit', kind: 'export:as50881' }, // OPERATIONS (admin + super-admin) ...(isAdmin ? [ { group: 'Admin', icon: 'folder', label: 'Workspace settings', hint: 'Company · branding · defaults', kind: 'admin:workspace' }, { group: 'Admin', icon: 'user', label: 'Manage users', hint: 'Add / remove · roles', kind: 'admin:users' }, { group: 'Admin', icon: 'share', label: 'Share links', hint: 'Read-only links · expiry · revoke', kind: 'admin:share' }, ] : []), // SYSTEM (super-admin only — Companies, Database backups, System health) ...(isSuper ? [ { group: 'Admin', icon: 'folder', label: 'Companies', hint: 'All tenants · status · approve · edit', kind: 'admin:companies' }, { group: 'Admin', icon: 'backup', label: 'Database backups', hint: 'Manual snapshot · pause switch · restore', kind: 'admin:backups' }, { group: 'Admin', icon: 'info', label: 'System health', hint: 'API version · last backup', kind: 'admin:health' }, ] : []), ...projects.map(p => ({ group: 'Projects', icon: 'folder', label: p.code + ' — ' + p.name, hint: `Rev ${p.revision} · ${p.wires} wires · ${p.modified}`, kind: 'project:' + p.id })), ]; if (!q.trim()) return all; const lq = q.toLowerCase(); return all.filter(it => (it.label + ' ' + (it.hint || '') + ' ' + it.group).toLowerCase().includes(lq)); }, [q, projects, isAdmin, isSuper]); const grouped = useMemo(() => { const out = {}; items.forEach(it => { if (!out[it.group]) out[it.group] = []; out[it.group].push(it); }); return out; }, [items]); useEffect(() => { const fn = e => { if (!open) return; if (e.key === 'Escape') onClose(); if (e.key === 'ArrowDown') { e.preventDefault(); setSel(s => Math.min(items.length - 1, s + 1)); } if (e.key === 'ArrowUp') { e.preventDefault(); setSel(s => Math.max(0, s - 1)); } if (e.key === 'Enter') { e.preventDefault(); const it = items[sel]; if (it) handle(it); } }; document.addEventListener('keydown', fn); return () => document.removeEventListener('keydown', fn); }, [open, items, sel]); const handle = (it) => { onNav(it.kind); onClose(); }; if (!open) return null; return (
e.stopPropagation()}>
{ setQ(e.target.value); setSel(0); }} placeholder="Search projects, actions, wires, nets, connectors…" aria-label="Command palette search"/> Esc
{items.length === 0 &&
No matches. Try a different search.
} {Object.entries(grouped).map(([group, gitems]) => (
{group}
{gitems.map(it => { const idx = items.indexOf(it); return ( ); })}
))}
navigate select Esc close
); }; const STYLE = ` .kf-pal-bg { position: fixed; inset: 0; background: rgba(7,9,15,.65); backdrop-filter: blur(6px) saturate(.7); display: flex; justify-content: center; align-items: flex-start; padding-top: 12vh; z-index: var(--z-palette); } .kf-pal { width: 640px; max-width: 92vw; background: var(--bg-2); border: 1px solid var(--line-strong); border-radius: var(--r-5); box-shadow: 0 28px 80px rgba(0,0,0,.7); overflow: hidden; display: flex; flex-direction: column; max-height: 70vh; } .kf-pal-input { display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--line); } .kf-pal-input input { flex: 1; background: transparent; border: none; outline: none; color: var(--t-1); font-size: 15px; font-family: var(--f-sans); } .kf-pal-input input::placeholder { color: var(--t-4); } .kf-pal-results { overflow-y: auto; padding: 6px; flex: 1; min-height: 0; } .kf-pal-group { margin-bottom: 4px; } .kf-pal-group-name { font-size: 9.5px; text-transform: uppercase; letter-spacing: 1px; color: var(--t-3); font-weight: 700; padding: 8px 10px 4px; } .kf-pal-item { width: 100%; display: flex; align-items: center; gap: 12px; padding: 8px 10px; background: transparent; border: none; cursor: pointer; color: var(--t-1); text-align: left; font-family: inherit; border-radius: var(--r-2); } .kf-pal-item.on { background: var(--info-soft); } .kf-pal-item-text { flex: 1; min-width: 0; } .kf-pal-item-label { font-size: 13px; font-weight: 500; } .kf-pal-item-hint { font-size: 11px; color: var(--t-3); margin-top: 2px; } .kf-pal-item-kbd { display: flex; gap: 3px; } .kf-pal-empty { padding: 28px; text-align: center; color: var(--t-3); font-size: 12.5px; } .kf-pal-foot { display: flex; gap: 16px; padding: 8px 16px; background: var(--bg-1); border-top: 1px solid var(--line); font-size: 10.5px; color: var(--t-3); } .kf-pal-foot span { display: inline-flex; align-items: center; gap: 4px; } `; document.head.appendChild(Object.assign(document.createElement('style'), { textContent: STYLE })); window.Palette = Palette;