c9-core/plugins/c9.ide.behaviors/page.js

704 wiersze
27 KiB
JavaScript

define(function(require, exports, module) {
main.consumes = [
"Plugin", "ui", "tabManager", "ace", "anims", "settings", "preferences"
];
main.provides = ["tabinteraction"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
var ui = imports.ui;
var anims = imports.anims;
var settings = imports.settings;
var prefs = imports.preferences;
var tabs = imports.tabManager;
var aceHandle = imports.ace;
var css = require("text!./style.css");
/***** Initialization *****/
var handle = new Plugin("Ajax.org", main.consumes);
// var emit = handle.getEmitter();
var divSplit, divButton, plusMargin = 11;
var loaded = false;
function load() {
if (loaded) return false;
loaded = true;
// Insert CSS
ui.insertCss(css, null, handle);
settings.on("read", function() {
settings.setDefaults("user/tabs", [["autoclosepanes", true]]);
}, handle);
tabs.on("tabCreate", function(e) {
var tab = e.tab;
addInteraction(tab);
// Make sure that events are put on the button when the skin changes
tab.aml.on("$skinchange", function() {
addInteraction(tab);
});
}, handle);
tabs.on("tabDestroy", function(e) {
if (e.tab.meta.$skipAnimation)
setTimeout(function() { e.tab.pane.unload(); }, 0);
}, handle);
tabs.on("tabAfterClose", function(e) {
if (e.last && canTabBeRemoved(e.tab.pane, 1)
&& settings.getBool("user/tabs/@autoclosepanes")) {
e.tab.meta.$skipAnimation = true;
}
}, handle);
prefs.add({
"General": {
"User Interface": {
position: 20,
"Automatically Close Empty Panes": {
type: "checkbox",
path: "user/tabs/@autoclosepanes",
position: 1150
}
}
}
}, handle);
ui.insertCss("* { }", false, handle);
}
function canTabBeRemoved(pane, min) {
if (!pane || pane.getTabs().length > (min || 0))
return false;
var containers = tabs.containers;
for (var i = 0; i < containers.length; i++) {
if (ui.isChildOf(containers[i], pane.aml)) {
return containers[i]
.getElementsByTagNameNS(apf.ns.aml, "tab").length > 1;
}
}
return false;
}
function addInteraction(plugin) {
var tab = plugin.aml;
var button = tab.$button;
if (!button) return;
var offsetX, offsetY, startX, startY, dragWidth;
var mode, rightPadding, originalTab, btnPlus, pane;
var started, tabWidth, leftPadding, leftPos, start, initMouse;
var pages, clean, originalPosition, splitDirection, splitTab;
function setOrderMode(toTab, e) {
if (toTab.isOrderCleaned === false) {
return setTimeout(function() {
setOrderMode(toTab, e);
}, 10);
}
mode = "order";
clean && clean();
var lastPane = pane;
// Set new pane
pane = toTab;
// Plus Button
btnPlus = pane.$ext.querySelector(".plus_tab_button");
// Attach tab to pane
if (e) {
var curpage = pane.getPage();
if (curpage) {
var curbtn = curpage.$button;
ui.setStyleClass(curbtn, "", ["curbtn"]);
}
ui.setStyleClass(tab.$button, "curbtn");
}
var container = pane.$buttons;
var nodes = container.childNodes;
var rect = container.getBoundingClientRect();
var btn = (pane.getPage() || { $button: button }).$button;
var diff = ui.getWidthDiff(btn);
var leftMargin = parseInt(ui.getStyle(btn, "marginLeft"), 10) || 0;
var rightMargin = parseInt(ui.getStyle(btn, "marginRight"), 10) || 0;
var maxWidth = parseInt(ui.getStyle(btn, "maxWidth"), 10) || 150;
if (maxWidth > 500) maxWidth = 150;
leftPos = rect.left;
pages = pane.getPages();
leftPadding = parseInt(ui.getStyle(container, "paddingLeft"), 10) || 0;
rightPadding = (parseInt(ui.getStyle(container, "paddingRight"), 10) || 0) + 24;
var addOne = pages.indexOf(tab) == -1;
var maxTabWidth = Math.min(maxWidth + diff,
((rect.width - leftPadding - rightPadding + rightMargin)
/ (pages.length + (addOne ? 1 : 0))) - rightMargin);
var newTabWidth = maxTabWidth - diff;
tabWidth = maxTabWidth + leftMargin + rightMargin;
// Get the positions info of the tab buttons
var info = [];
for (var i = nodes.length - 1; i >= 0; i--) {
if ((btn = nodes[i]).nodeType != 1) continue;
info.push([btn, btn.offsetLeft, btn.offsetTop, btn.offsetWidth]);
}
// Append the button to the button container
if (e || addOne) {
pane.$buttons.appendChild(button);
info.push([button, 0, button.offsetTop, dragWidth]);
}
// Set the info
var iter;
while ((iter = info.pop())) {
btn = iter[0];
btn.style.left = (iter[1]) + "px";
btn.style.top = (iter[2]) + "px";
btn.style.width = (iter[3] - ui.getWidthDiff(btn)) + "px";
btn.style.margin = 0;
btn.style.position = "absolute";
}
start = function() {
// Remove from childNodes of old pane
var lastIndex = pane.childNodes.indexOf(tab);
pane.childNodes.remove(tab);
};
if (started)
start();
// Set initial position
if (e || addOne)
mouseMoveOrder(e, newTabWidth); //, lastPane == pane);
apf.addListener(document, "mousemove", mouseMoveOrder);
apf.addListener(document, "mouseup", mouseUpOrder);
clean = function(change, toTab) {
if (!toTab)
toTab = pane;
toTab.isOrderCleaned = false;
if (change !== false) {
apf.removeListener(document, "mousemove", mouseMoveOrder);
apf.removeListener(document, "mouseup", mouseUpOrder);
}
if (change === true) {
var maxTabWidth = Math.min(maxWidth + diff,
((rect.width - leftPadding - rightPadding + rightMargin + 3)
/ pane.getPages().length) - rightMargin);
tabWidth = maxTabWidth + leftMargin + rightMargin;
var cb = clean.bind(this, false, toTab);
return animateTabs(cb, null, maxTabWidth - diff);
}
if (curbtn && curpage.parentNode && curpage == curpage.parentNode.getPage()) {
ui.setStyleClass(curbtn, "curbtn");
curbtn = null;
}
for (var i = nodes.length - 1; i >= 0; i--) {
if ((btn = nodes[i]).nodeType != 1) continue;
btn.style.left =
btn.style.top =
btn.style.width =
btn.style.margin =
btn.style.position = "";
}
toTab.isOrderCleaned = true;
};
}
function setSplitMode(e) {
mode = "split";
// Div that shows where to insert split
if (!divSplit) {
divSplit = document.createElement("div");
divSplit.className = "split-area";
document.body.appendChild(divSplit);
}
// Remove all pointer events from iframes
var frames = document.getElementsByTagName("iframe");
for (var i = 0; i < frames.length; i++)
frames[i].style.pointerEvents = "none";
start = function() {
// Fixate current position and width
var rect = button.getBoundingClientRect();
button.style.left = (rect.left) + "px";
button.style.top = (rect.top) + "px";
button.style.width = (dragWidth - ui.getWidthDiff(button)) + "px";
button.style.position = "absolute";
// Attach tab to body
if (!divButton) {
divButton = document.createElement("div");
document.body.appendChild(divButton);
}
var theme = aceHandle.theme || {};
divButton.className =
(theme.isDark ? "dark " : "") + (theme.cssClass || "");
divButton.appendChild(button);
// Remove from parent childNodes
pane.childNodes.remove(tab);
ui.setStyleRule("*", "cursor", "default!important");
};
apf.addListener(document, "mousemove", mouseMoveSplit);
apf.addListener(document, "mouseup", mouseUpSplit);
if (started)
start();
clean && clean(true);
clean = function() {
button.style.left =
button.style.top =
button.style.width =
button.style.margin =
button.style.position = "";
divSplit.style.display = "none";
ui.setStyleRule("*", "cursor", "");
apf.removeListener(document, "mousemove", mouseMoveSplit);
apf.removeListener(document, "mouseup", mouseUpSplit);
};
if (started) {
// Set initial position and detect immediate snap
if (mouseMoveSplit(e) === false)
return;
}
}
function finish() {
if (!initMouse) {
clean(null, null, true);
button.style.zIndex =
button.style.pointerEvents = "";
// Return all pointer events to iframes
var frames = document.getElementsByTagName("iframe");
for (var i = 0; i < frames.length; i++)
frames[i].style.pointerEvents = "";
}
tab.$dragging = false;
}
button.addEventListener("mousedown", function(e) {
// Tab needs to support ordering
if (!tab.parentNode.$order || tab.$dragging || e.button)
return;
// APF stuff
tab.$dragging = true;
startX = e.clientX;
startY = e.clientY;
initMouse = function() {
// Calculate where on the button was clicked
var rect = button.getBoundingClientRect();
offsetX = startX - rect.left;
offsetY = startY - rect.top;
// Prepare button for dragging
button.style.zIndex = 100000;
button.style.pointerEvents = "none";
// Initialize with order mode
setOrderMode(tab.parentNode, e);
initMouse = null;
};
// Use mine
started = false;
// Set current pane
pane = plugin.pane.aml;
// Store original info
originalTab = pane;
originalPosition = button.nextSibling;
dragWidth = button.offsetWidth;
apf.addListener(document, "mousemove", mouseMoveOrder);
apf.addListener(document, "mouseup", mouseUpOrder);
}, true);
function isNotSnapped(e, container) {
if (!container)
container = pane.$buttons;
var rect = container.getBoundingClientRect();
var x = e.clientX;
var y = e.clientY;
var diff = 10;
return (
x < rect.left - diff ||
x > rect.left + rect.width + diff ||
y < rect.top - 5 ||
y > rect.top + rect.height + diff
);
}
function showOrderPosition(idx, toWidth, finalize, finish) {
if (idx < 0) idx = 0;
var orderTab = (pages[idx - 1] == tab
? pages[idx + 1]
: pages[idx]) || null;
// Remove tab from childNodes
pane.childNodes.remove(tab);
if (finalize) {
// Get new pages with new order
pages = pane.getPages();
// Reparent for real
var insert = pages[idx] && pages[idx].cloud9tab;
plugin.attachTo(pane.cloud9pane, insert, true);
}
else {
// If we're already at this position do nothing
if (orderTab == tab)
return;
// Move tab to new position
idx = pane.childNodes.indexOf(orderTab);
if (idx > -1) pane.childNodes.splice(idx, 0, tab);
else pane.childNodes.push(tab);
pane.$buttons.insertBefore(tab.$button,
orderTab && orderTab.$button || btnPlus);
}
// Patch + button which is changed to "" again
// btnPlus.style.position = "absolute";
// btnPlus.style.top = "6px";
animateTabs(finish, finalize, toWidth);
}
function animateTabs(finish, includeTab, toWidth) {
// Get new pages array (with new order)
pages = pane.getPages();
pages.push({ $button: btnPlus });
// Animate all pages to their right position
var p, tweens = [], offset = 0;
for (var i = 0, l = pages.length; i < l; i++) {
p = pages[i];
// Ignore the tab we are dragging
if (!includeTab && tab === p) {
if (p.$button.parentNode == document.body)
offset = 1;
if (toWidth)
p.$button.style.width = toWidth + "px";
continue;
}
var curLeft = p.$button.offsetLeft;
var toLeft = leftPadding + ((i - offset) * tabWidth)
+ (!p.localName ? plusMargin : 0);
if (toWidth || toLeft != curLeft) {
var tween = {
node: p.$button,
duration: tab === p ? 0.20 : 0.15,
timingFunction: tab === p
? "cubic-bezier(.30, .08, 0, 1)"
: "linear"
};
if (includeTab || tab !== p)
tween.left = toLeft + "px";
if (toWidth && p.localName)
tween.width = toWidth + "px";
tweens.push(tween);
}
}
anims.animateMultiple(tweens, function() {
finish && finish();
});
}
function mouseMoveOrder(e, toWidth, finalize) {
if (!e) e = event;
if (!started) {
if (Math.abs(startX - e.clientX) < 4
&& Math.abs(startY - e.clientY) < 4)
return;
started = true;
initMouse();
start();
}
if (isNotSnapped(e))
return setSplitMode(e);
button.style.left = (e.clientX - leftPos - offsetX) + "px";
var x = button.offsetLeft - leftPadding + (tabWidth / 2);
var idx = Math.floor(x / tabWidth);
showOrderPosition(idx, toWidth, finalize);
}
function mouseUpOrder(e) {
apf.removeListener(document, "mousemove", mouseMoveOrder);
apf.removeListener(document, "mouseup", mouseUpOrder);
if (!started)
return finish();
button.style.left = (e.clientX - leftPos - offsetX) + "px";
var x = button.offsetLeft - leftPadding + (tabWidth / 2);
var idx = Math.floor(x / tabWidth);
// Show final order
var orderTab = showOrderPosition(idx, null, true, finish);
// Activate tab
plugin.activate();
// Remove pane if empty
if (originalTab && canTabBeRemoved(originalTab.cloud9pane)
&& settings.getBool("user/tabs/@autoclosepanes"))
originalTab.cloud9pane.unload();
}
function showSplitPosition(e) {
var el = document.elementFromPoint(e.clientX, e.clientY);
var aml = apf.findHost(el);
while (aml && aml.localName != "tab")
aml = aml.parentNode;
// If aml is not the pane we seek, lets abort
if (!aml) {
divSplit.style.display = "none";
splitTab = null;
splitDirection = null;
return;
}
var tab = (aml.getPage() || {}).cloud9tab;
var dark = !tab || tab.classList.names.indexOf("dark") > -1;
divSplit.className = "split-area" + (dark ? " dark" : "");
// Find the rotated quarter that we're in
var rect = aml.$ext.getBoundingClientRect();
var left = (e.clientX - rect.left) / rect.width;
var right = 1 - left;
var top = (e.clientY - rect.top) / rect.height;
var bottom = 1 - top;
// Check whether we're going to dock
if (!isNotSnapped(e, aml.$buttons)) {
setOrderMode(aml, e);
return false;
}
// Cannot split pane that would be removed later
if (aml.getPages().length === 0) { // && aml == originalTab
divSplit.style.display = "none";
splitTab = null;
splitDirection = null;
return;
}
splitTab = aml;
// Anchor to closes side
var min = Math.min(left, top, right, bottom);
// Get buttons height
var bHeight = pane.$buttons.offsetHeight - 1;
// Left
if (min == left) {
divSplit.style.left = rect.left + "px";
divSplit.style.top = (bHeight + rect.top) + "px";
divSplit.style.width = (rect.width / 2) + "px";
divSplit.style.height = (rect.height - bHeight) + "px";
splitDirection = "w";
}
// Right
else if (min == right) {
divSplit.style.left = rect.left + (rect.width / 2) + "px";
divSplit.style.top = (bHeight + rect.top) + "px";
divSplit.style.width = (rect.width / 2) + "px";
divSplit.style.height = (rect.height - bHeight) + "px";
splitDirection = "e";
}
// Top
else if (min == top) {
divSplit.style.left = rect.left + "px";
divSplit.style.top = (bHeight + rect.top) + "px";
divSplit.style.width = rect.width + "px";
divSplit.style.height = ((rect.height / 2) - bHeight) + "px";
splitDirection = "n";
}
// Bottom
else if (min == bottom) {
divSplit.style.left = rect.left + "px";
divSplit.style.top = (rect.top + (rect.height / 2)) + "px";
divSplit.style.width = rect.width + "px";
divSplit.style.height = (rect.height / 2) + "px";
splitDirection = "s";
}
divSplit.style.cursor = splitDirection + "-resize";
divSplit.style.display = "block";
}
function mouseMoveSplit(e) {
if (!started) {
if (Math.abs(startX - e.clientX) < 4
&& Math.abs(startY - e.clientY) < 4)
return;
started = true;
initMouse();
start();
}
button.style.left = (e.clientX - offsetX) + "px";
button.style.top = (e.clientY - offsetY) + "px";
return showSplitPosition(e);
}
// var waiting;
// function schedule(e){
// if (waiting) {
// waiting = e;
// return;
// }
// waiting = e;
// setTimeout(function(){
// showSplitPosition(waiting);
// waiting = false;
// }, 1000);
// }
function mouseUpSplit(e) {
button.style.left = (e.clientX - offsetX) + "px";
button.style.top = (e.clientY - offsetY) + "px";
apf.removeListener(document, "mousemove", mouseMoveSplit);
apf.removeListener(document, "mouseup", mouseUpSplit);
showSplitPosition(e);
if (splitTab) {
splitTab = splitTab.cloud9pane;
var newTab;
if (splitDirection == "n")
newTab = splitTab.vsplit();
else if (splitDirection == "s")
newTab = splitTab.vsplit(true);
else if (splitDirection == "w")
newTab = splitTab.hsplit();
else if (splitDirection == "e")
newTab = splitTab.hsplit(true);
var oldTab = pane;
plugin.attachTo(newTab, null, true);
pane = newTab.aml;
if (oldTab && canTabBeRemoved(oldTab.cloud9pane)) {
oldTab.cloud9pane.unload();
originalTab = null;
}
}
else {
tab.parentNode.$buttons.insertBefore(button,
originalPosition);
if (originalTab == tab.parentNode) {
var idx = tab.parentNode.childNodes.indexOf(tab.nextSibling);
if (idx == -1)
tab.parentNode.childNodes.push(tab);
else
tab.parentNode.childNodes.splice(idx, 0, tab);
}
}
// Remove pane if empty
if (originalTab && originalTab != tab.parentNode
&& canTabBeRemoved(originalTab.cloud9pane))
originalTab.cloud9pane.unload();
finish();
}
}
/***** Methods *****/
/***** Lifecycle *****/
handle.on("load", function() {
load();
});
handle.on("enable", function() {
});
handle.on("disable", function() {
});
handle.on("unload", function() {
loaded = false;
});
/***** Register and define API *****/
/**
**/
handle.freezePublicAPI({
get plusMargin() { return plusMargin; },
set plusMargin(v) { plusMargin = v; }
});
register(null, {
tabinteraction: handle
});
}
});