fix: terminal resize propagation and redraw
- Frontend: Add ResizeObserver with dimension tracking for accurate resize detection - Frontend: Fix cleanup function to properly disconnect ResizeObserver - Frontend: Use CSS grid for terminal wrapper layout - Backend: Add duplicate dimension check to avoid unnecessary resizes - Backend: Ensure stty command is sent correctly to container shell
This commit is contained in:
@@ -142,6 +142,11 @@ class TerminalSession:
|
|||||||
if self._closed:
|
if self._closed:
|
||||||
logger.warning("Cannot resize: session is closed")
|
logger.warning("Cannot resize: session is closed")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Only resize if dimensions actually changed
|
||||||
|
if cols == self._cols and rows == self._rows:
|
||||||
|
return
|
||||||
|
|
||||||
self._cols = cols
|
self._cols = cols
|
||||||
self._rows = rows
|
self._rows = rows
|
||||||
logger.info(f"resize() called for session {self.session_id}: {cols}x{rows}")
|
logger.info(f"resize() called for session {self.session_id}: {cols}x{rows}")
|
||||||
|
|||||||
@@ -260,18 +260,29 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
ws.send(data);
|
ws.send(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle window resize with debounce - use rAF to ensure layout settled
|
// Handle container resize with ResizeObserver for accurate dimension tracking
|
||||||
let resizeTimeout: ReturnType<typeof setTimeout>;
|
let resizeTimeout: ReturnType<typeof setTimeout>;
|
||||||
const handleResize = () => {
|
let lastWidth = 0;
|
||||||
|
let lastHeight = 0;
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
const entry = entries[0];
|
||||||
|
if (!entry) return;
|
||||||
|
|
||||||
|
const { width, height } = entry.contentRect;
|
||||||
|
// Only trigger if dimensions actually changed
|
||||||
|
if (width === lastWidth && height === lastHeight) return;
|
||||||
|
lastWidth = width;
|
||||||
|
lastHeight = height;
|
||||||
|
|
||||||
clearTimeout(resizeTimeout);
|
clearTimeout(resizeTimeout);
|
||||||
resizeTimeout = setTimeout(() => {
|
resizeTimeout = setTimeout(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
if (!container.isConnected) return;
|
||||||
fitTerminal();
|
fitTerminal();
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 50);
|
||||||
};
|
});
|
||||||
|
resizeObserver.observe(container);
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
|
|
||||||
// Refit after mobile header auto-hides (3s delay + 0.3s transition)
|
// Refit after mobile header auto-hides (3s delay + 0.3s transition)
|
||||||
const headerHideTimeout = setTimeout(() => {
|
const headerHideTimeout = setTimeout(() => {
|
||||||
@@ -306,7 +317,7 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
return () => {
|
return () => {
|
||||||
clearTimeout(resizeTimeout);
|
clearTimeout(resizeTimeout);
|
||||||
clearTimeout(headerHideTimeout);
|
clearTimeout(headerHideTimeout);
|
||||||
window.removeEventListener("resize", handleResize);
|
resizeObserver.disconnect();
|
||||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||||
if (ws) {
|
if (ws) {
|
||||||
ws.close();
|
ws.close();
|
||||||
|
|||||||
+9
-12
@@ -2506,8 +2506,8 @@ a.nav-item,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.terminal-wrapper {
|
.terminal-wrapper {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-rows: auto 1fr;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
@@ -2592,22 +2592,19 @@ a.nav-item,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.terminal-container {
|
.terminal-container {
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
padding: var(--space-2);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding: var(--space-2);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-container .xterm {
|
.terminal-container .xterm {
|
||||||
flex: 1;
|
position: absolute;
|
||||||
min-height: 0;
|
top: 0;
|
||||||
width: 100%;
|
left: 0;
|
||||||
height: 100%;
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-container canvas {
|
.terminal-container canvas {
|
||||||
|
|||||||
Reference in New Issue
Block a user