import { useRef } from "react"; import { Maximize2, RotateCw } from "lucide-react"; import { TerminalView } from "./TerminalView"; import { StatusRing } from "./StatusRing"; import { COLORS, FONT, STATE_COLOR } from "./theme"; import type { LayoutNode, SurfaceState, SurfaceView } from "./layoutTypes"; import { setRatios, restartSurface } from "./socketBridge"; interface Props { workspaceId: string; layout: LayoutNode | null; /** surface_id -> running flag, from the latest status/events. */ running: Record; states: Record; surfaces: Record; focusedId: string | null; onFocus: (id: string) => void; } /** Collapse an absolute cwd into a ~/ style label for the panel header. */ function shortPath(cwd: string): string { const leaf = cwd.split("/").filter(Boolean).pop(); return leaf ? `~/${leaf}` : cwd; } export function LayoutEngine({ workspaceId, layout, running, states, surfaces, focusedId, onFocus }: Props) { if (!layout) { return
Empty workspace — apply a preset to add panels.
; } return (
); } function Node({ workspaceId, node, path, running, states, surfaces, focusedId, onFocus }: { workspaceId: string; node: LayoutNode; path: number[]; running: Record; states: Record; surfaces: Record; focusedId: string | null; onFocus: (id: string) => void; }) { if ("leaf" in node) { const id = node.leaf.surface_id; const focused = focusedId === id; const card = (inner: React.ReactNode) => (
onFocus(id)} style={{ display: "flex", flexDirection: "column", width: "100%", height: "100%", background: COLORS.bgPanel, borderRadius: 8, overflow: "hidden", border: focused ? `2px solid ${COLORS.accent}` : `1px solid ${COLORS.borderSubtle}`, boxSizing: "border-box", }} > {inner}
); if (running[id] === false) { return card(
Process exited
); } const spec = surfaces[id]?.spec; const agent = spec?.agent_label ?? "shell"; const state = states[id] ?? "idle"; return card( <>
{agent} {spec?.cwd && {shortPath(spec.cwd)}} {state}
); } const { orient, ratios, children } = node.split; const dir = orient === "h" ? "row" : "column"; return (
{children.map((child, i) => ( { const next = [...ratios]; next[i] = Math.max(0.05, next[i] + deltaFrac); next[i + 1] = Math.max(0.05, (next[i + 1] ?? 1) - deltaFrac); void setRatios(workspaceId, path, next); }}> ))}
); } function Pane({ grow, isLast, orient, onResize, children }: { grow: number; isLast: boolean; orient: "h" | "v"; onResize: (deltaFrac: number) => void; children: React.ReactNode }) { const ref = useRef(null); const startDrag = (e: React.MouseEvent) => { e.preventDefault(); const parent = ref.current?.parentElement; if (!parent) return; const total = orient === "h" ? parent.clientWidth : parent.clientHeight; let last = orient === "h" ? e.clientX : e.clientY; const move = (ev: MouseEvent) => { const cur = orient === "h" ? ev.clientX : ev.clientY; const delta = (cur - last) / total; last = cur; onResize(delta); }; const up = () => { window.removeEventListener("mousemove", move); window.removeEventListener("mouseup", up); }; window.addEventListener("mousemove", move); window.addEventListener("mouseup", up); }; return ( <>
{children}
{!isLast && (
)} ); }