Spaces:
Running
Running
| /** | |
| * WHITEBOPHIR | |
| ********************************************************* | |
| * @licstart The following is the entire license notice for the | |
| * JavaScript code in this page. | |
| * | |
| * Copyright (C) 2013 Ophir LOJKINE | |
| * | |
| * | |
| * The JavaScript code in this page is free software: you can | |
| * redistribute it and/or modify it under the terms of the GNU | |
| * General Public License (GNU GPL) as published by the Free Software | |
| * Foundation, either version 3 of the License, or (at your option) | |
| * any later version. The code is distributed WITHOUT ANY WARRANTY; | |
| * without even the implied warranty of MERCHANTABILITY or FITNESS | |
| * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. | |
| * | |
| * As additional permission under GNU GPL version 3 section 7, you | |
| * may distribute non-source (e.g., minimized or compacted) forms of | |
| * that code without the copy of the GNU GPL normally required by | |
| * section 4, provided you include this license notice and a URL | |
| * through which recipients can access the Corresponding Source. | |
| * | |
| * @licend | |
| */ | |
| var Tools = {}; | |
| Tools.i18n = (function i18n() { | |
| var translations = JSON.parse(document.getElementById("translations").text); | |
| return { | |
| t: function translate(s) { | |
| var key = s.toLowerCase().replace(/ /g, "_"); | |
| return translations[key] || s; | |
| }, | |
| }; | |
| })(); | |
| Tools.server_config = JSON.parse(document.getElementById("configuration").text); | |
| Tools.board = document.getElementById("board"); | |
| Tools.svg = document.getElementById("canvas"); | |
| Tools.drawingArea = Tools.svg.getElementById("drawingArea"); | |
| //Initialization | |
| Tools.curTool = null; | |
| Tools.drawingEvent = true; | |
| Tools.showMarker = true; | |
| Tools.showOtherCursors = true; | |
| Tools.showMyCursor = true; | |
| Tools.isIE = /MSIE|Trident/.test(window.navigator.userAgent); | |
| Tools.socket = null; | |
| Tools.connect = function () { | |
| var self = this; | |
| // Destroy socket if one already exists | |
| if (self.socket) { | |
| self.socket.destroy(); | |
| delete self.socket; | |
| self.socket = null; | |
| } | |
| var url = new URL(window.location); | |
| var params = new URLSearchParams(url.search); | |
| var socket_params = { | |
| path: window.location.pathname.split("/boards/")[0] + "/socket.io", | |
| reconnection: true, | |
| reconnectionDelay: 100, //Make the xhr connections as fast as possible | |
| timeout: 1000 * 60 * 20, // Timeout after 20 minutes | |
| }; | |
| if (params.has("token")) { | |
| socket_params.query = "token=" + params.get("token"); | |
| } | |
| this.socket = io.connect("", socket_params); | |
| //Receive draw instructions from the server | |
| this.socket.on("broadcast", function (msg) { | |
| handleMessage(msg).finally(function afterload() { | |
| var loadingEl = document.getElementById("loadingMessage"); | |
| loadingEl.classList.add("hidden"); | |
| }); | |
| }); | |
| this.socket.on("reconnect", function onReconnection() { | |
| Tools.socket.emit("joinboard", Tools.boardName); | |
| }); | |
| }; | |
| Tools.connect(); | |
| Tools.boardName = (function () { | |
| var path = window.location.pathname.split("/"); | |
| return decodeURIComponent(path[path.length - 1]); | |
| })(); | |
| Tools.token = (function () { | |
| var url = new URL(window.location); | |
| var params = new URLSearchParams(url.search); | |
| return params.get("token"); | |
| })(); | |
| //Get the board as soon as the page is loaded | |
| Tools.socket.emit("getboard", Tools.boardName); | |
| function saveBoardNametoLocalStorage() { | |
| var boardName = Tools.boardName; | |
| if (boardName.toLowerCase() === "anonymous") return; | |
| var recentBoards, | |
| key = "recent-boards"; | |
| try { | |
| recentBoards = JSON.parse(localStorage.getItem(key)); | |
| if (!Array.isArray(recentBoards)) throw new Error("Invalid type"); | |
| } catch (e) { | |
| // On localstorage or json error, reset board list | |
| recentBoards = []; | |
| console.log("Board history loading error", e); | |
| } | |
| recentBoards = recentBoards.filter(function (name) { | |
| return name !== boardName; | |
| }); | |
| recentBoards.unshift(boardName); | |
| recentBoards = recentBoards.slice(0, 20); | |
| localStorage.setItem(key, JSON.stringify(recentBoards)); | |
| } | |
| // Refresh recent boards list on each page show | |
| window.addEventListener("pageshow", saveBoardNametoLocalStorage); | |
| Tools.HTML = { | |
| template: new Minitpl("#tools > .tool"), | |
| addShortcut: function addShortcut(key, callback) { | |
| window.addEventListener("keydown", function (e) { | |
| if (e.key === key && !e.target.matches("input[type=text], textarea")) { | |
| callback(); | |
| } | |
| }); | |
| }, | |
| addTool: function (toolName, toolIcon, toolIconHTML, toolShortcut, oneTouch) { | |
| var callback = function () { | |
| Tools.change(toolName); | |
| }; | |
| this.addShortcut(toolShortcut, function () { | |
| Tools.change(toolName); | |
| document.activeElement.blur && document.activeElement.blur(); | |
| }); | |
| return this.template.add(function (elem) { | |
| elem.addEventListener("click", callback); | |
| elem.id = "toolID-" + toolName; | |
| elem.getElementsByClassName("tool-name")[0].textContent = | |
| Tools.i18n.t(toolName); | |
| var toolIconElem = elem.getElementsByClassName("tool-icon")[0]; | |
| toolIconElem.src = toolIcon; | |
| toolIconElem.alt = toolIcon; | |
| if (oneTouch) elem.classList.add("oneTouch"); | |
| elem.title = | |
| Tools.i18n.t(toolName) + | |
| " (" + | |
| Tools.i18n.t("keyboard shortcut") + | |
| ": " + | |
| toolShortcut + | |
| ")" + | |
| (Tools.list[toolName].secondary | |
| ? " [" + Tools.i18n.t("click_to_toggle") + "]" | |
| : ""); | |
| if (Tools.list[toolName].secondary) { | |
| elem.classList.add("hasSecondary"); | |
| var secondaryIcon = elem.getElementsByClassName("secondaryIcon")[0]; | |
| secondaryIcon.src = Tools.list[toolName].secondary.icon; | |
| toolIconElem.classList.add("primaryIcon"); | |
| } | |
| }); | |
| }, | |
| changeTool: function (oldToolName, newToolName) { | |
| var oldTool = document.getElementById("toolID-" + oldToolName); | |
| var newTool = document.getElementById("toolID-" + newToolName); | |
| if (oldTool) oldTool.classList.remove("curTool"); | |
| if (newTool) newTool.classList.add("curTool"); | |
| }, | |
| toggle: function (toolName, name, icon) { | |
| var elem = document.getElementById("toolID-" + toolName); | |
| // Change secondary icon | |
| var primaryIcon = elem.getElementsByClassName("primaryIcon")[0]; | |
| var secondaryIcon = elem.getElementsByClassName("secondaryIcon")[0]; | |
| var primaryIconSrc = primaryIcon.src; | |
| var secondaryIconSrc = secondaryIcon.src; | |
| primaryIcon.src = secondaryIconSrc; | |
| secondaryIcon.src = primaryIconSrc; | |
| // Change primary icon | |
| elem.getElementsByClassName("tool-icon")[0].src = icon; | |
| elem.getElementsByClassName("tool-name")[0].textContent = | |
| Tools.i18n.t(name); | |
| }, | |
| addStylesheet: function (href) { | |
| //Adds a css stylesheet to the html or svg document | |
| var link = document.createElement("link"); | |
| link.href = href; | |
| link.rel = "stylesheet"; | |
| link.type = "text/css"; | |
| document.head.appendChild(link); | |
| }, | |
| colorPresetTemplate: new Minitpl("#colorPresetSel .colorPresetButton"), | |
| addColorButton: function (button) { | |
| var setColor = Tools.setColor.bind(Tools, button.color); | |
| if (button.key) this.addShortcut(button.key, setColor); | |
| return this.colorPresetTemplate.add(function (elem) { | |
| elem.addEventListener("click", setColor); | |
| elem.id = "color_" + button.color.replace(/^#/, ""); | |
| elem.style.backgroundColor = button.color; | |
| if (button.key) { | |
| elem.title = Tools.i18n.t("keyboard shortcut") + ": " + button.key; | |
| } | |
| }); | |
| }, | |
| }; | |
| Tools.list = {}; // An array of all known tools. {"toolName" : {toolObject}} | |
| Tools.isBlocked = function toolIsBanned(tool) { | |
| if (tool.name.includes(",")) | |
| throw new Error("Tool Names must not contain a comma"); | |
| return Tools.server_config.BLOCKED_TOOLS.includes(tool.name); | |
| }; | |
| /** | |
| * Register a new tool, without touching the User Interface | |
| */ | |
| Tools.register = function registerTool(newTool) { | |
| if (Tools.isBlocked(newTool)) return; | |
| if (newTool.name in Tools.list) { | |
| console.log( | |
| "Tools.add: The tool '" + | |
| newTool.name + | |
| "' is already" + | |
| "in the list. Updating it...", | |
| ); | |
| } | |
| //Format the new tool correctly | |
| Tools.applyHooks(Tools.toolHooks, newTool); | |
| //Add the tool to the list | |
| Tools.list[newTool.name] = newTool; | |
| // Register the change handlers | |
| if (newTool.onSizeChange) Tools.sizeChangeHandlers.push(newTool.onSizeChange); | |
| //There may be pending messages for the tool | |
| var pending = Tools.pendingMessages[newTool.name]; | |
| if (pending) { | |
| console.log("Drawing pending messages for '%s'.", newTool.name); | |
| var msg; | |
| while ((msg = pending.shift())) { | |
| //Transmit the message to the tool (precising that it comes from the network) | |
| newTool.draw(msg, false); | |
| } | |
| } | |
| }; | |
| /** | |
| * Add a new tool to the user interface | |
| */ | |
| Tools.add = function (newTool) { | |
| if (Tools.isBlocked(newTool)) return; | |
| Tools.register(newTool); | |
| if (newTool.stylesheet) { | |
| Tools.HTML.addStylesheet(newTool.stylesheet); | |
| } | |
| //Add the tool to the GUI | |
| Tools.HTML.addTool( | |
| newTool.name, | |
| newTool.icon, | |
| newTool.iconHTML, | |
| newTool.shortcut, | |
| newTool.oneTouch, | |
| ); | |
| }; | |
| Tools.change = function (toolName) { | |
| var newTool = Tools.list[toolName]; | |
| var oldTool = Tools.curTool; | |
| if (!newTool) | |
| throw new Error("Trying to select a tool that has never been added!"); | |
| if (newTool === oldTool) { | |
| if (newTool.secondary) { | |
| newTool.secondary.active = !newTool.secondary.active; | |
| var props = newTool.secondary.active ? newTool.secondary : newTool; | |
| Tools.HTML.toggle(newTool.name, props.name, props.icon); | |
| if (newTool.secondary.switch) newTool.secondary.switch(); | |
| } | |
| return; | |
| } | |
| if (!newTool.oneTouch) { | |
| //Update the GUI | |
| var curToolName = Tools.curTool ? Tools.curTool.name : ""; | |
| try { | |
| Tools.HTML.changeTool(curToolName, toolName); | |
| } catch (e) { | |
| console.error("Unable to update the GUI with the new tool. " + e); | |
| } | |
| Tools.svg.style.cursor = newTool.mouseCursor || "auto"; | |
| Tools.board.title = Tools.i18n.t(newTool.helpText || ""); | |
| //There is not necessarily already a curTool | |
| if (Tools.curTool !== null) { | |
| //It's useless to do anything if the new tool is already selected | |
| if (newTool === Tools.curTool) return; | |
| //Remove the old event listeners | |
| Tools.removeToolListeners(Tools.curTool); | |
| //Call the callbacks of the old tool | |
| Tools.curTool.onquit(newTool); | |
| } | |
| //Add the new event listeners | |
| Tools.addToolListeners(newTool); | |
| Tools.curTool = newTool; | |
| } | |
| //Call the start callback of the new tool | |
| newTool.onstart(oldTool); | |
| }; | |
| Tools.addToolListeners = function addToolListeners(tool) { | |
| for (var event in tool.compiledListeners) { | |
| var listener = tool.compiledListeners[event]; | |
| var target = listener.target || Tools.board; | |
| target.addEventListener(event, listener, { passive: false }); | |
| } | |
| }; | |
| Tools.removeToolListeners = function removeToolListeners(tool) { | |
| for (var event in tool.compiledListeners) { | |
| var listener = tool.compiledListeners[event]; | |
| var target = listener.target || Tools.board; | |
| target.removeEventListener(event, listener); | |
| // also attempt to remove with capture = true in IE | |
| if (Tools.isIE) target.removeEventListener(event, listener, true); | |
| } | |
| }; | |
| (function () { | |
| // Handle secondary tool switch with shift (key code 16) | |
| function handleShift(active, evt) { | |
| if ( | |
| evt.keyCode === 16 && | |
| Tools.curTool.secondary && | |
| Tools.curTool.secondary.active !== active | |
| ) { | |
| Tools.change(Tools.curTool.name); | |
| } | |
| } | |
| window.addEventListener("keydown", handleShift.bind(null, true)); | |
| window.addEventListener("keyup", handleShift.bind(null, false)); | |
| })(); | |
| Tools.send = function (data, toolName) { | |
| toolName = toolName || Tools.curTool.name; | |
| data.tool = toolName; | |
| Tools.applyHooks(Tools.messageHooks, data); | |
| var message = { | |
| board: Tools.boardName, | |
| data: data, | |
| }; | |
| Tools.socket.emit("broadcast", message); | |
| }; | |
| Tools.drawAndSend = function (data, tool) { | |
| if (tool == null) tool = Tools.curTool; | |
| tool.draw(data, true); | |
| Tools.send(data, tool.name); | |
| }; | |
| //Object containing the messages that have been received before the corresponding tool | |
| //is loaded. keys : the name of the tool, values : array of messages for this tool | |
| Tools.pendingMessages = {}; | |
| // Send a message to the corresponding tool | |
| function messageForTool(message) { | |
| var name = message.tool, | |
| tool = Tools.list[name]; | |
| if (tool) { | |
| Tools.applyHooks(Tools.messageHooks, message); | |
| tool.draw(message, false); | |
| } else { | |
| ///We received a message destinated to a tool that we don't have | |
| //So we add it to the pending messages | |
| if (!Tools.pendingMessages[name]) Tools.pendingMessages[name] = [message]; | |
| else Tools.pendingMessages[name].push(message); | |
| } | |
| if (message.tool !== "Hand" && message.transform != null) { | |
| //this message has special info for the mover | |
| messageForTool({ | |
| tool: "Hand", | |
| type: "update", | |
| transform: message.transform, | |
| id: message.id, | |
| }); | |
| } | |
| } | |
| var BATCH_SIZE = 1024; | |
| /** | |
| * Apply the function to all arguments by batches | |
| * @param {Function} fn - The function to apply to the arguments | |
| * @param {Array} args - The arguments to apply the function to | |
| * @param {number} [index] - The index to start from | |
| * @returns {Promise} | |
| */ | |
| function batchCall(fn, args, index) { | |
| index = index | 0; | |
| if (index >= args.length) { | |
| return Promise.resolve(); | |
| } else { | |
| var batch = args.slice(index, index + BATCH_SIZE); | |
| return Promise.all(batch.map(fn)) | |
| .then(function () { | |
| return new Promise(requestAnimationFrame); | |
| }) | |
| .then(function () { | |
| return batchCall(fn, args, index + BATCH_SIZE); | |
| }); | |
| } | |
| } | |
| // Call messageForTool recursively on the message and its children | |
| function handleMessage(message) { | |
| //Check if the message is in the expected format | |
| if (!message.tool && !message._children) { | |
| console.error("Received a badly formatted message (no tool). ", message); | |
| } | |
| if (message.tool) messageForTool(message); | |
| if (message._children) | |
| return batchCall(childMessageHandler(message), message._children); | |
| else return Promise.resolve(); | |
| } | |
| // Takes a parent message, and returns a function that will handle a single child message | |
| function childMessageHandler(parent) { | |
| if (!parent.id) return handleMessage; | |
| return function handleChild(child) { | |
| child.parent = parent.id; | |
| child.tool = parent.tool; | |
| child.type = "child"; | |
| return handleMessage(child); | |
| }; | |
| } | |
| Tools.unreadMessagesCount = 0; | |
| Tools.newUnreadMessage = function () { | |
| Tools.unreadMessagesCount++; | |
| updateDocumentTitle(); | |
| }; | |
| window.addEventListener("focus", function () { | |
| Tools.unreadMessagesCount = 0; | |
| updateDocumentTitle(); | |
| }); | |
| function updateDocumentTitle() { | |
| document.title = | |
| (Tools.unreadMessagesCount ? "(" + Tools.unreadMessagesCount + ") " : "") + | |
| Tools.boardName + | |
| " | WBO"; | |
| } | |
| (function () { | |
| // Scroll and hash handling | |
| var scrollTimeout, | |
| lastStateUpdate = Date.now(); | |
| window.addEventListener("scroll", function onScroll() { | |
| var scale = Tools.getScale(); | |
| var x = document.documentElement.scrollLeft / scale, | |
| y = document.documentElement.scrollTop / scale; | |
| clearTimeout(scrollTimeout); | |
| scrollTimeout = setTimeout(function updateHistory() { | |
| var hash = | |
| "#" + (x | 0) + "," + (y | 0) + "," + Tools.getScale().toFixed(1); | |
| if ( | |
| Date.now() - lastStateUpdate > 5000 && | |
| hash !== window.location.hash | |
| ) { | |
| window.history.pushState({}, "", hash); | |
| lastStateUpdate = Date.now(); | |
| } else { | |
| window.history.replaceState({}, "", hash); | |
| } | |
| }, 100); | |
| }); | |
| function setScrollFromHash() { | |
| var coords = window.location.hash.slice(1).split(","); | |
| var x = coords[0] | 0; | |
| var y = coords[1] | 0; | |
| var scale = parseFloat(coords[2]); | |
| resizeCanvas({ x: x, y: y }); | |
| Tools.setScale(scale); | |
| window.scrollTo(x * scale, y * scale); | |
| } | |
| window.addEventListener("hashchange", setScrollFromHash, false); | |
| window.addEventListener("popstate", setScrollFromHash, false); | |
| window.addEventListener("DOMContentLoaded", setScrollFromHash, false); | |
| })(); | |
| function resizeCanvas(m) { | |
| //Enlarge the canvas whenever something is drawn near its border | |
| var x = m.x | 0, | |
| y = m.y | 0; | |
| var MAX_BOARD_SIZE = Tools.server_config.MAX_BOARD_SIZE || 65536; // Maximum value for any x or y on the board | |
| if (x > Tools.svg.width.baseVal.value - 2000) { | |
| Tools.svg.width.baseVal.value = Math.min(x + 2000, MAX_BOARD_SIZE); | |
| } | |
| if (y > Tools.svg.height.baseVal.value - 2000) { | |
| Tools.svg.height.baseVal.value = Math.min(y + 2000, MAX_BOARD_SIZE); | |
| } | |
| } | |
| function updateUnreadCount(m) { | |
| if (document.hidden && ["child", "update"].indexOf(m.type) === -1) { | |
| Tools.newUnreadMessage(); | |
| } | |
| } | |
| // List of hook functions that will be applied to messages before sending or drawing them | |
| Tools.messageHooks = [resizeCanvas, updateUnreadCount]; | |
| Tools.scale = 1.0; | |
| var scaleTimeout = null; | |
| Tools.setScale = function setScale(scale) { | |
| var fullScale = | |
| Math.max(window.innerWidth, window.innerHeight) / | |
| Tools.server_config.MAX_BOARD_SIZE; | |
| var minScale = Math.max(0.1, fullScale); | |
| var maxScale = 10; | |
| if (isNaN(scale)) scale = 1; | |
| scale = Math.max(minScale, Math.min(maxScale, scale)); | |
| Tools.svg.style.willChange = "transform"; | |
| Tools.svg.style.transform = "scale(" + scale + ")"; | |
| clearTimeout(scaleTimeout); | |
| scaleTimeout = setTimeout(function () { | |
| Tools.svg.style.willChange = "auto"; | |
| }, 1000); | |
| Tools.scale = scale; | |
| return scale; | |
| }; | |
| Tools.getScale = function getScale() { | |
| return Tools.scale; | |
| }; | |
| //List of hook functions that will be applied to tools before adding them | |
| Tools.toolHooks = [ | |
| function checkToolAttributes(tool) { | |
| if (typeof tool.name !== "string") throw "A tool must have a name"; | |
| if (typeof tool.listeners !== "object") { | |
| tool.listeners = {}; | |
| } | |
| if (typeof tool.onstart !== "function") { | |
| tool.onstart = function () {}; | |
| } | |
| if (typeof tool.onquit !== "function") { | |
| tool.onquit = function () {}; | |
| } | |
| }, | |
| function compileListeners(tool) { | |
| //compile listeners into compiledListeners | |
| var listeners = tool.listeners; | |
| //A tool may provide precompiled listeners | |
| var compiled = tool.compiledListeners || {}; | |
| tool.compiledListeners = compiled; | |
| function compile(listener) { | |
| //closure | |
| return function listen(evt) { | |
| var x = evt.pageX / Tools.getScale(), | |
| y = evt.pageY / Tools.getScale(); | |
| return listener(x, y, evt, false); | |
| }; | |
| } | |
| function compileTouch(listener) { | |
| //closure | |
| return function touchListen(evt) { | |
| //Currently, we don't handle multitouch | |
| if (evt.changedTouches.length === 1) { | |
| //evt.preventDefault(); | |
| var touch = evt.changedTouches[0]; | |
| var x = touch.pageX / Tools.getScale(), | |
| y = touch.pageY / Tools.getScale(); | |
| return listener(x, y, evt, true); | |
| } | |
| return true; | |
| }; | |
| } | |
| function wrapUnsetHover(f, toolName) { | |
| return function unsetHover(evt) { | |
| document.activeElement && | |
| document.activeElement.blur && | |
| document.activeElement.blur(); | |
| return f(evt); | |
| }; | |
| } | |
| if (listeners.press) { | |
| compiled["mousedown"] = wrapUnsetHover( | |
| compile(listeners.press), | |
| tool.name, | |
| ); | |
| compiled["touchstart"] = wrapUnsetHover( | |
| compileTouch(listeners.press), | |
| tool.name, | |
| ); | |
| } | |
| if (listeners.move) { | |
| compiled["mousemove"] = compile(listeners.move); | |
| compiled["touchmove"] = compileTouch(listeners.move); | |
| } | |
| if (listeners.release) { | |
| var release = compile(listeners.release), | |
| releaseTouch = compileTouch(listeners.release); | |
| compiled["mouseup"] = release; | |
| if (!Tools.isIE) compiled["mouseleave"] = release; | |
| compiled["touchleave"] = releaseTouch; | |
| compiled["touchend"] = releaseTouch; | |
| compiled["touchcancel"] = releaseTouch; | |
| } | |
| }, | |
| ]; | |
| Tools.applyHooks = function (hooks, object) { | |
| //Apply every hooks on the object | |
| hooks.forEach(function (hook) { | |
| hook(object); | |
| }); | |
| }; | |
| // Utility functions | |
| Tools.generateUID = function (prefix, suffix) { | |
| var uid = Date.now().toString(36); //Create the uids in chronological order | |
| uid += Math.round(Math.random() * 36).toString(36); //Add a random character at the end | |
| if (prefix) uid = prefix + uid; | |
| if (suffix) uid = uid + suffix; | |
| return uid; | |
| }; | |
| Tools.createSVGElement = function createSVGElement(name, attrs) { | |
| var elem = document.createElementNS(Tools.svg.namespaceURI, name); | |
| if (typeof attrs !== "object") return elem; | |
| Object.keys(attrs).forEach(function (key, i) { | |
| elem.setAttributeNS(null, key, attrs[key]); | |
| }); | |
| return elem; | |
| }; | |
| Tools.positionElement = function (elem, x, y) { | |
| elem.style.top = y + "px"; | |
| elem.style.left = x + "px"; | |
| }; | |
| Tools.colorPresets = [ | |
| { color: "#001f3f", key: "1" }, | |
| { color: "#FF4136", key: "2" }, | |
| { color: "#0074D9", key: "3" }, | |
| { color: "#FF851B", key: "4" }, | |
| { color: "#FFDC00", key: "5" }, | |
| { color: "#3D9970", key: "6" }, | |
| { color: "#91E99B", key: "7" }, | |
| { color: "#90468b", key: "8" }, | |
| { color: "#7FDBFF", key: "9" }, | |
| { color: "#AAAAAA", key: "0" }, | |
| { color: "#E65194" }, | |
| ]; | |
| Tools.color_chooser = document.getElementById("chooseColor"); | |
| Tools.setColor = function (color) { | |
| Tools.color_chooser.value = color; | |
| }; | |
| Tools.getColor = (function color() { | |
| var color_index = (Math.random() * Tools.colorPresets.length) | 0; | |
| var initial_color = Tools.colorPresets[color_index].color; | |
| Tools.setColor(initial_color); | |
| return function () { | |
| return Tools.color_chooser.value; | |
| }; | |
| })(); | |
| Tools.colorPresets.forEach(Tools.HTML.addColorButton.bind(Tools.HTML)); | |
| Tools.sizeChangeHandlers = []; | |
| Tools.setSize = (function size() { | |
| var chooser = document.getElementById("chooseSize"); | |
| function update() { | |
| var size = Math.max(1, Math.min(50, chooser.value | 0)); | |
| chooser.value = size; | |
| Tools.sizeChangeHandlers.forEach(function (handler) { | |
| handler(size); | |
| }); | |
| } | |
| update(); | |
| chooser.onchange = chooser.oninput = update; | |
| return function (value) { | |
| if (value !== null && value !== undefined) { | |
| chooser.value = value; | |
| update(); | |
| } | |
| return parseInt(chooser.value); | |
| }; | |
| })(); | |
| Tools.getSize = function () { | |
| return Tools.setSize(); | |
| }; | |
| Tools.getOpacity = (function opacity() { | |
| var chooser = document.getElementById("chooseOpacity"); | |
| var opacityIndicator = document.getElementById("opacityIndicator"); | |
| function update() { | |
| opacityIndicator.setAttribute("opacity", chooser.value); | |
| } | |
| update(); | |
| chooser.onchange = chooser.oninput = update; | |
| return function () { | |
| return Math.max(0.1, Math.min(1, chooser.value)); | |
| }; | |
| })(); | |
| //Scale the canvas on load | |
| Tools.svg.width.baseVal.value = document.body.clientWidth; | |
| Tools.svg.height.baseVal.value = document.body.clientHeight; | |
| /** | |
| What does a "tool" object look like? | |
| newtool = { | |
| "name" : "SuperTool", | |
| "listeners" : { | |
| "press" : function(x,y,evt){...}, | |
| "move" : function(x,y,evt){...}, | |
| "release" : function(x,y,evt){...}, | |
| }, | |
| "draw" : function(data, isLocal){ | |
| //Print the data on Tools.svg | |
| }, | |
| "onstart" : function(oldTool){...}, | |
| "onquit" : function(newTool){...}, | |
| "stylesheet" : "style.css", | |
| } | |
| */ | |
| (function () { | |
| var pos = { top: 0, scroll: 0 }; | |
| var menu = document.getElementById("menu"); | |
| function menu_mousedown(evt) { | |
| pos = { | |
| top: menu.scrollTop, | |
| scroll: evt.clientY, | |
| }; | |
| menu.addEventListener("mousemove", menu_mousemove); | |
| document.addEventListener("mouseup", menu_mouseup); | |
| } | |
| function menu_mousemove(evt) { | |
| var dy = evt.clientY - pos.scroll; | |
| menu.scrollTop = pos.top - dy; | |
| } | |
| function menu_mouseup(evt) { | |
| menu.removeEventListener("mousemove", menu_mousemove); | |
| document.removeEventListener("mouseup", menu_mouseup); | |
| } | |
| menu.addEventListener("mousedown", menu_mousedown); | |
| })(); | |