{ "version": 3, "sources": ["../websocket.js", "../../node_modules/preact/src/vnode.js", "../../node_modules/preact/src/options.js", "../../node_modules/preact/src/h.js", "../../node_modules/preact/src/util.js", "../../node_modules/preact/src/clone-element.js", "../../node_modules/preact/src/constants.js", "../../node_modules/preact/src/render-queue.js", "../../node_modules/preact/src/vdom/index.js", "../../node_modules/preact/src/dom/index.js", "../../node_modules/preact/src/vdom/diff.js", "../../node_modules/preact/src/vdom/component-recycler.js", "../../node_modules/preact/src/vdom/component.js", "../../node_modules/preact/src/component.js", "../../node_modules/preact/src/render.js", "../../node_modules/preact/src/preact.js", "../Page.jsx", "../alert.js", "../App.jsx", "../simplify.js", "../model.js", "../snowflake.js", "../elements.js", "../index.jsx"], "sourcesContent": ["/* eslint-disable */\n// MIT License:\n//\n// Copyright (c) 2010-2012, Joe Walnes\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n// THE SOFTWARE.\n\n/**\n * This behaves like a WebSocket in every way, except if it fails to connect,\n * or it gets disconnected, it will repeatedly poll until it successfully connects\n * again.\n *\n * It is API compatible, so when you have:\n * ws = new WebSocket('ws://....');\n * you can replace with:\n * ws = new ReconnectingWebSocket('ws://....');\n *\n * The event stream will typically look like:\n * onconnecting\n * onopen\n * onmessage\n * onmessage\n * onclose // lost connection\n * onconnecting\n * onopen // sometime later...\n * onmessage\n * onmessage\n * etc...\n *\n * It is API compatible with the standard WebSocket API, apart from the following members:\n *\n * - `bufferedAmount`\n * - `extensions`\n * - `binaryType`\n *\n * Latest version: https://github.com/joewalnes/reconnecting-websocket/\n * - Joe Walnes\n *\n * Syntax\n * ======\n * var socket = new ReconnectingWebSocket(url, protocols, options);\n *\n * Parameters\n * ==========\n * url - The url you are connecting to.\n * protocols - Optional string or array of protocols.\n * options - See below\n *\n * Options\n * =======\n * Options can either be passed upon instantiation or set after instantiation:\n *\n * var socket = new ReconnectingWebSocket(url, null, { debug: true, reconnectInterval: 4000 });\n *\n * or\n *\n * var socket = new ReconnectingWebSocket(url);\n * socket.debug = true;\n * socket.reconnectInterval = 4000;\n *\n * debug\n * - Whether this instance should log debug messages. Accepts true or false. Default: false.\n *\n * automaticOpen\n * - Whether or not the websocket should attempt to connect immediately upon instantiation. The socket can be manually opened or closed at any time using ws.open() and ws.close().\n *\n * reconnectInterval\n * - The number of milliseconds to delay before attempting to reconnect. Accepts integer. Default: 1000.\n *\n * maxReconnectInterval\n * - The maximum number of milliseconds to delay a reconnection attempt. Accepts integer. Default: 30000.\n *\n * reconnectDecay\n * - The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. Accepts integer or float. Default: 1.5.\n *\n * timeoutInterval\n * - The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. Accepts integer. Default: 2000.\n *\n */\n(function (global, factory) {\n if (typeof define === 'function' && define.amd) {\n define([], factory);\n } else if (typeof module !== 'undefined' && module.exports){\n module.exports = factory();\n } else {\n global.ReconnectingWebSocket = factory();\n }\n})(this, function () {\n\n if (!('WebSocket' in window)) {\n return;\n }\n\n function ReconnectingWebSocket(url, protocols, options) {\n\n // Default settings\n var settings = {\n\n /** Whether this instance should log debug messages. */\n debug: false,\n\n /** Whether or not the websocket should attempt to connect immediately upon instantiation. */\n automaticOpen: true,\n\n /** The number of milliseconds to delay before attempting to reconnect. */\n reconnectInterval: 1000,\n /** The maximum number of milliseconds to delay a reconnection attempt. */\n maxReconnectInterval: 30000,\n /** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */\n reconnectDecay: 1.5,\n\n /** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */\n timeoutInterval: 2000,\n\n /** The maximum number of reconnection attempts to make. Unlimited if null. */\n maxReconnectAttempts: null,\n\n /** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */\n binaryType: 'blob'\n }\n if (!options) { options = {}; }\n\n // Overwrite and define settings with options if they exist.\n for (var key in settings) {\n if (typeof options[key] !== 'undefined') {\n this[key] = options[key];\n } else {\n this[key] = settings[key];\n }\n }\n\n // These should be treated as read-only properties\n\n /** The URL as resolved by the constructor. This is always an absolute URL. Read only. */\n this.url = url;\n\n /** The number of attempted reconnects since starting, or the last successful connection. Read only. */\n this.reconnectAttempts = 0;\n\n /**\n * The current state of the connection.\n * Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED\n * Read only.\n */\n this.readyState = WebSocket.CONNECTING;\n\n /**\n * A string indicating the name of the sub-protocol the server selected; this will be one of\n * the strings specified in the protocols parameter when creating the WebSocket object.\n * Read only.\n */\n this.protocol = null;\n\n // Private state variables\n\n var self = this;\n var ws;\n var forcedClose = false;\n var timedOut = false;\n var eventTarget = document.createElement('div');\n\n // Wire up \"on*\" properties as event handlers\n\n eventTarget.addEventListener('open', function(event) { self.onopen(event); });\n eventTarget.addEventListener('close', function(event) { self.onclose(event); });\n eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); });\n eventTarget.addEventListener('message', function(event) { self.onmessage(event); });\n eventTarget.addEventListener('error', function(event) { self.onerror(event); });\n\n // Expose the API required by EventTarget\n\n this.addEventListener = eventTarget.addEventListener.bind(eventTarget);\n this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget);\n this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget);\n\n /**\n * This function generates an event that is compatible with standard\n * compliant browsers and IE9 - IE11\n *\n * This will prevent the error:\n * Object doesn't support this action\n *\n * http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563\n * @param s String The name that the event should use\n * @param args Object an optional object that the event will use\n */\n function generateEvent(s, args) {\n \tvar evt = document.createEvent(\"CustomEvent\");\n \tevt.initCustomEvent(s, false, false, args);\n \treturn evt;\n };\n\n this.open = function (reconnectAttempt) {\n ws = new WebSocket(self.url, protocols || []);\n ws.binaryType = this.binaryType;\n\n if (reconnectAttempt) {\n if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) {\n return;\n }\n } else {\n eventTarget.dispatchEvent(generateEvent('connecting'));\n this.reconnectAttempts = 0;\n }\n\n if (self.debug || ReconnectingWebSocket.debugAll) {\n console.debug('ReconnectingWebSocket', 'attempt-connect', self.url);\n }\n\n var localWs = ws;\n var timeout = setTimeout(function() {\n if (self.debug || ReconnectingWebSocket.debugAll) {\n console.debug('ReconnectingWebSocket', 'connection-timeout', self.url);\n }\n timedOut = true;\n localWs.close();\n timedOut = false;\n }, self.timeoutInterval);\n\n ws.onopen = function(event) {\n clearTimeout(timeout);\n if (self.debug || ReconnectingWebSocket.debugAll) {\n console.debug('ReconnectingWebSocket', 'onopen', self.url);\n }\n self.protocol = ws.protocol;\n self.readyState = WebSocket.OPEN;\n self.reconnectAttempts = 0;\n var e = generateEvent('open');\n e.isReconnect = reconnectAttempt;\n reconnectAttempt = false;\n eventTarget.dispatchEvent(e);\n };\n\n ws.onclose = function(event) {\n clearTimeout(timeout);\n ws = null;\n if (forcedClose) {\n self.readyState = WebSocket.CLOSED;\n eventTarget.dispatchEvent(generateEvent('close'));\n } else {\n self.readyState = WebSocket.CONNECTING;\n var e = generateEvent('connecting');\n e.code = event.code;\n e.reason = event.reason;\n e.wasClean = event.wasClean;\n eventTarget.dispatchEvent(e);\n if (!reconnectAttempt && !timedOut) {\n if (self.debug || ReconnectingWebSocket.debugAll) {\n console.debug('ReconnectingWebSocket', 'onclose', self.url);\n }\n eventTarget.dispatchEvent(generateEvent('close'));\n }\n\n var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts);\n setTimeout(function() {\n self.reconnectAttempts++;\n self.open(true);\n }, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout);\n }\n };\n ws.onmessage = function(event) {\n if (self.debug || ReconnectingWebSocket.debugAll) {\n console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data);\n }\n var e = generateEvent('message');\n e.data = event.data;\n eventTarget.dispatchEvent(e);\n };\n ws.onerror = function(event) {\n if (self.debug || ReconnectingWebSocket.debugAll) {\n console.debug('ReconnectingWebSocket', 'onerror', self.url, event);\n }\n eventTarget.dispatchEvent(generateEvent('error'));\n };\n }\n\n // Whether or not to create a websocket upon instantiation\n if (this.automaticOpen == true) {\n this.open(false);\n }\n\n /**\n * Transmits data to the server over the WebSocket connection.\n *\n * @param data a text string, ArrayBuffer or Blob to send to the server.\n */\n this.send = function(data) {\n if (ws) {\n if (self.debug || ReconnectingWebSocket.debugAll) {\n console.debug('ReconnectingWebSocket', 'send', self.url, data);\n }\n return ws.send(data);\n } else {\n throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';\n }\n };\n\n /**\n * Closes the WebSocket connection or connection attempt, if any.\n * If the connection is already CLOSED, this method does nothing.\n */\n this.close = function(code, reason) {\n // Default CLOSE_NORMAL code\n if (typeof code == 'undefined') {\n code = 1000;\n }\n forcedClose = true;\n if (ws) {\n ws.close(code, reason);\n }\n };\n\n /**\n * Additional public API method to refresh the connection if still open (close, re-open).\n * For example, if the app suspects bad data / missed heart beats, it can try to refresh.\n */\n this.refresh = function() {\n if (ws) {\n ws.close();\n }\n };\n }\n\n /**\n * An event listener to be called when the WebSocket connection's readyState changes to OPEN;\n * this indicates that the connection is ready to send and receive data.\n */\n ReconnectingWebSocket.prototype.onopen = function(event) {};\n /** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */\n ReconnectingWebSocket.prototype.onclose = function(event) {};\n /** An event listener to be called when a connection begins being attempted. */\n ReconnectingWebSocket.prototype.onconnecting = function(event) {};\n /** An event listener to be called when a message is received from the server. */\n ReconnectingWebSocket.prototype.onmessage = function(event) {};\n /** An event listener to be called when an error occurs. */\n ReconnectingWebSocket.prototype.onerror = function(event) {};\n\n /**\n * Whether all instances of ReconnectingWebSocket should log debug messages.\n * Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true.\n */\n ReconnectingWebSocket.debugAll = false;\n\n ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING;\n ReconnectingWebSocket.OPEN = WebSocket.OPEN;\n ReconnectingWebSocket.CLOSING = WebSocket.CLOSING;\n ReconnectingWebSocket.CLOSED = WebSocket.CLOSED;\n\n return ReconnectingWebSocket;\n});\n", "/**\n * Virtual DOM Node\n * @typedef VNode\n * @property {string | function} nodeName The string of the DOM node to create or Component constructor to render\n * @property {Array} children The children of node\n * @property {string | number | undefined} key The key used to identify this VNode in a list\n * @property {object} attributes The properties of this VNode\n */\nexport const VNode = function VNode() {};\n", "/**\n * @typedef {import('./component').Component} Component\n * @typedef {import('./vnode').VNode} VNode\n */\n\n/**\n * Global options\n * @public\n * @typedef Options\n * @property {boolean} [syncComponentUpdates] If `true`, `prop` changes trigger synchronous component updates. Defaults to true.\n * @property {(vnode: VNode) => void} [vnode] Processes all created VNodes.\n * @property {(component: Component) => void} [afterMount] Hook invoked after a component is mounted.\n * @property {(component: Component) => void} [afterUpdate] Hook invoked after the DOM is updated with a component's latest render.\n * @property {(component: Component) => void} [beforeUnmount] Hook invoked immediately before a component is unmounted.\n * @property {(rerender: function) => void} [debounceRendering] Hook invoked whenever a rerender is requested. Can be used to debounce rerenders.\n * @property {(event: Event) => Event | void} [event] Hook invoked before any Preact event listeners. The return value (if any) replaces the native browser event given to event listeners\n */\n\n/** @type {Options} */\nconst options = {};\n\nexport default options;\n", "import { VNode } from './vnode';\nimport options from './options';\n\n\nconst stack = [];\n\nconst EMPTY_CHILDREN = [];\n\n/**\n * JSX/hyperscript reviver.\n * @see http://jasonformat.com/wtf-is-jsx\n * Benchmarks: https://esbench.com/bench/57ee8f8e330ab09900a1a1a0\n *\n * Note: this is exported as both `h()` and `createElement()` for compatibility\n * reasons.\n *\n * Creates a VNode (virtual DOM element). A tree of VNodes can be used as a\n * lightweight representation of the structure of a DOM tree. This structure can\n * be realized by recursively comparing it against the current _actual_ DOM\n * structure, and applying only the differences.\n *\n * `h()`/`createElement()` accepts an element name, a list of attributes/props,\n * and optionally children to append to the element.\n *\n * @example The following DOM tree\n *\n * `
Hello!
`\n *\n * can be constructed using this function as:\n *\n * `h('div', { id: 'foo', name : 'bar' }, 'Hello!');`\n *\n * @param {string | function} nodeName An element name. Ex: `div`, `a`, `span`, etc.\n * @param {object | null} attributes Any attributes/props to set on the created element.\n * @param {VNode[]} [rest] Additional arguments are taken to be children to\n * append. Can be infinitely nested Arrays.\n *\n * @public\n */\nexport function h(nodeName, attributes) {\n\tlet children=EMPTY_CHILDREN, lastSimple, child, simple, i;\n\tfor (i=arguments.length; i-- > 2; ) {\n\t\tstack.push(arguments[i]);\n\t}\n\tif (attributes && attributes.children!=null) {\n\t\tif (!stack.length) stack.push(attributes.children);\n\t\tdelete attributes.children;\n\t}\n\twhile (stack.length) {\n\t\tif ((child = stack.pop()) && child.pop!==undefined) {\n\t\t\tfor (i=child.length; i--; ) stack.push(child[i]);\n\t\t}\n\t\telse {\n\t\t\tif (typeof child==='boolean') child = null;\n\n\t\t\tif ((simple = typeof nodeName!=='function')) {\n\t\t\t\tif (child==null) child = '';\n\t\t\t\telse if (typeof child==='number') child = String(child);\n\t\t\t\telse if (typeof child!=='string') simple = false;\n\t\t\t}\n\n\t\t\tif (simple && lastSimple) {\n\t\t\t\tchildren[children.length-1] += child;\n\t\t\t}\n\t\t\telse if (children===EMPTY_CHILDREN) {\n\t\t\t\tchildren = [child];\n\t\t\t}\n\t\t\telse {\n\t\t\t\tchildren.push(child);\n\t\t\t}\n\n\t\t\tlastSimple = simple;\n\t\t}\n\t}\n\n\tlet p = new VNode();\n\tp.nodeName = nodeName;\n\tp.children = children;\n\tp.attributes = attributes==null ? undefined : attributes;\n\tp.key = attributes==null ? undefined : attributes.key;\n\n\t// if a \"vnode hook\" is defined, pass every created VNode to it\n\tif (options.vnode!==undefined) options.vnode(p);\n\n\treturn p;\n}\n", "/**\n * Copy all properties from `props` onto `obj`.\n * @param {object} obj Object onto which properties should be copied.\n * @param {object} props Object from which to copy properties.\n * @returns {object}\n * @private\n */\nexport function extend(obj, props) {\n\tfor (let i in props) obj[i] = props[i];\n\treturn obj;\n}\n\n/**\n * Call a function asynchronously, as soon as possible. Makes\n * use of HTML Promise to schedule the callback if available,\n * otherwise falling back to `setTimeout` (mainly for IE<11).\n * @type {(callback: function) => void}\n */\nexport const defer = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;\n", "import { extend } from './util';\nimport { h } from './h';\n\n/**\n * Clones the given VNode, optionally adding attributes/props and replacing its\n * children.\n * @param {import('./vnode').VNode} vnode The virtual DOM element to clone\n * @param {object} props Attributes/props to add when cloning\n * @param {Array} [rest] Any additional arguments will be used as replacement\n * children.\n */\nexport function cloneElement(vnode, props) {\n\treturn h(\n\t\tvnode.nodeName,\n\t\textend(extend({}, vnode.attributes), props),\n\t\targuments.length>2 ? [].slice.call(arguments, 2) : vnode.children\n\t);\n}\n", "// render modes\n\n/** Do not re-render a component */\nexport const NO_RENDER = 0;\n/** Synchronously re-render a component and its children */\nexport const SYNC_RENDER = 1;\n/** Synchronously re-render a component, even if its lifecycle methods attempt to prevent it. */\nexport const FORCE_RENDER = 2;\n/** Queue asynchronous re-render of a component and it's children */\nexport const ASYNC_RENDER = 3;\n\n\nexport const ATTR_KEY = '__preactattr_';\n\n/** DOM properties that should NOT have \"px\" added when numeric */\nexport const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i;\n\n", "import options from './options';\nimport { defer } from './util';\nimport { renderComponent } from './vdom/component';\n\n/**\n * Managed queue of dirty components to be re-rendered\n * @type {Array}\n */\nlet items = [];\n\n/**\n * Enqueue a rerender of a component\n * @param {import('./component').Component} component The component to rerender\n */\nexport function enqueueRender(component) {\n\tif (!component._dirty && (component._dirty = true) && items.push(component)==1) {\n\t\t(options.debounceRendering || defer)(rerender);\n\t}\n}\n\n/** Rerender all enqueued dirty components */\nexport function rerender() {\n\tlet p, list = items;\n\titems = [];\n\twhile ( (p = list.pop()) ) {\n\t\tif (p._dirty) renderComponent(p);\n\t}\n}\n", "import { extend } from '../util';\n\n\n/**\n * Check if two nodes are equivalent.\n * @param {import('../dom').PreactElement} node DOM Node to compare\n * @param {import('../vnode').VNode} vnode Virtual DOM node to compare\n * @param {boolean} [hydrating=false] If true, ignores component constructors\n * when comparing.\n * @private\n */\nexport function isSameNodeType(node, vnode, hydrating) {\n\tif (typeof vnode==='string' || typeof vnode==='number') {\n\t\treturn node.splitText!==undefined;\n\t}\n\tif (typeof vnode.nodeName==='string') {\n\t\treturn !node._componentConstructor && isNamedNode(node, vnode.nodeName);\n\t}\n\treturn hydrating || node._componentConstructor===vnode.nodeName;\n}\n\n\n/**\n * Check if an Element has a given nodeName, case-insensitively.\n * @param {import('../dom').PreactElement} node A DOM Element to inspect the name of.\n * @param {string} nodeName Unnormalized name to compare against.\n */\nexport function isNamedNode(node, nodeName) {\n\treturn node.normalizedNodeName===nodeName || node.nodeName.toLowerCase()===nodeName.toLowerCase();\n}\n\n\n/**\n * Reconstruct Component-style `props` from a VNode.\n * Ensures default/fallback values from `defaultProps`:\n * Own-properties of `defaultProps` not present in `vnode.attributes` are added.\n * @param {import('../vnode').VNode} vnode The VNode to get props for\n * @returns {object} The props to use for this VNode\n */\nexport function getNodeProps(vnode) {\n\tlet props = extend({}, vnode.attributes);\n\tprops.children = vnode.children;\n\n\tlet defaultProps = vnode.nodeName.defaultProps;\n\tif (defaultProps!==undefined) {\n\t\tfor (let i in defaultProps) {\n\t\t\tif (props[i]===undefined) {\n\t\t\t\tprops[i] = defaultProps[i];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn props;\n}\n", "import { IS_NON_DIMENSIONAL } from '../constants';\nimport options from '../options';\n\n/**\n * A DOM event listener\n * @typedef {(e: Event) => void} EventListner\n */\n\n/**\n * A mapping of event types to event listeners\n * @typedef {Object.} EventListenerMap\n */\n\n/**\n * Properties Preact adds to elements it creates\n * @typedef PreactElementExtensions\n * @property {string} [normalizedNodeName] A normalized node name to use in diffing\n * @property {EventListenerMap} [_listeners] A map of event listeners added by components to this DOM node\n * @property {import('../component').Component} [_component] The component that rendered this DOM node\n * @property {function} [_componentConstructor] The constructor of the component that rendered this DOM node\n */\n\n/**\n * A DOM element that has been extended with Preact properties\n * @typedef {Element & ElementCSSInlineStyle & PreactElementExtensions} PreactElement\n */\n\n/**\n * Create an element with the given nodeName.\n * @param {string} nodeName The DOM node to create\n * @param {boolean} [isSvg=false] If `true`, creates an element within the SVG\n * namespace.\n * @returns {PreactElement} The created DOM node\n */\nexport function createNode(nodeName, isSvg) {\n\t/** @type {PreactElement} */\n\tlet node = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', nodeName) : document.createElement(nodeName);\n\tnode.normalizedNodeName = nodeName;\n\treturn node;\n}\n\n\n/**\n * Remove a child node from its parent if attached.\n * @param {Node} node The node to remove\n */\nexport function removeNode(node) {\n\tlet parentNode = node.parentNode;\n\tif (parentNode) parentNode.removeChild(node);\n}\n\n\n/**\n * Set a named attribute on the given Node, with special behavior for some names\n * and event handlers. If `value` is `null`, the attribute/handler will be\n * removed.\n * @param {PreactElement} node An element to mutate\n * @param {string} name The name/key to set, such as an event or attribute name\n * @param {*} old The last value that was set for this name/node pair\n * @param {*} value An attribute value, such as a function to be used as an\n * event handler\n * @param {boolean} isSvg Are we currently diffing inside an svg?\n * @private\n */\nexport function setAccessor(node, name, old, value, isSvg) {\n\tif (name==='className') name = 'class';\n\n\n\tif (name==='key') {\n\t\t// ignore\n\t}\n\telse if (name==='ref') {\n\t\tif (old) old(null);\n\t\tif (value) value(node);\n\t}\n\telse if (name==='class' && !isSvg) {\n\t\tnode.className = value || '';\n\t}\n\telse if (name==='style') {\n\t\tif (!value || typeof value==='string' || typeof old==='string') {\n\t\t\tnode.style.cssText = value || '';\n\t\t}\n\t\tif (value && typeof value==='object') {\n\t\t\tif (typeof old!=='string') {\n\t\t\t\tfor (let i in old) if (!(i in value)) node.style[i] = '';\n\t\t\t}\n\t\t\tfor (let i in value) {\n\t\t\t\tnode.style[i] = typeof value[i]==='number' && IS_NON_DIMENSIONAL.test(i)===false ? (value[i]+'px') : value[i];\n\t\t\t}\n\t\t}\n\t}\n\telse if (name==='dangerouslySetInnerHTML') {\n\t\tif (value) node.innerHTML = value.__html || '';\n\t}\n\telse if (name[0]=='o' && name[1]=='n') {\n\t\tlet useCapture = name !== (name=name.replace(/Capture$/, ''));\n\t\tname = name.toLowerCase().substring(2);\n\t\tif (value) {\n\t\t\tif (!old) node.addEventListener(name, eventProxy, useCapture);\n\t\t}\n\t\telse {\n\t\t\tnode.removeEventListener(name, eventProxy, useCapture);\n\t\t}\n\t\t(node._listeners || (node._listeners = {}))[name] = value;\n\t}\n\telse if (name!=='list' && name!=='type' && !isSvg && name in node) {\n\t\t// Attempt to set a DOM property to the given value.\n\t\t// IE & FF throw for certain property-value combinations.\n\t\ttry {\n\t\t\tnode[name] = value==null ? '' : value;\n\t\t} catch (e) { }\n\t\tif ((value==null || value===false) && name!='spellcheck') node.removeAttribute(name);\n\t}\n\telse {\n\t\tlet ns = isSvg && (name !== (name = name.replace(/^xlink:?/, '')));\n\t\t// spellcheck is treated differently than all other boolean values and\n\t\t// should not be removed when the value is `false`. See:\n\t\t// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-spellcheck\n\t\tif (value==null || value===false) {\n\t\t\tif (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase());\n\t\t\telse node.removeAttribute(name);\n\t\t}\n\t\telse if (typeof value!=='function') {\n\t\t\tif (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase(), value);\n\t\t\telse node.setAttribute(name, value);\n\t\t}\n\t}\n}\n\n\n/**\n * Proxy an event to hooked event handlers\n * @param {Event} e The event object from the browser\n * @private\n */\nfunction eventProxy(e) {\n\treturn this._listeners[e.type](options.event && options.event(e) || e);\n}\n", "import { ATTR_KEY } from '../constants';\nimport { isSameNodeType, isNamedNode } from './index';\nimport { buildComponentFromVNode } from './component';\nimport { createNode, setAccessor } from '../dom/index';\nimport { unmountComponent } from './component';\nimport options from '../options';\nimport { removeNode } from '../dom/index';\n\n/**\n * Queue of components that have been mounted and are awaiting componentDidMount\n * @type {Array}\n */\nexport const mounts = [];\n\n/** Diff recursion count, used to track the end of the diff cycle. */\nexport let diffLevel = 0;\n\n/** Global flag indicating if the diff is currently within an SVG */\nlet isSvgMode = false;\n\n/** Global flag indicating if the diff is performing hydration */\nlet hydrating = false;\n\n/** Invoke queued componentDidMount lifecycle methods */\nexport function flushMounts() {\n\tlet c;\n\twhile ((c=mounts.pop())) {\n\t\tif (options.afterMount) options.afterMount(c);\n\t\tif (c.componentDidMount) c.componentDidMount();\n\t}\n}\n\n\n/**\n * Apply differences in a given vnode (and it's deep children) to a real DOM Node.\n * @param {import('../dom').PreactElement} dom A DOM node to mutate into the shape of a `vnode`\n * @param {import('../vnode').VNode} vnode A VNode (with descendants forming a tree) representing\n * the desired DOM structure\n * @param {object} context The current context\n * @param {boolean} mountAll Whether or not to immediately mount all components\n * @param {Element} parent ?\n * @param {boolean} componentRoot ?\n * @returns {import('../dom').PreactElement} The created/mutated element\n * @private\n */\nexport function diff(dom, vnode, context, mountAll, parent, componentRoot) {\n\t// diffLevel having been 0 here indicates initial entry into the diff (not a subdiff)\n\tif (!diffLevel++) {\n\t\t// when first starting the diff, check if we're diffing an SVG or within an SVG\n\t\tisSvgMode = parent!=null && parent.ownerSVGElement!==undefined;\n\n\t\t// hydration is indicated by the existing element to be diffed not having a prop cache\n\t\thydrating = dom!=null && !(ATTR_KEY in dom);\n\t}\n\n\tlet ret = idiff(dom, vnode, context, mountAll, componentRoot);\n\n\t// append the element if its a new parent\n\tif (parent && ret.parentNode!==parent) parent.appendChild(ret);\n\n\t// diffLevel being reduced to 0 means we're exiting the diff\n\tif (!--diffLevel) {\n\t\thydrating = false;\n\t\t// invoke queued componentDidMount lifecycle methods\n\t\tif (!componentRoot) flushMounts();\n\t}\n\n\treturn ret;\n}\n\n\n/**\n * Internals of `diff()`, separated to allow bypassing diffLevel / mount flushing.\n * @param {import('../dom').PreactElement} dom A DOM node to mutate into the shape of a `vnode`\n * @param {import('../vnode').VNode} vnode A VNode (with descendants forming a tree) representing the desired DOM structure\n * @param {object} context The current context\n * @param {boolean} mountAll Whether or not to immediately mount all components\n * @param {boolean} [componentRoot] ?\n * @private\n */\nfunction idiff(dom, vnode, context, mountAll, componentRoot) {\n\tlet out = dom,\n\t\tprevSvgMode = isSvgMode;\n\n\t// empty values (null, undefined, booleans) render as empty Text nodes\n\tif (vnode==null || typeof vnode==='boolean') vnode = '';\n\n\n\t// Fast case: Strings & Numbers create/update Text nodes.\n\tif (typeof vnode==='string' || typeof vnode==='number') {\n\n\t\t// update if it's already a Text node:\n\t\tif (dom && dom.splitText!==undefined && dom.parentNode && (!dom._component || componentRoot)) {\n\t\t\t/* istanbul ignore if */ /* Browser quirk that can't be covered: https://github.com/developit/preact/commit/fd4f21f5c45dfd75151bd27b4c217d8003aa5eb9 */\n\t\t\tif (dom.nodeValue!=vnode) {\n\t\t\t\tdom.nodeValue = vnode;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t// it wasn't a Text node: replace it with one and recycle the old Element\n\t\t\tout = document.createTextNode(vnode);\n\t\t\tif (dom) {\n\t\t\t\tif (dom.parentNode) dom.parentNode.replaceChild(out, dom);\n\t\t\t\trecollectNodeTree(dom, true);\n\t\t\t}\n\t\t}\n\n\t\tout[ATTR_KEY] = true;\n\n\t\treturn out;\n\t}\n\n\n\t// If the VNode represents a Component, perform a component diff:\n\tlet vnodeName = vnode.nodeName;\n\tif (typeof vnodeName==='function') {\n\t\treturn buildComponentFromVNode(dom, vnode, context, mountAll);\n\t}\n\n\n\t// Tracks entering and exiting SVG namespace when descending through the tree.\n\tisSvgMode = vnodeName==='svg' ? true : vnodeName==='foreignObject' ? false : isSvgMode;\n\n\n\t// If there's no existing element or it's the wrong type, create a new one:\n\tvnodeName = String(vnodeName);\n\tif (!dom || !isNamedNode(dom, vnodeName)) {\n\t\tout = createNode(vnodeName, isSvgMode);\n\n\t\tif (dom) {\n\t\t\t// move children into the replacement node\n\t\t\twhile (dom.firstChild) out.appendChild(dom.firstChild);\n\n\t\t\t// if the previous Element was mounted into the DOM, replace it inline\n\t\t\tif (dom.parentNode) dom.parentNode.replaceChild(out, dom);\n\n\t\t\t// recycle the old element (skips non-Element node types)\n\t\t\trecollectNodeTree(dom, true);\n\t\t}\n\t}\n\n\n\tlet fc = out.firstChild,\n\t\tprops = out[ATTR_KEY],\n\t\tvchildren = vnode.children;\n\n\tif (props==null) {\n\t\tprops = out[ATTR_KEY] = {};\n\t\tfor (let a=out.attributes, i=a.length; i--; ) props[a[i].name] = a[i].value;\n\t}\n\n\t// Optimization: fast-path for elements containing a single TextNode:\n\tif (!hydrating && vchildren && vchildren.length===1 && typeof vchildren[0]==='string' && fc!=null && fc.splitText!==undefined && fc.nextSibling==null) {\n\t\tif (fc.nodeValue!=vchildren[0]) {\n\t\t\tfc.nodeValue = vchildren[0];\n\t\t}\n\t}\n\t// otherwise, if there are existing or new children, diff them:\n\telse if (vchildren && vchildren.length || fc!=null) {\n\t\tinnerDiffNode(out, vchildren, context, mountAll, hydrating || props.dangerouslySetInnerHTML!=null);\n\t}\n\n\n\t// Apply attributes/props from VNode to the DOM Element:\n\tdiffAttributes(out, vnode.attributes, props);\n\n\n\t// restore previous SVG mode: (in case we're exiting an SVG namespace)\n\tisSvgMode = prevSvgMode;\n\n\treturn out;\n}\n\n\n/**\n * Apply child and attribute changes between a VNode and a DOM Node to the DOM.\n * @param {import('../dom').PreactElement} dom Element whose children should be compared & mutated\n * @param {Array} vchildren Array of VNodes to compare to `dom.childNodes`\n * @param {object} context Implicitly descendant context object (from most\n * recent `getChildContext()`)\n * @param {boolean} mountAll Whether or not to immediately mount all components\n * @param {boolean} isHydrating if `true`, consumes externally created elements\n * similar to hydration\n */\nfunction innerDiffNode(dom, vchildren, context, mountAll, isHydrating) {\n\tlet originalChildren = dom.childNodes,\n\t\tchildren = [],\n\t\tkeyed = {},\n\t\tkeyedLen = 0,\n\t\tmin = 0,\n\t\tlen = originalChildren.length,\n\t\tchildrenLen = 0,\n\t\tvlen = vchildren ? vchildren.length : 0,\n\t\tj, c, f, vchild, child;\n\n\t// Build up a map of keyed children and an Array of unkeyed children:\n\tif (len!==0) {\n\t\tfor (let i=0; i;\n * }\n * }\n */\nexport function Component(props, context) {\n\tthis._dirty = true;\n\n\t/**\n\t * @public\n\t * @type {object}\n\t */\n\tthis.context = context;\n\n\t/**\n\t * @public\n\t * @type {object}\n\t */\n\tthis.props = props;\n\n\t/**\n\t * @public\n\t * @type {object}\n\t */\n\tthis.state = this.state || {};\n\n\tthis._renderCallbacks = [];\n}\n\n\nextend(Component.prototype, {\n\n\t/**\n\t * Update component state and schedule a re-render.\n\t * @param {object} state A dict of state properties to be shallowly merged\n\t * \tinto the current state, or a function that will produce such a dict. The\n\t * \tfunction is called with the current state and props.\n\t * @param {() => void} callback A function to be called once component state is\n\t * \tupdated\n\t */\n\tsetState(state, callback) {\n\t\tif (!this.prevState) this.prevState = this.state;\n\t\tthis.state = extend(\n\t\t\textend({}, this.state),\n\t\t\ttypeof state === 'function' ? state(this.state, this.props) : state\n\t\t);\n\t\tif (callback) this._renderCallbacks.push(callback);\n\t\tenqueueRender(this);\n\t},\n\n\n\t/**\n\t * Immediately perform a synchronous re-render of the component.\n\t * @param {() => void} callback A function to be called after component is\n\t * \tre-rendered.\n\t * @private\n\t */\n\tforceUpdate(callback) {\n\t\tif (callback) this._renderCallbacks.push(callback);\n\t\trenderComponent(this, FORCE_RENDER);\n\t},\n\n\n\t/**\n\t * Accepts `props` and `state`, and returns a new Virtual DOM tree to build.\n\t * Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx).\n\t * @param {object} props Props (eg: JSX attributes) received from parent\n\t * \telement/component\n\t * @param {object} state The component's current state\n\t * @param {object} context Context object, as returned by the nearest\n\t * ancestor's `getChildContext()`\n\t * @returns {import('./vnode').VNode | void}\n\t */\n\trender() {}\n\n});\n", "import { diff } from './vdom/diff';\n\n/**\n * Render JSX into a `parent` Element.\n * @param {import('./vnode').VNode} vnode A (JSX) VNode to render\n * @param {import('./dom').PreactElement} parent DOM element to render into\n * @param {import('./dom').PreactElement} [merge] Attempt to re-use an existing DOM tree rooted at\n * `merge`\n * @public\n *\n * @example\n * // render a div into :\n * render(
hello!
, document.body);\n *\n * @example\n * // render a \"Thing\" component into #foo:\n * const Thing = ({ name }) => { name };\n * render(, document.querySelector('#foo'));\n */\nexport function render(vnode, parent, merge) {\n\treturn diff(merge, vnode, {}, false, parent, false);\n}\n", "import { h, h as createElement } from './h';\nimport { cloneElement } from './clone-element';\nimport { Component } from './component';\nimport { render } from './render';\nimport { rerender } from './render-queue';\nimport options from './options';\n\nexport default {\n\th,\n\tcreateElement,\n\tcloneElement,\n\tComponent,\n\trender,\n\trerender,\n\toptions\n};\n\nexport {\n\th,\n\tcreateElement,\n\tcloneElement,\n\tComponent,\n\trender,\n\trerender,\n\toptions\n};\n", "/** @jsx h */\nimport { h, Component } from 'preact';\n\nconst dot = (v1, v2) => (v1.x * v2.x) + (v1.y * v2.y);\nconst getLen = v => Math.sqrt(dot(v, v));\n\nexport default class Page extends Component {\n constructor(props) {\n super(props);\n this.active = {};\n this.state = {\n offsetX: 0,\n offsetY: 0,\n zoom: 1,\n cursorX: 50,\n cursorY: 50,\n canDraw: false,\n pathID: null,\n textInput: '',\n textID: null,\n };\n this.isTouchDevice = (('ontouchstart' in window)\n || (window.DocumentTouch && document instanceof window.DocumentTouch));\n this.touch = {\n pathID: null,\n preV: { x: null, y: null },\n pinchStartLen: null,\n scale: 1,\n isDoubleTap: false,\n delta: null,\n last: null,\n now: null,\n end: null,\n multiTouch: false,\n tapTimeout: null,\n longTapTimeout: null,\n singleTapTimeout: null,\n swipeTimeout: null,\n x1: null,\n x2: null,\n y1: null,\n y2: null,\n pinchStart: { x: null, y: null },\n preTapPosition: { x: null, y: null },\n afterLongTap: false,\n afterLongTapTimeout: null,\n };\n }\n\n componentDidMount() {\n this.svgEl.focus();\n }\n\n onMouseDown({ x, y, e }) {\n if (e.buttons === 1) {\n if (e.ctrlKey || e.shiftKey || e.metaKey) {\n this.drag = {\n x: e.clientX,\n y: e.clientY,\n offsetX: this.state.offsetX,\n offsetY: this.state.offsetY,\n };\n } else {\n const id = this.props.pathStart({ x, y });\n this.setState({ canDraw: true, pathID: id });\n }\n } else if (e.buttons === 4) {\n this.deleteActive();\n }\n }\n\n onMouseUp() {\n if (this.state.pathID) {\n this.props.pathEnd(this.state.pathID);\n this.setState({ canDraw: false, pathID: null });\n }\n if (this.drag) {\n this.drag = undefined;\n }\n }\n\n onMouseMove({ x, y, e }) {\n this.isTouchDevice = false;\n if (this.drag) {\n const dx = e.clientX - this.drag.x;\n const dy = e.clientY - this.drag.y;\n this.setState({ offsetX: this.drag.offsetX + dx, offsetY: this.drag.offsetY + dy });\n return;\n }\n if (this.state.canDraw) {\n this.props.pathAdd({ id: this.state.pathID, x, y });\n }\n if (this.state.textID) {\n if (new Date() - this.state.lastKeyPressTime < 300) {\n return;\n }\n this.textID = null;\n this.textInput = '';\n this.props.textEnd();\n }\n this.setState({\n cursorX: x,\n cursorY: y,\n textID: null,\n textInput: '',\n });\n }\n\n onTouchStart(e) {\n if (!e.touches) return;\n this.isTouchDevice = true;\n this.touch.now = Date.now();\n this.touch.x1 = e.touches[0].clientX;\n this.touch.y1 = e.touches[0].clientY;\n this.touch.scale = this.state.zoom;\n this.touch.delta = this.touch.now - (this.touch.last || this.touch.now);\n if (this.touch.preTapPosition.x !== null) {\n this.touch.isDoubleTap = (this.touch.delta > 0 && this.touch.delta <= 250\n && Math.abs(this.touch.preTapPosition.x - this.touch.x1) < 30\n && Math.abs(this.touch.preTapPosition.y - this.touch.y1) < 30);\n }\n this.touch.preTapPosition.x = this.touch.x1;\n this.touch.preTapPosition.y = this.touch.y1;\n this.touch.last = this.touch.now;\n const { preV } = this.touch;\n const len = e.touches.length;\n if (len > 1) {\n clearTimeout(this.touch.longTapTimeout);\n clearTimeout(this.touch.singleTapTimeout);\n const v = {\n x: e.touches[1].clientX - this.touch.x1,\n y: e.touches[1].clientY - this.touch.y1,\n };\n preV.x = v.x;\n preV.y = v.y;\n this.touch.pinchStartLen = getLen(preV);\n this.touch.pinchStart = {\n x: (e.touches[1].clientX + this.touch.x1) / 2,\n y: (e.touches[1].clientY + this.touch.y1) / 2,\n offsetX: this.state.offsetX,\n offsetY: this.state.offsetY,\n };\n }\n this.touch.longTapTimeout = setTimeout(() => {\n const text = window.prompt('Enter text:'); // eslint-disable-line no-alert\n if (text) {\n const w = this.props.elements.measure(text);\n this.props.textCreate({\n x: ((this.touch.x1 - this.state.offsetX) / this.state.zoom) - (w / 2),\n y: (this.touch.y1 - this.state.offsetY) / this.state.zoom,\n text,\n });\n }\n this.touch.afterLongTap = true;\n this.touch.afterLongTapTimeout = setTimeout(() => {\n this.touch.afterLongTap = false;\n }, 1000);\n }, 750);\n }\n\n onTouchMove(e) {\n e.preventDefault();\n const { preV } = this.touch;\n const len = e.touches.length;\n const currentX = e.touches[0].clientX;\n const currentY = e.touches[0].clientY;\n this.touch.isDoubleTap = false;\n if (len > 1) {\n const v = {\n x: e.touches[1].clientX - currentX,\n y: e.touches[1].clientY - currentY,\n };\n if (preV.x !== null) {\n if (this.touch.pinchStartLen > 0) {\n const scale = (getLen(v) / this.touch.pinchStartLen) * this.touch.scale;\n const newZoom = Math.min(Math.max(scale, 0.2), 5);\n const center = {\n x: (e.touches[1].clientX + currentX) / 2,\n y: (e.touches[1].clientY + currentY) / 2,\n };\n const oldSVGCenter = {\n x: (this.touch.pinchStart.offsetX - this.touch.pinchStart.x) / this.touch.scale,\n y: (this.touch.pinchStart.offsetY - this.touch.pinchStart.y) / this.touch.scale,\n };\n this.setState({\n zoom: newZoom,\n offsetX: (oldSVGCenter.x * newZoom) + center.x,\n offsetY: (oldSVGCenter.y * newZoom) + center.y,\n });\n }\n }\n preV.x = v.x;\n preV.y = v.y;\n this.touch.multiTouch = true;\n } else {\n if (this.touch.x2 !== null) {\n e.deltaX = currentX - this.touch.x2;\n e.deltaY = currentY - this.touch.y2;\n } else {\n e.deltaX = 0;\n e.deltaY = 0;\n }\n if (!this.touch.pathID && this.touch.x1 !== null && this.touch.y1 !== null) {\n this.touch.pathID = this.props.pathStart({\n x: (this.touch.x1 - this.state.offsetX) / this.state.zoom,\n y: (this.touch.y1 - this.state.offsetY) / this.state.zoom,\n });\n }\n if (this.touch.pathID) {\n this.props.pathAdd({\n id: this.touch.pathID,\n x: (currentX - this.state.offsetX) / this.state.zoom,\n y: (currentY - this.state.offsetY) / this.state.zoom,\n });\n }\n }\n clearTimeout(this.touch.longTapTimeout);\n this.touch.x2 = currentX;\n this.touch.y2 = currentY;\n if (len > 1) {\n e.preventDefault();\n }\n }\n\n onTouchEnd(e) {\n e.preventDefault();\n this.touch.end = Date.now();\n clearTimeout(this.touch.longTapTimeout);\n e.origin = [this.touch.x1, this.touch.y1];\n if (this.touch.multiTouch === false) {\n if (this.touch.afterLongTap) {\n clearTimeout(this.touch.afterLongTapTimeout);\n this.touch.afterLongTap = false;\n } else {\n this.touch.tapTimeout = setTimeout(() => {\n if (this.touch.isDoubleTap) {\n clearTimeout(this.touch.singleTapTimeout);\n this.touch.isDoubleTap = false;\n const x = (e.origin[0] - this.state.offsetX) / this.state.zoom;\n const y = (e.origin[1] - this.state.offsetY) / this.state.zoom;\n const dist = 25 / this.state.zoom;\n this.props.elements.map((el, id) => {\n if (this.props.elements.nearby(x, y, id, dist)) {\n this.props.remove(id);\n }\n return null;\n });\n }\n }, 0);\n }\n }\n this.touch.preV.x = 0;\n this.touch.preV.y = 0;\n this.touch.scale = this.state.zoom;\n this.touch.pinchStartLen = null;\n this.touch.x1 = null;\n this.touch.x2 = null;\n this.touch.y1 = null;\n this.touch.y2 = null;\n this.touch.multiTouch = false;\n if (this.touch.pathID) {\n this.props.pathEnd(this.touch.pathID);\n this.touch.pathID = null;\n }\n }\n\n onTouchCancel() {\n clearTimeout(this.touch.singleTapTimeout);\n clearTimeout(this.touch.tapTimeout);\n clearTimeout(this.touch.longTapTimeout);\n clearTimeout(this.touch.swipeTimeout);\n if (this.touch.pathID) {\n this.props.pathEnd(this.touch.pathID);\n this.touch.pathID = null;\n }\n }\n\n onZoom({ e }) {\n const scale = this.state.zoom * (e.deltaY > 0 ? 0.95 : 1.05);\n this.zoom(scale, e.clientX, e.clientY);\n }\n\n onKeyDown(e) {\n const keyCode = 'which' in e ? e.which : e.keyCode;\n if (keyCode === 8) {\n // \"Backspace\"\n e.preventDefault();\n const s = this.state.textInput;\n if (s) {\n this.setState({ textInput: s.substring(0, s.length - 1) });\n this.props.textUpdate(this.state.textID, this.state.textInput);\n } else {\n this.deleteActive();\n }\n } else if (keyCode === 46) {\n // \"Delete\"\n e.preventDefault();\n this.deleteActive();\n } else if (keyCode === 37) {\n // \"Left\"\n this.setState(state => ({ offsetX: state.offsetX + 10 }));\n } else if (keyCode === 38) {\n // \"Up\"\n this.setState(state => ({ offsetY: state.offsetY + 10 }));\n } else if (keyCode === 39) {\n // \"Right\"\n this.setState(state => ({ offsetX: state.offsetX - 10 }));\n } else if (keyCode === 40) {\n // \"Down\"\n this.setState(state => ({ offsetY: state.offsetY - 10 }));\n } else if (keyCode === 33) {\n // \"PageUp\"\n this.zoom(this.state.zoom * 1.05, this.svgEl.clientWidth / 2, this.svgEl.clientHeight / 2);\n } else if (keyCode === 34) {\n // \"PageDown\"\n this.zoom(this.state.zoom * 0.95, this.svgEl.clientWidth / 2, this.svgEl.clientHeight / 2);\n } else if (keyCode === 35 || keyCode === 36) {\n // \"End\" / \"Home\"\n this.setState({ zoom: 1, offsetX: 0, offsetY: 0 });\n } else if (keyCode === 90 && (e.ctrlKey || e.metaKey)) {\n this.undo();\n } else if (keyCode === 89 && (e.ctrlKey || e.metaKey)) {\n this.redo();\n }\n }\n\n onKeyPress(e) {\n const keyCode = 'which' in e ? e.which : e.keyCode;\n if (keyCode !== 0 && keyCode !== 13 && !e.ctrlKey && !e.metaKey) {\n // anything but \"Enter\"\n e.preventDefault();\n const c = `${String.fromCharCode(keyCode)}`;\n if (!this.state.textInput) {\n this.setState(state => ({\n textInput: c,\n textID: this.props.textCreate({\n x: state.cursorX,\n y: state.cursorY,\n text: c,\n }),\n }));\n } else {\n this.setState(state => ({ textInput: state.textInput + c }));\n this.props.textUpdate(this.state.textID, this.state.textInput);\n }\n }\n this.setState({ lastKeyPressTime: new Date() });\n }\n\n zoom(scale, x, y) {\n const oldZoom = this.state.zoom;\n const newZoom = Math.min(Math.max(scale, 0.2), 5);\n this.setState(state => ({\n zoom: newZoom,\n offsetX: (((state.offsetX - x) * newZoom) / oldZoom) + x,\n offsetY: (((state.offsetY - y) * newZoom) / oldZoom) + y,\n }));\n }\n\n deleteActive() {\n Object.keys(this.active).forEach((id) => {\n if (this.active[id]) {\n this.props.remove(id);\n }\n });\n this.active = {};\n this.setState({\n canDraw: false,\n pathID: null,\n textInput: '',\n textID: null,\n });\n this.forceUpdate();\n }\n\n undo() {\n this.setState({\n canDraw: false,\n pathID: null,\n textInput: '',\n textID: null,\n });\n this.props.undo();\n this.forceUpdate();\n }\n\n redo() {\n this.setState({\n canDraw: false,\n pathID: null,\n textInput: '',\n textID: null,\n });\n this.props.redo();\n this.forceUpdate();\n }\n\n render() {\n const xy = (e) => {\n e.preventDefault();\n const x = (e.clientX - this.state.offsetX) / this.state.zoom;\n const y = (e.clientY - this.state.offsetY) / this.state.zoom;\n this.setState({ cursorX: e.clientX, cursorY: e.clientY });\n return { x, y, e };\n };\n const dist = 25 / this.state.zoom;\n const renderPath = (id, el) => {\n const active = !this.isTouchDevice\n && this.props.elements.nearby(this.state.cursorX, this.state.cursorY, id, dist);\n const line = (a, b) => {\n const lx = b.x - a.x;\n const ly = b.y - a.y;\n return {\n length: Math.sqrt((lx ** 2) + (ly ** 2)),\n angle: Math.atan2(ly, lx),\n };\n };\n const cp = (current, previous, next, reverse) => {\n const smoothing = 0.2;\n const p = previous || current;\n const n = next || current;\n const o = line(p, n);\n const angle = o.angle + (reverse ? Math.PI : 0);\n const length = o.length * smoothing;\n const x = current.x + (Math.cos(angle) * length);\n const y = current.y + (Math.sin(angle) * length);\n return [x, y];\n };\n const bezier = (p, i, a) => {\n const [cpsX, cpsY] = cp(a[i - 1], a[i - 2], p);\n const [cpeX, cpeY] = cp(p, a[i - 1], a[i + 1], true);\n return `C ${cpsX},${cpsY} ${cpeX},${cpeY} ${p.x},${p.y}`;\n };\n const l = el.points.reduce(\n (acc, p, i, a) => (i === 0 ? `M ${p.x} ${p.y}` : `${acc} ${bezier(p, i, a)}`),\n '',\n );\n this.active[id] = active;\n return (\n \n );\n };\n const renderText = (id, el) => {\n const active = !this.isTouchDevice\n && this.props.elements.nearby(this.state.cursorX, this.state.cursorY, id, dist);\n this.active[id] = active;\n return (\n \n {el.text}\n \n );\n };\n return (\n this.onMouseMove(xy(e))}\n onMouseDown={e => this.onMouseDown(xy(e))}\n onMouseUp={e => this.onMouseUp(xy(e))}\n onMouseLeave={e => this.onMouseUp(xy(e))}\n onWheel={e => this.onZoom(xy(e))}\n onTouchStart={e => this.onTouchStart(e)}\n onTouchMove={e => this.onTouchMove(e)}\n onTouchEnd={e => this.onTouchEnd(e)}\n onTouchCancel={e => this.onTouchCancel(e)}\n onKeyDown={e => this.onKeyDown(e)}\n onKeyPress={e => this.onKeyPress(e)}\n ref={(el) => { this.svgEl = el; this.props.onRef(el); }}\n >\n \n {\n this.props.elements.map((el, id) => (\n el.stroke ? renderPath(id, el.stroke) : renderText(id, el.text)\n ))\n }\n \n \n );\n }\n}\n", "function h(tag, className = '', text = '') {\n const el = document.createElement(tag);\n el.setAttribute('class', className);\n if (text) {\n el.appendChild(document.createTextNode(text));\n }\n return el;\n}\n\nconst buildDialog = (type, options) => new Promise((resolve, reject) => {\n const prev = document.getElementsByClassName('dialog');\n if (prev.length > 0) {\n document.body.removeChild(prev[0]);\n }\n\n const dialog = h('div', 'dialog');\n const overlay = h('div', options.forced ? 'dialog-overlay-forced' : 'dialog-overlay');\n const closeBtn = h('button', 'dialog-btn-close');\n const content = h('div', 'dialog-content');\n const cTitle = h('h1', 'dialog-title', options.title || '');\n const body = h('div', 'dialog-body');\n const action = h('div', 'dialog-action');\n const okBtn = h('button', 'dialog-btn-ok', options.okText || 'OK');\n const cancelBtn = h('button', 'dialog-btn-cancel', options.cancelText || 'Cancel');\n const input = h('input', 'dialog-input');\n\n let destroy;\n const ok = () => {\n destroy();\n resolve(input.value);\n };\n\n const cancel = () => {\n if (options.forced) {\n return;\n }\n destroy();\n reject();\n };\n\n const hide = (e) => {\n const keyCode = 'which' in e ? e.which : e.keyCode;\n if (keyCode === 27) {\n cancel();\n } else if (keyCode === 13) {\n ok();\n }\n };\n\n destroy = () => {\n closeBtn.removeEventListener('click', cancel);\n okBtn.removeEventListener('click', ok);\n cancelBtn.removeEventListener('click', cancel);\n overlay.removeEventListener('click', cancel);\n document.removeEventListener('keyup', hide);\n document.body.removeChild(dialog);\n };\n\n if (!options.forced) {\n overlay.appendChild(closeBtn);\n }\n overlay.addEventListener('click', cancel);\n closeBtn.addEventListener('click', cancel);\n body.appendChild(h('p', 'dialog-sub', options.content || ''));\n action.appendChild(okBtn);\n\n if (type !== 'alert' && !options.forced) {\n action.appendChild(cancelBtn);\n cancelBtn.addEventListener('click', cancel);\n }\n\n okBtn.addEventListener('click', ok);\n\n content.appendChild(cTitle);\n content.appendChild(body);\n content.appendChild(action);\n\n dialog.appendChild(overlay);\n dialog.appendChild(content);\n document.body.appendChild(dialog);\n dialog.style.display = 'block';\n content.classList.add('dialog-animate');\n if (type === 'prompt') {\n if (options.password) {\n input.setAttribute('type', 'password');\n } else {\n input.setAttribute('type', 'text');\n }\n input.setAttribute('placeholder', options.placeholder || '');\n input.value = options.defaultValue || '';\n input.addEventListener('keyup', hide);\n body.appendChild(input);\n input.focus();\n } else if (type === 'alert') {\n okBtn.focus();\n } else {\n cancelBtn.focus();\n }\n document.addEventListener('keyup', hide);\n});\n\nexport const confirm = options => buildDialog('confirm', options);\nexport const prompt = options => buildDialog('prompt', options);\nexport const alert = options => buildDialog('alert', options);\n\nexport function close() {\n const prev = document.getElementsByClassName('dialog');\n if (prev.length > 0) {\n document.body.removeChild(prev[0]);\n }\n}\n", "/** @jsx h */\nimport { h, Component } from 'preact';\nimport Page from './Page';\nimport { prompt } from './alert';\n\nexport default class App extends Component {\n constructor(props) {\n super(props);\n this.state = { menu: false, nightMode: sessionStorage.nightMode || false };\n this.notify = () => this.forceUpdate();\n }\n\n componentDidMount() {\n this.props.model.subscribe(this.notify);\n }\n\n componentWillUpdate() {\n if (this.props.model.online !== this.online) {\n this.online = this.props.model.online;\n document.title = `on the same page${this.online ? '' : ' (offline)'}`;\n }\n }\n\n componentWillUnmount() {\n this.props.model.unsubscribe(this.notify);\n }\n\n onShowMenu(e) {\n e.preventDefault();\n e.stopPropagation();\n this.setState({ menu: true });\n }\n\n onHideMenu(e) {\n e.preventDefault();\n e.stopPropagation();\n this.setState({ menu: false });\n this.svgEl.focus();\n }\n\n render() {\n if (this.props.model.unauthorized) {\n return undefined;\n }\n return (\n \n {\n this.state.menu\n ? (\n
\n
\n
\n
\n

On the same page

\n

{this.props.model.boardName}

\n
\n
\n
    \n
  • \n \n
  • \n
  • \n {\n this.onHideMenu(e);\n prompt({\n title: 'Open',\n content: 'Enter board name:',\n }).then((name) => { window.location.pathname = name; }, () => {\n this.svgEl.focus();\n });\n }}\n >\n Open\u2026\n \n
  • \n
  • \n {\n this.onHideMenu(e);\n prompt({\n title: 'Rename',\n content: 'Enter new board name:',\n }).then(newName => this.props.model.renameBoard(newName), () => {\n this.svgEl.focus();\n });\n }}\n >\n Change URL\u2026\n \n
  • \n
  • \n {\n this.onHideMenu(e);\n prompt({\n title: 'Protect',\n content: 'Set board password:',\n password: true,\n }).then((password) => {\n this.props.model.protectBoard(password);\n this.svgEl.focus();\n }, () => {\n this.svgEl.focus();\n });\n }}\n >\n Add password\u2026\n \n
  • \n
  • \n {\n this.onHideMenu(e);\n this.setState(state => ({ nightMode: !state.nightMode }));\n sessionStorage.nightMode = this.state.nightMode;\n }}\n >\n { this.state.nightMode ? 'Day mode' : 'Night mode' }\n \n
  • \n
  • \n {\n window.location.pathname = '/about';\n }}\n >\n About\n \n
  • \n
\n
\n
\n ) : ''\n }\n\n