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:
|
||||
logger.warning("Cannot resize: session is closed")
|
||||
return
|
||||
|
||||
# Only resize if dimensions actually changed
|
||||
if cols == self._cols and rows == self._rows:
|
||||
return
|
||||
|
||||
self._cols = cols
|
||||
self._rows = 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);
|
||||
});
|
||||
|
||||
// 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>;
|
||||
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);
|
||||
resizeTimeout = setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (!container.isConnected) return;
|
||||
fitTerminal();
|
||||
});
|
||||
}, 100);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
}, 50);
|
||||
});
|
||||
resizeObserver.observe(container);
|
||||
|
||||
// Refit after mobile header auto-hides (3s delay + 0.3s transition)
|
||||
const headerHideTimeout = setTimeout(() => {
|
||||
@@ -306,7 +317,7 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
||||
return () => {
|
||||
clearTimeout(resizeTimeout);
|
||||
clearTimeout(headerHideTimeout);
|
||||
window.removeEventListener("resize", handleResize);
|
||||
resizeObserver.disconnect();
|
||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
if (ws) {
|
||||
ws.close();
|
||||
|
||||
+9
-12
@@ -2506,8 +2506,8 @@ a.nav-item,
|
||||
}
|
||||
|
||||
.terminal-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
border: 1px solid var(--border);
|
||||
@@ -2592,22 +2592,19 @@ a.nav-item,
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: var(--space-2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: var(--space-2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.terminal-container .xterm {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.terminal-container canvas {
|
||||
|
||||
Reference in New Issue
Block a user