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 = "
" + "" + "" + "

" + "
" + "" + "
"; 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 "; 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 }); } });