feat(app): scrollback search bar (⌘F) on the focused panel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+16
-2
@@ -3,6 +3,7 @@ import { LayoutEngine } from "./LayoutEngine";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { TopBar } from "./TopBar";
|
||||
import { CenterToolbar } from "./CenterToolbar";
|
||||
import { SearchBar } from "./SearchBar";
|
||||
import { Wizard } from "./Wizard";
|
||||
import { EventCenter } from "./EventCenter";
|
||||
import { maybeNotify } from "./notify";
|
||||
@@ -24,6 +25,8 @@ export function App() {
|
||||
const [health, setHealth] = useState<DaemonHealth | null>(null);
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [focusedId, setFocusedId] = useState<string | null>(null);
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
const [searchNonce, setSearchNonce] = useState(0);
|
||||
const activeRef = useRef<string | null>(null);
|
||||
const wsRef = useRef<WorkspaceView[]>([]);
|
||||
activeRef.current = activeId;
|
||||
@@ -91,6 +94,16 @@ export function App() {
|
||||
return () => { void unlisten.then((f) => f()); void reconnect.then((f) => f()); };
|
||||
}, [refresh, seedEvents, loadHealth]);
|
||||
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "f") {
|
||||
if (activeRef.current) { e.preventDefault(); setSearchOpen(true); setSearchNonce((n) => n + 1); }
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, []);
|
||||
|
||||
const unread = useMemo(() => events.filter((e) => !e.read).length, [events]);
|
||||
const active = workspaces.find((w) => w.id === activeId) ?? null;
|
||||
const leaves = active ? leafIds(active.layout) : [];
|
||||
@@ -109,12 +122,13 @@ export function App() {
|
||||
<Sidebar groups={groups} workspaces={workspaces} activeId={activeId} onSelect={selectWorkspace} onNew={() => setWizard(true)} health={health} connected={connected} />
|
||||
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
|
||||
{active && (
|
||||
<CenterToolbar selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} />
|
||||
<CenterToolbar selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} onOpenSearch={() => setSearchOpen(true)} />
|
||||
)}
|
||||
<div style={{ flex: 1, minHeight: 0 }}>
|
||||
<div style={{ flex: 1, minHeight: 0, position: "relative" }}>
|
||||
{active
|
||||
? <LayoutEngine workspaceId={active.id} layout={active.layout} running={running} states={states} surfaces={active.surfaces} focusedId={effectiveFocus} onFocus={setFocusedId} zoomed={active.zoomed} />
|
||||
: <div style={{ color: COLORS.textMuted, padding: 24 }}>No workspace — create one to begin.</div>}
|
||||
{searchOpen && active && <SearchBar surfaceId={effectiveFocus} reopenNonce={searchNonce} onClose={() => setSearchOpen(false)} />}
|
||||
</div>
|
||||
</div>
|
||||
{eventsOpen && (
|
||||
|
||||
Reference in New Issue
Block a user