Merge settings-bugfixes: live font apply + daemon uptime refresh
This commit is contained in:
+1
-1
@@ -170,7 +170,7 @@ export function App() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{settingsOpen && config && <Settings config={config} health={health} onClose={() => setSettingsOpen(false)} />}
|
{settingsOpen && config && <Settings config={config} health={health} onClose={() => setSettingsOpen(false)} onReload={() => { void loadHealth(); void refresh(); }} />}
|
||||||
{wizard && <Wizard onDone={(id) => { setWizard(false); setActiveId(id); void refresh(); }} onCancel={() => setWizard(false)} />}
|
{wizard && <Wizard onDone={(id) => { setWizard(false); setActiveId(id); void refresh(); }} onCancel={() => setWizard(false)} />}
|
||||||
{deleteTarget && (
|
{deleteTarget && (
|
||||||
<ConfirmDelete
|
<ConfirmDelete
|
||||||
|
|||||||
+10
-4
@@ -5,7 +5,7 @@ import type { ConfigView, DaemonHealth } from "./socketBridge";
|
|||||||
|
|
||||||
const FONTS = ["JetBrains Mono", "Menlo", "Monaco", "SF Mono", "Fira Code", "Cascadia Code"];
|
const FONTS = ["JetBrains Mono", "Menlo", "Monaco", "SF Mono", "Fira Code", "Cascadia Code"];
|
||||||
|
|
||||||
export function Settings({ config, health, onClose }: { config: ConfigView; health: DaemonHealth | null; onClose: () => void }) {
|
export function Settings({ config, health, onClose, onReload }: { config: ConfigView; health: DaemonHealth | null; onClose: () => void; onReload: () => void }) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
useEffect(() => { ref.current?.focus(); }, []);
|
useEffect(() => { ref.current?.focus(); }, []);
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ export function Settings({ config, health, onClose }: { config: ConfigView; heal
|
|||||||
<input value={shellLocal} onChange={(e) => setShellLocal(e.target.value)} onBlur={() => void setConfig({ default_shell: shellLocal })}
|
<input value={shellLocal} onChange={(e) => setShellLocal(e.target.value)} onBlur={() => void setConfig({ default_shell: shellLocal })}
|
||||||
style={{ width: "100%", padding: 8, marginBottom: 18, background: COLORS.bgPanel, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 8 }} />
|
style={{ width: "100%", padding: 8, marginBottom: 18, background: COLORS.bgPanel, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 8 }} />
|
||||||
|
|
||||||
<DaemonSection health={health} />
|
<DaemonSection health={health} onReload={onReload} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -70,8 +70,14 @@ function fmtUptime(ms: number): string {
|
|||||||
return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`;
|
return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DaemonSection({ health }: { health: DaemonHealth | null }) {
|
function DaemonSection({ health, onReload }: { health: DaemonHealth | null; onReload: () => void }) {
|
||||||
const [confirm, setConfirm] = useState<null | "stop" | "restart">(null);
|
const [confirm, setConfirm] = useState<null | "stop" | "restart">(null);
|
||||||
|
// Tick so uptime counts up live while the modal is open.
|
||||||
|
const [, setTick] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
const t = setInterval(() => setTick((n) => n + 1), 1000);
|
||||||
|
return () => clearInterval(t);
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: 8, paddingTop: 16, borderTop: `1px solid ${COLORS.borderSubtle}` }}>
|
<div style={{ marginTop: 8, paddingTop: 16, borderTop: `1px solid ${COLORS.borderSubtle}` }}>
|
||||||
<div style={{ fontSize: 12, color: COLORS.textSecondary, marginBottom: 8 }}>Daemon</div>
|
<div style={{ fontSize: 12, color: COLORS.textSecondary, marginBottom: 8 }}>Daemon</div>
|
||||||
@@ -92,7 +98,7 @@ function DaemonSection({ health }: { health: DaemonHealth | null }) {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
|
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
|
||||||
<button onClick={() => setConfirm(null)} style={{ padding: "5px 12px", background: COLORS.bgElevated, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 6, fontSize: 12 }}>Cancel</button>
|
<button onClick={() => setConfirm(null)} style={{ padding: "5px 12px", background: COLORS.bgElevated, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 6, fontSize: 12 }}>Cancel</button>
|
||||||
<button onClick={() => { const c = confirm; setConfirm(null); void (c === "stop" ? shutdownDaemon() : restartDaemon()); }}
|
<button onClick={() => { const c = confirm; setConfirm(null); void (c === "stop" ? shutdownDaemon() : restartDaemon()).then(onReload); }}
|
||||||
style={{ padding: "5px 12px", background: COLORS.stError, color: "#fff", border: "none", borderRadius: 6, fontSize: 12, fontWeight: 600 }}>
|
style={{ padding: "5px 12px", background: COLORS.stError, color: "#fff", border: "none", borderRadius: 6, fontSize: 12, fontWeight: 600 }}>
|
||||||
{confirm === "stop" ? "Stop" : "Restart"}
|
{confirm === "stop" ? "Stop" : "Restart"}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export function TerminalView({ surfaceId, font, palette }: { surfaceId: string;
|
|||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const termRef = useRef<Terminal | null>(null);
|
const termRef = useRef<Terminal | null>(null);
|
||||||
const fitRef = useRef<FitAddon | null>(null);
|
const fitRef = useRef<FitAddon | null>(null);
|
||||||
|
const webglRef = useRef<WebglAddon | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
@@ -39,7 +40,9 @@ export function TerminalView({ surfaceId, font, palette }: { surfaceId: string;
|
|||||||
termRef.current = term;
|
termRef.current = term;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
term.loadAddon(new WebglAddon());
|
const webgl = new WebglAddon();
|
||||||
|
term.loadAddon(webgl);
|
||||||
|
webglRef.current = webgl;
|
||||||
} catch {
|
} catch {
|
||||||
// webgl unavailable → fall back to canvas/dom renderer silently
|
// webgl unavailable → fall back to canvas/dom renderer silently
|
||||||
}
|
}
|
||||||
@@ -99,6 +102,7 @@ export function TerminalView({ surfaceId, font, palette }: { surfaceId: string;
|
|||||||
term.dispose();
|
term.dispose();
|
||||||
termRef.current = null;
|
termRef.current = null;
|
||||||
fitRef.current = null;
|
fitRef.current = null;
|
||||||
|
webglRef.current = null;
|
||||||
};
|
};
|
||||||
}, [surfaceId]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [surfaceId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
@@ -110,6 +114,10 @@ export function TerminalView({ surfaceId, font, palette }: { surfaceId: string;
|
|||||||
if (font) {
|
if (font) {
|
||||||
t.options.fontFamily = `'${font.family}', monospace`;
|
t.options.fontFamily = `'${font.family}', monospace`;
|
||||||
t.options.fontSize = font.size;
|
t.options.fontSize = font.size;
|
||||||
|
// The WebGL renderer caches rasterized glyphs in a texture atlas keyed by
|
||||||
|
// the old font/size; without clearing it the grid keeps rendering stale
|
||||||
|
// glyphs after a font change.
|
||||||
|
webglRef.current?.clearTextureAtlas();
|
||||||
}
|
}
|
||||||
if (palette) t.options.theme = xtermTheme(palette);
|
if (palette) t.options.theme = xtermTheme(palette);
|
||||||
requestAnimationFrame(() => { try { fitRef.current?.fit(); } catch { /* ignore */ } });
|
requestAnimationFrame(() => { try { fitRef.current?.fit(); } catch { /* ignore */ } });
|
||||||
|
|||||||
Reference in New Issue
Block a user