/*global requirejs*/ define(function(require, exports, module) { main.consumes = [ "PreferencePanel", "settings", "ui", "util", "Form", "ext", "c9", "dialog.alert", "dialog.confirm", "layout", "proc", "menus", "commands", "dialog.error", "dialog.info", "tree.favorites", "fs", "tree", "plugin.debug", "preferences.experimental" ]; main.provides = ["plugin.manager"]; return main; /* - Show Packages to be updated - Open Plugin Store - Open Cloud9 in Debug Mode - List all installed packages - Filter - Core packages - Name - Version - Description - Load Time - Plugin profile - Pre-installed - * - Custom packages - * - Actions: - Uninstall - Report Issue - Open README - Open in Cloud9 DataProvider.variableHeightRowMixin.call(this) in datagrid constructor and set node.height harutyun [1:11 PM] or add a custom getItemHeight function like https://github.com/c9/newclient/blob/master/node_modules/ace_tree/demo/demo.js#L63 (edited) */ function main(options, imports, register) { var PreferencePanel = imports.PreferencePanel; var settings = imports.settings; var layout = imports.layout; var ui = imports.ui; var c9 = imports.c9; var menus = imports.menus; var fs = imports.fs; var commands = imports.commands; var ext = imports.ext; var tree = imports.tree; var proc = imports.proc; var util = imports.util; var qs = require("querystring"); var alert = imports["dialog.alert"].show; var confirm = imports["dialog.confirm"].show; var showError = imports["dialog.error"].show; var showInfo = imports["dialog.info"].show; var favs = imports["tree.favorites"]; var pluginDebug = imports["plugin.debug"]; var experimental = imports["preferences.experimental"]; var search = require("../c9.ide.navigate/search"); var Tree = require("ace_tree/tree"); var TreeData = require("./managerdp"); var join = require("path").join; var basename = require("path").basename; var dirname = require("path").dirname; var staticPrefix = options.staticPrefix; var architect; var CORE = { "c9.core":1,"c9.fs":1,"c9.ide.preferences":1,"c9.ide.panels":1, "c9.ide.plugins":1,"c9.ide.login":1,"c9.vfs.client":1, "c9.ide.console":1,"c9.ide.editors":1,"c9.ide.dialog.common":1, "c9.ide.dialog.file":1,"c9.ide.dialog.login":1,"c9.ide.errorhandler":1, "c9.ide.help":1,"c9.ide.keys":1,"c9.ide.restore":1,"c9.ide.watcher":1, "c9.ide.tree":1, "c9.ide.info":1, "c9.ide.browsersupport":1, "c9.ide.layout.classic":1, "c9.ide.terminal":1, "c9.ide.ace":1, "c9.ide.clipboard":1, "c9.nodeapi":1 }; var GROUPS = { "custom": "Installed Plugins", "pre": "Pre-installed plugins", "core": "Core Plugins", "runtime": "Plugins created runtime" }; var TEMPLATES = { "plugin.simple": "Empty Plugin", "plugin.default": "Full Plugin", "plugin.installer": "Installer Plugin", "plugin.bundle": "Cloud9 Bundle" }; // @TODO add sorting /***** Initialization *****/ var ENABLED = c9.location.indexOf("debug=2") > -1 || experimental.addExperiment( "plugin-manager", options.devel, "SDK/Plugin Manager" ); var plugin = new PreferencePanel("Ajax.org", main.consumes, { caption: "Plugin Manager", className: "plugins", form: false, noscroll: true, index: 200, visible: ENABLED, }); // var emit = plugin.getEmitter(); var model, datagrid, filterbox; var btnUninstall, btnReport, btnReadme, btnCloud9, btnReload; var loaded = false; function load() { if (loaded) return false; loaded = true; if (!ENABLED) return; // @TODO enable/disable plugins -> move to ext // settings.on("read", function(e) { // updateCommandsFromSettings(); // }, plugin); // commands.on("update", function(){ // changed = true; // updateCommandsFromSettings(); // }, plugin); if (options.devel) { commands.addCommand({ name: "reloadLastPlugin", bindKey: {mac: "F4", win: "F4"}, hint: "reload plugin last reloaded in plugin manager", exec: function() { var name = getLastReloaded(); if (!name) return commands.exec("reloadPlugin", null, { panel: plugin }); reload(name); } }, plugin); commands.addCommand({ name: "reloadPlugin", group: "Plugins", exec: function(){ commands.exec("openpreferences", null, { panel: plugin }); } }, plugin); menus.addItemByPath("Tools/~", new ui.divider(), 100000, plugin); menus.addItemByPath("Tools/Developer", null, 100100, plugin); menus.addItemByPath("Tools/Developer/Reload Built-in Plugin...", new ui.item({ command: "reloadPlugin" }), 1100, plugin); menus.addItemByPath("Tools/Developer/Reload Last Plugin", new ui.item({ command: "reloadLastPlugin", isAvailable: getLastReloaded }), 1200, plugin); } menus.addItemByPath("File/New Plugin", null, 210, plugin); Object.keys(TEMPLATES).forEach(function(name){ menus.addItemByPath("File/New Plugin/" + TEMPLATES[name], new ui.item({ onclick: function(){ createNewPlugin(name); } }), 210, plugin); }); ext.on("register", function(){ setTimeout(reloadModel); }); } var drawn; function draw(e) { if (drawn) return; drawn = true; model = new TreeData(); model.emptyMessage = "No plugins found"; model.columns = [{ caption: "Name", value: "name", // getText: function(p){ // return p.name + " (" + p.items.length + ")"; // }, width: "250", type: "tree" }, { caption: "Version", // value: "version", getText: function(p){ return p.version || (p.isPackage ? p.items.length && p.items[0].version || "" : ""); }, width: "100" }, { caption: "Startup Time", // value: "time", width: "100", getText: function(p){ if (p.time !== undefined) return (p.time || 0) + "ms"; var total = 0; if (p.isPackage || p.name == "runtime") { p.items.forEach(function(item){ total += item.time || 0 }); } else { p.items.forEach(function(p){ p.items && p.items.forEach(function(item){ total += item.time || 0 }); }); } return (p.time = total) + "ms"; } }, { caption: "Enabled", value: "enabled", width: "100" // }, { // caption: "Package", // value: "package", // @todo make a link // width: "100%" }, { caption: "Developer", // value: "developer", getText: function(p){ return p.developer || (p.isPackage ? p.items.length && p.items[0].developer || "" : ""); }, width: "150" }]; layout.on("eachTheme", function(e){ var height = parseInt(ui.getStyleRule(".bar-preferences .blackdg .tree-row", "height"), 10) || 24; model.rowHeightInner = height; model.rowHeight = height; if (e.changed) datagrid.resize(true); }); reloadModel(); // type: "custom", // title: "Introduction", // position: 1, // node: intro = new ui.bar({ // height: 149, // "class" : "intro", // style: "padding:12px;position:relative;" // }) // intro.$int.innerHTML = // '

Keybindings

Change these settings to configure ' // + 'how Cloud9 responds to your keyboard commands.

' // + '

You can also manually edit your keymap file.

' // + '

Hint: Double click on the keystroke cell in the table below to change the keybinding.

'; // intro.$int.querySelector("a").onclick = function(){ editUserKeys(); }; var hbox = new ui.hbox({ htmlNode: e.html, padding: 5, edge: "10 10 0 10", childNodes: [ filterbox = new apf.codebox({ realtime: true, skin: "codebox", "class": "dark", clearbutton: true, focusselect: true, height: 27, width: 250, singleline: true, "initial-message": "Search installed plugins" }), new ui.filler({}), btnUninstall = new ui.button({ skin: "btn-default-css3", caption: "Uninstall", class: "btn-red", onclick: function(){ var item = datagrid.selection.getCursor(); if (item.isPackage) uninstall(item.name, function(){}); else if (item.enabled == "true") disable(item.name, function(){}); else enable(item.name, function(){}); } }), btnReport = new ui.button({ skin: "btn-default-css3", caption: "Report Issue" }), btnReadme = new ui.button({ skin: "btn-default-css3", caption: "Open README" }), btnCloud9 = new ui.button({ skin: "btn-default-css3", caption: "Open in Cloud9" }), btnReload = new ui.button({ skin: "btn-default-css3", caption: "Reload", onclick: function(){ var item = datagrid.selection.getCursor(); if (item.enabled && item.name) reload(item.name); } }) ] }); var div = e.html.appendChild(document.createElement("div")); div.style.position = "absolute"; div.style.left = "10px"; div.style.right = "10px"; div.style.bottom = "10px"; div.style.top = "50px"; datagrid = new Tree(div); datagrid.setTheme({ cssClass: "blackdg" }); datagrid.setDataProvider(model); layout.on("resize", function(){ datagrid.resize() }, plugin); function setTheme(e) { filterbox.setAttribute("class", e.theme.indexOf("dark") > -1 ? "dark" : ""); } layout.on("themeChange", setTheme); setTheme({ theme: settings.get("user/general/@skin") }); filterbox.ace.commands.addCommands([ { bindKey: "Enter", exec: function(){ } }, { bindKey: "Esc", exec: function(ace){ ace.setValue(""); } } ]); filterbox.ace.on("input", function(e) { applyFilter(); }); // when tab is restored datagrids size might be wrong // todo: remove this when apf bug is fixed datagrid.once("mousemove", function() { datagrid.resize(true); }); datagrid.on("changeSelection", function(e){ var item = datagrid.selection.getCursor(); if (item.isGroup) { btnUninstall.disable(); btnReport.disable(); btnReadme.disable(); btnCloud9.disable(); btnReload.disable(); } else { if (item.isPackage) { btnUninstall.setCaption("Uninstall"); btnUninstall.setAttribute("class", "btn-red"); } else { btnUninstall.setCaption(item.enabled == "true" ? "Disable" : "Enable"); btnUninstall.setAttribute("class", item.enabled == "true" ? "btn-red" : "btn-green"); } if (CORE[item.name] || item.parent.parent && item.parent.parent.isType == "core") { btnUninstall.disable(); } else { btnUninstall.enable(); } if (item.isPackage || CORE[item.name] || item.parent.parent && item.parent.parent.isType == "core") { btnReload.disable(); } else { btnReload.enable(); } btnReport.enable(); btnReadme.enable(); btnCloud9.enable(); } }); } /***** Methods *****/ function reloadModel() { if (!model) return; var groups = {}; var packages = {}; var root = []; ["custom", "pre", "core", "runtime"].forEach(function(name){ root.push(groups[name] = { items: [], isOpen: name != "runtime", className: "group", isGroup: true, isType: name, noSelect: true, name: GROUPS[name] }); }); var lut = ext.named; ext.plugins.forEach(function(plugin) { var info = architect.pluginToPackage[plugin.name]; var packageName = info && info.package || "runtime"; var groupName; if (CORE[packageName]) groupName = "core"; else if (info && info.isAdditionalMode) groupName = "custom"; else groupName = "pre"; var package; if (packageName == "runtime") { package = groups.runtime; } else { package = packages[packageName]; if (!package) groups[groupName].items.push(package = packages[packageName] = { items: [], isPackage: true, className: "package", parent: groups[groupName], name: packageName }); } package.items.push({ name: plugin.name, enabled: lut[plugin.name].loaded ? "true" : "false", time: plugin.time, version: info && info.version || "N/A", parent: package, package: packageName, developer: plugin.developer == "Ajax.org" ? "Cloud9" : plugin.developer }); }); model.cachedRoot = { items: root }; applyFilter(); } function applyFilter() { model.keyword = filterbox && filterbox.getValue(); if (!model.keyword) { model.reKeyword = null; model.setRoot(model.cachedRoot); // model.isOpen = function(node){ return node.isOpen; } } else { model.reKeyword = new RegExp("(" + util.escapeRegExp(model.keyword) + ")", 'i'); var root = search.treeSearch(model.cachedRoot.items, model.keyword, true); model.setRoot(root); // model.isOpen = function(node){ return true; }; } } function uninstall(name){ btnUninstall.setAttribute("caption", "..."); btnUninstall.disable(); // @TODO first disable the plugin proc.spawn("c9", { args: ["uninstall", name] }, function(err, p){ p.stdout.on("data", function(c){ }); p.stderr.on("data", function(c){ }); p.on("exit", function(code){ if (code) { return alert("Could not uninstall plugin", "Could not uninstall plugin", "Could not uninstall plugin"); } btnUninstall.setAttribute("caption", "Uninstall"); btnUninstall.enable(); }); }); } function enable(name){ try{ ext.enablePlugin(name); } catch(e){ alert("Could not disable plugin", "Got an error when disabling plugin: " + name, e.message); return false; } reloadModel(); } function disable(name, callback){ var deps = ext.getDependencies(name); var plugins = ext.named; for (var i = 0; i < deps.length; i++) { ext.getDependencies(deps[i]).forEach(function(name){ if (deps.indexOf(name) == -1) deps.push(name); }); } if (deps.length) { confirm("Found " + deps.length + " plugins that depend on this plugin.", "Would you like to disable all the plugins that depend on '" + name + "'?", "These plugins would also be disabled: " + deps.join(", "), // Yes function(){ if (deps.reverse().every(function(name){ console.log("Disabling", name); return !recurDisable(name); })) { disable(name); reloadModel(); } }, // No function(){ callback(new Error("User Cancelled")); }); } else { var e = disable(name); if (!e) reloadModel(); callback(e); } function recurDisable(name){ var deps = ext.getDependencies(name); if (deps.length) { if (!deps.every(function(name){ return !recurDisable(name); })) return false; } return disable(name); } function disable(name) { if (!plugins[name].loaded) return; try{ ext.disablePlugin(name); } catch(e){ alert("Could not disable plugin", "Got an error when disabling plugin: " + name, e.message); return e; } } } function createNewPlugin(template){ if (!template) template = "c9.ide.default"; var url = staticPrefix + "/" + join("templates", template + ".tar.gz"); if (!url.match(/^http/)) url = location.origin + url; function getPath(callback, i){ i = i || 0; var path = join("~", ".c9/plugins/", template + (i ? "." + i : "")); fs.exists(path, function(exists){ if (exists) return getPath(callback, i+1); callback(null, path); }); } function handleError(err){ showError("Could not create plugin."); console.error(err); } getPath(function(err, path){ if (err) return handleError(err); var pluginsDir = join("~", ".c9/plugins/_/"); var pluginsDirAbsolute = pluginsDir.replace(/^~/, c9.home); var tarPath = join(pluginsDir, template + ".tar.gz"); var tarPathAbsolute = tarPath.replace(/^~/, c9.home); // Download tar file with template for plugin proc.execFile("bash", { args: ["-c", [ // using mkdirp since "--create-dirs" is broken on windows "mkdir", "-p", util.escapeShell(dirname(tarPathAbsolute)), ";", "curl", "-L", util.escapeShell(url), "-o", util.escapeShell(tarPathAbsolute)].join(" ") ] }, function(err, stderr, stdout){ if (err) return handleError(err); // Untar tar file proc.execFile("bash", { args: ["-c", ["tar", "-zxvf", util.escapeShell(tarPath), "-C", util.escapeShell(pluginsDirAbsolute)].join(" ")] }, function(err, stderr, stdout){ if (err) return handleError(err); // Move template to the right folder var dirPath = join(dirname(tarPath), template); fs.rename(dirPath, path, function(err){ if (err) return handleError(err); // Remove .tar.gz fs.unlink(tarPath, function(){ // Add plugin to favorites favs.addFavorite(dirname(pluginsDir), "plugins"); // Select and expand the folder of the plugin tree.expandAndSelect(path); }); }); }); }); }); } function reload(name) { showReloadTip(name); var href = document.location.href.replace(/[?&]reload=[^&]+/, ""); href += (href.match(/\?/) ? "&" : "?") + "reload=" + name; window.history.replaceState(window.history.state, null, href); for (var plugin in architect.lut) { if (architect.lut[plugin].provides.indexOf(name) < 0) continue; pluginDebug.reloadPackage(plugin); return; } } function showReloadTip(name) { if (options.devel) { var key = commands.getHotkey("reloadLastPlugin"); if (commands.platform == "mac") key = apf.hotkeys.toMacNotation(key); if (!getLastReloaded()) { showInfo("Reloaded " + name + ". Press " + key + " to reload again.", 3000); return; } } showInfo("Reloaded " + name + ".", 1000); } function getLastReloaded() { return qs.parse(document.location.search.substr(1)).reload; } /***** Lifecycle *****/ plugin.on("load", function() { load(); }); plugin.on("draw", function(e) { draw(e); }); plugin.on("activate", function(e) { datagrid && datagrid.resize(); }); plugin.on("resize", function(e) { datagrid && datagrid.resize(); }); plugin.on("enable", function() { }); plugin.on("disable", function() { }); plugin.on("unload", function() { loaded = false; drawn = false; architect = null; model = null; datagrid = null; filterbox = null; btnUninstall = null; btnReport = null; btnReadme = null; btnCloud9 = null; btnReload = null; }); /***** Register and define API *****/ /** * **/ plugin.freezePublicAPI({ /** * */ get architect(){ throw new Error(); }, set architect(v){ architect = v; architect.on("ready-additional", function(){ reloadModel(); }); }, /** * */ createNewPlugin: createNewPlugin, /** * */ uninstall: uninstall, /** * */ enable: enable, /** * */ disable: disable, /** * */ reload: reload }); register(null, { "plugin.manager" : plugin }); } });