56893c51d0
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { TerminalView } from "./TerminalView";
|
|
import { SurfaceList } from "./SurfaceList";
|
|
import { openWorkspace, newSurface, getStatus, onDaemonEvent, onDaemonRawEvent } from "./socketBridge";
|
|
|
|
export function App() {
|
|
const [surfaces, setSurfaces] = useState<string[]>([]);
|
|
const [active, setActive] = useState<string | null>(null);
|
|
const [workspaceId, setWorkspaceId] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
void (async () => {
|
|
const ws = await getStatus();
|
|
const flat = ws.flatMap((w) => w.surfaces);
|
|
setSurfaces(flat);
|
|
if (flat.length) setActive(flat[0]);
|
|
})();
|
|
|
|
const unlisten = onDaemonEvent((evt) => {
|
|
if (evt.evt === "surface_created") {
|
|
setSurfaces((s) => [...s, evt.data.surface_id]);
|
|
} else if (evt.evt === "surface_closed" || evt.evt === "exit") {
|
|
// exit leaves the surface visible; surface_closed removes it.
|
|
if (evt.evt === "surface_closed") {
|
|
setSurfaces((s) => s.filter((id) => id !== evt.data.surface_id));
|
|
}
|
|
}
|
|
});
|
|
|
|
const reconnect = onDaemonRawEvent("spacesh:disconnected", () => {
|
|
// Force a remount of the active TerminalView by toggling the key.
|
|
setActive((cur) => cur);
|
|
void getStatus().then((ws) => {
|
|
const flat = ws.flatMap((w) => w.surfaces);
|
|
setSurfaces(flat);
|
|
});
|
|
});
|
|
|
|
return () => {
|
|
void unlisten.then((f) => f());
|
|
void reconnect.then((f) => f());
|
|
};
|
|
}, []);
|
|
|
|
async function handleNewSurface() {
|
|
let ws = workspaceId;
|
|
if (!ws) {
|
|
ws = await openWorkspace(".");
|
|
setWorkspaceId(ws);
|
|
}
|
|
const id = await newSurface(ws, 80, 24);
|
|
setActive(id);
|
|
}
|
|
|
|
return (
|
|
<div style={{ display: "flex", height: "100vh", background: "#000" }}>
|
|
<div style={{ display: "flex", flexDirection: "column", width: 160 }}>
|
|
<button onClick={handleNewSurface} style={{ margin: 8 }}>
|
|
+ surface
|
|
</button>
|
|
<SurfaceList surfaces={surfaces} active={active} onSelect={setActive} />
|
|
</div>
|
|
<div style={{ flex: 1 }}>
|
|
{active ? <TerminalView key={active} surfaceId={active} /> : <div style={{ color: "#666", padding: 16 }}>no surface</div>}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|