feat(app): GUI backlog — splitter, drag-reorder, fit, persist, modal focus
- LayoutEngine: fix splitter resize (track pointer 1:1 via delta-from-start) and add panel drag-to-reorder using raw pointer events with drop indicators - TerminalView: auto-fit xterm to container via FitAddon + ResizeObserver - App/TopBar: toggleable sidebar; persist sidebar/events collapse in localStorage; bell icon opens the activity log - Wizard: new-workspace modal now grabs focus and handles keyboard - deps: add @xterm/addon-fit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+16
-3
@@ -13,6 +13,15 @@ import type { EventRecord, DaemonHealth } from "./socketBridge";
|
||||
import { leafIds } from "./layoutTypes";
|
||||
import type { Group, WorkspaceView, SurfaceState } from "./layoutTypes";
|
||||
|
||||
/** Read a boolean UI flag from localStorage, falling back to `def`. */
|
||||
function loadFlag(key: string, def: boolean): boolean {
|
||||
try { const v = localStorage.getItem(key); return v === null ? def : v === "1"; }
|
||||
catch { return def; }
|
||||
}
|
||||
function saveFlag(key: string, value: boolean): void {
|
||||
try { localStorage.setItem(key, value ? "1" : "0"); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
export function App() {
|
||||
const [groups, setGroups] = useState<Group[]>([]);
|
||||
const [workspaces, setWorkspaces] = useState<WorkspaceView[]>([]);
|
||||
@@ -21,7 +30,8 @@ export function App() {
|
||||
const [states, setStates] = useState<Record<string, SurfaceState>>({});
|
||||
const [events, setEvents] = useState<EventRecord[]>([]);
|
||||
const [wizard, setWizard] = useState(false);
|
||||
const [eventsOpen, setEventsOpen] = useState(true);
|
||||
const [eventsOpen, setEventsOpen] = useState(() => loadFlag("spacesh.eventsOpen", true));
|
||||
const [sidebarOpen, setSidebarOpen] = useState(() => loadFlag("spacesh.sidebarOpen", true));
|
||||
const [health, setHealth] = useState<DaemonHealth | null>(null);
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [focusedId, setFocusedId] = useState<string | null>(null);
|
||||
@@ -104,6 +114,9 @@ export function App() {
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, []);
|
||||
|
||||
useEffect(() => { saveFlag("spacesh.eventsOpen", eventsOpen); }, [eventsOpen]);
|
||||
useEffect(() => { saveFlag("spacesh.sidebarOpen", sidebarOpen); }, [sidebarOpen]);
|
||||
|
||||
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) : [];
|
||||
@@ -117,9 +130,9 @@ export function App() {
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", height: "100vh", background: COLORS.bgApp }}>
|
||||
<TopBar active={active} eventsOpen={eventsOpen} onToggleEvents={() => setEventsOpen((v) => !v)} unread={unread} />
|
||||
<TopBar active={active} eventsOpen={eventsOpen} onToggleEvents={() => setEventsOpen((v) => !v)} onShowEvents={() => setEventsOpen(true)} sidebarOpen={sidebarOpen} onToggleSidebar={() => setSidebarOpen((v) => !v)} unread={unread} />
|
||||
<div style={{ flex: 1, display: "flex", minHeight: 0 }}>
|
||||
<Sidebar groups={groups} workspaces={workspaces} activeId={activeId} onSelect={selectWorkspace} onNew={() => setWizard(true)} health={health} connected={connected} />
|
||||
{sidebarOpen && <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, []); }} onOpenSearch={() => setSearchOpen(true)} />
|
||||
|
||||
Reference in New Issue
Block a user