~bigbes/lethe

ref: 1818fac37d6418d347f1129fa97704b63096b8a9 lethe/docs/design_handoff_assistant_log/proto-palette.jsx -rw-r--r-- 3.7 KiB
1818fac3 — Eugene Blikh project: drop unreachable rawHosts/rawTools fields on Project a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// Command palette overlay

const Palette = ({ onClose }) => {
  const { go } = useRouter();
  const [q, setQ] = React.useState('');
  const [sel, setSel] = React.useState(0);
  const inputRef = React.useRef(null);
  React.useEffect(() => { inputRef.current && inputRef.current.focus(); }, []);
  React.useEffect(() => { setSel(0); }, [q]);

  const jumps = [
    { kind: 'jump',   label: 'Recent',   hint: 'g h', go: { name: 'home' } },
    { kind: 'jump',   label: 'Projects', hint: 'g p', go: { name: 'projects' } },
    { kind: 'jump',   label: 'Stats',    hint: 'g s', go: { name: 'stats' } },
    { kind: 'jump',   label: 'Health',   hint: 'g i', go: { name: 'health' } },
    { kind: 'jump',   label: 'Settings', hint: '',    go: { name: 'settings' } },
  ];
  const sessionItems = SESSIONS.map(s => ({
    kind: 'session', label: s.q, hint: `${s.tool} · ${s.cwd}`,
    go: { name: 'session', id: s.id }
  }));
  const projectItems = PROJECTS.map(p => ({
    kind: 'project', label: p.cwd, hint: `${p.sessions} sessions`,
    go: { name: 'project', cwd: p.cwd }
  }));
  const all = [...jumps, ...projectItems, ...sessionItems];

  const filtered = q.trim() === '' ? all : all.filter(x =>
    (x.label + ' ' + x.hint).toLowerCase().includes(q.toLowerCase())
  );
  const showSearch = q.trim() !== '';
  const total = filtered.length + (showSearch ? 1 : 0);

  const fire = (idx) => {
    if (showSearch && idx === 0) { go({ name: 'search', q }); onClose(); return; }
    const item = filtered[showSearch ? idx - 1 : idx];
    if (item) { go(item.go); onClose(); }
  };

  const onKey = (e) => {
    if (e.key === 'Escape') { e.preventDefault(); onClose(); }
    else if (e.key === 'ArrowDown') { e.preventDefault(); setSel(s => Math.min(total - 1, s + 1)); }
    else if (e.key === 'ArrowUp')   { e.preventDefault(); setSel(s => Math.max(0, s - 1)); }
    else if (e.key === 'Enter')     { e.preventDefault(); fire(sel); }
  };

  return (
    <div className="scrim" onClick={onClose}>
      <div className="palette" onClick={e => e.stopPropagation()}>
        <div className="pin">
          <span className="mono muted" style={{ fontSize: 11 }}>K</span>
          <input ref={inputRef} value={q} onChange={e => setQ(e.target.value)}
            onKeyDown={onKey}
            placeholder="search turns, jump to a page, pick a session…" />
        </div>
        <div className="plist">
          {showSearch && (
            <div className={'pitem' + (sel === 0 ? ' active' : '')}
              onClick={() => fire(0)}>
              <span className="kind accent-c">search</span>
              <span style={{ flex: 1 }}>"{q}"</span>
              <span className="mono muted" style={{ fontSize: 10 }}></span>
            </div>
          )}
          {filtered.map((item, i) => {
            const idx = showSearch ? i + 1 : i;
            const active = idx === sel;
            return (
              <div key={i} className={'pitem' + (active ? ' active' : '')}
                onClick={() => fire(idx)}>
                <span className="kind">{item.kind}</span>
                <span className="truncate" style={{ flex: 1 }}>{item.label}</span>
                <span className="mono muted" style={{ fontSize: 10.5 }}>{item.hint}</span>
              </div>
            );
          })}
          {filtered.length === 0 && !showSearch && (
            <div className="muted" style={{ padding: 14, fontSize: 11.5 }}>no matches</div>
          )}
        </div>
        <div className="pfoot">
          <span>↑↓ move</span><span> open</span><span>esc close</span>
          <span style={{ flex: 1 }} />
          <span>g h · g p · g s · g i</span>
        </div>
      </div>
    </div>
  );
};

Object.assign(window, { Palette });