Stop shell terminal width growth
This commit is contained in:
@@ -16,6 +16,7 @@ export function ShellTerminal({ csrfToken }: { csrfToken: string }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!host.current) return;
|
if (!host.current) return;
|
||||||
|
const hostElement = host.current;
|
||||||
const terminal = new Terminal({
|
const terminal = new Terminal({
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
cursorStyle: "bar",
|
cursorStyle: "bar",
|
||||||
@@ -36,8 +37,7 @@ export function ShellTerminal({ csrfToken }: { csrfToken: string }) {
|
|||||||
});
|
});
|
||||||
const fit = new FitAddon();
|
const fit = new FitAddon();
|
||||||
terminal.loadAddon(fit);
|
terminal.loadAddon(fit);
|
||||||
terminal.open(host.current);
|
terminal.open(hostElement);
|
||||||
fit.fit();
|
|
||||||
terminal.writeln("\x1b[38;2;185;231;105mPi Car Companion shell\x1b[0m");
|
terminal.writeln("\x1b[38;2;185;231;105mPi Car Companion shell\x1b[0m");
|
||||||
terminal.writeln("Restricted local session running as pi-companion.\r\n");
|
terminal.writeln("Restricted local session running as pi-companion.\r\n");
|
||||||
|
|
||||||
@@ -48,6 +48,24 @@ export function ShellTerminal({ csrfToken }: { csrfToken: string }) {
|
|||||||
socket.send(JSON.stringify({ type: "resize", cols: terminal.cols, rows: terminal.rows }));
|
socket.send(JSON.stringify({ type: "resize", cols: terminal.cols, rows: terminal.rows }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let resizeFrame: number | null = null;
|
||||||
|
let fittedWidth = -1;
|
||||||
|
let fittedHeight = -1;
|
||||||
|
const fitToHost = () => {
|
||||||
|
resizeFrame = null;
|
||||||
|
const width = hostElement.clientWidth;
|
||||||
|
const height = hostElement.clientHeight;
|
||||||
|
if (width <= 0 || height <= 0 || (width === fittedWidth && height === fittedHeight)) return;
|
||||||
|
fittedWidth = width;
|
||||||
|
fittedHeight = height;
|
||||||
|
fit.fit();
|
||||||
|
sendSize();
|
||||||
|
};
|
||||||
|
const scheduleFit = () => {
|
||||||
|
if (resizeFrame !== null) window.cancelAnimationFrame(resizeFrame);
|
||||||
|
resizeFrame = window.requestAnimationFrame(fitToHost);
|
||||||
|
};
|
||||||
|
fitToHost();
|
||||||
socket.addEventListener("open", () => {
|
socket.addEventListener("open", () => {
|
||||||
socket.send(JSON.stringify({ type: "authenticate", csrfToken }));
|
socket.send(JSON.stringify({ type: "authenticate", csrfToken }));
|
||||||
});
|
});
|
||||||
@@ -56,6 +74,7 @@ export function ShellTerminal({ csrfToken }: { csrfToken: string }) {
|
|||||||
const message = JSON.parse(String(event.data)) as ServerMessage;
|
const message = JSON.parse(String(event.data)) as ServerMessage;
|
||||||
if (message.type === "ready") {
|
if (message.type === "ready") {
|
||||||
setState("ready");
|
setState("ready");
|
||||||
|
fitToHost();
|
||||||
sendSize();
|
sendSize();
|
||||||
terminal.focus();
|
terminal.focus();
|
||||||
} else if (message.type === "output") terminal.write(message.data);
|
} else if (message.type === "output") terminal.write(message.data);
|
||||||
@@ -80,14 +99,12 @@ export function ShellTerminal({ csrfToken }: { csrfToken: string }) {
|
|||||||
const input = terminal.onData((data) => {
|
const input = terminal.onData((data) => {
|
||||||
if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "input", data }));
|
if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "input", data }));
|
||||||
});
|
});
|
||||||
const resize = new ResizeObserver(() => {
|
const resize = new ResizeObserver(scheduleFit);
|
||||||
fit.fit();
|
resize.observe(hostElement);
|
||||||
sendSize();
|
|
||||||
});
|
|
||||||
resize.observe(host.current);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
resize.disconnect();
|
resize.disconnect();
|
||||||
|
if (resizeFrame !== null) window.cancelAnimationFrame(resizeFrame);
|
||||||
input.dispose();
|
input.dispose();
|
||||||
socket.close(1000, "Terminal tab closed");
|
socket.close(1000, "Terminal tab closed");
|
||||||
terminal.dispose();
|
terminal.dispose();
|
||||||
|
|||||||
+6
-5
@@ -137,7 +137,8 @@ button:disabled { opacity: 0.55; cursor: not-allowed; }
|
|||||||
.activity-empty { min-height: 74px; display: grid; place-items: center; padding: 18px; color: var(--muted); font-size: 0.8rem; text-align: center; }
|
.activity-empty { min-height: 74px; display: grid; place-items: center; padding: 18px; color: var(--muted); font-size: 0.8rem; text-align: center; }
|
||||||
.activity-privacy { margin: 0; color: var(--muted); font-size: 0.72rem; text-align: right; }
|
.activity-privacy { margin: 0; color: var(--muted); font-size: 0.72rem; text-align: right; }
|
||||||
|
|
||||||
.settings-page { display: grid; gap: 18px; }
|
.settings-page { min-width: 0; width: 100%; display: grid; gap: 18px; }
|
||||||
|
.settings-page > [role="tabpanel"] { min-width: 0; width: 100%; max-width: 100%; }
|
||||||
.settings-tabs { width: fit-content; display: flex; gap: 5px; padding: 5px; background: #121610; border: 1px solid var(--line); border-radius: 12px; }
|
.settings-tabs { width: fit-content; display: flex; gap: 5px; padding: 5px; background: #121610; border: 1px solid var(--line); border-radius: 12px; }
|
||||||
.settings-tabs button { min-height: 48px; display: inline-flex; align-items: center; gap: 9px; padding: 0 19px; color: var(--muted); background: transparent; border: 0; border-radius: 8px; font-weight: 700; cursor: pointer; transition: color 160ms ease, background 160ms ease, transform 120ms ease; }
|
.settings-tabs button { min-height: 48px; display: inline-flex; align-items: center; gap: 9px; padding: 0 19px; color: var(--muted); background: transparent; border: 0; border-radius: 8px; font-weight: 700; cursor: pointer; transition: color 160ms ease, background 160ms ease, transform 120ms ease; }
|
||||||
.settings-tabs button:hover { color: var(--text); background: var(--surface-raised); }
|
.settings-tabs button:hover { color: var(--text); background: var(--surface-raised); }
|
||||||
@@ -189,8 +190,8 @@ button:disabled { opacity: 0.55; cursor: not-allowed; }
|
|||||||
.behavior-list { display: grid; gap: 24px; margin: 26px 0 0; }
|
.behavior-list { display: grid; gap: 24px; margin: 26px 0 0; }
|
||||||
.behavior-list div { display: grid; gap: 7px; }
|
.behavior-list div { display: grid; gap: 7px; }
|
||||||
.behavior-list dd { color: #dce4d6; }
|
.behavior-list dd { color: #dce4d6; }
|
||||||
.shell-panel { overflow: hidden; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
|
.shell-panel { min-width: 0; width: 100%; max-width: 100%; overflow: hidden; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
|
||||||
.shell-loading { min-height: 420px; display: grid; place-items: center; color: var(--muted); background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
|
.shell-loading { min-width: 0; width: 100%; max-width: 100%; min-height: 420px; display: grid; place-items: center; color: var(--muted); background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
|
||||||
.shell-toolbar { min-height: 68px; display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 10px 14px 10px 20px; border-bottom: 1px solid var(--line); }
|
.shell-toolbar { min-height: 68px; display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 10px 14px 10px 20px; border-bottom: 1px solid var(--line); }
|
||||||
.shell-toolbar > div { display: grid; grid-template-columns: auto auto; align-items: center; gap: 3px 9px; }
|
.shell-toolbar > div { display: grid; grid-template-columns: auto auto; align-items: center; gap: 3px 9px; }
|
||||||
.shell-toolbar strong { font-size: 0.87rem; }
|
.shell-toolbar strong { font-size: 0.87rem; }
|
||||||
@@ -198,8 +199,8 @@ button:disabled { opacity: 0.55; cursor: not-allowed; }
|
|||||||
.shell-state { grid-row: 1 / 3; width: 9px; height: 9px; border-radius: 50%; background: #7c8477; }
|
.shell-state { grid-row: 1 / 3; width: 9px; height: 9px; border-radius: 50%; background: #7c8477; }
|
||||||
.shell-state.ready { background: var(--accent); box-shadow: 0 0 0 5px rgba(185, 231, 105, 0.08); }
|
.shell-state.ready { background: var(--accent); box-shadow: 0 0 0 5px rgba(185, 231, 105, 0.08); }
|
||||||
.shell-state.error { background: var(--danger); }
|
.shell-state.error { background: var(--danger); }
|
||||||
.terminal-host { height: min(590px, calc(100dvh - 290px)); min-height: 360px; padding: 16px 12px; background: #0b0e0a; }
|
.terminal-host { contain: inline-size; min-width: 0; width: 100%; max-width: 100%; height: min(590px, calc(100dvh - 290px)); min-height: 360px; overflow: hidden; padding: 16px 12px; background: #0b0e0a; }
|
||||||
.terminal-host .xterm { height: 100%; }
|
.terminal-host .xterm { width: 100%; max-width: 100%; height: 100%; }
|
||||||
.shell-panel > .settings-note { margin: 0; padding: 14px 20px 16px; border-top: 1px solid var(--line); }
|
.shell-panel > .settings-note { margin: 0; padding: 14px 20px 16px; border-top: 1px solid var(--line); }
|
||||||
.shell-panel code { color: #dce4d6; }
|
.shell-panel code { color: #dce4d6; }
|
||||||
.modal-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 24px; background: rgba(8, 10, 8, 0.78); }
|
.modal-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 24px; background: rgba(8, 10, 8, 0.78); }
|
||||||
|
|||||||
Reference in New Issue
Block a user