Merge branch 'dev' of ssh://git.commumedia.org:2222/alex/headquarter into dev

This commit is contained in:
2026-05-24 11:42:37 +00:00
2 changed files with 58 additions and 26 deletions
+36 -25
View File
@@ -21,7 +21,7 @@ interface TerminalProps {
} }
const FONT_SIZE_KEY = "terminal-font-size"; const FONT_SIZE_KEY = "terminal-font-size";
const MIN_FONT_SIZE = 16; const MIN_FONT_SIZE = 10;
const MAX_FONT_SIZE = 24; const MAX_FONT_SIZE = 24;
const RECONNECT_ATTEMPTS = 3; const RECONNECT_ATTEMPTS = 3;
const RECONNECT_DELAY_BASE = 1000; const RECONNECT_DELAY_BASE = 1000;
@@ -163,11 +163,17 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
term.loadAddon(new WebLinksAddon()); term.loadAddon(new WebLinksAddon());
term.open(terminalRef.current); term.open(terminalRef.current);
fitAddon.fit();
// Connect WebSocket // Connect WebSocket
const ws = connectWebSocket(); const ws = connectWebSocket();
// Fit after layout settles - use rAF to ensure DOM is ready
requestAnimationFrame(() => {
requestAnimationFrame(() => {
fitAddon.fit();
});
});
// Handle terminal input // Handle terminal input
term.onData((data) => { term.onData((data) => {
if (ws.readyState !== WebSocket.OPEN) return; if (ws.readyState !== WebSocket.OPEN) return;
@@ -242,7 +248,8 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
ws.close(); ws.close();
term.dispose(); term.dispose();
}; };
}, [instanceId, connectWebSocket, calculateFontSize]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [instanceId, connectWebSocket]);
// Update parent about status changes // Update parent about status changes
useEffect(() => { useEffect(() => {
@@ -266,9 +273,17 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
const newSize = Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, fontSize + delta)); const newSize = Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, fontSize + delta));
setFontSize(newSize); setFontSize(newSize);
localStorage.setItem(FONT_SIZE_KEY, newSize.toString()); localStorage.setItem(FONT_SIZE_KEY, newSize.toString());
if (termRef.current) { if (termRef.current && fitAddonRef.current) {
termRef.current.options.fontSize = newSize; termRef.current.options.fontSize = newSize;
fitAddonRef.current?.fit(); requestAnimationFrame(() => {
if (termRef.current && fitAddonRef.current) {
try {
fitAddonRef.current.fit();
} catch {
// Ignore fit errors during re-initialization
}
}
});
} }
}; };
@@ -345,26 +360,22 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
)} )}
</div> </div>
<div className="terminal-header-right"> <div className="terminal-header-right">
{isMobile && ( <button
<> className="terminal-header-button"
<button onClick={() => handleFontSizeChange(-1)}
className="terminal-header-button" type="button"
onClick={() => handleFontSizeChange(-1)} aria-label="Decrease font size"
type="button" >
aria-label="Decrease font size" A-
> </button>
A- <button
</button> className="terminal-header-button"
<button onClick={() => handleFontSizeChange(1)}
className="terminal-header-button" type="button"
onClick={() => handleFontSizeChange(1)} aria-label="Increase font size"
type="button" >
aria-label="Increase font size" A+
> </button>
A+
</button>
</>
)}
{onClose && ( {onClose && (
<button className="terminal-close" onClick={onClose} type="button"> <button className="terminal-close" onClick={onClose} type="button">
Close Close
+22 -1
View File
@@ -166,6 +166,9 @@ a {
.shell-content { .shell-content {
padding: 1.25rem; padding: 1.25rem;
overflow-x: hidden; overflow-x: hidden;
display: flex;
flex-direction: column;
min-height: 0;
} }
.eyebrow { .eyebrow {
@@ -2482,7 +2485,8 @@ a.nav-item,
.terminal-page { .terminal-page {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; height: 100%;
min-height: 0;
padding: var(--space-4); padding: var(--space-4);
gap: var(--space-4); gap: var(--space-4);
} }
@@ -2592,12 +2596,24 @@ a.nav-item,
.terminal-container .xterm { .terminal-container .xterm {
height: 100%; height: 100%;
width: 100%;
} }
.terminal-container .xterm-viewport { .terminal-container .xterm-viewport {
height: 100% !important;
width: 100% !important;
background: #1e1e1e !important; background: #1e1e1e !important;
} }
.terminal-container .xterm-screen {
height: 100% !important;
width: 100% !important;
}
.terminal-container canvas {
display: block;
}
/* Responsive terminal */ /* Responsive terminal */
@media (max-width: 767px) { @media (max-width: 767px) {
.terminal-page { .terminal-page {
@@ -3058,6 +3074,8 @@ a.nav-item,
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
display: flex;
flex-direction: column;
} }
/* Special Keys Strip */ /* Special Keys Strip */
@@ -3183,6 +3201,8 @@ a.nav-item,
border: none; border: none;
border-radius: 0; border-radius: 0;
height: 100%; height: 100%;
min-height: 100%;
width: 100%;
} }
.terminal-wrapper.mobile .terminal-header { .terminal-wrapper.mobile .terminal-header {
@@ -3252,6 +3272,7 @@ a.nav-item,
.terminal-container { .terminal-container {
touch-action: none; touch-action: none;
padding: 0;
} }
} }