diff --git a/static/terminal-window/terminal-window.css b/static/terminal-window/terminal-window.css index 92000a47..a54a4f1f 100644 --- a/static/terminal-window/terminal-window.css +++ b/static/terminal-window/terminal-window.css @@ -5,23 +5,6 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - width: calc( - round( - up, - min( - calc(9.6px * 80), - round(down, calc(96% - var(--scrollbar-width) - 1.6px), 9.6px) - ), - 1px - ) + var(--scrollbar-width) + 1.6px - ); - height: calc( - round( - up, - min(calc(22.4px * 24), round(down, calc(96% - 24px - 1.6px), 22.4px)), - 1px - ) + 24px + 1.6px - ); border: 1px solid #444; border-radius: 6px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); @@ -31,6 +14,7 @@ backface-visibility: hidden; -webkit-transform-style: preserve-3d; -webkit-backface-visibility: hidden; + overflow: hidden; } #terminal-window.dragging { @@ -47,17 +31,6 @@ pointer-events: none; } -#terminal-window.maximized { - width: 100%; - height: 100%; - top: 0; - left: 0; - transform: none; - border-radius: 0; - border: none; - box-shadow: none; -} - .window-title { display: flex; align-items: center; @@ -115,11 +88,6 @@ display: flex; } -#terminal-content { - width: 100%; - height: 100%; -} - .window-controls { position: absolute; top: 6px; @@ -156,8 +124,3 @@ .window-button.close { background: #ff5f56; } - -.xterm { - width: 100%; - height: 100%; -} diff --git a/static/terminal-window/terminal-window.js b/static/terminal-window/terminal-window.js index ae26df49..225c468d 100644 --- a/static/terminal-window/terminal-window.js +++ b/static/terminal-window/terminal-window.js @@ -99,28 +99,6 @@ const blinkTime = 530; document.title = blinkStates[0]; document.addEventListener("DOMContentLoaded", () => { - // Calculate scrollbar width and set CSS variable - const calculateScrollbarWidth = () => { - const outer = document.createElement("div"); - outer.style.visibility = "hidden"; - outer.style.overflow = "scroll"; - document.body.appendChild(outer); - - const inner = document.createElement("div"); - outer.appendChild(inner); - - const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; - document.documentElement.style.setProperty( - "--scrollbar-width", - `${scrollbarWidth}px` - ); - - document.body.removeChild(outer); - }; - - calculateScrollbarWidth(); - window.addEventListener("resize", calculateScrollbarWidth); - const state = new TerminalState(); const ui = new TerminalUI(state); @@ -161,15 +139,20 @@ document.addEventListener("DOMContentLoaded", () => { state.fitAddon = new FitAddon.FitAddon(); state.term.loadAddon(state.fitAddon); - // Initialize and load the WebGL addon - state.webglAddon = new WebglAddon.WebglAddon(); - state.term.loadAddon(state.webglAddon); + // Try to initialize WebGL addon + try { + state.webglAddon = new WebglAddon.WebglAddon(); + state.term.loadAddon(state.webglAddon); - // Handle WebGL addon errors - state.webglAddon.onContextLoss(() => { - state.webglAddon.dispose(); + // Handle WebGL addon errors + state.webglAddon.onContextLoss(() => { + state.webglAddon.dispose(); + state.webglAddon = null; + }); + } catch (e) { + console.log("WebGL not available, falling back to canvas renderer"); state.webglAddon = null; - }); + } }; const activateTerminal = () => { @@ -187,7 +170,7 @@ document.addEventListener("DOMContentLoaded", () => { state.term.focus(); // Fit terminal to container - ui.handleResize(); + ui.handleResize(0); terminal.print("=== RESTRICTED ACCESS TERMINAL ==="); terminal.print("Type 'help' to see available commands"); @@ -611,10 +594,40 @@ class TerminalUI { this.elem.classList.remove("dragging"); } - handleResize() { + handleResize(timeout = CONSTANTS.TIMEOUT.TRANSITION) { if (this.state.active) { setTimeout(() => { - this.state.fitAddon.fit(); + // Calculate scrollbar width + const calculateScrollbarWidth = () => { + const outer = document.createElement("div"); + outer.style.visibility = "hidden"; + outer.style.overflow = "scroll"; + document.body.appendChild(outer); + + const inner = document.createElement("div"); + outer.appendChild(inner); + + const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; + + document.body.removeChild(outer); + return scrollbarWidth; + }; + const scrollbarWidth = calculateScrollbarWidth(); + + // Fit the terminal with parameters + if (!this.elem.classList.contains("maximized")) { + this.state.fitAddon.fit({ + scrollbarWidth, + sizePercent: 0.96, + isMaximized: false, + }); + } else { + this.state.fitAddon.fit({ + scrollbarWidth, + sizePercent: 1, + isMaximized: true, + }); + } // Only reposition if not maximized and not dragging if (!this.elem.classList.contains("maximized") && !this.isDragging) { @@ -632,7 +645,20 @@ class TerminalUI { this.elem.style.top = "50%"; } } - }, CONSTANTS.TIMEOUT.TRANSITION); + + // Get terminal dimensions before fitting + const dimensions = this.state.term._core._renderService.dimensions; + + // Calculate the window size based on terminal dimensions + const cols = this.state.term.cols; + const rows = this.state.term.rows; + const cellWidth = dimensions.css.cell.width; + const cellHeight = dimensions.css.cell.height; + + // Set window size to match terminal content + scrollbar + this.elem.style.width = `${cols * cellWidth + scrollbarWidth}px`; + this.elem.style.height = `${rows * cellHeight + 24}px`; // 24px for titlebar + }, timeout); } } @@ -656,14 +682,11 @@ class TerminalUI { toggleMaximize() { if (this.elem.classList.contains("maximized")) { this.elem.classList.remove("maximized"); + } else { + this.elem.classList.add("maximized"); this.elem.style.transform = "translate(-50%, -50%)"; this.elem.style.left = "50%"; this.elem.style.top = "50%"; - } else { - this.elem.classList.add("maximized"); - this.elem.style.transform = "none"; - this.elem.style.left = "0"; - this.elem.style.top = "0"; } this.handleResize(); } diff --git a/static/xterm/addon-fit.js b/static/xterm/addon-fit.js index 03889719..412eae80 100644 --- a/static/xterm/addon-fit.js +++ b/static/xterm/addon-fit.js @@ -1,2 +1,72 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})())); -//# sourceMappingURL=addon-fit.js.map \ No newline at end of file +!(function (e, t) { + "object" == typeof exports && "object" == typeof module + ? (module.exports = t()) + : "function" == typeof define && define.amd + ? define([], t) + : "object" == typeof exports + ? (exports.FitAddon = t()) + : (e.FitAddon = t()); +})(self, () => + (() => { + "use strict"; + var e = {}; + return ( + (() => { + var t = e; + Object.defineProperty(t, "__esModule", { value: !0 }), + (t.FitAddon = void 0), + (t.FitAddon = class { + activate(e) { + this._terminal = e; + } + dispose() {} + fit(options = {}) { + const e = this.proposeDimensions(options); + if (!e || !this._terminal || isNaN(e.cols) || isNaN(e.rows)) + return; + const t = this._terminal._core; + (this._terminal.rows === e.rows && + this._terminal.cols === e.cols) || + (t._renderService.clear(), + this._terminal.resize(e.cols, e.rows)); + } + proposeDimensions({ + scrollbarWidth = 0, + sizePercent = 0.96, + isMaximized = false, + } = {}) { + if (!this._terminal) return; + if (!this._terminal.element) return; + + const e = this._terminal._core, + t = e._renderService.dimensions; + if (0 === t.css.cell.width || 0 === t.css.cell.height) return; + + // Calculate minimum dimensions based on 80x24 + const minCols = 80; + const minRows = 24; + + // Get viewport size (use provided percent only when not maximized) + const viewWidth = + document.documentElement.clientWidth * sizePercent; + const viewHeight = + document.documentElement.clientHeight * sizePercent; + + // Calculate dimensions based on viewport size + const maxCols = Math.floor( + (viewWidth - scrollbarWidth) / t.css.cell.width + ); + const maxRows = Math.floor((viewHeight - 24) / t.css.cell.height); + + // When maximized use full size, otherwise use minimum between calculated and standard size + const cols = isMaximized ? maxCols : Math.min(minCols, maxCols); + const rows = isMaximized ? maxRows : Math.min(minRows, maxRows); + + return { cols, rows }; + } + }); + })(), + e + ); + })() +);