feat(app): stopped panel paints last screen + Resume/Restart fresh controls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+59
-14
@@ -1,11 +1,12 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Maximize2, Minimize2, RotateCw, GripVertical } from "lucide-react";
|
||||
import { Maximize2, Minimize2, RotateCw, GripVertical, Play } from "lucide-react";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import { TerminalView } from "./TerminalView";
|
||||
import { SearchBar } from "./SearchBar";
|
||||
import { StatusRing } from "./StatusRing";
|
||||
import { COLORS, FONT, STATE_COLOR } from "./theme";
|
||||
import type { LayoutNode, SurfaceState, SurfaceView } from "./layoutTypes";
|
||||
import { setRatios, restartSurface, setZoom, moveSurface } from "./socketBridge";
|
||||
import { setRatios, restartSurface, setZoom, moveSurface, attachSurface } from "./socketBridge";
|
||||
|
||||
interface Props {
|
||||
workspaceId: string;
|
||||
@@ -116,6 +117,43 @@ function Node({ node, path, ...rest }: NodeProps) {
|
||||
return <SplitView split={node.split} path={path} {...rest} />;
|
||||
}
|
||||
|
||||
const NERD_FALLBACK_LE = "'Symbols Nerd Font Mono'";
|
||||
const fontStackLE = (family: string | null) =>
|
||||
family ? `'${family}', ${NERD_FALLBACK_LE}, monospace`
|
||||
: `'JetBrains Mono Variable', 'JetBrains Mono', ${NERD_FALLBACK_LE}, monospace`;
|
||||
|
||||
function xtermThemeLE(p: Record<string, string>) {
|
||||
return {
|
||||
background: p["bg-panel"],
|
||||
foreground: p["text-primary"],
|
||||
cursor: p["text-primary"],
|
||||
selectionBackground: p["search-match"],
|
||||
};
|
||||
}
|
||||
|
||||
function StoppedSnapshot({ surfaceId, font, palette }: { surfaceId: string; font: { family: string; size: number } | null; palette: Record<string, string> | null }) {
|
||||
const hostRef = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
const host = hostRef.current;
|
||||
if (!host) return;
|
||||
const term = new Terminal({
|
||||
fontFamily: fontStackLE(font?.family ?? null),
|
||||
fontSize: font?.size ?? 13,
|
||||
theme: palette ? xtermThemeLE(palette) : undefined,
|
||||
cursorBlink: false,
|
||||
disableStdin: true,
|
||||
scrollback: 0,
|
||||
});
|
||||
term.open(host);
|
||||
let disposed = false;
|
||||
void attachSurface(surfaceId, () => {}).then((res) => {
|
||||
if (!disposed && res.snapshot) term.write(res.snapshot);
|
||||
});
|
||||
return () => { disposed = true; term.dispose(); };
|
||||
}, [surfaceId, font, palette]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
return <div ref={hostRef} style={{ position: "absolute", inset: 0, opacity: 0.45, pointerEvents: "none" }} />;
|
||||
}
|
||||
|
||||
function Leaf({ id, workspaceId, running, states, surfaces, focusedId, onFocus, zoomed, drop, onStartPanelDrag, searchSurfaceId, searchNonce, onCloseSearch, font, palette }: Omit<NodeProps, "node" | "path"> & { id: string }) {
|
||||
const focused = focusedId === id;
|
||||
const dropEdge = drop && drop.id === id ? drop.edge : null;
|
||||
@@ -142,19 +180,26 @@ function Leaf({ id, workspaceId, running, states, surfaces, focusedId, onFocus,
|
||||
|
||||
if (running[id] === false) {
|
||||
return card(
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100%", width: "100%", color: COLORS.textSecondary, flexDirection: "column", gap: 10 }}>
|
||||
<div style={{ fontFamily: FONT.mono, fontSize: 13 }}>Process exited</div>
|
||||
<div style={{ display: "flex", gap: 8 }}>
|
||||
<button onClick={() => void restartSurface(id)}
|
||||
style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", background: COLORS.bgElevated, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 7, fontSize: 12 }}>
|
||||
<RotateCw size={13} /> Restart
|
||||
</button>
|
||||
{zoomed === id && (
|
||||
<button onClick={() => void setZoom(workspaceId, null)}
|
||||
style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", background: "transparent", color: COLORS.textSecondary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 7, fontSize: 12 }}>
|
||||
<Minimize2 size={13} /> Exit zoom
|
||||
<div style={{ position: "relative", height: "100%", width: "100%" }}>
|
||||
<StoppedSnapshot surfaceId={id} font={font} palette={palette} />
|
||||
<div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column", gap: 10, color: COLORS.textSecondary, background: "rgba(0,0,0,0.35)" }}>
|
||||
<div style={{ fontFamily: FONT.mono, fontSize: 13 }}>Stopped</div>
|
||||
<div style={{ display: "flex", gap: 8 }}>
|
||||
<button onClick={() => void restartSurface(id, true)}
|
||||
style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", background: COLORS.accent, color: COLORS.bgApp, border: "none", borderRadius: 7, fontSize: 12, fontWeight: 600 }}>
|
||||
<Play size={13} /> Resume
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => void restartSurface(id, false)}
|
||||
style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", background: COLORS.bgElevated, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 7, fontSize: 12 }}>
|
||||
<RotateCw size={13} /> Restart fresh
|
||||
</button>
|
||||
{zoomed === id && (
|
||||
<button onClick={() => void setZoom(workspaceId, null)}
|
||||
style={{ display: "flex", alignItems: "center", gap: 6, padding: "6px 14px", background: "transparent", color: COLORS.textSecondary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 7, fontSize: 12 }}>
|
||||
<Minimize2 size={13} /> Exit zoom
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user