c9-core/plugins/c9.ide.guide/guide.js

455 wiersze
15 KiB
JavaScript

define(function(require, exports, module) {
main.consumes = [
"Plugin", "ui", "commands", "menus", "settings", "info",
"c9.analytics", "c9"
];
main.provides = ["guide"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
var ui = imports.ui;
var menus = imports.menus;
var commands = imports.commands;
var settings = imports.settings;
var c9 = imports.c9;
var info = imports.info;
var analytics = imports["c9.analytics"];
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
var emit = plugin.getEmitter();
var RIGHT = 1 << 1;
var LEFT = 1 << 2;
var BOTTOM = 1 << 3;
var TOP = 1 << 4;
var THINGY_MARGIN = 0;
var THINGY_SIZE = 10;
var POPUP_MARGIN = 17;
var thingies, popup, showing, currentPopup;
var timer, listen;
function load() {
menus.addItemByPath("Support/Show Guided Tour", new ui.item({
onclick: show,
}), 150, plugin);
}
var drawn = false;
function draw() {
if (drawn) return;
drawn = true;
// Insert CSS
ui.insertCss(require("text!./style.css"), null, plugin);
// Draw the thingies
thingies.forEach(drawThingy);
emit("draw");
}
/***** Methods *****/
function add(t) {
if (!(t instanceof Array))
t = [t];
if (!thingies)
thingies = t;
else {
t.forEach(function(i) {
thingies.push(i);
if (!drawn)
drawThingy(i);
});
}
}
function setPosition(htmlNode, pos, def, width, height, margin, isThingy, isUpdate) {
htmlNode.style.right =
htmlNode.style.left =
htmlNode.style.top =
htmlNode.style.bottom = "";
function right() { return window.innerWidth - pos.left - pos.width; }
function bottom() { return window.innerHeight - pos.top - pos.height; }
var offsetW = isThingy ? width / 2 : 0;
var offsetH = isThingy ? height / 2 : 0;
var corW = isThingy ? 0 : width;
var corH = isThingy ? 0 : height;
var maxW = window.innerWidth - corW - margin;
var maxH = window.innerHeight - corH - margin;
var where = isThingy ? def.where : def.wherePopup || def.where;
if (where & LEFT) {
if (def.attachment & RIGHT)
htmlNode.style.right = (right() + pos.width - offsetW + margin) + "px";
else
htmlNode.style.left = (pos.left - corW - offsetW - margin) + "px";
}
else if (where & RIGHT) {
if (def.attachment & RIGHT)
htmlNode.style.right = (right() + pos.width + margin - offsetW) + "px";
else
htmlNode.style.left = (pos.left + pos.width + margin - offsetW) + "px";
}
else {
if (def.attachment & RIGHT)
htmlNode.style.right = Math.max(margin, (right() + ((pos.width - width) / 2))) + "px";
else
htmlNode.style.left = Math.min(maxW, Math.max(margin, (pos.left + ((pos.width - width) / 2)))) + "px";
}
if (where & TOP) {
if (def.attachment & BOTTOM)
htmlNode.style.bottom = (bottom() + pos.height - offsetH + margin) + "px";
else
htmlNode.style.top = (pos.top - corH - offsetH - margin) + "px";
}
else if (where & BOTTOM) {
if (def.attachment & BOTTOM)
htmlNode.style.bottom = (bottom() + pos.height + margin - offsetH) + "px";
else
htmlNode.style.top = (pos.top + pos.height + margin - offsetH) + "px";
}
else {
if (def.attachment & BOTTOM)
htmlNode.style.bottom = Math.max(margin, (bottom() + ((pos.height - height) / 2))) + "px";
else
htmlNode.style.top = Math.min(maxH, Math.max(margin, (pos.top + ((pos.height - height) / 2)))) + "px";
}
if (!isThingy) updateBalloon(htmlNode, def);
}
function updateBalloon(htmlNode, def) {
var h;
htmlNode.classList.remove("balloon-right", "balloon-left",
"balloon-top", "balloon-bottom");
var where = def.wherePopup || def.where;
if (where & LEFT) htmlNode.classList.add("balloon-right"), h = 0;
else if (where & RIGHT) htmlNode.classList.add("balloon-left"), h = 0;
if (where & BOTTOM) htmlNode.classList.add("balloon-top"), h = 1;
else if (where & TOP) htmlNode.classList.add("balloon-bottom"), h = 1;
var balloon = popup.firstElementChild;
balloon.style.left =
balloon.style.top = "";
if (!(where & BOTTOM))
balloon.classList.add("white");
else
balloon.classList.remove("white");
if (htmlNode.className.match(/balloon/g).length == 2)
return;
if (h == 0) {
balloon.style.top = (def.thingy.offsetTop - htmlNode.offsetTop - THINGY_SIZE - 1) + "px";
}
else {
balloon.style.left = (def.thingy.offsetLeft - htmlNode.offsetLeft - THINGY_SIZE - 1) + "px";
}
}
function drawThingy(def) {
var el = typeof def.query === "function"
? def.query()
: document.querySelector(def.query);
if (!el) return;
var thingy = document.body.appendChild(document.createElement("div"));
thingy.className = "thingy";
var pos = el.getBoundingClientRect();
setPosition(thingy, pos, def, THINGY_SIZE, THINGY_SIZE, THINGY_MARGIN, true);
thingy.onclick = function() { togglePopup(def); };
def.body = def.body.replace(/\$\{key:([a-zA-Z]+)\}/g, function(match, name) {
return commands.getPrettyHotkey(name);
});
def.el = el;
def.thingy = thingy;
}
function togglePopup(def) {
if (popup && currentPopup === def) {
hidePopup(true);
return;
}
showPopup(def);
}
function showPopup(def) {
analytics.track("Showed Guide Popup", { title: def.name });
if (!popup) {
popup = document.body.appendChild(document.createElement("div"));
popup.className = "thingy-popup";
// popup.title = def.title;
popup.innerHTML = "<div class='balloon'></div>"
+ "<span class='close'></span>"
+ "<span class='title'></span>"
+ "<p></p>"
+ "<div class='tourButtons'>"
+ "<a href='javascript:void(0)' class='skip' title='Reopen the tour via the Help menu'>End Tour</a>"
+ "</div>";
var buttons = popup.querySelector(".tourButtons");
popup.querySelector(".skip").onclick = function() { hide(); };
var btnDone = new ui.button({
htmlNode: buttons,
skin: "btn-default-css3",
style: "display:inline-block;",
"class": "btn-green",
onclick: function() {
var idx = thingies.indexOf(currentPopup);
while (thingies[++idx] && !thingies[idx].thingy) {}
if (!thingies[idx]) {
idx = -1;
while (thingies[++idx] && !thingies[idx].thingy) {}
}
if (!thingies[idx] || thingies[idx].thingy.style.display == "none") {
return hidePopup();
}
showPopup(thingies[idx]);
}
});
btnDone.oCaption.parentNode.innerHTML = "Next <span class='arrow'>&#x21E5;</span>";
popup.querySelector(".close").onclick = function() {
hidePopup();
};
}
else {
hidePopup();
}
popup.style.transition = "";
popup.classList.remove("green", "blue", "orange");
popup.classList.add(def.color);
if (def.width)
popup.style.width = def.width + "px";
popup.querySelector("span.title").innerHTML = def.title;
popup.querySelector("p").innerHTML = def.body;
if (!def.thingy) return;
var thingy = def.thingy;
var pos = thingy.getBoundingClientRect();
popup.style.display = "block";
thingy.classList.add("active");
setPosition(popup, pos, def, popup.offsetWidth, popup.offsetHeight, POPUP_MARGIN);
if (def.onshow)
def.onshow();
currentPopup = def;
}
function hidePopup(onlyCurrent) {
if (currentPopup) {
if (!onlyCurrent) {
currentPopup.thingy.style.display = "none";
currentPopup.shown = true;
}
currentPopup.thingy.classList.remove("active");
if (!onlyCurrent)
emit("close", currentPopup);
currentPopup = null;
}
popup.style.display = "none";
}
function enable() {
timer = setInterval(check, 1000);
document.body.addEventListener("mouseup", delayCheck);
}
function disable() {
clearInterval(timer);
document.body.removeEventListener("mouseup", delayCheck);
}
function check() {
thingies.forEach(function(def) {
if (def.shown) return;
if (!def.thingy) {
drawThingy(def);
if (def.thingy && !currentPopup)
showPopup(def);
}
else if (def.el && !def.el.offsetHeight && !def.el.offsetWidth) {
if (currentPopup == def)
hidePopup();
def.thingy.parentNode.removeChild(def.thingy);
def.thingy = def.el = null;
}
else {
// [NO] This was breaking IDE in the cs50 workspace.
if (!def || !def.el) return;
var pos = def.el.getBoundingClientRect();
setPosition(def.thingy, pos, def,
THINGY_SIZE, THINGY_SIZE, THINGY_MARGIN, true);
if (currentPopup == def) {
var p = def.thingy.getBoundingClientRect();
pos = {
left: parseFloat(def.thingy.style.left) || p.left,
width: p.width,
top: parseFloat(def.thingy.style.top) || p.top,
height: p.height
};
popup.style.transition = "0.5s";
setPosition(popup, pos, def, popup.offsetWidth,
popup.offsetHeight, POPUP_MARGIN, false, true);
}
}
});
}
function delayCheck() {
setTimeout(check, 500);
}
function show(list) {
if (!c9.isReady)
return c9.on("ready", function() {
setTimeout(function() { show(list); });
});
draw();
thingies.forEach(function(def) {
if (!def.thingy || list && list[def.name]) {
if (def.thingy)
def.thingy.style.display = "none";
return;
}
if (!def.el)
drawThingy(def);
else if (!def.el.offsetWidth && !def.el.offsetHeight) {
delete def.el;
return;
}
def.thingy.style.display = "block";
def.shown = false;
});
emit("show");
showing = true;
enable();
}
function hide() {
if (!drawn) return;
thingies.forEach(function(def) {
if (!def.thingy) return;
def.thingy.style.display = "none";
def.thingy.classList.remove("active");
delete def.shown;
});
hidePopup();
currentPopup = null;
emit("hide");
showing = false;
disable();
}
/***** Lifecycle *****/
plugin.on("load", function() {
load();
});
plugin.on("unload", function() {
drawn = false;
showing = false;
thingies = null;
popup = null;
showing = null;
currentPopup = null;
timer = null;
listen = null;
});
/***** Register and define API *****/
/**
* This is an example of an implementation of a plugin.
* @singleton
*/
plugin.freezePublicAPI({
/**
* @property showing whether this plugin is being shown
*/
get showing() {
return showing;
},
_events: [
/**
* @event show The plugin is shown
*/
"show",
/**
* @event hide The plugin is hidden
*/
"hide"
],
/**
* Add Bubbles
*/
add: add,
/**
* Show the plugin
*/
show: show,
/**
* Hide the plugin
*/
hide: hide,
});
register(null, {
"guide": plugin
});
}
});