fix: improve Multmux terminal resize handling
- Add ResizeObserver for reliable resize detection - Use requestAnimationFrame for smoother fit operations - Apply full-size styles to xterm elements after fit - Hide tags to maximize terminal area - Fix flex layout for proper container sizing - Add error handling for fit operations during rapid resize 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5ca4b19aec
commit
abb80c05d8
|
|
@ -249,9 +249,33 @@ export class MultmuxShape extends BaseBoxShapeUtil<IMultmuxShape> {
|
||||||
term.loadAddon(fitAddon)
|
term.loadAddon(fitAddon)
|
||||||
term.open(terminalRef.current)
|
term.open(terminalRef.current)
|
||||||
|
|
||||||
// Small delay to ensure container is sized
|
// Force xterm elements to fill container completely
|
||||||
|
const applyFullSizeStyles = () => {
|
||||||
|
if (!terminalRef.current) return
|
||||||
|
|
||||||
|
// Get all xterm elements and force them to fill container
|
||||||
|
const xterm = terminalRef.current.querySelector('.xterm') as HTMLElement
|
||||||
|
const xtermScreen = terminalRef.current.querySelector('.xterm-screen') as HTMLElement
|
||||||
|
const xtermViewport = terminalRef.current.querySelector('.xterm-viewport') as HTMLElement
|
||||||
|
|
||||||
|
if (xterm) {
|
||||||
|
xterm.style.width = '100%'
|
||||||
|
xterm.style.height = '100%'
|
||||||
|
}
|
||||||
|
if (xtermScreen) {
|
||||||
|
xtermScreen.style.width = '100%'
|
||||||
|
xtermScreen.style.height = '100%'
|
||||||
|
}
|
||||||
|
if (xtermViewport) {
|
||||||
|
xtermViewport.style.width = '100%'
|
||||||
|
xtermViewport.style.height = '100%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay to ensure container is sized, then fit and apply styles
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fitAddon.fit()
|
fitAddon.fit()
|
||||||
|
applyFullSizeStyles()
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
||||||
xtermRef.current = term
|
xtermRef.current = term
|
||||||
|
|
@ -266,13 +290,62 @@ export class MultmuxShape extends BaseBoxShapeUtil<IMultmuxShape> {
|
||||||
|
|
||||||
// Fit terminal when shape resizes
|
// Fit terminal when shape resizes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fitAddonRef.current && xtermRef.current) {
|
if (fitAddonRef.current && xtermRef.current && terminalRef.current) {
|
||||||
setTimeout(() => {
|
// Use requestAnimationFrame to ensure DOM has updated
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
// Double-check the terminal container exists and has dimensions
|
||||||
|
if (terminalRef.current && terminalRef.current.clientWidth > 0 && terminalRef.current.clientHeight > 0) {
|
||||||
|
try {
|
||||||
fitAddonRef.current?.fit()
|
fitAddonRef.current?.fit()
|
||||||
}, 50)
|
} catch (e) {
|
||||||
|
console.warn('Failed to fit terminal:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}, [shape.props.w, shape.props.h, isMinimized])
|
}, [shape.props.w, shape.props.h, isMinimized])
|
||||||
|
|
||||||
|
// Also add a ResizeObserver for more reliable resize detection
|
||||||
|
useEffect(() => {
|
||||||
|
if (!terminalRef.current || !fitAddonRef.current) return
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
if (fitAddonRef.current && xtermRef.current && terminalRef.current) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
try {
|
||||||
|
fitAddonRef.current?.fit()
|
||||||
|
|
||||||
|
// Reapply full-size styles after fit
|
||||||
|
const xterm = terminalRef.current?.querySelector('.xterm') as HTMLElement
|
||||||
|
const xtermScreen = terminalRef.current?.querySelector('.xterm-screen') as HTMLElement
|
||||||
|
const xtermViewport = terminalRef.current?.querySelector('.xterm-viewport') as HTMLElement
|
||||||
|
|
||||||
|
if (xterm) {
|
||||||
|
xterm.style.width = '100%'
|
||||||
|
xterm.style.height = '100%'
|
||||||
|
}
|
||||||
|
if (xtermScreen) {
|
||||||
|
xtermScreen.style.width = '100%'
|
||||||
|
xtermScreen.style.height = '100%'
|
||||||
|
}
|
||||||
|
if (xtermViewport) {
|
||||||
|
xtermViewport.style.width = '100%'
|
||||||
|
xtermViewport.style.height = '100%'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore fit errors during rapid resizing
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resizeObserver.observe(terminalRef.current)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
}
|
||||||
|
}, [shape.props.token]) // Re-create observer when terminal is initialized
|
||||||
|
|
||||||
// WebSocket connection
|
// WebSocket connection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!shape.props.token || !shape.props.serverUrl) {
|
if (!shape.props.token || !shape.props.serverUrl) {
|
||||||
|
|
@ -649,19 +722,14 @@ export class MultmuxShape extends BaseBoxShapeUtil<IMultmuxShape> {
|
||||||
shapeId={shape.id}
|
shapeId={shape.id}
|
||||||
isPinnedToView={shape.props.pinnedToView}
|
isPinnedToView={shape.props.pinnedToView}
|
||||||
onPinToggle={handlePinToggle}
|
onPinToggle={handlePinToggle}
|
||||||
tags={shape.props.tags}
|
tags={[]} // Hide tags for terminal to maximize terminal area
|
||||||
onTagsChange={(newTags) => {
|
tagsEditable={false}
|
||||||
this.editor.updateShape<IMultmuxShape>({
|
|
||||||
id: shape.id,
|
|
||||||
type: 'Multmux',
|
|
||||||
props: { ...shape.props, tags: newTags }
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
tagsEditable={true}
|
|
||||||
>
|
>
|
||||||
<div style={{
|
<div style={{
|
||||||
|
flex: '1 1 0',
|
||||||
|
minHeight: 0,
|
||||||
|
minWidth: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
|
||||||
backgroundColor: '#1e1e2e',
|
backgroundColor: '#1e1e2e',
|
||||||
color: '#cdd6f4',
|
color: '#cdd6f4',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
|
|
@ -669,6 +737,8 @@ export class MultmuxShape extends BaseBoxShapeUtil<IMultmuxShape> {
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
overflow: 'hidden',
|
||||||
|
boxSizing: 'border-box',
|
||||||
}}>
|
}}>
|
||||||
{/* Status bar */}
|
{/* Status bar */}
|
||||||
<div style={{
|
<div style={{
|
||||||
|
|
@ -678,6 +748,7 @@ export class MultmuxShape extends BaseBoxShapeUtil<IMultmuxShape> {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
flexShrink: 0, // Don't shrink the status bar
|
||||||
}}>
|
}}>
|
||||||
<span>
|
<span>
|
||||||
{connected ? '🟢 Connected' : '🔴 Disconnected'}
|
{connected ? '🟢 Connected' : '🔴 Disconnected'}
|
||||||
|
|
@ -691,9 +762,13 @@ export class MultmuxShape extends BaseBoxShapeUtil<IMultmuxShape> {
|
||||||
<div
|
<div
|
||||||
ref={terminalRef}
|
ref={terminalRef}
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: '1 1 0',
|
||||||
padding: '4px',
|
minHeight: 0,
|
||||||
|
minWidth: 0,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
position: 'relative',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
backgroundColor: '#1e1e2e', // Match terminal background to hide gaps
|
||||||
}}
|
}}
|
||||||
onPointerDown={(e) => {
|
onPointerDown={(e) => {
|
||||||
// Allow pointer events for text selection but stop propagation to prevent tldraw interactions
|
// Allow pointer events for text selection but stop propagation to prevent tldraw interactions
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue