feat(terminal): implement resize handling with ResizeObserver for improved terminal responsiveness
This commit is contained in:
@@ -30,6 +30,9 @@ export function initializeTerminalComponent() {
|
||||
pingTimeoutId: null,
|
||||
heartbeatMissed: 0,
|
||||
maxHeartbeatMisses: 3,
|
||||
// Resize handling
|
||||
resizeObserver: null,
|
||||
resizeTimeout: null,
|
||||
|
||||
init() {
|
||||
this.setupTerminal();
|
||||
@@ -55,8 +58,18 @@ export function initializeTerminalComponent() {
|
||||
if (active) {
|
||||
this.$refs.terminalWrapper.style.display = 'block';
|
||||
this.resizeTerminal();
|
||||
|
||||
// Start observing terminal wrapper for resize changes
|
||||
if (this.resizeObserver && this.$refs.terminalWrapper) {
|
||||
this.resizeObserver.observe(this.$refs.terminalWrapper);
|
||||
}
|
||||
} else {
|
||||
this.$refs.terminalWrapper.style.display = 'none';
|
||||
|
||||
// Stop observing when terminal is inactive
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -70,6 +83,17 @@ export function initializeTerminalComponent() {
|
||||
window.onresize = () => {
|
||||
this.resizeTerminal()
|
||||
};
|
||||
|
||||
// Set up ResizeObserver for more reliable terminal resizing
|
||||
if (window.ResizeObserver) {
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
// Debounce resize calls to avoid performance issues
|
||||
clearTimeout(this.resizeTimeout);
|
||||
this.resizeTimeout = setTimeout(() => {
|
||||
this.resizeTerminal();
|
||||
}, 50);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
cleanup() {
|
||||
@@ -79,15 +103,27 @@ export function initializeTerminalComponent() {
|
||||
if (this.socket) {
|
||||
this.socket.close(1000, 'Client cleanup');
|
||||
}
|
||||
|
||||
// Clean up resize observer
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
|
||||
// Clear resize timeout
|
||||
if (this.resizeTimeout) {
|
||||
clearTimeout(this.resizeTimeout);
|
||||
}
|
||||
},
|
||||
|
||||
clearAllTimers() {
|
||||
[this.keepAliveInterval, this.reconnectInterval, this.connectionTimeoutId, this.pingTimeoutId]
|
||||
[this.keepAliveInterval, this.reconnectInterval, this.connectionTimeoutId, this.pingTimeoutId, this.resizeTimeout]
|
||||
.forEach(timer => timer && clearInterval(timer));
|
||||
this.keepAliveInterval = null;
|
||||
this.reconnectInterval = null;
|
||||
this.connectionTimeoutId = null;
|
||||
this.pingTimeoutId = null;
|
||||
this.resizeTimeout = null;
|
||||
},
|
||||
|
||||
resetTerminal() {
|
||||
@@ -308,8 +344,15 @@ export function initializeTerminalComponent() {
|
||||
this.terminalActive = true;
|
||||
this.term.focus();
|
||||
document.querySelector('.xterm-viewport').classList.add('scrollbar', 'rounded-sm');
|
||||
|
||||
// Initial resize after terminal is ready
|
||||
this.resizeTerminal();
|
||||
|
||||
// Additional resize after a short delay to ensure proper sizing
|
||||
setTimeout(() => {
|
||||
this.resizeTerminal();
|
||||
}, 200);
|
||||
|
||||
// Notify parent component that terminal is connected
|
||||
this.$wire.dispatch('terminalConnected');
|
||||
} else if (event.data === 'unprocessable') {
|
||||
@@ -418,7 +461,13 @@ export function initializeTerminalComponent() {
|
||||
makeFullscreen() {
|
||||
this.fullscreen = !this.fullscreen;
|
||||
this.$nextTick(() => {
|
||||
this.resizeTerminal();
|
||||
// Force a layout reflow to ensure DOM changes are applied
|
||||
this.$refs.terminalWrapper.offsetHeight;
|
||||
|
||||
// Add a small delay to ensure CSS transitions complete
|
||||
setTimeout(() => {
|
||||
this.resizeTerminal();
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -426,25 +475,53 @@ export function initializeTerminalComponent() {
|
||||
if (!this.terminalActive || !this.term || !this.fitAddon) return;
|
||||
|
||||
try {
|
||||
// Force a refresh of the fit addon dimensions
|
||||
this.fitAddon.fit();
|
||||
const height = this.$refs.terminalWrapper.clientHeight;
|
||||
const width = this.$refs.terminalWrapper.clientWidth;
|
||||
const charSize = this.term._core._renderService._charSizeService;
|
||||
|
||||
if (!charSize.height || !charSize.width) {
|
||||
// Fallback values if char size not available yet
|
||||
// Get fresh dimensions after fit
|
||||
const wrapperHeight = this.$refs.terminalWrapper.clientHeight;
|
||||
const wrapperWidth = this.$refs.terminalWrapper.clientWidth;
|
||||
|
||||
// Account for terminal container padding (px-2 py-1 = 8px left/right, 4px top/bottom)
|
||||
const horizontalPadding = 16; // 8px * 2 (left + right)
|
||||
const verticalPadding = 8; // 4px * 2 (top + bottom)
|
||||
const height = wrapperHeight - verticalPadding;
|
||||
const width = wrapperWidth - horizontalPadding;
|
||||
|
||||
// Check if dimensions are valid
|
||||
if (height <= 0 || width <= 0) {
|
||||
console.warn('[Terminal] Invalid wrapper dimensions, retrying...', { height, width });
|
||||
setTimeout(() => this.resizeTerminal(), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
const charSize = this.term._core._renderService._charSizeService;
|
||||
|
||||
if (!charSize.height || !charSize.width) {
|
||||
// Fallback values if char size not available yet
|
||||
console.warn('[Terminal] Character size not available, retrying...');
|
||||
setTimeout(() => this.resizeTerminal(), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate new dimensions with padding considerations
|
||||
const rows = Math.floor(height / charSize.height) - 1;
|
||||
const cols = Math.floor(width / charSize.width) - 1;
|
||||
|
||||
if (rows > 0 && cols > 0) {
|
||||
this.term.resize(cols, rows);
|
||||
this.sendMessage({
|
||||
resize: { cols: cols, rows: rows }
|
||||
});
|
||||
// Check if dimensions actually changed to avoid unnecessary resizes
|
||||
const currentCols = this.term.cols;
|
||||
const currentRows = this.term.rows;
|
||||
|
||||
if (cols !== currentCols || rows !== currentRows) {
|
||||
console.log(`[Terminal] Resizing terminal: ${currentCols}x${currentRows} -> ${cols}x${rows}`);
|
||||
this.term.resize(cols, rows);
|
||||
this.sendMessage({
|
||||
resize: { cols: cols, rows: rows }
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn('[Terminal] Invalid calculated dimensions:', { rows, cols, height, width, charSize });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Terminal] Resize error:', error);
|
||||
|
@@ -17,17 +17,20 @@
|
||||
</div>
|
||||
@else
|
||||
<div x-ref="terminalWrapper"
|
||||
:class="fullscreen ? 'fullscreen' : 'relative w-full h-full py-4 mx-auto max-h-[510px]'">
|
||||
<div id="terminal" wire:ignore></div>
|
||||
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-6 text-white"
|
||||
x-on:click="makeFullscreen"><svg class="w-5 h-5 opacity-30 hover:opacity-100" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
:class="fullscreen ? 'fullscreen !bg-black' : 'relative w-full h-full py-4 mx-auto max-h-[510px]'">
|
||||
<!-- Terminal container -->
|
||||
<div id="terminal" wire:ignore
|
||||
:class="fullscreen ? 'px-2 py-1 h-full bg-black' : 'px-2 py-1 rounded-sm bg-black'"
|
||||
x-show="terminalActive"></div>
|
||||
<button title="Minimize" x-show="fullscreen" class="fixed bg-black/40 top-4 right-6 text-white"
|
||||
x-on:click="makeFullscreen"><svg class="w-5 h-5 text-gray-500 hover:text-white bg-black/80"
|
||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
|
||||
</svg></button>
|
||||
<button title="Fullscreen" x-show="!fullscreen && terminalActive" class="absolute right-5 top-6 text-white"
|
||||
x-on:click="makeFullscreen"> <svg class="w-5 h-5 opacity-30 hover:opacity-100" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<button title="Fullscreen" x-show="!fullscreen && terminalActive" class="absolute right-5 top-6 text-white "
|
||||
x-on:click="makeFullscreen"> <svg class="w-5 h-5 text-gray-500 hover:text-white bg-black/80"
|
||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none">
|
||||
<path
|
||||
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
|
||||
|
Reference in New Issue
Block a user