kopia lustrzana https://github.com/c9/core
600 wiersze
22 KiB
JavaScript
600 wiersze
22 KiB
JavaScript
define(function(require, module, exports) {
|
|
main.consumes = [
|
|
"Plugin", "tabManager", "menus", "settings", "layout", "ui",
|
|
"commands", "anims"
|
|
];
|
|
main.provides = ["console"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var Plugin = imports.Plugin;
|
|
var ui = imports.ui;
|
|
var tabs = imports.tabManager;
|
|
var settings = imports.settings;
|
|
var menus = imports.menus;
|
|
var anims = imports.anims;
|
|
var commands = imports.commands;
|
|
var layout = imports.layout;
|
|
|
|
var cssString = require("text!./style.css");
|
|
var markup = require("text!./console.xml");
|
|
|
|
/***** Initialization *****/
|
|
|
|
var plugin = new Plugin("Ajax.org", main.consumes);
|
|
var emit = plugin.getEmitter();
|
|
|
|
var onFinishTimer, consoleRow, animating;
|
|
var container, changed, maximized, maxHeight, lastZIndex, height;
|
|
|
|
var hidden = true;
|
|
var minHeight = 60;
|
|
var collapsedHeight = 0;
|
|
|
|
var loaded = false;
|
|
function load(){
|
|
if (loaded) return false;
|
|
loaded = true;
|
|
|
|
// Commands
|
|
commands.addCommand({
|
|
name: "toggleconsole",
|
|
group: "Panels",
|
|
bindKey: {mac: "Ctrl-Esc", win: "F6"},
|
|
exec: function(editor, args) {
|
|
var el;
|
|
if (hidden || args.show) {
|
|
show();
|
|
el = container;
|
|
} else {
|
|
hide();
|
|
el = tabs.container;
|
|
}
|
|
var pane = tabs.findPane(container.$activePaneName);
|
|
var tab = pane && pane.activeTab || tabs.getTabs(el)[0];
|
|
tabs.focusTab(tab);
|
|
}
|
|
}, plugin);
|
|
|
|
// Menus
|
|
menus.addItemByPath("View/Console", new apf.item({
|
|
type: "check",
|
|
command: "toggleconsole",
|
|
checked: "state/console/@expanded"
|
|
}), 700, plugin);
|
|
|
|
// Settings
|
|
settings.on("read", function(e) {
|
|
// Defaults
|
|
settings.setDefaults("state/console", [
|
|
["expanded", "true"],
|
|
["maximized", "false"],
|
|
["height", "153"]
|
|
]);
|
|
|
|
// Height
|
|
height = Math.max(minHeight, Math.min(layout.maxConsoleHeight,
|
|
settings.getNumber("state/console/@height") || 0));
|
|
|
|
setTimeout(function() {
|
|
// Expanded
|
|
if (settings.getBool("state/console/@expanded"))
|
|
show(true);
|
|
|
|
// Maximized
|
|
if (settings.getBool("state/console/@maximized"))
|
|
maximizeConsoleHeight();
|
|
}, 0);
|
|
|
|
changed = false;
|
|
}, plugin);
|
|
|
|
settings.on("write", function(e) {
|
|
// if (!changed)
|
|
// return;
|
|
if (!container) return;
|
|
|
|
if (drawn) {
|
|
var state = getState(true);
|
|
settings.setJson("state/console", state);
|
|
}
|
|
}, plugin);
|
|
|
|
tabs.on("focus", function(e) {
|
|
if (drawn && ui.isChildOf(container, e.tab.aml))
|
|
show();
|
|
});
|
|
|
|
if (apf.isGecko) {
|
|
ui.setStyleRule(".console .btnsesssioncontainer .inside",
|
|
"padding-right", "30px");
|
|
}
|
|
|
|
// @todo fix Focus Handling in pane
|
|
}
|
|
|
|
var drawn = false;
|
|
function draw(){
|
|
if (drawn) return;
|
|
drawn = true;
|
|
|
|
ui.insertCss(cssString, options.staticPrefix, plugin);
|
|
|
|
consoleRow = layout.findParent(plugin);
|
|
container = consoleRow.appendChild(new ui.bar({
|
|
zindex: 99,
|
|
height: height,
|
|
// minheight : 60,
|
|
"class" : "console codeditorHolder"
|
|
}));
|
|
|
|
plugin.addElement(container);
|
|
tabs.containers.push(container);
|
|
|
|
if (settings.getBool("user/tabs/@asterisk"))
|
|
ui.setStyleClass(container, "asterisk");
|
|
|
|
ui.insertMarkup(container, markup, plugin);
|
|
|
|
// Track splitter and update state
|
|
var splitter = consoleRow.$handle;
|
|
splitter && splitter.on("dragdrop", function(e) {
|
|
height = Math.max(minHeight, container.height);
|
|
if (height)
|
|
settings.set("state/console/@height", height);
|
|
emit("resize");
|
|
});
|
|
|
|
plugin.getElement("btnClose").onclick = function(){
|
|
hide();
|
|
this.blur();
|
|
};
|
|
plugin.getElement("btnConsoleMax").onclick = function(){
|
|
if (this.value)
|
|
maximizeConsoleHeight();
|
|
else
|
|
restoreConsoleHeight();
|
|
};
|
|
|
|
// Create new tabs
|
|
var state = settings.getJson("state/console");
|
|
if (!state) {
|
|
state = options.defaultState || {
|
|
type: "pane",
|
|
nodes: [
|
|
{
|
|
type: "tab",
|
|
editorType: "terminal",
|
|
active: "true"
|
|
},
|
|
{
|
|
type: "tab",
|
|
editorType: "immediate",
|
|
document: {
|
|
title: "Immediate"
|
|
}
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
if (options.testing != 2) {
|
|
setState(state, true, function(){});
|
|
emit.sticky("ready");
|
|
}
|
|
|
|
tabs.on("paneCreate", function(e) {
|
|
if (hidden && container && ui.isChildOf(container, e.pane.aml)) {
|
|
e.pane._visible = false;
|
|
}
|
|
});
|
|
|
|
emit("draw");
|
|
}
|
|
|
|
/***** Methods *****/
|
|
|
|
function getState(filter) {
|
|
var state = tabs.getState(container.lastChild, filter);
|
|
var tab = tabs.focus && tabs.findTab(state.focus);
|
|
if (!tab || !ui.isChildOf(container, tab.pane.aml))
|
|
delete state.focus;
|
|
return state;
|
|
}
|
|
|
|
function setState(state, init, callback) {
|
|
state.container = container;
|
|
return tabs.setState(state, init, callback);
|
|
}
|
|
|
|
function getTabs(){
|
|
return tabs.getTabs(container);
|
|
}
|
|
|
|
function getPanes(){
|
|
return tabs.getPanes(container);
|
|
}
|
|
|
|
function clear(){
|
|
var tabNodes = tabs.getPanes(container);
|
|
|
|
for (var i = tabNodes.length - 1; i >= 0; i--) {
|
|
var pane = tabNodes[i], nodes = pane.getTabs();
|
|
if (!ui.isChildOf(container, pane.aml))
|
|
continue;
|
|
|
|
for (var j = nodes.length - 1; j >= 0; j--) {
|
|
var tab = nodes[j];
|
|
tab.unload();
|
|
}
|
|
pane.unload();
|
|
}
|
|
}
|
|
|
|
function maximizeConsoleHeight(){
|
|
if (maximized)
|
|
return;
|
|
maximized = true;
|
|
|
|
apf.document.documentElement.appendChild(container);
|
|
var top = layout.getElement("root").getTop() + 1;
|
|
container.setAttribute('anchors', top + ' 0 0 0');
|
|
lastZIndex = container.$ext.style.zIndex;
|
|
container.removeAttribute('height');
|
|
container.$ext.style.maxHeight = "10000px";
|
|
container.$ext.style.zIndex = 100000;
|
|
|
|
settings.set("state/console/@maximized", true);
|
|
plugin.getElement("btnConsoleMax").setValue(true);
|
|
|
|
setTimeout(function(){
|
|
getPanes().forEach(function(pane) {
|
|
var tab = pane.activeTab;
|
|
if (tab)
|
|
tab.editor.resize();
|
|
});
|
|
});
|
|
|
|
emit("resize");
|
|
}
|
|
|
|
function restoreConsoleHeight(){
|
|
if (!maximized)
|
|
return;
|
|
maximized = false;
|
|
|
|
layout.findParent(plugin).appendChild(container);
|
|
container.removeAttribute('anchors');
|
|
maxHeight = window.innerHeight - 70;
|
|
// container.$ext.style.maxHeight = maxHeight + "px";
|
|
|
|
container.setAttribute('height', maxHeight && height > maxHeight ? maxHeight : height);
|
|
container.$ext.style.zIndex = lastZIndex;
|
|
|
|
settings.set("state/console/@maximized", false);
|
|
plugin.getElement("btnConsoleMax").setValue(false);
|
|
|
|
getPanes().forEach(function(pane) {
|
|
var tab = pane.activeTab;
|
|
if (tab)
|
|
tab.editor.resize();
|
|
});
|
|
|
|
emit("resize");
|
|
}
|
|
|
|
function hide(immediate) { show(immediate, true); }
|
|
|
|
function show(immediate, shouldHide) {
|
|
if (!shouldHide)
|
|
draw();
|
|
|
|
if (hidden == shouldHide || animating)
|
|
return;
|
|
|
|
hidden = shouldHide;
|
|
animating = true;
|
|
maxHeight = window.innerHeight - 70;
|
|
|
|
getPanes().forEach(function(pane) {
|
|
pane._visible = !shouldHide;
|
|
});
|
|
|
|
if (!shouldHide && !tabs.focussedTab) {
|
|
getPanes().some(function(pane) {
|
|
if (pane.getTab()) {
|
|
tabs.focusTab(pane.getTab());
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
var finish = function() {
|
|
if (onFinishTimer)
|
|
clearTimeout(onFinishTimer);
|
|
|
|
onFinishTimer = setTimeout(function(){
|
|
if (shouldHide) {
|
|
container.hide();
|
|
|
|
// If the focussed tab is in the console, make the first
|
|
// tab we can find inside the tabs the focussed tab.
|
|
if (tabs.focussedTab
|
|
&& getPanes().indexOf(tabs.focussedTab.pane) > -1) {
|
|
tabs.getPanes(tabs.container).every(function(pane) {
|
|
var tab = pane.getTab();
|
|
if (!tab) {
|
|
tabs.focusTab(); // blur
|
|
return true;
|
|
}
|
|
else {
|
|
tabs.focusTab(tab);
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
else {
|
|
container.$ext.style.minHeight = minHeight + "px";
|
|
container.minheight = minHeight;
|
|
|
|
maxHeight = window.innerHeight - 70;
|
|
// container.$ext.style.maxHeight = maxHeight + "px";
|
|
}
|
|
|
|
container.height = height + 1;
|
|
container.setAttribute("height", height);
|
|
container.$ext.style[apf.CSSPREFIX + "TransitionDuration"] = "";
|
|
|
|
animating = false;
|
|
|
|
settings.set("state/console/@expanded", !shouldHide);
|
|
|
|
apf.layout.forceResize();
|
|
emit("resize");
|
|
}, 100);
|
|
};
|
|
|
|
var toHeight;
|
|
var animOn = settings.getBool("user/general/@animateui");
|
|
if (!shouldHide) {
|
|
toHeight = Math.max(minHeight, Math.min(maxHeight, height));
|
|
|
|
container.$ext.style.minHeight = 0;
|
|
container.setHeight(collapsedHeight);
|
|
//container.$ext.style.height = collapsedHeight + "px";
|
|
|
|
container.show();
|
|
|
|
anims.animateSplitBoxNode(container, {
|
|
height: toHeight + "px",
|
|
immediate: immediate || !animOn,
|
|
duration: 0.2,
|
|
timingFunction: "cubic-bezier(.30, .08, 0, 1)"
|
|
}, finish);
|
|
}
|
|
else {
|
|
toHeight = collapsedHeight;
|
|
|
|
if (container.parentNode != consoleRow)
|
|
restoreConsoleHeight();
|
|
|
|
container.$ext.style.minHeight = 0;
|
|
container.$ext.style.maxHeight = "10000px";
|
|
|
|
anims.animateSplitBoxNode(container, {
|
|
height: toHeight + "px",
|
|
immediate: immediate || !animOn,
|
|
duration: 0.2,
|
|
timingFunction: "ease-in-out"
|
|
}, finish);
|
|
}
|
|
}
|
|
|
|
function openFile(path, active, callback) {
|
|
if (typeof active == "function")
|
|
callback = active, active = false;
|
|
|
|
open({path: path, active: active}, callback);
|
|
}
|
|
|
|
function openEditor(type, active, callback) {
|
|
if (typeof active == "function")
|
|
callback = active, active = false;
|
|
|
|
open({editorType: type, active: active}, callback);
|
|
}
|
|
|
|
function open(options, callback) {
|
|
if (!options.pane) {
|
|
draw();
|
|
options.pane = container.getElementsByTagNameNS(apf.ns.aml, "tab")[0].cloud9pane;
|
|
}
|
|
return tabs.open(options, callback);
|
|
}
|
|
|
|
/***** Lifecycle *****/
|
|
|
|
plugin.on("load", function(){
|
|
load();
|
|
});
|
|
plugin.on("enable", function(){
|
|
|
|
});
|
|
plugin.on("disable", function(){
|
|
|
|
});
|
|
plugin.on("unload", function(){
|
|
tabs.containers.remove(container);
|
|
clear();
|
|
loaded = false;
|
|
});
|
|
|
|
/***** Register and define API *****/
|
|
|
|
/**
|
|
* A collapsable panel in the bottom of Cloud9's UI that contains panes,
|
|
* tabs and editors.
|
|
*
|
|
* To open new tabs in the console you can use:
|
|
*
|
|
* console.open({
|
|
* path : "/file.txt",
|
|
* active : true
|
|
* }, function(){});
|
|
*
|
|
* This is equivalent to using the {@link tabManager} to open new tabs:
|
|
*
|
|
* tabManager.open({
|
|
* path : "/file.txt",
|
|
* active : true,
|
|
* pane : console.getPanes()[0]
|
|
* }, function(){});
|
|
*
|
|
* @singleton
|
|
*/
|
|
plugin.freezePublicAPI({
|
|
/**
|
|
* The AMLElement for the main panes area.
|
|
* @property {AMLElement} container
|
|
* @readonly
|
|
*/
|
|
get container(){ return container; },
|
|
|
|
_events: [
|
|
/**
|
|
* Fires after the console is drawn.
|
|
* @event draw
|
|
*/
|
|
"draw",
|
|
/**
|
|
* Fires when the console is resized
|
|
* @event resize
|
|
**/
|
|
"resize",
|
|
/**
|
|
* Fires when the console is ready for adding new tabs
|
|
*/
|
|
"ready"
|
|
],
|
|
|
|
/**
|
|
* Returns an array containing all the tabs in the console.
|
|
*/
|
|
getTabs: getTabs,
|
|
|
|
/**
|
|
* Returns an array containing all the panes in the console.
|
|
*/
|
|
getPanes: getPanes,
|
|
|
|
/**
|
|
* Retrieves the state of all panes, tabs and documents
|
|
* as a single serializable object.
|
|
* @returns {Object}
|
|
*/
|
|
getState: getState,
|
|
|
|
/**
|
|
* Loads the state of all panes panes, tabs anddocuments from a
|
|
* simple object.
|
|
* @param {Object} state The state describing the pane layout.
|
|
* @param {Boolean} [init] When set to true, optimizes the state
|
|
* loading for initialization of Cloud9.
|
|
* @param {Function} callback Called when the state loading is completed.
|
|
*/
|
|
setState: setState,
|
|
|
|
/**
|
|
* Removes all panes, except one, and destroys all tabs, documents
|
|
* and editors.
|
|
*
|
|
* @param {Boolean} [soft=false] When set to true clear
|
|
* will not unload tabs. This can be useful when loading a new
|
|
* state with exactly the same tabs. WARNING: this can lead to
|
|
* leaking tabs/documents, etc. Use with caution! getTabs() will
|
|
* continue to return all the tabs. Even though they are no longer
|
|
* attached to a pane.
|
|
*/
|
|
clear: clear,
|
|
|
|
/**
|
|
* Expands the console.
|
|
*/
|
|
show: show,
|
|
|
|
/**
|
|
* Collapses the console.
|
|
*/
|
|
hide: hide,
|
|
|
|
/**
|
|
* Opens a new tab in the console. If the tab with a specified
|
|
* path already exists, that tab is activated. If state is given
|
|
* for a document, then that state is set prior to
|
|
* loading the tab. If a path is specified the file contents is
|
|
* loaded into the document. If no editorType is specified, the
|
|
* editor is determined based on the extension of the filename.
|
|
*
|
|
* See also {@link tabManager#method-open}
|
|
*
|
|
* @param options
|
|
* @param {String} [options.path] The path of the file to open
|
|
* @param {Pane} [options.pane] The pane to attach the new tab to
|
|
* @param {String} [options.editorType] The type of the editor for this tab
|
|
* @param {Boolean} [options.active=false] Whether this tab is set as active
|
|
* @param {Boolean} [options.demandExisting=false] Whether to try opening an
|
|
* existing tab even for tabs without a path.
|
|
* @param {Object} [options.document] Object describing the
|
|
* state of the document (see {@link Document#method-getState})
|
|
* @param {String} [options.value] The contents of the file
|
|
* @param {String} [options.title] The title of the tab
|
|
* @param {String} [options.tooltip] The tooltip at the button of the tab
|
|
* @param {Function} callback
|
|
* @param {Error} callback.err An error that might
|
|
* occur during the load of the file contents.
|
|
* @param {Tab} callback.tab The created tab.
|
|
* @param {Function} callback.done Call this function
|
|
* when done retrieving the value. This is only relevant if
|
|
* -1 is passed to `value`. You are responsible for settings the
|
|
* document value yourself, like so: `tab.document.value = "value";`
|
|
* @returns {Tab} The created tab.
|
|
* @fires open
|
|
* @fires beforeOpen
|
|
*/
|
|
open: open,
|
|
|
|
/**
|
|
* Opens a new pane tab in the console with the default editor and loads the file
|
|
* contents into the document. This is a convenience method. For
|
|
* the full method description see {@link tabManager#method-open}.
|
|
*
|
|
* @param {String} path The path of the file to open.
|
|
* @param {Boolean} [active] When set to true the new tab will become active in it's pane.
|
|
* @param {Function} callback Called when the file contents is loaded.
|
|
* @param {Error} callback.err An error that might
|
|
* occur during the load of the file contents.
|
|
* @param {Tab} callback.tab The created tab.
|
|
* @returns {Tab} The created tab.
|
|
*/
|
|
openFile: openFile,
|
|
|
|
/**
|
|
* Opens a new tab in the console with a specific editor
|
|
* @param {String} editorType The type of the editor for this tab.
|
|
* @param {Boolean} [active] When set to true the new tab will become active in it's pane.
|
|
* @param {Function} callback Called when the editor is loaded.
|
|
* @param {Error} callback.err An error that might
|
|
* occur during the load of the editor.
|
|
* @param {Tab} callback.tab The created tab.
|
|
* @returns {Tab} The created tab.
|
|
*/
|
|
openEditor: openEditor
|
|
});
|
|
|
|
register(null, {
|
|
console: plugin
|
|
});
|
|
}
|
|
});
|