diff --git a/plugins-client/ext.dockpanel/dockpanel.js b/plugins-client/ext.dockpanel/dockpanel.js
index 421d63440..27a16abbb 100644
--- a/plugins-client/ext.dockpanel/dockpanel.js
+++ b/plugins-client/ext.dockpanel/dockpanel.js
@@ -412,7 +412,10 @@ module.exports = ext.register("ext/dockpanel/dockpanel", {
},
expandBar : function(bar){
- this.layout.expandBar(bar.cache);
+ if (bar.cache)
+ this.layout.expandBar(bar.cache);
+ else if (bar.uniqueId != null)
+ this.layout.expandBar(bar.uniqueId);
},
//@todo removal of pages
diff --git a/plugins-client/ext.dockpanel/libdock.js b/plugins-client/ext.dockpanel/libdock.js
index 6167346f2..a540e36ec 100644
--- a/plugins-client/ext.dockpanel/libdock.js
+++ b/plugins-client/ext.dockpanel/libdock.js
@@ -666,12 +666,18 @@ var DockableLayout = module.exports = function(parentHBox, cbFindPage, cbStorePa
if (!bar.vbox) {
var _self = this;
- if (bar.$dockData.minWidth)
- pNode.minwidth = bar.$dockData.minWidth;
+ var dockMinWidthVal = bar.$dockData.minWidth || bar.$dockData["min-width"];
+ if (dockMinWidthVal)
+ pNode.minwidth = dockMinWidthVal;
+
+ var dockWidth = bar.$dockData && bar.$dockData.width || 260;
+ var dockMinWidth = dockMinWidthVal || 0;
+ if (dockMinWidth && dockWidth < dockMinWidth)
+ dockWidth = dockMinWidth;
bar.vbox = pNode.insertBefore(new ppc.vbox({
padding : 0,
- width : bar.$dockData && bar.$dockData.width || 260,
+ width : dockWidth,
splitters : true,
vdock : 1,
"class" : "dockcol unselectable expandedpanel",
@@ -772,6 +778,12 @@ var DockableLayout = module.exports = function(parentHBox, cbFindPage, cbStorePa
}
if (bar.vbox) {
+ // Enforce min-width on the vbox — always set if width is too small
+ var minW = bar.$dockData && (bar.$dockData.minWidth || bar.$dockData["min-width"]);
+ var curW = bar.vbox.getWidth ? bar.vbox.getWidth() : 0;
+ if (minW && (!curW || curW < minW))
+ bar.vbox.setWidth(minW);
+
bar.vbox.show();
bar.vbox.expanded = true;
bar.vbox.firstChild.$ext.onmousemove({});
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/plugins-client/ext.preview/preview.js b/plugins-client/ext.preview/preview.js
index 62071127a..04027b4e3 100644
--- a/plugins-client/ext.preview/preview.js
+++ b/plugins-client/ext.preview/preview.js
@@ -167,9 +167,17 @@ module.exports = ext.register($name, {
if (!button || !button.cache)
return;
var pNode = button.cache.$dockpage.$pHtmlNode;
- if (pNode.children.length === 4) {
- pNode.removeChild(pNode.children[2]);
- pNode.children[2].style.top = 0;
+ // Find and hide the caption element by class instead of
+ // removing by index, which could destroy the iframe.
+ for (var i = 0; i < pNode.children.length; i++) {
+ var child = pNode.children[i];
+ if (child.className && child.className.indexOf('docktab_page_caption') !== -1) {
+ child.style.display = 'none';
+ // Expand the content area below to fill the space
+ var next = pNode.children[i + 1];
+ if (next) next.style.top = '0';
+ break;
+ }
}
},
@@ -182,10 +190,10 @@ module.exports = ext.register($name, {
})[0];
if (page)
this.live.value = page.$doc.getValue();
- var iframe = this.getIframe().$ext;
- if (!iframe || !iframe.contentWindow)
+ var frm = this.getIframe();
+ if (!frm || !frm.$browser || !frm.$browser.contentWindow)
return;
- var html = iframe.contentWindow.document.getElementsByTagName("html")[0];
+ var html = frm.$browser.contentWindow.document.getElementsByTagName("html")[0];
html.innerHTML = this.live.value;
},
@@ -195,8 +203,8 @@ module.exports = ext.register($name, {
dock.expandBar(bar);
dock.showSection(this.$name, this.$button);
this.hidePageHeader();
- var frmPreview = this.getIframe();
- if (frmPreview.$ext.src !== url)
+ var frm = this.getIframe();
+ if (frm && frm.$browser && frm.$browser.src !== url)
this.refresh(url);
this.live = live;
},
@@ -207,9 +215,10 @@ module.exports = ext.register($name, {
},
refresh: function (url) {
- var frmPreview = this.getIframe();
+ var frm = this.getIframe();
+ if (!frm) return;
url = url || txtPreview.getValue();
- frmPreview.$ext.src = url;
+ frm.$browser.src = url;
txtPreview.setValue(url);
settings.save();
},
@@ -222,10 +231,45 @@ module.exports = ext.register($name, {
init: function() {
ppc.importCssString(this.css || "");
+
+ // Size the browser iframe to fill the page below the toolbar.
+ // PPC page elements don't support flexbox so we calculate
+ // the height dynamically based on the docktab container.
+ var fixSize = function() {
+ if (typeof frmPreview === 'undefined' || !frmPreview.$ext ||
+ typeof pgPreview === 'undefined' || !pgPreview.$ext) {
+ setTimeout(fixSize, 200);
+ return;
+ }
+
+ var iframe = frmPreview.$ext;
+ iframe.style.border = 'none';
+ iframe.style.width = '100%';
+
+ var resize = function() {
+ // Find the docktab ancestor to get the available height
+ var el = pgPreview.$ext;
+ while (el && !(el.className || '').match(/docktab/)) {
+ el = el.parentElement;
+ }
+ if (!el) return;
+ var availH = el.getBoundingClientRect().height;
+ // 36px for the toolbar bar
+ iframe.style.height = Math.max(0, availH - 36 - 28) + 'px';
+ };
+
+ resize();
+ // Re-size when window resizes
+ window.addEventListener('resize', resize);
+ // Also periodically check in case dock panel is resized
+ setInterval(resize, 500);
+ };
+ fixSize();
},
getIframe: function() {
- return pgPreview.selectSingleNode("iframe");
+ // frmPreview is the PPC browser element defined in preview.xml
+ return typeof frmPreview !== 'undefined' ? frmPreview : null;
},
enable : function() {
diff --git a/plugins-client/ext.preview/preview.xml b/plugins-client/ext.preview/preview.xml
index fa9b76831..3ec9edc6f 100755
--- a/plugins-client/ext.preview/preview.xml
+++ b/plugins-client/ext.preview/preview.xml
@@ -1,7 +1,7 @@
-
+
-
-
+
+
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;
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;
}