From 02c162ab27e9037b8cff567d207d128ef276a0b3 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Fri, 27 Mar 2026 23:46:13 +0100 Subject: [PATCH 1/3] Replace floating terminal windows with tiling window manager Terminals now use a flexbox tiling layout instead of absolute-positioned floating windows. The first terminal fills the entire panel; splitting creates nested flex containers with draggable splitter bars. Keybindings: Alt+D (split h), Alt+Shift+D (split v), Alt+W (close pane), Alt+J/K (navigate panes), Alt+T (new tab). Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins-client/ext.terminal/static/style.css | 227 +++-- plugins-client/ext.terminal/tty.js | 988 +++++++++++-------- 2 files changed, 690 insertions(+), 525 deletions(-) diff --git a/plugins-client/ext.terminal/static/style.css b/plugins-client/ext.terminal/static/style.css index c49585d25..99b637b7c 100644 --- a/plugins-client/ext.terminal/static/style.css +++ b/plugins-client/ext.terminal/static/style.css @@ -1,5 +1,6 @@ /** - * style.css (https://github.com/chjj/tty.js) + * style.css - Tiling terminal window manager + * Based on tty.js (https://github.com/chjj/tty.js) * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) */ @@ -22,154 +23,172 @@ html.dark { background: #333; } -html.maximized { +/** + * Terminal Window Container + */ + +#terminalWindow { + width: 100%; + height: 100%; overflow: hidden; } -body { - margin: 25px; +/** + * Tiling Layout + */ + +.tiling-split { + display: flex; + overflow: hidden; +} + +.tiling-horizontal { + flex-direction: row; } -/* Only allow plaintext? -[contenteditable=""], -[contenteditable="true"] { - -webkit-user-modify: read-write-plaintext-only !important; +.tiling-vertical { + flex-direction: column; } -*/ /** - * Terminal + * Splitter bars */ -.window { - padding-top: 15px; - border: #888888 solid 1px; - background: #272829; - position: absolute; - top: 20px; - left: 20px; - z-index: 1000; - box-shadow: rgba(0, 0, 0, 0.8) 2px 2px 20px; +.tiling-splitter { + flex-shrink: 0; + background: #444; + z-index: 10; +} + +.tiling-splitter:hover { + background: #0078d4; } -.maximized .window { - border: none; - box-shadow: none; +.tiling-splitter-horizontal { + width: 4px; + cursor: col-resize; } -.dark .window { - box-shadow: none; +.tiling-splitter-vertical { + height: 4px; + cursor: row-resize; } -.bar { - position: absolute; - top: 0; - left: 0; - right: 0; - height: 13px; - padding: 1px 0; - font-family: menlo,courier new,fixed,swiss,sans-serif; - color: #888888; +/** + * Pane + */ + +.tiling-pane { + display: flex; + flex-direction: column; + overflow: hidden; + min-width: 60px; + min-height: 40px; + background: #1e1e1e; } -.dark .bar, -.maximized .bar { +.tiling-pane-focused { + outline: 1px solid #0078d4; + outline-offset: -1px; } -.title { - position: absolute; - right: 5px; - top: 2px; +/** + * Pane title bar + */ + +.tiling-pane-bar { + display: flex; + align-items: center; + height: 22px; + min-height: 22px; + background: #2d2d2d; + border-bottom: 1px solid #444; + font-family: menlo, "courier new", monospace; font-size: 11px; - cursor: default; + color: #aaa; + padding: 0 4px; + user-select: none; + -webkit-user-select: none; } -.tabT { - font-size: 16px; - margin-left: 8px; - margin-top: -2px; - float: left; - cursor: pointer; +.tiling-pane-focused .tiling-pane-bar { + background: #333; + border-bottom-color: #0078d4; } -.tabT:hover { - font-weight: bold; +.tiling-pane-title { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: right; + padding-right: 4px; + color: #888; } -.grip { - position: absolute; - bottom: -10px; - right: -10px; - width: 22px; - height: 22px; - cursor: se-resize; - z-index: -1; - background: transparent; +/** + * Buttons in pane bar + */ + +.tiling-btn { + padding: 0 5px; + cursor: pointer; + color: #888; + font-size: 12px; + line-height: 22px; } -.grip:hover { +.tiling-btn:hover { + color: #fff; + background: rgba(255, 255, 255, 0.1); } -.reverse-video { - color: #000; - background: #f0f0f0; +.tiling-btn-close:hover { + color: #fff; + background: #c42b1c; } /** - * Page + * Tab buttons */ -h1 { - font: 20px/1.5 sans-serif; - background: #333; - margin: -25px -25px 0 -25px; - height: 30px; - padding: 10px 25px; - color: white; - border-top: #e15729 solid 10px; -} - -#open, #lights { - font: 20px/1.5 sans-serif; - position: fixed; - right: 0; - padding: 10px; - height: 50px; - color: white; +.tiling-tab-btn { + padding: 0 4px; cursor: pointer; - margin: 0; - background: none; - border: none; - -webkit-appearance: none; + color: #888; + font-size: 14px; + line-height: 22px; } -#open { - top: 10px; - background: #333; +.tiling-tab-btn:hover { + color: #fff; } -#lights { - font-size: 18px; - height: 46px; - top: 60px; - background: #555; -} +/** + * Terminal container + */ -#open:hover, #lights:hover { - color: slategrey; - background: white; +.tiling-term-container { + flex: 1; + overflow: hidden; + background: #1e1e1e; + position: relative; } -#open:active, #lights:active { - color: orange; +.tiling-term-container .xterm { + height: 100%; + padding: 2px; } -#help { - font: 12px/1.5 sans-serif; - color: #999; - position: fixed; - bottom: 10px; - left: 10px; +.tiling-term-container .xterm-viewport { + overflow-y: auto; } -.dark h1, .dark #help { display: none; } +/** + * Reverse video + */ + +.reverse-video { + color: #000; + background: #f0f0f0; +} diff --git a/plugins-client/ext.terminal/tty.js b/plugins-client/ext.terminal/tty.js index aadddced0..cf5c76c44 100644 --- a/plugins-client/ext.terminal/tty.js +++ b/plugins-client/ext.terminal/tty.js @@ -1,11 +1,12 @@ define(function(require) { var Terminal = require("xterm/xterm").Terminal; - - var Fit = require("xterm-fit/xterm-addon-fit").FitAddon; - var fit = new Fit(); + var FitAddon = require("xterm-fit/xterm-addon-fit").FitAddon; /** + * Tiling terminal window manager for Pylon IDE + * Replaces the original floating window system with a tiling layout. + * * Based on tty.js * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) */ @@ -36,7 +37,7 @@ define(function(require) { ev.stopPropagation(); return false; } - + function inherits(child, parent) { function f() { this.constructor = child; @@ -45,7 +46,6 @@ define(function(require) { child.prototype = new f(); } - /** * tty */ @@ -62,6 +62,378 @@ define(function(require) { tty.terms; tty.elements; + /** + * Tiling layout manager + * + * The layout is a binary tree of splits. Each leaf node is a Pane + * containing a Window (which has tabs of terminals). + * Each internal node is a Split with a direction (horizontal/vertical), + * two children, and a splitter bar between them. + * + * The root container is #terminalWindow. + */ + + var tilingRoot = null; // Root node of the tiling tree (Pane or Split) + var focusedPane = null; // Currently focused Pane + + /** + * Split node - contains two children separated by a splitter bar + */ + function Split(direction, first, second, container) { + this.type = 'split'; + this.direction = direction; // 'horizontal' or 'vertical' + this.parent = null; + this.first = first; + this.second = second; + this.ratio = 0.5; + + first.parent = this; + second.parent = this; + + this.element = container || document.createElement('div'); + this.element.className = 'tiling-split tiling-' + direction; + + this.splitter = document.createElement('div'); + this.splitter.className = 'tiling-splitter tiling-splitter-' + direction; + + this.element.innerHTML = ''; + this.element.appendChild(first.element); + this.element.appendChild(this.splitter); + this.element.appendChild(second.element); + + this._bindSplitter(); + this._applyRatio(); + } + + Split.prototype._applyRatio = function () { + var pct1 = (this.ratio * 100).toFixed(2) + '%'; + var pct2 = ((1 - this.ratio) * 100).toFixed(2) + '%'; + var splitterSize = '4px'; + + if (this.direction === 'horizontal') { + this.first.element.style.width = 'calc(' + pct1 + ' - 2px)'; + this.first.element.style.height = '100%'; + this.second.element.style.width = 'calc(' + pct2 + ' - 2px)'; + this.second.element.style.height = '100%'; + } else { + this.first.element.style.height = 'calc(' + pct1 + ' - 2px)'; + this.first.element.style.width = '100%'; + this.second.element.style.height = 'calc(' + pct2 + ' - 2px)'; + this.second.element.style.width = '100%'; + } + }; + + Split.prototype._bindSplitter = function () { + var self = this; + var splitter = this.splitter; + + splitter.addEventListener('mousedown', function (ev) { + ev.preventDefault(); + var startX = ev.pageX; + var startY = ev.pageY; + var startRatio = self.ratio; + var rect = self.element.getBoundingClientRect(); + + document.body.style.cursor = self.direction === 'horizontal' ? 'col-resize' : 'row-resize'; + // Overlay to prevent iframes/xterm from eating mouse events + var overlay = document.createElement('div'); + overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;z-index:99999;cursor:' + + (self.direction === 'horizontal' ? 'col-resize' : 'row-resize'); + document.body.appendChild(overlay); + + function move(ev) { + var delta, total; + if (self.direction === 'horizontal') { + delta = ev.pageX - startX; + total = rect.width; + } else { + delta = ev.pageY - startY; + total = rect.height; + } + self.ratio = Math.min(0.9, Math.max(0.1, startRatio + delta / total)); + self._applyRatio(); + fitAllPanes(); + } + + function up() { + document.body.style.cursor = ''; + document.body.removeChild(overlay); + document.removeEventListener('mousemove', move, false); + document.removeEventListener('mouseup', up, false); + fitAllPanes(); + } + + document.addEventListener('mousemove', move, false); + document.addEventListener('mouseup', up, false); + }, false); + }; + + Split.prototype.replaceChild = function (oldChild, newChild) { + newChild.parent = this; + if (this.first === oldChild) { + this.first = newChild; + this.element.replaceChild(newChild.element, oldChild.element); + } else if (this.second === oldChild) { + this.second = newChild; + this.element.replaceChild(newChild.element, oldChild.element); + } + this._applyRatio(); + }; + + Split.prototype.getLeaves = function () { + var leaves = []; + function walk(node) { + if (node.type === 'pane') leaves.push(node); + else { + walk(node.first); + walk(node.second); + } + } + walk(this); + return leaves; + }; + + Split.prototype.destroy = function () { + if (this.element.parentNode) { + this.element.parentNode.removeChild(this.element); + } + }; + + /** + * Pane - a leaf node containing a Window (terminal with tabs) + */ + function Pane(container) { + this.type = 'pane'; + this.parent = null; + this.window = null; + + this.element = container || document.createElement('div'); + this.element.className = 'tiling-pane'; + + // The bar at the top of each pane + this.bar = document.createElement('div'); + this.bar.className = 'tiling-pane-bar'; + + // Buttons + this.splitHBtn = document.createElement('div'); + this.splitHBtn.className = 'tiling-btn'; + this.splitHBtn.innerHTML = '┇'; // vertical dots = split horizontal + this.splitHBtn.title = 'Split horizontal (Alt+D)'; + + this.splitVBtn = document.createElement('div'); + this.splitVBtn.className = 'tiling-btn'; + this.splitVBtn.innerHTML = '┉'; // horizontal dots = split vertical + this.splitVBtn.title = 'Split vertical (Alt+Shift+D)'; + + this.closeBtn = document.createElement('div'); + this.closeBtn.className = 'tiling-btn tiling-btn-close'; + this.closeBtn.innerHTML = '✕'; + this.closeBtn.title = 'Close pane (Alt+W)'; + + this.newTabBtn = document.createElement('div'); + this.newTabBtn.className = 'tiling-btn'; + this.newTabBtn.innerHTML = '+'; + this.newTabBtn.title = 'New tab / Shift+click to close tab'; + + this.title = document.createElement('div'); + this.title.className = 'tiling-pane-title'; + + // Container for the xterm + this.termContainer = document.createElement('div'); + this.termContainer.className = 'tiling-term-container'; + + this.bar.appendChild(this.newTabBtn); + this.bar.appendChild(this.splitHBtn); + this.bar.appendChild(this.splitVBtn); + this.bar.appendChild(this.title); + this.bar.appendChild(this.closeBtn); + + this.element.appendChild(this.bar); + this.element.appendChild(this.termContainer); + + this._bind(); + } + + Pane.prototype._bind = function () { + var self = this; + + this.splitHBtn.addEventListener('click', function () { + splitPane(self, 'horizontal'); + }, false); + + this.splitVBtn.addEventListener('click', function () { + splitPane(self, 'vertical'); + }, false); + + this.closeBtn.addEventListener('click', function () { + closePane(self); + }, false); + + this.newTabBtn.addEventListener('click', function (ev) { + if (!self.window) return; + if (ev.ctrlKey || ev.altKey || ev.metaKey || ev.shiftKey) { + // Close current tab + if (self.window.focused) { + self.window.focused.destroy(); + } + } else { + self.window.createTab(); + } + }, false); + + this.element.addEventListener('mousedown', function () { + focusPane(self); + }, false); + }; + + Pane.prototype.getLeaves = function () { + return [this]; + }; + + Pane.prototype.destroy = function () { + if (this.window) { + this.window.destroy(); + } + if (this.element.parentNode) { + this.element.parentNode.removeChild(this.element); + } + }; + + /** + * Tiling operations + */ + + function createPane(container) { + var pane = new Pane(container); + var win = new Window(tty.socket, false, pane); + pane.window = win; + return pane; + } + + function splitPane(pane, direction) { + var newPane = createPane(); + + if (pane === tilingRoot && !pane.parent) { + // Root pane — wrap in a new split + var rootContainer = pane.element.parentNode; + rootContainer.removeChild(pane.element); + + // Reset inline styles that may have been set as root + pane.element.style.width = ''; + pane.element.style.height = ''; + + var split = new Split(direction, pane, newPane, null); + split.element.style.width = '100%'; + split.element.style.height = '100%'; + rootContainer.appendChild(split.element); + tilingRoot = split; + } else { + // Nested pane — replace in parent split + var parentSplit = pane.parent; + pane.element.style.width = ''; + pane.element.style.height = ''; + + var split = new Split(direction, pane, newPane, null); + parentSplit.replaceChild(pane, split); + } + + focusPane(newPane); + fitAllPanes(); + } + + function closePane(pane) { + if (!pane.parent) { + // Last pane — don't close, just destroy terminals + // Actually, let's keep at least one terminal + return; + } + + var parentSplit = pane.parent; + var sibling = (parentSplit.first === pane) ? parentSplit.second : parentSplit.first; + + // Detach sibling from split + sibling.parent = parentSplit.parent; + + if (parentSplit === tilingRoot) { + // Parent split is root + var rootContainer = parentSplit.element.parentNode; + rootContainer.removeChild(parentSplit.element); + sibling.element.style.width = '100%'; + sibling.element.style.height = '100%'; + rootContainer.appendChild(sibling.element); + tilingRoot = sibling; + sibling.parent = null; + } else { + // Parent split is nested + var grandparent = parentSplit.parent; + grandparent.replaceChild(parentSplit, sibling); + } + + pane.destroy(); + + // Focus the sibling or first leaf + var leaves = tilingRoot.getLeaves(); + if (leaves.length > 0) { + focusPane(leaves[0]); + } + fitAllPanes(); + } + + function focusPane(pane) { + // Remove focus from previous + if (focusedPane && focusedPane.element) { + focusedPane.element.classList.remove('tiling-pane-focused'); + } + focusedPane = pane; + if (pane && pane.element) { + pane.element.classList.add('tiling-pane-focused'); + if (pane.window && pane.window.focused) { + pane.window.focused.focus(); + } + } + } + + function getAllPanes() { + if (!tilingRoot) return []; + return tilingRoot.getLeaves(); + } + + function fitAllPanes() { + // Delay to allow layout to settle + setTimeout(function () { + var panes = getAllPanes(); + for (var i = 0; i < panes.length; i++) { + var pane = panes[i]; + if (pane.window && pane.window.focused) { + var tab = pane.window.focused; + try { + var fit = new FitAddon(); + tab.loadAddon(fit); + fit.fit(); + // Notify server of new size + if (tab.cols && tab.rows && tab.id) { + tab.socket.send(JSON.stringify({ + cmd: 'resize', id: tab.id, + cols: tab.cols, rows: tab.rows + })); + } + } catch (e) { + // fit may fail if element not yet visible + } + } + } + }, 50); + } + + function navigatePane(offset) { + var panes = getAllPanes(); + if (panes.length <= 1) return; + var idx = panes.indexOf(focusedPane); + if (idx === -1) idx = 0; + idx = (idx + offset + panes.length) % panes.length; + focusPane(panes[idx]); + } + /** * Open */ @@ -129,12 +501,27 @@ define(function(require) { if (newTerminal) { newTerminal.addEventListener('click', function () { - new Window; + if (!focusedPane) { + // No panes yet, shouldn't happen but create one + return; + } + // Split the focused pane horizontally + splitPane(focusedPane, 'horizontal'); }, false); } tty.socket.on('open', function () { tty.reset(); + // Create initial tiling layout with one pane + var container = document.getElementById('terminalWindow'); + container.innerHTML = ''; + var pane = createPane(null); + pane.element.style.width = '100%'; + pane.element.style.height = '100%'; + container.appendChild(pane.element); + tilingRoot = pane; + focusPane(pane); + fitAllPanes(); }); tty.socket.on('close', function (reason) { @@ -167,56 +554,89 @@ define(function(require) { tty.reset(); - Object.keys(data.terms).forEach(function (key) { - var tdata = data.terms[key] - , win = new Window(tty.socket, true) - , tab = win.tabs[0]; - - delete tty.terms[tab.id]; - tab.pty = tdata.pty; - tab.id = tdata.id; - tty.terms[tdata.id] = tab; - win.resize(tdata.cols, tdata.rows); - win.move(tdata.left, tdata.top); - tab.setProcessName(tdata.process); - console.log(' - ' + tdata.id) - - /* This is a hack but otherwise the focus remains on the hidden - * console at the bottom of the page - */ - setTimeout(function () { - win.focus(); - }, 50); - }); + var container = document.getElementById('terminalWindow'); + container.innerHTML = ''; + tilingRoot = null; + focusedPane = null; + + var termKeys = Object.keys(data.terms); + if (termKeys.length === 0) { + // Create a fresh pane + var pane = createPane(null); + pane.element.style.width = '100%'; + pane.element.style.height = '100%'; + container.appendChild(pane.element); + tilingRoot = pane; + focusPane(pane); + } else { + // Restore terminals — one pane per synced terminal + var firstPane = null; + termKeys.forEach(function (key, idx) { + var tdata = data.terms[key]; + + if (idx === 0) { + var pane = createPane(null); + pane.element.style.width = '100%'; + pane.element.style.height = '100%'; + container.appendChild(pane.element); + tilingRoot = pane; + firstPane = pane; + + // Replace the auto-created terminal with the synced one + var win = pane.window; + var tab = win.tabs[0]; + delete tty.terms[tab.id]; + tab.pty = tdata.pty; + tab.id = tdata.id; + tty.terms[tdata.id] = tab; + tab.setProcessName(tdata.process); + } else { + // Split to create additional panes + splitPane(focusedPane || firstPane, 'horizontal'); + var panes = getAllPanes(); + var newPane = panes[panes.length - 1]; + + var win = newPane.window; + var tab = win.tabs[0]; + delete tty.terms[tab.id]; + tab.pty = tdata.pty; + tab.id = tdata.id; + tty.terms[tdata.id] = tab; + tab.setProcessName(tdata.process); + } + }); + if (firstPane) focusPane(firstPane); + } + + setTimeout(function () { + fitAllPanes(); + }, 100); } }); - // We would need to poll the os on the serverside - // anyway. there's really no clean way to do this. - // This is just easier to do on the - // clientside, rather than poll on the - // server, and *then* send it to the client. + // Poll process names setInterval(function () { - var i = tty.windows.length; - while (i--) { - if (!tty.windows[i].focused) continue; - tty.windows[i].focused.pollProcessName(); + var panes = getAllPanes(); + for (var i = 0; i < panes.length; i++) { + if (panes[i].window && panes[i].window.focused) { + panes[i].window.focused.pollProcessName(); + } } }, 2 * 1000); - // Keep windows maximized when browser size changes + // Re-fit on browser resize window.addEventListener('resize', function () { - var i = tty.windows.length - , win; - - while (i--) { - win = tty.windows[i]; - if (win.minimize) { - win.minimize(); - win.maximize(); - } - } + fitAllPanes(); }, false); + + // Watch for console panel resize via MutationObserver + var termWin = document.getElementById('terminalWindow'); + if (termWin && typeof ResizeObserver !== 'undefined') { + var ro = new ResizeObserver(function () { + fitAllPanes(); + }); + ro.observe(termWin); + } }; /** @@ -228,64 +648,21 @@ define(function(require) { while (i--) { tty.windows[i].destroy(); } - tty.windows = []; tty.terms = {}; }; /** - * Window + * Window - a terminal container with tab support + * Now lives inside a Pane instead of floating. */ - function Window(socket, resume) { + function Window(socket, resume, pane) { var self = this; - var el - , winId - , grip - , xterm - , bar - , button - , title - , defaultS - , container; - - el = document.createElement('div'); - el.className = 'window'; - - grip = document.createElement('div'); - grip.className = 'grip'; - - xterm = document.createElement('div'); - winId = (Math.random() + 1).toString(36).substring(7); - xterm.className = 'xterm-container-' + winId; - - bar = document.createElement('div'); - bar.className = 'bar'; - - button = document.createElement('div'); - button.innerHTML = '~'; - button.title = 'new/close'; - button.className = 'tabT'; - - title = document.createElement('div'); - title.className = 'title'; - title.innerHTML = ''; - - defaultS = document.createElement('div'); - defaultS.innerHTML = '='; - defaultS.title = 'Default size'; - defaultS.className = 'tabT'; - this.socket = socket || tty.socket; this.resume = resume || false; - this.element = el; - this.grip = grip; - this.bar = bar; - this.button = button; - this.defaultS = defaultS; - this.title = title; - this.winId = winId; + this.pane = pane; this.tabs = []; this.focused = null; @@ -293,273 +670,39 @@ define(function(require) { this.cols = 80; this.rows = 24; - // The following is to accomodate very small console areas - container = document.getElementsByClassName('page pgTerminal curpage')[0] - - if (container != undefined && container.clientHeight < 370) { - this.rows = container.clientHeight / 27 | 0; - } - if (container != undefined && container.clientWidth < 600) { - this.cols = container.clientWidth / 8 | 0; + // Adjust for small containers + if (pane && pane.termContainer) { + var w = pane.termContainer.clientWidth; + var h = pane.termContainer.clientHeight; + if (h > 0 && h < 370) { + this.rows = Math.max(4, h / 27 | 0); + } + if (w > 0 && w < 600) { + this.cols = Math.max(10, w / 8 | 0); + } } - - el.appendChild(grip); - el.appendChild(xterm); - el.appendChild(bar); - bar.appendChild(button); - bar.appendChild(defaultS); - bar.appendChild(title); - document.getElementById('terminalWindow').appendChild(el); tty.windows.push(this); this.createTab(); - this.focus(); - this.bind(); - this.resume = false; } - Window.prototype.bind = function () { - var self = this - , el = this.element - , bar = this.bar - , grip = this.grip - , button = this.button - , defaultS = this.defaultS - , last = 0; - - button.addEventListener('click', function (ev) { - if (ev.ctrlKey || ev.altKey || ev.metaKey || ev.shiftKey) { - self.destroy(); - } else { - self.createTab(); - } - }, false); - - defaultS.addEventListener('click', function (ev) { - self.resize(80, 24); - return cancel(ev); - }, false); - - grip.addEventListener('mousedown', function (ev) { - self.focus(); - self.resizing(ev); - }, false); - - el.addEventListener('mousedown', function (ev) { - if (ev.target !== el && ev.target !== bar) { - if (ppc.document.activeElement == null) return; - return ppc.document.activeElement.blur(); - } - - self.focus(); - - if (new Date - last < 600) { - return self.maximize(); - } - last = new Date; - - self.drag(ev); - }, false); - }; - Window.prototype.focus = function () { - // Restack - var parent = this.element.parentNode; - if (parent) { - parent.removeChild(this.element); - parent.appendChild(this.element); - if (document.getElementsByClassName('ace_text-input')[0] != undefined) { - var length = document.getElementsByClassName('ace_text-input').length; - for (var i = 0; i < length; i++) { - document.getElementsByClassName('ace_text-input')[i].blur(); - } - } - } - - // Focus Foreground Tab - this.focused.focus(); + if (this.focused) this.focused.focus(); }; Window.prototype.destroy = function () { if (this.destroyed) return; this.destroyed = true; - if (this.minimize) this.minimize(); - splice(tty.windows, this); - if (tty.windows.length) tty.windows[0].focus(); - - this.element.parentNode.removeChild(this.element); this.each(function (term) { term.destroy(); }); }; - Window.prototype.drag = function (ev) { - var self = this - , el = this.element - , socket = this.socket - , id = this.tabs[0].id; - - if (this.minimize) return; - - var drag = { - left: el.offsetLeft, - top: el.offsetTop, - pageX: ev.pageX, - pageY: ev.pageY - }; - - el.style.opacity = '0.60'; - el.style.cursor = 'move'; - root.style.cursor = 'move'; - - function move(ev) { - el.style.left = - (drag.left + ev.pageX - drag.pageX) + 'px'; - el.style.top = - (drag.top + ev.pageY - drag.pageY) + 'px'; - } - - function up() { - el.style.opacity = ''; - el.style.cursor = ''; - root.style.cursor = ''; - - document.removeEventListener('mousemove', move, false); - document.removeEventListener('mouseup', up, false); - - var ev = { - left: el.style.left.replace(/\w+/g, ''), - top: el.style.top.replace(/\w+/g, '') - }; - - socket.send(JSON.stringify({cmd: 'move', id: id, left: el.style.left, top: el.style.top})); - - tty.terms[id].focus(); - - } - - document.addEventListener('mousemove', move, false); - document.addEventListener('mouseup', up, false); - }; - - Window.prototype.resizing = function (ev) { - var self = this - , el = this.element - , term = this.focused; - - if (this.minimize) delete this.minimize; - - var resize = { - w: el.clientWidth, - h: el.clientHeight - }; - - el.style.overflow = 'hidden'; - el.style.opacity = '0.70'; - el.style.cursor = 'se-resize'; - root.style.cursor = 'se-resize'; - term.element.style.height = '100%'; - - function move(ev) { - var x, y; - y = window.innerHeight - document.getElementsByClassName('page pgTerminal curpage')[0].clientHeight + 15; - x = ev.pageX - el.offsetLeft; - y = (ev.pageY - el.offsetTop) - y; - el.style.width = x + 'px'; - el.style.height = y + 'px'; - } - - function up() { - var x, y; - - x = el.clientWidth / resize.w; - y = el.clientHeight / resize.h; - x = (x * term.cols) | 0; - y = (y * term.rows) | 0; - - self.resize(x, y); - - el.style.height = ''; - - el.style.overflow = ''; - el.style.opacity = ''; - el.style.cursor = ''; - root.style.cursor = ''; - term.element.style.height = ''; - term.element.focus(); - - document.removeEventListener('mousemove', move, false); - document.removeEventListener('mouseup', up, false); - } - - document.addEventListener('mousemove', move, false); - document.addEventListener('mouseup', up, false); - }; - - Window.prototype.maximize = function () { - if (this.minimize) return this.minimize(); - - var self = this - , el = this.element - , term = this.focused - , x - , y; - - var m = { - cols: term.cols, - rows: term.rows, - left: el.offsetLeft, - top: el.offsetTop, - width: el.style.width, - height: el.style.height, - root: root.className - }; - - this.minimize = function () { - delete this.minimize; - - el.style.left = m.left + 'px'; - el.style.top = m.top + 'px'; - el.style.width = m.width; - el.style.height = m.height; - term.element.style.width = ''; - term.element.style.height = ''; - el.style.boxSizing = ''; - self.grip.style.display = ''; - root.className = m.root; - - self.resize(m.cols, m.rows); - - // This seems to be required by Chrome for proper focusing - setTimeout(function() { - term.element.focus(); - }, 50); - }; - - window.scrollTo(0, 0); - - var xterm = el.getElementsByClassName('xterm-container-' + this.winId)[0]; - xterm.style.width = '100%'; - xterm.style.height = '100%'; - - el.style.left = '0px'; - el.style.top = '0px'; - el.style.width = '100%'; - el.style.height = '100%'; - el.style.boxSizing = 'border-box'; - this.grip.style.display = 'none'; - root.className = 'maximized'; - - term.loadAddon(fit); - fit.fit(); - term.element.focus(); - }; - Window.prototype.resize = function (cols, rows) { this.cols = cols; this.rows = rows; @@ -569,11 +712,6 @@ define(function(require) { }); }; - Window.prototype.move = function (left, top) { - this.element.style.left = left; - this.element.style.top = top; - }; - Window.prototype.each = function (func) { var i = this.tabs.length; while (i--) { @@ -585,17 +723,6 @@ define(function(require) { return new Tab(this, this.socket, this.resume); }; - Window.prototype.highlight = function () { - var self = this; - - this.element.style.borderColor = 'orange'; - setTimeout(function () { - self.element.style.borderColor = ''; - }, 200); - - this.focus(); - }; - Window.prototype.focusTab = function (next) { var tabs = this.tabs , i = indexOf(tabs, this.focused) @@ -637,16 +764,21 @@ define(function(require) { tabStopWidth: 2, fontSize: 12 }); - + this._core = this.xterm._core; this._addonManager = this.xterm._addonManager; this._publicOptions = this.xterm._publicOptions; delete this.xterm; - + + // Tab button in the pane's bar var button = document.createElement('div'); - button.className = 'tabT'; + button.className = 'tiling-tab-btn'; button.innerHTML = '\u2022'; - win.bar.appendChild(button); + + if (win.pane && win.pane.bar) { + // Insert before title + win.pane.bar.insertBefore(button, win.pane.title); + } button.addEventListener('click', function (ev) { if (ev.ctrlKey || ev.altKey || ev.metaKey || ev.shiftKey) { @@ -662,14 +794,13 @@ define(function(require) { this.button = button; this.element = null; this.process = ''; - this.open(document.getElementsByClassName('xterm-container-' + win.winId)[0]); + + this.open(win.pane.termContainer); this.hookKeys(); this.hookMouse(); win.tabs.push(this); - // Starting with xterm.js v4.14.1 the viewport overflows with scroll-bar part unless we correct the window size during creation - win.element.style.width = win.element.getElementsByClassName('xterm-viewport')[0].clientWidth + 15 + 'px'; if (!resume) { this.socket.send(JSON.stringify({cmd: 'create', cols: cols, rows: rows})); @@ -687,28 +818,22 @@ define(function(require) { } this.focus(); - } inherits(Tab, Terminal); -// We could just hook in `tab.on('data', ...)` -// in the constructor, but this is faster. Tab.prototype.handler = function (data) { this.socket.send(JSON.stringify({cmd: 'data', id: this.id, payload: data})); }; -// We could just hook in `tab.on('title', ...)` -// in the constructor, but this is faster. Tab.prototype.handleTitle = function (title) { if (!title) return; title = sanitize(title); this.title = title; - if (this.window.focused === this) { - this.window.bar.title = title; - // this.setProcessName(this.process); + if (this.window.focused === this && this.window.pane) { + this.window.pane.bar.title = title; } }; @@ -722,35 +847,35 @@ define(function(require) { Tab.prototype._focus = Tab.prototype.focus; Tab.prototype.focus = function () { - var win = this.window; + var container = win.pane ? win.pane.termContainer : null; + if (!container) return; - var xterm = win.element.getElementsByClassName('xterm-container-' + win.winId)[0]; - - // maybe move to Tab.prototype.switch if (win.focused !== this) { if (win.focused) { - if (win.focused.element.parentNode) { + if (win.focused.element && win.focused.element.parentNode) { win.focused.element.parentNode.removeChild(win.focused.element); } win.focused.button.style.fontWeight = ''; } - xterm.appendChild(this.element); + container.appendChild(this.element); win.focused = this; - win.title.innerHTML = this.process; + if (win.pane) { + win.pane.title.innerHTML = this.process; + } this.button.style.fontWeight = 'bold'; this.button.style.color = ''; } this.handleTitle(this.title); - - if(ppc.isIphone) { - this.element.focus(); // Focus on element + + if (ppc.isIphone) { + this.element.focus(); } else { - this._focus(); // Use xterm.js focus + this._focus(); } }; @@ -761,7 +886,7 @@ define(function(require) { this._resize(cols, rows); }; - Tab.prototype.__destroy = Tab.prototype.dispose; // Release xterm resources + Tab.prototype.__destroy = Tab.prototype.dispose; Tab.prototype._destroy = function () { if (this.destroyed) return; @@ -769,8 +894,8 @@ define(function(require) { var win = this.window; - this.button.parentNode.removeChild(this.button); - if (this.element.parentNode) { + if (this.button.parentNode) this.button.parentNode.removeChild(this.button); + if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); } @@ -782,7 +907,14 @@ define(function(require) { } if (!win.tabs.length) { - win.destroy(); + // No more tabs — close the pane if there are other panes + if (win.pane && win.pane.parent) { + closePane(win.pane); + } + // If it's the last pane, create a new tab + else if (win.pane) { + win.createTab(); + } } this.__destroy(); @@ -798,22 +930,22 @@ define(function(require) { var self = this; // Ctrl-V (Paste on Windows) - if(ppc.isWin) { + if (ppc.isWin) { this.attachCustomKeyEventHandler(function (e) { if (e.ctrlKey == true && e.keyCode == 86) { - return false; // Do nothing + return false; } }); } - - // Handle space in iOS & keep focus off from the xterm.js textarea - if(ppc.isIphone) { + + // Handle space in iOS + if (ppc.isIphone) { self.element.addEventListener('keydown', function (ev) { - if(ev.charCode === 0 && ev.code === "Space") { + if (ev.charCode === 0 && ev.code === "Space") { self.handler(" "); } }); - + self.element.addEventListener('keyup', function (ev) { self.element.focus(); }); @@ -823,38 +955,51 @@ define(function(require) { self.handler(data); }); - // Alt-[jk] to quickly swap between windows. + // Keyboard shortcuts for tiling this.attachCustomKeyEventHandler(function (key) { - var offset - , i; + if (key.type !== 'keydown') return true; - if(key.altKey === true && (key.key === "j" || key.key === "k")) { - if (key.key === 'j') { - offset = -1; - } else if (key.key === 'k') { - offset = +1; - } + // Alt+D: split horizontal + if (key.altKey && !key.shiftKey && !key.ctrlKey && key.key === 'd') { + var pane = self.window.pane; + if (pane) splitPane(pane, 'horizontal'); + return false; + } - i = indexOf(tty.windows, this.window) + offset; + // Alt+Shift+D: split vertical + if (key.altKey && key.shiftKey && !key.ctrlKey && key.key === 'D') { + var pane = self.window.pane; + if (pane) splitPane(pane, 'vertical'); + return false; + } - if (tty.windows[i]) { - tty.windows[i].highlight(); - } + // Alt+W: close pane + if (key.altKey && !key.shiftKey && !key.ctrlKey && key.key === 'w') { + var pane = self.window.pane; + if (pane && pane.parent) closePane(pane); + return false; + } - if (offset > 0) { - if (tty.windows[0]) { - tty.windows[0].highlight(); - } - } - else { - i = tty.windows.length - 1; - if (tty.windows[i]) { - tty.windows[i].highlight(); - } + // Alt+J / Alt+K: navigate between panes + if (key.altKey && !key.shiftKey && !key.ctrlKey) { + if (key.key === 'j') { + navigatePane(-1); + return false; + } else if (key.key === 'k') { + navigatePane(1); + return false; } + } + // Alt+T: new tab in current pane + if (key.altKey && !key.shiftKey && !key.ctrlKey && key.key === 't') { + self.window.createTab(); return false; } + + // Alt+Shift+T or Alt+number to switch tabs? Keep it simple for now. + + return true; }); }; @@ -862,7 +1007,6 @@ define(function(require) { var self = this; self.element.addEventListener('mouseup', function (ev) { - // Left mouse button if (ev.which == 1 && self.hasSelection()) { var termTextarea = self._core.textarea; @@ -882,7 +1026,6 @@ define(function(require) { termTextarea.value = ""; } - // Right mouse button else if (ev.which == 3 && !ppc.clipboard.empty) { if (typeof ppc.clipboard.store === 'string') { self.handler(ppc.clipboard.store); @@ -927,10 +1070,8 @@ define(function(require) { if ((ev.type === 'mousewheel' && ev.wheelDeltaY > 0) || (ev.type === 'DOMMouseScroll' && ev.detail < 0)) { - // page up self.keyDown({keyCode: 33}); } else { - // page down self.keyDown({keyCode: 34}); } @@ -960,8 +1101,8 @@ define(function(require) { this.process = name; this.button.title = name; - if (this.window.focused === this) { - this.window.title.innerHTML = name; + if (this.window.focused === this && this.window.pane) { + this.window.pane.title.innerHTML = name; } }; @@ -1011,6 +1152,11 @@ define(function(require) { tty.Window = Window; tty.Tab = Tab; tty.Terminal = Terminal; + tty.splitPane = splitPane; + tty.closePane = closePane; + tty.navigatePane = navigatePane; + tty.getAllPanes = getAllPanes; + tty.fitAllPanes = fitAllPanes; this.tty = tty; From defe2e55da68e0562b19e5ab3efe8c50e7e2ab06 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Fri, 27 Mar 2026 23:46:19 +0100 Subject: [PATCH 2/3] Migrate PPC from deprecated CSS box model to standard flexbox Replace -webkit-box/-moz-box display values with display:flex, and prefixed box properties (BoxOrient, BoxFlex, BoxAlign, BoxPack, BoxDirection, BoxSizing) with their standard equivalents (flex-direction, flex, align-items, justify-content, flexDirection, boxSizing). This fixes Firefox support, which dropped -moz-box in modern versions. Chrome continues to work as it supports both old and standard flexbox. Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins-client/ext.main/style/skins.xml | 40 ++++++------ ppc/platform/core/lib/util/style.js | 6 +- ppc/platform/elements/hbox.js | 81 +++++++++++++------------ ppc/platform/elements/splitbox.js | 2 +- 4 files changed, 65 insertions(+), 64 deletions(-) diff --git a/plugins-client/ext.main/style/skins.xml b/plugins-client/ext.main/style/skins.xml index 1a14c0d86..9fd11aedc 100644 --- a/plugins-client/ext.main/style/skins.xml +++ b/plugins-client/ext.main/style/skins.xml @@ -4093,7 +4093,7 @@ } .datagrid .headings div{ - display : -moz-inline-box; + display : inline-block; overflow : visible; position : relative; } @@ -4102,7 +4102,7 @@ } .datagrid .records .row span{ - display : -moz-inline-box; + display : inline-block; overflow : visible; position : relative; } @@ -4623,7 +4623,7 @@ } .dgcheck .headings div{ - display : -moz-inline-box; + display : inline-block; overflow : visible; position : relative; } @@ -4632,7 +4632,7 @@ } .dgcheck .records .row span{ - display : -moz-inline-box; + display : inline-block; overflow : visible; position : relative; } @@ -6682,7 +6682,7 @@ ]]> .sbios{ - display : -webkit-box; + display : flex; } .sbios span{ @@ -8055,7 +8055,7 @@ ]]> .console_scrollbar{ - display : -webkit-box; + display : flex; } .console_scrollbar span{ @@ -8779,8 +8779,7 @@ z-index : 90000; background-color: #e8e8e8; - display : -webkit-box; - display : -ms-box; + display : flex; } .w-resize { @@ -9314,7 +9313,7 @@ right : 0; clip: rect(0px 4000px 30px 0px); - display : -webkit-box; + display : flex; } .tab_console .btncontainer > .btntab { @@ -9322,7 +9321,7 @@ min-width : 17px; max-width : 70px; - -webkit-box-flex : 1; + flex : 1; position : relative; width : 0; @@ -9543,7 +9542,7 @@ /*box-shadow: 0 -1px 0 0 black inset, 0 1px 0 0 rgba(255, 255, 255, .06) inset;*/ margin-bottom: 1px; - display : -webkit-box; + display : flex; } .editor_tab .btnsesssioncontainer.morepadding{ @@ -9561,7 +9560,7 @@ min-width : 17px; max-width : 150px; - -webkit-box-flex : 1; + flex : 1; position : relative; width : 0; line-height : 1.2em; @@ -10811,6 +10810,8 @@ -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; + width: 100%; + min-width: 0; } .docktab .btncontainer{ @@ -10822,7 +10823,7 @@ width : auto; clip: rect(0px 3000px 30px 0px); - display : -webkit-box; + display : flex; } .dockcol .docktab .btncontainer{ @@ -10845,7 +10846,7 @@ min-width : 17px; max-width : 100px; - -webkit-box-flex : 1; + flex : 1; width : 0; padding : 3px 2px 4px; @@ -12542,13 +12543,10 @@ .img div { width : 100%; height : 100%; - display: -webkit-box; - -webkit-box-orient: horizontal; - box-orient: horizontal; - -webkit-box-pack: center; - box-pack: center; - -webkit-box-align: center; - box-align: center; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; } .img IMG { diff --git a/ppc/platform/core/lib/util/style.js b/ppc/platform/core/lib/util/style.js index 31a9f3b24..3fe1ae6c7 100644 --- a/ppc/platform/core/lib/util/style.js +++ b/ppc/platform/core/lib/util/style.js @@ -463,7 +463,7 @@ ppc.getVerBorders = function(oHtml){ */ ppc.getWidthDiff = function(oHtml){ if (ppc.hasFlexibleBox - && ppc.getStyle(oHtml, ppc.CSSPREFIX + "BoxSizing") != "content-box") + && ppc.getStyle(oHtml, "boxSizing") != "content-box") return 0; return Math.max(0, (parseInt(ppc.getStyle(oHtml, "paddingLeft")) || 0) @@ -479,7 +479,7 @@ ppc.getWidthDiff = function(oHtml){ */ ppc.getHeightDiff = function(oHtml){ if (ppc.hasFlexibleBox - && ppc.getStyle(oHtml, ppc.CSSPREFIX + "BoxSizing") != "content-box") + && ppc.getStyle(oHtml, "boxSizing") != "content-box") return 0; return Math.max(0, (parseInt(ppc.getStyle(oHtml, "paddingTop")) || 0) @@ -496,7 +496,7 @@ ppc.getHeightDiff = function(oHtml){ */ ppc.getDiff = function(oHtml){ if (ppc.hasFlexibleBox - && ppc.getStyle(oHtml, ppc.CSSPREFIX + "BoxSizing") != "content-box") + && ppc.getStyle(oHtml, "boxSizing") != "content-box") return [0,0]; return [Math.max(0, (parseInt(ppc.getStyle(oHtml, "paddingLeft")) || 0) diff --git a/ppc/platform/elements/hbox.js b/ppc/platform/elements/hbox.js index 097a96a14..dde972e00 100644 --- a/ppc/platform/elements/hbox.js +++ b/ppc/platform/elements/hbox.js @@ -179,9 +179,14 @@ ppc.vbox = function(struct, tagName){ this.$resize(); } + function getFlexDirection(isVbox, isReverse) { + if (isVbox) return isReverse ? "column-reverse" : "column"; + return isReverse ? "row-reverse" : "row"; + } + this.$propHandlers["reverse"] = function(value){ if (ppc.hasFlexibleBox) - this.$int.style[ppc.CSSPREFIX + "BoxDirection"] = value ? "reverse" : "normal"; + this.$int.style.flexDirection = getFlexDirection(this.$vbox, value); else { //@todo } @@ -196,9 +201,10 @@ ppc.vbox = function(struct, tagName){ }; this.$propHandlers["pack"] = function(value){ - if (ppc.hasFlexibleBox) - this.$int.style[ppc.CSSPREFIX + "BoxPack"] = value || "start"; - else if (this.$amlLoaded) { + if (ppc.hasFlexibleBox) { + var packMap = {start: "flex-start", center: "center", end: "flex-end"}; + this.$int.style.justifyContent = packMap[value] || "flex-start"; + } else if (this.$amlLoaded) { if (this.$vbox) { this.$int.style.verticalAlign = value == "center" ? "middle" : (value == "end" ? "bottom" : "top"); } @@ -222,7 +228,8 @@ ppc.vbox = function(struct, tagName){ this.$propHandlers["align"] = function(value){ if (ppc.hasFlexibleBox) { - this.$int.style[ppc.CSSPREFIX + "BoxAlign"] = value || "stretch"; + var alignMap = {stretch: "stretch", start: "flex-start", center: "center", end: "flex-end"}; + this.$int.style.alignItems = alignMap[value] || "stretch"; if (ppc.isGecko) this.$int.style.overflow = "visible"; @@ -245,14 +252,10 @@ ppc.vbox = function(struct, tagName){ continue; //(this[size] || this.anchors || (this.$vbox ? this.top && this.bottom : this.left && this.right) - if (stretch && !node[size]) //(node.$altExt || - node.$ext.style[size] = ppc.isGecko && (this.flex || node.flex) - ? (isInFixed ? "1px" : "auto") - : (ppc.isWebkit && input[node.$ext.tagName] - ? "100%" - : (false && ppc.isWebkit && node[this.$vbox ? "minwidth" : "minheight"] && this.flex //nasty bug fix - ? "0px" - : "auto"));//(ppc.isWebkit && node.flex && size == "height" ? "100%" : "auto"); // && (this.flex && node.flex) + if (stretch && !node[size]) //(node.$altExt || + node.$ext.style[size] = (input[node.$ext.tagName] + ? "100%" + : "auto");//(ppc.isWebkit && node.flex && size == "height" ? "100%" : "auto"); // && (this.flex && node.flex) else if (node[size]) handlers["true"][size].call(node, node[size]); } @@ -333,7 +336,7 @@ ppc.vbox = function(struct, tagName){ if (ppc.hasFlexibleBox) { if (this.$altExt) this.$altExt.style.display = e.value - ? (ppc.isGecko ? MOZSTACK : ppc.CSSPREFIX2 + "-box") + ? "flex" : "none"; return; } @@ -415,10 +418,10 @@ ppc.vbox = function(struct, tagName){ this.$altExt = this.$ext.ownerDocument.createElement("div"); this.parentNode.$int.replaceChild(this.$altExt, this.$ext); this.$altExt.appendChild(this.$ext); - this.$altExt.style[ppc.CSSPREFIX + "BoxSizing"] = "border-box"; - this.$altExt.style.display = ppc.CSSPREFIX2 + "-box"; - this.$altExt.style[ppc.CSSPREFIX + "BoxOrient"] = "vertical"; - this.$ext.style[ppc.CSSPREFIX + "BoxFlex"] = 1; + this.$altExt.style.boxSizing = "border-box"; + this.$altExt.style.display = "flex"; + this.$altExt.style.flexDirection = "column"; + this.$ext.style.flex = "1"; var size = this.parentNode.$vbox ? "height" : "width"; //var osize = this.parentNode.$vbox ? "width" : "height"; @@ -428,11 +431,11 @@ ppc.vbox = function(struct, tagName){ } } - (this.$altExt || this.$ext).style[ppc.CSSPREFIX + "BoxFlex"] = parseInt(value) || 1; + (this.$altExt || this.$ext).style.flex = String(parseInt(value) || 1); } else if (this.$altExt) { this.parentNode.$int.replaceChild(this.$ext, this.$altExt); - this.$ext.style[ppc.CSSPREFIX + "BoxFlex"] = ""; + this.$ext.style.flex = ""; if (ppc.isGecko) this.$ext.style.overflow = ""; delete this.$altExt; @@ -532,9 +535,9 @@ ppc.vbox = function(struct, tagName){ var doc = amlNode.$ext.ownerDocument; amlNode.$altExt = doc.createElement("div"); amlNode.parentNode.$int.replaceChild(amlNode.$altExt, amlNode.$ext); - amlNode.$altExt.style[ppc.CSSPREFIX + "BoxSizing"] = "border-box"; + amlNode.$altExt.style.boxSizing = "border-box"; amlNode.$altExt.appendChild(amlNode.$ext); - + if (ppc.isWebkit) { var d = ppc.getDiff(amlNode.$ext); //amlNode.$altExt.style.padding = "0 " + d[0] + "px " + d[1] + "px 0"; @@ -548,10 +551,10 @@ ppc.vbox = function(struct, tagName){ amlNode.$ext.style.position = "relative"; } else { - amlNode.$altExt.style.display = ppc.CSSPREFIX2 + "-box"; - amlNode.$altExt.style[ppc.CSSPREFIX + "BoxOrient"] = "horizontal"; - amlNode.$altExt.style[ppc.CSSPREFIX + "BoxAlign"] = "stretch"; - amlNode.$ext.style[ppc.CSSPREFIX + "BoxFlex"] = 1; + amlNode.$altExt.style.display = "flex"; + amlNode.$altExt.style.flexDirection = "row"; + amlNode.$altExt.style.alignItems = "stretch"; + amlNode.$ext.style.flex = "1"; } } else { @@ -562,7 +565,7 @@ ppc.vbox = function(struct, tagName){ //amlNode.$ext.style.position = "relative"; //@todo undo } - amlNode.$ext.style[ppc.CSSPREFIX + "BoxSizing"] = "border-box"; + amlNode.$ext.style.boxSizing = "border-box"; } else { if (this.$vbox) { @@ -681,7 +684,7 @@ ppc.vbox = function(struct, tagName){ } if (ppc.hasFlexibleBox) { - amlNode.$ext.style[ppc.CSSPREFIX + "BoxSizing"] = ""; + amlNode.$ext.style.boxSizing = ""; if (ppc.isGecko) { this.$int.style.overflow = "visible"; @@ -747,7 +750,7 @@ ppc.vbox = function(struct, tagName){ this.addEventListener("DOMNodeInserted", function(e){ if (e.currentTarget == this) { if (this.visible) - this.$ext.style.display = ppc.CSSPREFIX2 + "-box"; //Webkit issue + this.$ext.style.display = "flex"; return; } @@ -767,7 +770,7 @@ ppc.vbox = function(struct, tagName){ function myVisibleHandler(e){ if (e.value) - this.$int.style.display = ppc.CSSPREFIX2 + "-box"; + this.$int.style.display = "flex"; } function myHeightHandler(e){ @@ -815,7 +818,7 @@ ppc.vbox = function(struct, tagName){ && "hbox|vbox".indexOf(this.parentNode.localName) > -1)) { this.$int.style.width = "100%"; this.$int.style.height = "100%"; - this.$int.style.display = "-webkit-box"; + this.$int.style.display = "flex"; } else if (!ppc.hasFlexibleBox && this.$vbox) { this.$int.style.display = ppc.INLINE; @@ -823,17 +826,17 @@ ppc.vbox = function(struct, tagName){ this.$int.style.zoom = 1; this.$int.style.width = "100%"; } - + if (ppc.hasFlexibleBox) { - this.$display = "-" + ppc.CSSPREFIX +"-box"; - - this.$int.style.display = ppc.CSSPREFIX2 + "-box"; - this.$int.style[ppc.CSSPREFIX + "BoxOrient"] = this.localName == "hbox" ? "horizontal" : "vertical"; - if (ppc.isGecko) { //!webkit - this.$int.style[ppc.CSSPREFIX + "BoxSizing"] = "border-box"; + this.$display = "flex"; + + this.$int.style.display = "flex"; + this.$int.style.flexDirection = getFlexDirection(this.$vbox, this.reverse); + if (ppc.isGecko) { + this.$int.style.boxSizing = "border-box"; this.$int.style.verticalAlign = "top"; } - this.$int.style[ppc.CSSPREFIX + "BoxAlign"] = "stretch"; + this.$int.style.alignItems = "stretch"; this.addEventListener("prop.visible", myVisibleHandler); } diff --git a/ppc/platform/elements/splitbox.js b/ppc/platform/elements/splitbox.js index f287bb6c8..ec2552dfc 100644 --- a/ppc/platform/elements/splitbox.js +++ b/ppc/platform/elements/splitbox.js @@ -512,7 +512,7 @@ ppc.vsplitbox = function(struct, tagName){ this.addEventListener("DOMNodeInserted", function(e){ if (e.currentTarget == this) { if (this.visible) - this.$ext.style.display = ppc.CSSPREFIX2 + "-box"; //Webkit issue + this.$ext.style.display = "flex"; return; } From d02a0faae47eb9dbc5439ba5682cbadfae50f682 Mon Sep 17 00:00:00 2001 From: Kreijstal Date: Fri, 27 Mar 2026 23:46:27 +0100 Subject: [PATCH 3/3] Fix Preview plugin and dock panel rendering - Use PPC's element instead of raw