diff --git a/examples/updated/grid_ex_2.osdpi b/examples/updated/grid_ex_2.osdpi index 88936d26..d5ab09fd 100644 Binary files a/examples/updated/grid_ex_2.osdpi and b/examples/updated/grid_ex_2.osdpi differ diff --git a/examples/updated/keyboard_predict_ex_1.osdpi b/examples/updated/keyboard_predict_ex_1.osdpi index 60178652..a75414bb 100644 Binary files a/examples/updated/keyboard_predict_ex_1.osdpi and b/examples/updated/keyboard_predict_ex_1.osdpi differ diff --git a/examples/updated/utterance_Contact.osdpi b/examples/updated/utterance_Contact.osdpi index 6c50307d..0245e3ab 100644 Binary files a/examples/updated/utterance_Contact.osdpi and b/examples/updated/utterance_Contact.osdpi differ diff --git a/index.css b/index.css index 775841bb..f6eb51ae 100644 --- a/index.css +++ b/index.css @@ -35,9 +35,15 @@ div#UI { position: absolute; left: 0; top: 0; - width: 5em; padding: 0.5em; z-index: 10; + font-size: 0.6em; + background-color: white; + border: 1px solid var(--brand); +} + +#timer:empty { + display: none; } div#ErrorReport:empty { display: none; @@ -301,6 +307,7 @@ table.GridFilter td:nth-child(4) label.labeledInput { border-radius: 5px; background-color: inherit; user-select: none; + border: 1px solid black; } .grid button div { display: flex; @@ -441,6 +448,7 @@ body:not(.designing) video[dbsrc]:not([src]) { } .tabcontrol .panels { display: flex; + min-height: 0; } .tabcontrol .buttons { display: flex; @@ -630,8 +638,14 @@ div.vsd img, div.vsd video { flex: 1 1 0; object-fit: contain; - width: 100%; - height: 100%; + max-width: 100%; + max-height: 100%; +} + +div.vsd div.markers { + width: 0; + height: 0; + overflow: hidden; } div.vsd div.markers button:focus-within { @@ -786,12 +800,12 @@ details summary > * { #designer .settings[aria-selected="true"] { background-color: var(--surface1); color: var(--text1); - border: 4px dashed var(--brand); + outline: 4px dashed var(--brand); } #designer .settings:has(.settings [aria-selected="true"]) { background-color: var(--surface2); color: var(--text2); - border: 0px; + outline: 0px; box-shadow: none; } @@ -964,38 +978,36 @@ details summary > * { vertical-align: bottom; margin-right: 0.5em; } - #PleaseWait { - position: fixed; - width: 100vw; - height: 100vh; - background-color: rgb(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 100; - font-size: 2em; - transition: all 0.5s ease-in; - opacity: 1; - } - - #PleaseWait:empty { - background-color: rgb(0, 0, 0, 0); - opacity: 0; - } +#PleaseWait { + position: fixed; + width: 100vw; + height: 100vh; + background-color: rgb(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + font-size: 2em; + transition: all 0.5s ease-in; + opacity: 1; + top: 0; + left: 0; +} - #PleaseWait div { - padding: 5em; - border: 1px solid black; - background-color: white; - } +#PleaseWait:empty { + background-color: rgb(0, 0, 0, 0); + opacity: 0; +} - #PleaseWait .message { - color: blue; - } +#PleaseWait div { + padding: 5em; + border: 1px solid black; + background-color: white; +} - #PleaseWait .error { - color: red; - } +#PleaseWait .message { + color: blue; +} div.logging-indicator { position: absolute; top: 2px; @@ -1113,12 +1125,10 @@ div.actions div.scroll { background: white; } -#designer .actions tbody { +#designer .actions tbody.settings { border-top: 2px solid black; border-left: 2px solid black; border-right: 2px solid black; - position: relative; - z-index: 10; } .actions thead { border-top: 0px; @@ -1278,13 +1288,18 @@ body.HeadMouse .tracky-mouse-pointer { font-size: 2rem; } -dialog#OpenDialog { +dialog { margin: auto; } -dialog#OpenDialog button { +dialog button { margin-top: 1em; } + +dialog#ImportURL input { + width: 100%; + min-width: 50ch; +} .Menu { display: inline-block; } diff --git a/index.html b/index.html index d1f48f49..57e6667e 100644 --- a/index.html +++ b/index.html @@ -3,12 +3,11 @@ OS-DPI - - + @@ -21,6 +20,5 @@
- diff --git a/index.js b/index.js index 2ec709c6..e0256aca 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,9 @@ +function __vite__mapDeps(indexes) { + if (!__vite__mapDeps.viteFileDeps) { + __vite__mapDeps.viteFileDeps = [] + } + return indexes.map((i) => __vite__mapDeps.viteFileDeps[i]) +} true&&(function polyfill() { const relList = document.createElement('link').relList; if (relList && relList.supports && relList.supports('modulepreload')) { @@ -42,7 +48,559 @@ true&&(function polyfill() { } }()); -const site = ''; +/*! (c) Andrea Giammarchi @webreflection ISC */ +(function () { + + var attributesObserver = (function (whenDefined, MutationObserver) { + var attributeChanged = function attributeChanged(records) { + for (var i = 0, length = records.length; i < length; i++) dispatch(records[i]); + }; + var dispatch = function dispatch(_ref) { + var target = _ref.target, + attributeName = _ref.attributeName, + oldValue = _ref.oldValue; + target.attributeChangedCallback(attributeName, oldValue, target.getAttribute(attributeName)); + }; + return function (target, is) { + var attributeFilter = target.constructor.observedAttributes; + if (attributeFilter) { + whenDefined(is).then(function () { + new MutationObserver(attributeChanged).observe(target, { + attributes: true, + attributeOldValue: true, + attributeFilter: attributeFilter + }); + for (var i = 0, length = attributeFilter.length; i < length; i++) { + if (target.hasAttribute(attributeFilter[i])) dispatch({ + target: target, + attributeName: attributeFilter[i], + oldValue: null + }); + } + }); + } + return target; + }; + }); + + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + return arr2; + } + function _createForOfIteratorHelper(o, allowArrayLike) { + var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; + if (!it) { + if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { + if (it) o = it; + var i = 0; + var F = function () {}; + return { + s: F, + n: function () { + if (i >= o.length) return { + done: true + }; + return { + done: false, + value: o[i++] + }; + }, + e: function (e) { + throw e; + }, + f: F + }; + } + throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + var normalCompletion = true, + didErr = false, + err; + return { + s: function () { + it = it.call(o); + }, + n: function () { + var step = it.next(); + normalCompletion = step.done; + return step; + }, + e: function (e) { + didErr = true; + err = e; + }, + f: function () { + try { + if (!normalCompletion && it.return != null) it.return(); + } finally { + if (didErr) throw err; + } + } + }; + } + + /*! (c) Andrea Giammarchi - ISC */ + var TRUE = true, + FALSE = false, + QSA$1 = 'querySelectorAll'; + + /** + * Start observing a generic document or root element. + * @param {(node:Element, connected:boolean) => void} callback triggered per each dis/connected element + * @param {Document|Element} [root=document] by default, the global document to observe + * @param {Function} [MO=MutationObserver] by default, the global MutationObserver + * @param {string[]} [query=['*']] the selectors to use within nodes + * @returns {MutationObserver} + */ + var notify = function notify(callback) { + var root = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document; + var MO = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : MutationObserver; + var query = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ['*']; + var loop = function loop(nodes, selectors, added, removed, connected, pass) { + var _iterator = _createForOfIteratorHelper(nodes), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var node = _step.value; + if (pass || QSA$1 in node) { + if (connected) { + if (!added.has(node)) { + added.add(node); + removed["delete"](node); + callback(node, connected); + } + } else if (!removed.has(node)) { + removed.add(node); + added["delete"](node); + callback(node, connected); + } + if (!pass) loop(node[QSA$1](selectors), selectors, added, removed, connected, TRUE); + } + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + }; + var mo = new MO(function (records) { + if (query.length) { + var selectors = query.join(','); + var added = new Set(), + removed = new Set(); + var _iterator2 = _createForOfIteratorHelper(records), + _step2; + try { + for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { + var _step2$value = _step2.value, + addedNodes = _step2$value.addedNodes, + removedNodes = _step2$value.removedNodes; + loop(removedNodes, selectors, added, removed, FALSE, FALSE); + loop(addedNodes, selectors, added, removed, TRUE, FALSE); + } + } catch (err) { + _iterator2.e(err); + } finally { + _iterator2.f(); + } + } + }); + var observe = mo.observe; + (mo.observe = function (node) { + return observe.call(mo, node, { + subtree: TRUE, + childList: TRUE + }); + })(root); + return mo; + }; + + var QSA = 'querySelectorAll'; + var _self$1 = self, + document$2 = _self$1.document, + Element$1 = _self$1.Element, + MutationObserver$2 = _self$1.MutationObserver, + Set$2 = _self$1.Set, + WeakMap$1 = _self$1.WeakMap; + var elements = function elements(element) { + return QSA in element; + }; + var filter = [].filter; + var qsaObserver = (function (options) { + var live = new WeakMap$1(); + var drop = function drop(elements) { + for (var i = 0, length = elements.length; i < length; i++) live["delete"](elements[i]); + }; + var flush = function flush() { + var records = observer.takeRecords(); + for (var i = 0, length = records.length; i < length; i++) { + parse(filter.call(records[i].removedNodes, elements), false); + parse(filter.call(records[i].addedNodes, elements), true); + } + }; + var matches = function matches(element) { + return element.matches || element.webkitMatchesSelector || element.msMatchesSelector; + }; + var notifier = function notifier(element, connected) { + var selectors; + if (connected) { + for (var q, m = matches(element), i = 0, length = query.length; i < length; i++) { + if (m.call(element, q = query[i])) { + if (!live.has(element)) live.set(element, new Set$2()); + selectors = live.get(element); + if (!selectors.has(q)) { + selectors.add(q); + options.handle(element, connected, q); + } + } + } + } else if (live.has(element)) { + selectors = live.get(element); + live["delete"](element); + selectors.forEach(function (q) { + options.handle(element, connected, q); + }); + } + }; + var parse = function parse(elements) { + var connected = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + for (var i = 0, length = elements.length; i < length; i++) notifier(elements[i], connected); + }; + var query = options.query; + var root = options.root || document$2; + var observer = notify(notifier, root, MutationObserver$2, query); + var attachShadow = Element$1.prototype.attachShadow; + if (attachShadow) Element$1.prototype.attachShadow = function (init) { + var shadowRoot = attachShadow.call(this, init); + observer.observe(shadowRoot); + return shadowRoot; + }; + if (query.length) parse(root[QSA](query)); + return { + drop: drop, + flush: flush, + observer: observer, + parse: parse + }; + }); + + var _self = self, + document$1 = _self.document, + Map = _self.Map, + MutationObserver$1 = _self.MutationObserver, + Object$1 = _self.Object, + Set$1 = _self.Set, + WeakMap = _self.WeakMap, + Element = _self.Element, + HTMLElement = _self.HTMLElement, + Node = _self.Node, + Error = _self.Error, + TypeError$1 = _self.TypeError, + Reflect = _self.Reflect; + var defineProperty = Object$1.defineProperty, + keys = Object$1.keys, + getOwnPropertyNames = Object$1.getOwnPropertyNames, + setPrototypeOf = Object$1.setPrototypeOf; + var legacy = !self.customElements; + var expando = function expando(element) { + var key = keys(element); + var value = []; + var ignore = new Set$1(); + var length = key.length; + for (var i = 0; i < length; i++) { + value[i] = element[key[i]]; + try { + delete element[key[i]]; + } catch (SafariTP) { + ignore.add(i); + } + } + return function () { + for (var _i = 0; _i < length; _i++) ignore.has(_i) || (element[key[_i]] = value[_i]); + }; + }; + if (legacy) { + var HTMLBuiltIn = function HTMLBuiltIn() { + var constructor = this.constructor; + if (!classes.has(constructor)) throw new TypeError$1('Illegal constructor'); + var is = classes.get(constructor); + if (override) return augment(override, is); + var element = createElement.call(document$1, is); + return augment(setPrototypeOf(element, constructor.prototype), is); + }; + var createElement = document$1.createElement; + var classes = new Map(); + var defined = new Map(); + var prototypes = new Map(); + var registry = new Map(); + var query = []; + var handle = function handle(element, connected, selector) { + var proto = prototypes.get(selector); + if (connected && !proto.isPrototypeOf(element)) { + var redefine = expando(element); + override = setPrototypeOf(element, proto); + try { + new proto.constructor(); + } finally { + override = null; + redefine(); + } + } + var method = "".concat(connected ? '' : 'dis', "connectedCallback"); + if (method in proto) element[method](); + }; + var _qsaObserver = qsaObserver({ + query: query, + handle: handle + }), + parse = _qsaObserver.parse; + var override = null; + var whenDefined = function whenDefined(name) { + if (!defined.has(name)) { + var _, + $ = new Promise(function ($) { + _ = $; + }); + defined.set(name, { + $: $, + _: _ + }); + } + return defined.get(name).$; + }; + var augment = attributesObserver(whenDefined, MutationObserver$1); + self.customElements = { + define: function define(is, Class) { + if (registry.has(is)) throw new Error("the name \"".concat(is, "\" has already been used with this registry")); + classes.set(Class, is); + prototypes.set(is, Class.prototype); + registry.set(is, Class); + query.push(is); + whenDefined(is).then(function () { + parse(document$1.querySelectorAll(is)); + }); + defined.get(is)._(Class); + }, + get: function get(is) { + return registry.get(is); + }, + whenDefined: whenDefined + }; + defineProperty(HTMLBuiltIn.prototype = HTMLElement.prototype, 'constructor', { + value: HTMLBuiltIn + }); + self.HTMLElement = HTMLBuiltIn; + document$1.createElement = function (name, options) { + var is = options && options.is; + var Class = is ? registry.get(is) : registry.get(name); + return Class ? new Class() : createElement.call(document$1, name); + }; + // in case ShadowDOM is used through a polyfill, to avoid issues + // with builtin extends within shadow roots + if (!('isConnected' in Node.prototype)) defineProperty(Node.prototype, 'isConnected', { + configurable: true, + get: function get() { + return !(this.ownerDocument.compareDocumentPosition(this) & this.DOCUMENT_POSITION_DISCONNECTED); + } + }); + } else { + legacy = !self.customElements.get('extends-br'); + if (legacy) { + try { + var BR = function BR() { + return self.Reflect.construct(HTMLBRElement, [], BR); + }; + BR.prototype = HTMLLIElement.prototype; + var is = 'extends-br'; + self.customElements.define('extends-br', BR, { + 'extends': 'br' + }); + legacy = document$1.createElement('br', { + is: is + }).outerHTML.indexOf(is) < 0; + var _self$customElements = self.customElements, + get = _self$customElements.get, + _whenDefined = _self$customElements.whenDefined; + self.customElements.whenDefined = function (is) { + var _this = this; + return _whenDefined.call(this, is).then(function (Class) { + return Class || get.call(_this, is); + }); + }; + } catch (o_O) {} + } + } + if (legacy) { + var _parseShadow = function _parseShadow(element) { + var root = shadowRoots.get(element); + _parse(root.querySelectorAll(this), element.isConnected); + }; + var customElements = self.customElements; + var _createElement = document$1.createElement; + var define = customElements.define, + _get = customElements.get, + upgrade = customElements.upgrade; + var _ref = Reflect || { + construct: function construct(HTMLElement) { + return HTMLElement.call(this); + } + }, + construct = _ref.construct; + var shadowRoots = new WeakMap(); + var shadows = new Set$1(); + var _classes = new Map(); + var _defined = new Map(); + var _prototypes = new Map(); + var _registry = new Map(); + var shadowed = []; + var _query = []; + var getCE = function getCE(is) { + return _registry.get(is) || _get.call(customElements, is); + }; + var _handle = function _handle(element, connected, selector) { + var proto = _prototypes.get(selector); + if (connected && !proto.isPrototypeOf(element)) { + var redefine = expando(element); + _override = setPrototypeOf(element, proto); + try { + new proto.constructor(); + } finally { + _override = null; + redefine(); + } + } + var method = "".concat(connected ? '' : 'dis', "connectedCallback"); + if (method in proto) element[method](); + }; + var _qsaObserver2 = qsaObserver({ + query: _query, + handle: _handle + }), + _parse = _qsaObserver2.parse; + var _qsaObserver3 = qsaObserver({ + query: shadowed, + handle: function handle(element, connected) { + if (shadowRoots.has(element)) { + if (connected) shadows.add(element);else shadows["delete"](element); + if (_query.length) _parseShadow.call(_query, element); + } + } + }), + parseShadowed = _qsaObserver3.parse; + // qsaObserver also patches attachShadow + // be sure this runs *after* that + var attachShadow = Element.prototype.attachShadow; + if (attachShadow) Element.prototype.attachShadow = function (init) { + var root = attachShadow.call(this, init); + shadowRoots.set(this, root); + return root; + }; + var _whenDefined2 = function _whenDefined2(name) { + if (!_defined.has(name)) { + var _, + $ = new Promise(function ($) { + _ = $; + }); + _defined.set(name, { + $: $, + _: _ + }); + } + return _defined.get(name).$; + }; + var _augment = attributesObserver(_whenDefined2, MutationObserver$1); + var _override = null; + getOwnPropertyNames(self).filter(function (k) { + return /^HTML.*Element$/.test(k); + }).forEach(function (k) { + var HTMLElement = self[k]; + function HTMLBuiltIn() { + var constructor = this.constructor; + if (!_classes.has(constructor)) throw new TypeError$1('Illegal constructor'); + var _classes$get = _classes.get(constructor), + is = _classes$get.is, + tag = _classes$get.tag; + if (is) { + if (_override) return _augment(_override, is); + var element = _createElement.call(document$1, tag); + element.setAttribute('is', is); + return _augment(setPrototypeOf(element, constructor.prototype), is); + } else return construct.call(this, HTMLElement, [], constructor); + } + + defineProperty(HTMLBuiltIn.prototype = HTMLElement.prototype, 'constructor', { + value: HTMLBuiltIn + }); + defineProperty(self, k, { + value: HTMLBuiltIn + }); + }); + document$1.createElement = function (name, options) { + var is = options && options.is; + if (is) { + var Class = _registry.get(is); + if (Class && _classes.get(Class).tag === name) return new Class(); + } + var element = _createElement.call(document$1, name); + if (is) element.setAttribute('is', is); + return element; + }; + customElements.get = getCE; + customElements.whenDefined = _whenDefined2; + customElements.upgrade = function (element) { + var is = element.getAttribute('is'); + if (is) { + var _constructor = _registry.get(is); + if (_constructor) { + _augment(setPrototypeOf(element, _constructor.prototype), is); + // apparently unnecessary because this is handled by qsa observer + // if (element.isConnected && element.connectedCallback) + // element.connectedCallback(); + return; + } + } + upgrade.call(customElements, element); + }; + customElements.define = function (is, Class, options) { + if (getCE(is)) throw new Error("'".concat(is, "' has already been defined as a custom element")); + var selector; + var tag = options && options["extends"]; + _classes.set(Class, tag ? { + is: is, + tag: tag + } : { + is: '', + tag: is + }); + if (tag) { + selector = "".concat(tag, "[is=\"").concat(is, "\"]"); + _prototypes.set(selector, Class.prototype); + _registry.set(is, Class); + _query.push(selector); + } else { + define.apply(customElements, arguments); + shadowed.push(selector = is); + } + _whenDefined2(is).then(function () { + if (tag) { + _parse(document$1.querySelectorAll(selector)); + shadows.forEach(_parseShadow, [selector]); + } else parseShadowed(document$1.querySelectorAll(selector)); + }); + _defined.get(is)._(Class); + }; + } + +})(); var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; @@ -3148,314 +3706,32 @@ function requireStacktraceGps () { var stacktraceExports = stacktrace.exports; -const { isArray: isArray$3 } = Array; -const { getPrototypeOf: getPrototypeOf$1, getOwnPropertyDescriptor } = Object; - -const empty = []; - -const newRange = () => document.createRange(); - -/** - * Set the `key` `value` pair to the *Map* or *WeakMap* and returns the `value` - * @template T - * @param {Map | WeakMap} map - * @param {any} key - * @param {T} value - * @returns {T} - */ -const set = (map, key, value) => { - map.set(key, value); - return value; -}; - -const gPD = (ref, prop) => { - let desc; - do { desc = getOwnPropertyDescriptor(ref, prop); } - while(!desc && (ref = getPrototypeOf$1(ref))); - return desc; -}; - -/** @typedef {import("domconstants/constants").ATTRIBUTE_NODE} ATTRIBUTE_NODE */ -/** @typedef {import("domconstants/constants").TEXT_NODE} TEXT_NODE */ -/** @typedef {import("domconstants/constants").COMMENT_NODE} COMMENT_NODE */ -/** @typedef {ATTRIBUTE_NODE | TEXT_NODE | COMMENT_NODE} Type */ - -/** @typedef {import("./persistent-fragment.js").PersistentFragment} PersistentFragment */ -/** @typedef {import("./rabbit.js").Hole} Hole */ - -/** @typedef {unknown} Value */ -/** @typedef {Node | Element | PersistentFragment} Target */ -/** @typedef {null | undefined | string | number | boolean | Node | Element | PersistentFragment} DOMValue */ - -/** - * @typedef {Object} Entry - * @property {Type} type - * @property {number[]} path - * @property {function} update - * @property {string} name - */ - -/** - * @param {PersistentFragment} c content retrieved from the template - * @param {Entry[]} e entries per each hole in the template - * @param {number} l the length of content childNodes - * @returns - */ -const cel = (c, e, l) => ({ c, e, l }); - -/** - * @typedef {Object} HoleDetails - * @property {null | Node | PersistentFragment} n the current live node, if any and not the `t` one - */ - -/** @type {() => HoleDetails} */ -const comment = () => ({ n: null }); - -/** - * @typedef {Object} Detail - * @property {any} v the current value of the interpolation / hole - * @property {function} u the callback to update the value - * @property {Node} t the target comment node or element - * @property {string} n the name of the attribute, if any - */ - -/** - * @param {any} v the current value of the interpolation / hole - * @param {function} u the callback to update the value - * @param {Node} t the target comment node or element - * @param {string} n the name of the attribute, if any - * @returns {Detail} - */ -const detail = (v, u, t, n) => ({ v, u, t, n }); - -/** - * @param {Type} t the operation type - * @param {number[]} p the path to retrieve the node - * @param {function} u the update function - * @param {string} n the attribute name, if any - * @returns {Entry} - */ -const entry = (t, p, u, n = '') => ({ t, p, u, n }); - -/** - * @typedef {Object} Cache - * @property {Cache[]} s the stack of caches per each interpolation / hole - * @property {null | TemplateStringsArray} t the cached template - * @property {null | Node | PersistentFragment} n the node returned when parsing the template - * @property {Detail[]} d the list of updates to perform - */ - -/** - * @param {Cache[]} s the cache stack - * @returns {Cache} - */ -const cache$1 = s => ({ s, t: null, n: null, d: empty}); - -/** - * @typedef {Object} Parsed - * @property {Node | PersistentFragment} n the returned node after parsing the template - * @property {Detail[]} d the list of details to update the node - */ - /** - * @param {Node | PersistentFragment} n the returned node after parsing the template - * @param {Detail[]} d the list of details to update the node - * @returns {Parsed} + * ISC License + * + * Copyright (c) 2020, Andrea Giammarchi, @WebReflection + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. */ -const parsed = (n, d) => ({ n, d }); - -const ATTRIBUTE_NODE = 2; -const TEXT_NODE = 3; -const COMMENT_NODE = 8; -const DOCUMENT_FRAGMENT_NODE = 11; - -/*! (c) Andrea Giammarchi - ISC */ -const {setPrototypeOf} = Object; /** - * @param {Function} Class any base class to extend without passing through it via super() call. - * @returns {Function} an extensible class for the passed one. - * @example - * // creating this very same module utility - * import custom from 'custom-function/factory'; - * const CustomFunction = custom(Function); - * class MyFunction extends CustomFunction {} - * const mf = new MyFunction(() => {}); - */ -const custom = Class => { - function Custom(target) { - return setPrototypeOf(target, new.target.prototype); - } - Custom.prototype = Class.prototype; - return Custom; -}; - -let range$1; -/** - * @param {Node | Element} firstChild - * @param {Node | Element} lastChild - * @param {boolean} preserve - * @returns - */ -const drop = (firstChild, lastChild, preserve) => { - if (!range$1) range$1 = newRange(); - if (preserve) - range$1.setStartAfter(firstChild); - else - range$1.setStartBefore(firstChild); - range$1.setEndAfter(lastChild); - range$1.deleteContents(); - return firstChild; -}; - -/** - * @param {PersistentFragment} fragment - * @returns {Node | Element} - */ -const remove = ({firstChild, lastChild}, preserve) => drop(firstChild, lastChild, preserve); - -let checkType = false; - -/** - * @param {Node} node - * @param {1 | 0 | -0 | -1} operation - * @returns {Node} - */ -const diffFragment = (node, operation) => ( - checkType && node.nodeType === DOCUMENT_FRAGMENT_NODE ? - ((1 / operation) < 0 ? - (operation ? remove(node, true) : node.lastChild) : - (operation ? node.valueOf() : node.firstChild)) : - node -); - -/** @extends {DocumentFragment} */ -class PersistentFragment extends custom(DocumentFragment) { - #nodes; - #length; - constructor(fragment) { - const _nodes = [...fragment.childNodes]; - super(fragment); - this.#nodes = _nodes; - this.#length = _nodes.length; - checkType = true; - } - get firstChild() { return this.#nodes[0]; } - get lastChild() { return this.#nodes.at(-1); } - get parentNode() { return this.#nodes[0].parentNode; } - remove() { - remove(this, false); - } - replaceWith(node) { - remove(this, true).replaceWith(node); - } - valueOf() { - if (this.childNodes.length !== this.#length) - this.replaceChildren(...this.#nodes); - return this; - } -} - -/** - * @param {DocumentFragment} content - * @param {number[]} path - * @returns {Element} - */ -const find = (content, path) => path.reduceRight(childNodesIndex, content); -const childNodesIndex = (node, i) => node.childNodes[i]; - -/** @param {(template: TemplateStringsArray, values: any[]) => import("./parser.js").Resolved} parse */ -const create = parse => ( - /** @param {(template: TemplateStringsArray, values: any[]) => import("./literals.js").Parsed} parse */ - (template, values) => { - const { c: content, e: entries, l: length } = parse(template, values); - const root = content.cloneNode(true); - // reverse loop to avoid missing paths while populating - // TODO: is it even worth to pre-populate nodes? see rabbit.js too - let current, prev, i = entries.length, details = i ? entries.slice(0) : empty; - while (i--) { - const { t: type, p: path, u: update, n: name } = entries[i]; - const node = path === prev ? current : (current = find(root, (prev = path))); - const callback = type === COMMENT_NODE ? update() : update; - details[i] = detail(callback(node, values[i], name, empty), callback, node, name); - } - return parsed( - length === 1 ? root.firstChild : new PersistentFragment(root), - details - ); - } -); - -const TEXT_ELEMENTS = /^(?:plaintext|script|style|textarea|title|xmp)$/i; -const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i; - -/*! (c) Andrea Giammarchi - ISC */ - -const elements = /<([a-zA-Z0-9]+[a-zA-Z0-9:._-]*)([^>]*?)(\/?)>/g; -const attributes = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g; -const holes = /[\x01\x02]/g; - -// \x01 Node.ELEMENT_NODE -// \x02 Node.ATTRIBUTE_NODE - -/** - * Given a template, find holes as both nodes and attributes and - * return a string with holes as either comment nodes or named attributes. - * @param {string[]} template a template literal tag array - * @param {string} prefix prefix to use per each comment/attribute - * @param {boolean} xml enforces self-closing tags - * @returns {string} X/HTML with prefixed comments or attributes - */ -const parser$1 = (template, prefix, xml) => { - let i = 0; - return template - .join('\x01') - .trim() - .replace( - elements, - (_, name, attrs, selfClosing) => `<${ - name - }${ - attrs.replace(attributes, '\x02=$2$1').trimEnd() - }${ - selfClosing ? ( - (xml || VOID_ELEMENTS.test(name)) ? ' /' : `>` - ) - .replace( - holes, - hole => hole === '\x01' ? `` : (prefix + i++) - ) - ; -}; - -/** - * ISC License - * - * Copyright (c) 2020, Andrea Giammarchi, @WebReflection - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE - * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - -/** - * @param {Node} parentNode The container where children live - * @param {Node[]} a The list of current/live children - * @param {Node[]} b The list of future children - * @param {(entry: Node, action: number) => Node} get - * The callback invoked per each entry related DOM operation. - * @param {Node} [before] The optional node used as anchor to insert before. - * @returns {Node[]} The same list of future children. + * @param {Node} parentNode The container where children live + * @param {Node[]} a The list of current/live children + * @param {Node[]} b The list of future children + * @param {(entry: Node, action: number) => Node} get + * The callback invoked per each entry related DOM operation. + * @param {Node} [before] The optional node used as anchor to insert before. + * @returns {Node[]} The same list of future children. */ const udomdiff = (parentNode, a, b, get, before) => { const bLength = b.length; @@ -3588,104 +3864,259 @@ const udomdiff = (parentNode, a, b, get, before) => { return b; }; -const setAttribute = (element, name, value) => - element.setAttribute(name, value); +const { isArray: isArray$3 } = Array; +const { getPrototypeOf: getPrototypeOf$1, getOwnPropertyDescriptor } = Object; -const removeAttribute = (element, name) => - element.removeAttribute(name); +const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; + +const empty = []; + +const newRange = () => document.createRange(); /** + * Set the `key` `value` pair to the *Map* or *WeakMap* and returns the `value` * @template T - * @param {Element} element + * @param {Map | WeakMap} map + * @param {any} key * @param {T} value * @returns {T} */ -const aria = (element, value) => { - for (const key in value) { - const $ = value[key]; - const name = key === 'role' ? key : `aria-${key}`; - if ($ == null) removeAttribute(element, name); - else setAttribute(element, name, $); - } +const set = (map, key, value) => { + map.set(key, value); return value; }; -const arrayComment = () => array; - -let listeners; - /** - * @template T - * @param {Element} element - * @param {T} value - * @param {string} name - * @returns {T} + * Return a descriptor, if any, for the referenced *Element* + * @param {Element} ref + * @param {string} prop + * @returns */ -const at = (element, value, name) => { - name = name.slice(1); - if (!listeners) listeners = new WeakMap; - const known = listeners.get(element) || set(listeners, element, {}); - let current = known[name]; - if (current && current[0]) element.removeEventListener(name, ...current); - current = isArray$3(value) ? value : [value, false]; - known[name] = current; - if (current[0]) element.addEventListener(name, ...current); - return value; +const gPD = (ref, prop) => { + let desc; + do { desc = getOwnPropertyDescriptor(ref, prop); } + while(!desc && (ref = getPrototypeOf$1(ref))); + return desc; }; +const ELEMENT_NODE = 1; +const COMMENT_NODE = 8; +const DOCUMENT_FRAGMENT_NODE = 11; + +/*! (c) Andrea Giammarchi - ISC */ +const {setPrototypeOf} = Object; + /** - * @template T - * @this {import("./literals.js").HoleDetails} - * @param {Node} node - * @param {T} value - * @returns {T} + * @param {Function} Class any base class to extend without passing through it via super() call. + * @returns {Function} an extensible class for the passed one. + * @example + * // creating this very same module utility + * import custom from 'custom-function/factory'; + * const CustomFunction = custom(Function); + * class MyFunction extends CustomFunction {} + * const mf = new MyFunction(() => {}); */ -function hole(node, value) { - const n = this.n || (this.n = node); - switch (typeof value) { - case 'string': - case 'number': - case 'boolean': { - if (n !== node) n.replaceWith((this.n = node)); - this.n.data = value; - break; - } - case 'object': - case 'undefined': { - if (value == null) (this.n = node).data = ''; - else this.n = value.valueOf(); - n.replaceWith(this.n); - break; - } +const custom = Class => { + function Custom(target) { + return setPrototypeOf(target, new.target.prototype); } - return value; -} -const boundComment = () => hole.bind(comment()); + Custom.prototype = Class.prototype; + return Custom; +}; +let range$1; /** - * @template T - * @param {Element} element - * @param {T} value - * @returns {T} + * @param {Node | Element} firstChild + * @param {Node | Element} lastChild + * @param {boolean} preserve + * @returns */ -const className = (element, value) => maybeDirect( - element, value, value == null ? 'class' : 'className' -); +const drop = (firstChild, lastChild, preserve) => { + if (!range$1) range$1 = newRange(); + /* c8 ignore start */ + if (preserve) + range$1.setStartAfter(firstChild); + else + range$1.setStartBefore(firstChild); + /* c8 ignore stop */ + range$1.setEndAfter(lastChild); + range$1.deleteContents(); + return firstChild; +}; /** - * @template T - * @param {Element} element - * @param {T} value - * @returns {T} + * @param {PersistentFragment} fragment + * @returns {Node | Element} */ -const data = (element, value) => { - const { dataset } = element; - for (const key in value) { - if (value[key] == null) delete dataset[key]; - else dataset[key] = value[key]; - } - return value; -}; +const remove = ({firstChild, lastChild}, preserve) => drop(firstChild, lastChild, preserve); + +let checkType = false; + +/** + * @param {Node} node + * @param {1 | 0 | -0 | -1} operation + * @returns {Node} + */ +const diffFragment = (node, operation) => ( + checkType && node.nodeType === DOCUMENT_FRAGMENT_NODE ? + ((1 / operation) < 0 ? + (operation ? remove(node, true) : node.lastChild) : + (operation ? node.valueOf() : node.firstChild)) : + node +); + +const comment = value => document.createComment(value); + +/** @extends {DocumentFragment} */ +class PersistentFragment extends custom(DocumentFragment) { + #firstChild = comment('<>'); + #lastChild = comment(''); + #nodes = empty; + constructor(fragment) { + super(fragment); + this.replaceChildren(...[ + this.#firstChild, + ...fragment.childNodes, + this.#lastChild, + ]); + checkType = true; + } + get firstChild() { return this.#firstChild; } + get lastChild() { return this.#lastChild; } + get parentNode() { return this.#firstChild.parentNode; } + remove() { + remove(this, false); + } + replaceWith(node) { + remove(this, true).replaceWith(node); + } + valueOf() { + let { firstChild, lastChild, parentNode } = this; + if (parentNode === this) { + if (this.#nodes === empty) + this.#nodes = [...this.childNodes]; + } + else { + /* c8 ignore start */ + // there are cases where a fragment might be just appended + // out of the box without valueOf() invoke (first render). + // When these are moved around and lose their parent and, + // such parent is not the fragment itself, it's possible there + // where changes or mutations in there to take care about. + // This is a render-only specific issue but it's tested and + // it's worth fixing to me to have more consistent fragments. + if (parentNode) { + this.#nodes = [firstChild]; + while (firstChild !== lastChild) + this.#nodes.push((firstChild = firstChild.nextSibling)); + } + /* c8 ignore stop */ + this.replaceChildren(...this.#nodes); + } + return this; + } +} + +const setAttribute = (element, name, value) => + element.setAttribute(name, value); + +/** + * @param {Element} element + * @param {string} name + * @returns {void} + */ +const removeAttribute = (element, name) => + element.removeAttribute(name); + +/** + * @template T + * @param {Element} element + * @param {T} value + * @returns {T} + */ +const aria = (element, value) => { + for (const key in value) { + const $ = value[key]; + const name = key === 'role' ? key : `aria-${key}`; + if ($ == null) removeAttribute(element, name); + else setAttribute(element, name, $); + } + return value; +}; + +let listeners; + +/** + * @template T + * @param {Element} element + * @param {T} value + * @param {string} name + * @returns {T} + */ +const at = (element, value, name) => { + name = name.slice(1); + if (!listeners) listeners = new WeakMap; + const known = listeners.get(element) || set(listeners, element, {}); + let current = known[name]; + if (current && current[0]) element.removeEventListener(name, ...current); + current = isArray$3(value) ? value : [value, false]; + known[name] = current; + if (current[0]) element.addEventListener(name, ...current); + return value; +}; + +/** + * @template T + * @param {import("./literals.js").Detail} detail + * @param {T} value + * @returns {T} + */ +const hole = (detail, value) => { + const { t: node, n: hole } = detail; + let nullish = false; + switch (typeof value) { + case 'object': + if (value !== null) { + (hole || node).replaceWith((detail.n = value.valueOf())); + break; + } + case 'undefined': + nullish = true; + default: + node.data = nullish ? '' : value; + if (hole) { + detail.n = null; + hole.replaceWith(node); + } + break; + } + return value; +}; + +/** + * @template T + * @param {Element} element + * @param {T} value + * @returns {T} + */ +const className = (element, value) => maybeDirect( + element, value, value == null ? 'class' : 'className' +); + +/** + * @template T + * @param {Element} element + * @param {T} value + * @returns {T} + */ +const data = (element, value) => { + const { dataset } = element; + for (const key in value) { + if (value[key] == null) delete dataset[key]; + else dataset[key] = value[key]; + } + return value; +}; /** * @template T @@ -3775,11 +4206,29 @@ const toggle = (element, value, name) => ( * @param {Node[]} prev * @returns {Node[]} */ -const array = (node, value, _, prev) => ( - value.length ? - udomdiff(node.parentNode, prev, value, diffFragment, node) : - (prev.length && drop(prev[0], prev.at(-1), false), empty) -); +const array = (node, value, prev) => { + // normal diff + const { length } = value; + node.data = `[${length}]`; + if (length) + return udomdiff(node.parentNode, prev, value, diffFragment, node); + /* c8 ignore start */ + switch (prev.length) { + case 1: + prev[0].remove(); + case 0: + break; + default: + drop( + diffFragment(prev[0], 0), + diffFragment(prev.at(-1), -0), + false + ); + break; + } + /* c8 ignore stop */ + return empty; +}; const attr = new Map([ ['aria', aria], @@ -3827,6 +4276,135 @@ const text = (element, value) => ( value ); +/** @typedef {import("./persistent-fragment.js").PersistentFragment} PersistentFragment */ +/** @typedef {import("./rabbit.js").Hole} Hole */ + +/** @typedef {unknown} Value */ +/** @typedef {Node | Element | PersistentFragment} Target */ +/** @typedef {null | undefined | string | number | boolean | Node | Element | PersistentFragment} DOMValue */ +/** @typedef {Hole | Node} ArrayValue */ + +const abc = (a, b, c) => ({ a, b, c }); + +const bc = (b, c) => ({ b, c }); + +/** + * @typedef {Object} Detail + * @property {any} v the current value of the interpolation / hole + * @property {function} u the callback to update the value + * @property {Node} t the target comment node or element + * @property {string | null | Node} n the attribute name, if any, or `null` + * @property {Cache | ArrayValue[] | null} c the cache value for this detail + */ + +/** + * @returns {Detail} + */ +const detail = (u, t, n, c) => ({ v: empty, u, t, n, c }); + +/** + * @typedef {Object} Entry + * @property {number[]} a the path to retrieve the node + * @property {function} b the update function + * @property {string | null} c the attribute name, if any, or `null` + */ + +/** + * @typedef {Object} Cache + * @property {null | TemplateStringsArray} a the cached template + * @property {null | Node | PersistentFragment} b the node returned when parsing the template + * @property {Detail[]} c the list of updates to perform + */ + +/** + * @returns {Cache} + */ +const cache$1 = () => abc(null, null, empty); + +/** + * @param {DocumentFragment} content + * @param {number[]} path + * @returns {Element} + */ +const find = (content, path) => path.reduceRight(childNodesIndex, content); +const childNodesIndex = (node, i) => node.childNodes[i]; + +/** @param {(template: TemplateStringsArray, values: any[]) => import("./parser.js").Resolved} parse */ +const create = parse => ( + /** + * @param {TemplateStringsArray} template + * @param {any[]} values + * @returns {import("./literals.js").Cache} + */ + (template, values) => { + const { a: fragment, b: entries, c: direct } = parse(template, values); + const root = document.importNode(fragment, true); + /** @type {import("./literals.js").Detail[]} */ + let details = empty; + if (entries !== empty) { + details = []; + for (let current, prev, i = 0; i < entries.length; i++) { + const { a: path, b: update, c: name } = entries[i]; + const node = path === prev ? current : (current = find(root, (prev = path))); + details[i] = detail( + update, + node, + name, + update === array ? [] : (update === hole ? cache$1() : null) + ); + } + } + return bc( + direct ? root.firstChild : new PersistentFragment(root), + details, + ); + } +); + +const TEXT_ELEMENTS = /^(?:plaintext|script|style|textarea|title|xmp)$/i; +const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i; + +/*! (c) Andrea Giammarchi - ISC */ + +const elements = /<([a-zA-Z0-9]+[a-zA-Z0-9:._-]*)([^>]*?)(\/?)>/g; +const attributes = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g; +const holes = /[\x01\x02]/g; + +// \x01 Node.ELEMENT_NODE +// \x02 Node.ATTRIBUTE_NODE + +/** + * Given a template, find holes as both nodes and attributes and + * return a string with holes as either comment nodes or named attributes. + * @param {string[]} template a template literal tag array + * @param {string} prefix prefix to use per each comment/attribute + * @param {boolean} xml enforces self-closing tags + * @returns {string} X/HTML with prefixed comments or attributes + */ +const parser$1 = (template, prefix, xml) => { + let i = 0; + return template + .join('\x01') + .trim() + .replace( + elements, + (_, name, attrs, selfClosing) => `<${ + name + }${ + attrs.replace(attributes, '\x02=$2$1').trimEnd() + }${ + selfClosing ? ( + (xml || VOID_ELEMENTS.test(name)) ? ' /' : `>` + ) + .replace( + holes, + hole => hole === '\x01' ? `` : (prefix + i++) + ) + ; +}; + let template = document.createElement('template'), svg, range; /** @@ -3837,7 +4415,7 @@ let template = document.createElement('template'), svg, range; const createContent = (text, xml) => { if (xml) { if (!svg) { - svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg = document.createElementNS(SVG_NAMESPACE, 'svg'); range = newRange(); range.selectNodeContents(svg); } @@ -3853,10 +4431,9 @@ const createContent = (text, xml) => { /** * @typedef {Object} Resolved - * @property {DocumentFragment} content - * @property {Entry[]} entries - * @property {function[]} updates - * @property {number} length + * @param {DocumentFragment} f content retrieved from the template + * @param {Entry[]} e entries per each hole in the template + * @param {boolean} d direct node to handle */ /** @@ -3873,6 +4450,8 @@ const createPath = node => { return path; }; +const textNode = () => document.createTextNode(''); + /** * @param {TemplateStringsArray} template * @param {boolean} xml @@ -3881,46 +4460,73 @@ const createPath = node => { const resolve = (template, values, xml) => { const content = createContent(parser$1(template, prefix, xml), xml); const { length } = template; - let asArray = false, entries = empty; + let entries = empty; if (length > 1) { - const tw = document.createTreeWalker(content, 1 | 128); const replace = []; + const tw = document.createTreeWalker(content, 1 | 128); let i = 0, search = `${prefix}${i++}`; entries = []; while (i < length) { const node = tw.nextNode(); + // these are holes or arrays if (node.nodeType === COMMENT_NODE) { if (node.data === search) { - let update = isArray$3(values[i - 1]) ? arrayComment : boundComment; - if (update === boundComment) replace.push(node); - else asArray = true; - entries.push(entry(COMMENT_NODE, createPath(node), update)); + // ⚠️ once array, always array! + const update = isArray$3(values[i - 1]) ? array : hole; + if (update === hole) replace.push(node); + entries.push(abc(createPath(node), update, null)); search = `${prefix}${i++}`; } } else { let path; + // these are attributes while (node.hasAttribute(search)) { if (!path) path = createPath(node); const name = node.getAttribute(search); - entries.push(entry(ATTRIBUTE_NODE, path, attribute(node, name, xml), name)); + entries.push(abc(path, attribute(node, name, xml), name)); removeAttribute(node, search); search = `${prefix}${i++}`; } + // these are special text-only nodes if ( + !xml && TEXT_ELEMENTS.test(node.localName) && node.textContent.trim() === `` ) { - entries.push(entry(TEXT_NODE, path || createPath(node), text)); + entries.push(abc(path || createPath(node), text, null)); search = `${prefix}${i++}`; } } } + // can't replace holes on the fly or the tree walker fails for (i = 0; i < replace.length; i++) - replace[i].replaceWith(document.createTextNode('')); + replace[i].replaceWith(textNode()); + } + + // need to decide if there should be a persistent fragment + const { childNodes } = content; + let { length: len } = childNodes; + + // html`` or svg`` to signal an empty content + // these nodes can be passed directly as never mutated + if (len < 1) { + len = 1; + content.appendChild(textNode()); } - const l = content.childNodes.length; - return set(cache, template, cel(content, entries, l === 1 && asArray ? 0 : l)); + // html`${'b'}` or svg`${[]}` cases + else if ( + len === 1 && + // ignore html`static` or svg`static` because + // these nodes can be passed directly as never mutated + length !== 1 && + childNodes[0].nodeType !== ELEMENT_NODE + ) { + // use a persistent fragment for these cases too + len = 0; + } + + return set(cache, template, abc(content, entries, len === 1)); }; /** @type {WeakMap} */ @@ -3937,32 +4543,43 @@ const parseHTML = create(parser(false)); const parseSVG = create(parser(true)); /** - * @param {import("./literals.js").Cache} cache + * @param {import("./literals.js").Cache} info * @param {Hole} hole * @returns {Node} */ -const unroll = (cache, { s: svg, t: template, v: values }) => { - if (values.length && cache.s === empty) cache.s = []; - const length = unrollValues(cache, values); - if (cache.t !== template) { - const { n: node, d: details } = (svg ? parseSVG : parseHTML)(template, values); - cache.t = template; - cache.n = node; - cache.d = details; - } - else { - const { d: details } = cache; - for (let i = 0; i < length; i++) { - const value = values[i]; - const detail = details[i]; - const { v: previous } = detail; - if (value !== previous) { - const { u: update, t: target, n: name } = detail; - detail.v = update(target, value, name, previous); - } +const unroll = (info, { s, t, v }) => { + if (info.a !== t) { + const { b, c } = (s ? parseSVG : parseHTML)(t, v); + info.a = t; + info.b = b; + info.c = c; + } + for (let { c } = info, i = 0; i < c.length; i++) { + const value = v[i]; + const detail = c[i]; + switch (detail.u) { + case array: + detail.v = array( + detail.t, + unrollValues(detail.c, value), + detail.v + ); + break; + case hole: + const current = value instanceof Hole ? + unroll(detail.c || (detail.c = cache$1()), value) : + (detail.c = null, value) + ; + if (current !== detail.v) + detail.v = hole(detail, current); + break; + default: + if (value !== detail.v) + detail.v = detail.u(detail.t, value, detail.n, detail.v); + break; } } - return cache.n; + return info.b; }; /** @@ -3970,19 +4587,16 @@ const unroll = (cache, { s: svg, t: template, v: values }) => { * @param {any[]} values * @returns {number} */ -const unrollValues = ({ s: stack }, values) => { - const { length } = values; - for (let i = 0; i < length; i++) { - const hole = values[i]; - if (hole instanceof Hole) - values[i] = unroll(stack[i] || (stack[i] = cache$1(empty)), hole); - else if (isArray$3(hole)) - unrollValues(stack[i] || (stack[i] = cache$1([])), hole); - else - stack[i] = null; - } +const unrollValues = (stack, values) => { + let i = 0, { length } = values; if (length < stack.length) stack.splice(length); - return length; + for (; i < length; i++) { + const value = values[i]; + if (value instanceof Hole) + values[i] = unroll(stack[i] || (stack[i] = cache$1()), value); + else stack[i] = null; + } + return values; }; /** @@ -3998,6 +4612,9 @@ class Hole { this.t = template; this.v = values; } + toDOM(info = cache$1()) { + return unroll(info, this); + } } /** @typedef {import("../rabbit.js").Hole} Hole */ @@ -4013,15 +4630,15 @@ const known = new WeakMap; * @returns */ const render = (where, what) => { - const info = known.get(where) || set(known, where, cache$1(empty)); - if (info.n !== unroll(info, typeof what === 'function' ? what() : what)) - where.replaceChildren(info.n); + const info = known.get(where) || set(known, where, cache$1()); + const { b } = info; + if (b !== (typeof what === 'function' ? what() : what).toDOM(info)) + where.replaceChildren(info.b.valueOf()); return where; }; /*! (c) Andrea Giammarchi - MIT */ - /** @typedef {import("./literals.js").Value} Value */ const tag = svg => (template, ...values) => new Hole(svg, template, values); @@ -4029,10 +4646,6 @@ const tag = svg => (template, ...values) => new Hole(svg, template, values); /** @type {(template: TemplateStringsArray, ...values:Value[]) => Hole} A tag to render HTML content. */ const html = tag(false); -const errors = ''; - -const props = ''; - var main = {}; var parse$1 = {}; @@ -4233,8 +4846,8 @@ function setHashKey(obj, h) { } ``` */ -function noop$2() {} -noop$2.$inject = []; +function noop$1() {} +noop$1.$inject = []; /** * @ngdoc function @@ -5799,7 +6412,7 @@ ASTCompiler.prototype = { args, expression, computed; - recursionFn = recursionFn || noop$2; + recursionFn = recursionFn || noop$1; if (!skipWatchIdCheck && isDefined(ast.watchId)) { intoId = intoId || this.nextId(); this.if_( @@ -6392,7 +7005,7 @@ ASTInterpreter.prototype = { }); var fn = ast.body.length === 0 - ? noop$2 + ? noop$1 : ast.body.length === 1 ? expressions[0] : function (scope, locals) { @@ -6890,13 +7503,11 @@ main.filters = filters; * @property {State} state * @property {import("./data").Data} data * @property {import("./components/actions").Actions} actions - * @property {TreeBase} tree * @property {import('./components/layout').Layout} layout * @property {import('./components/access/pattern').PatternList} patterns * @property {import('./components/access/cues').CueList} cues * @property {import('./components/access/method').MethodChooser} method * @property {import('./components/monitor').Monitor} monitor - * @property {import('./components/toolbar').ToolBar} toolbar * @property {import('./components/designer').Designer} designer * @property {import('./components/errors').Messages} error * @property {function():Promise} restart @@ -6906,6317 +7517,6256 @@ main.filters = filters; // @ts-ignore Object missing properties const Globals = {}; // values are supplied in start.js -/** @param {function(string, string): string} f */ -function updateString(f) { - /** @param {string} value */ - return function (value) { - /** @param {string | undefined} old */ - return function (old) { - return f(old || "", value); - }; - }; +const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c); + +let idbProxyableTypes; +let cursorAdvanceMethods; +// This is a function to prevent it throwing up in node environments. +function getIdbProxyableTypes() { + return (idbProxyableTypes || + (idbProxyableTypes = [ + IDBDatabase, + IDBObjectStore, + IDBIndex, + IDBCursor, + IDBTransaction, + ])); } -/** @param {function(number, number): number} f */ -function updateNumber(f) { - /** @param {number} value */ - return function (value) { - /** @param {number | undefined} old */ - return function (old) { - return f(old || 0, value); +// This is a function to prevent it throwing up in node environments. +function getCursorAdvanceMethods() { + return (cursorAdvanceMethods || + (cursorAdvanceMethods = [ + IDBCursor.prototype.advance, + IDBCursor.prototype.continue, + IDBCursor.prototype.continuePrimaryKey, + ])); +} +const transactionDoneMap = new WeakMap(); +const transformCache = new WeakMap(); +const reverseTransformCache = new WeakMap(); +function promisifyRequest(request) { + const promise = new Promise((resolve, reject) => { + const unlisten = () => { + request.removeEventListener('success', success); + request.removeEventListener('error', error); + }; + const success = () => { + resolve(wrap(request.result)); + unlisten(); + }; + const error = () => { + reject(request.error); + unlisten(); + }; + request.addEventListener('success', success); + request.addEventListener('error', error); + }); + // This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This + // is because we create many promises from a single IDBRequest. + reverseTransformCache.set(promise, request); + return promise; +} +function cacheDonePromiseForTransaction(tx) { + // Early bail if we've already created a done promise for this transaction. + if (transactionDoneMap.has(tx)) + return; + const done = new Promise((resolve, reject) => { + const unlisten = () => { + tx.removeEventListener('complete', complete); + tx.removeEventListener('error', error); + tx.removeEventListener('abort', error); + }; + const complete = () => { + resolve(); + unlisten(); + }; + const error = () => { + reject(tx.error || new DOMException('AbortError', 'AbortError')); + unlisten(); + }; + tx.addEventListener('complete', complete); + tx.addEventListener('error', error); + tx.addEventListener('abort', error); + }); + // Cache it for later retrieval. + transactionDoneMap.set(tx, done); +} +let idbProxyTraps = { + get(target, prop, receiver) { + if (target instanceof IDBTransaction) { + // Special handling for transaction.done. + if (prop === 'done') + return transactionDoneMap.get(target); + // Make tx.store return the only store in the transaction, or undefined if there are many. + if (prop === 'store') { + return receiver.objectStoreNames[1] + ? undefined + : receiver.objectStore(receiver.objectStoreNames[0]); + } + } + // Else transform whatever we get back. + return wrap(target[prop]); + }, + set(target, prop, value) { + target[prop] = value; + return true; + }, + has(target, prop) { + if (target instanceof IDBTransaction && + (prop === 'done' || prop === 'store')) { + return true; + } + return prop in target; + }, +}; +function replaceTraps(callback) { + idbProxyTraps = callback(idbProxyTraps); +} +function wrapFunction(func) { + // Due to expected object equality (which is enforced by the caching in `wrap`), we + // only create one new func per func. + // Cursor methods are special, as the behaviour is a little more different to standard IDB. In + // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the + // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense + // with real promises, so each advance methods returns a new promise for the cursor object, or + // undefined if the end of the cursor has been reached. + if (getCursorAdvanceMethods().includes(func)) { + return function (...args) { + // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use + // the original object. + func.apply(unwrap(this), args); + return wrap(this.request); + }; + } + return function (...args) { + // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use + // the original object. + return wrap(func.apply(unwrap(this), args)); }; - }; } -const Functions = { - increment: updateNumber((old, value) => old + value), - add_word: updateString((old, value) => old + value + " "), - add_letter: updateString((old, value) => old + value), - complete: updateString((old, value) => { - if (old.length == 0 || old.endsWith(" ")) { - return old + value; - } else { - return old.replace(/\w+$/, value); +function transformCachableValue(value) { + if (typeof value === 'function') + return wrapFunction(value); + // This doesn't return, it just creates a 'done' promise for the transaction, + // which is later returned for transaction.done (see idbObjectHandler). + if (value instanceof IDBTransaction) + cacheDonePromiseForTransaction(value); + if (instanceOfAny(value, getIdbProxyableTypes())) + return new Proxy(value, idbProxyTraps); + // Return the same value back if we're not going to transform it. + return value; +} +function wrap(value) { + // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because + // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached. + if (value instanceof IDBRequest) + return promisifyRequest(value); + // If we've already transformed this value before, reuse the transformed value. + // This is faster, but it also provides object equality. + if (transformCache.has(value)) + return transformCache.get(value); + const newValue = transformCachableValue(value); + // Not all types are transformed. + // These may be primitive types, so they can't be WeakMap keys. + if (newValue !== value) { + transformCache.set(value, newValue); + reverseTransformCache.set(newValue, value); } - }), - replace_last: updateString((old, value) => old.replace(/\w*\s*$/, value)), - replace_last_letter: updateString((old, value) => old.slice(0, -1) + value), - random: (/** @type {string} */ arg) => { - let args = arg.split(","); - return args[Math.floor(Math.random() * args.length)]; - }, - max: Math.max, - min: Math.min, - if: (/** @type {boolean} */ c, /** @type {any} */ t, /** @type {any} */ f) => - c ? t : f, - abs: (/** @type {number} */ v) => Math.abs(v), -}; - -/** - * Translate an expression from Excel-like to Javascript - * - * @param {string} expression - * @returns {string} - */ -function translate(expression) { - /* translate the expression from the excel like form to javascript */ - // remove any initial = sign - let exp = expression.replace(/^=/, ""); - // translate single = to == - exp = exp.replaceAll(/(?!])=/g, "=="); - // translate words - exp = exp.replaceAll(/(? reverseTransformCache.get(value); /** - * Cleanup access to state and data + * Open a database. * - * @param {State} state - * @param {Row} data - * @returns {function(string): any} + * @param name Name of the database. + * @param version Schema version. + * @param callbacks Additional callbacks. */ -function access(state, data) { - return function (name) { - if (!name) return ""; - if (state && name.startsWith("$")) { - return state.get(name); +function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) { + const request = indexedDB.open(name, version); + const openPromise = wrap(request); + if (upgrade) { + request.addEventListener('upgradeneeded', (event) => { + upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event); + }); } - if (data && name.startsWith("#")) { - const r = data[name.slice(1)]; - if (r == null) return ""; - return r; + if (blocked) { + request.addEventListener('blocked', (event) => blocked( + // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405 + event.oldVersion, event.newVersion, event)); } - return ""; - }; + openPromise + .then((db) => { + if (terminated) + db.addEventListener('close', () => terminated()); + if (blocking) { + db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event)); + } + }) + .catch(() => { }); + return openPromise; } -/** Track access to states and fields, true if the value was undefined - * @type {Map} - */ -const accessed = new Map(); - -/* intercept access to variables so I can track access to undefined state and field values - * and map them to empty strings. - */ -const variableHandler = { - /** @param {Object} target - * @param {string} prop - */ - get(target, prop) { - let result = undefined; - if (prop.startsWith("$")) { - result = target.states[prop]; - accessed.set(prop, prop in target.states); - } else if (prop.startsWith("_")) { - let ps = prop.slice(1); - result = target.data[ps]; - accessed.set(prop, Globals.data.allFields.has("#" + ps)); - } else if (prop in Functions) { - result = Functions[prop]; - } else { - console.error("undefined", prop); +const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count']; +const writeMethods = ['put', 'add', 'delete', 'clear']; +const cachedMethods = new Map(); +function getMethod(target, prop) { + if (!(target instanceof IDBDatabase && + !(prop in target) && + typeof prop === 'string')) { + return; } - if (result === undefined || result === null) { - result = ""; + if (cachedMethods.get(prop)) + return cachedMethods.get(prop); + const targetFuncName = prop.replace(/FromIndex$/, ''); + const useIndex = prop !== targetFuncName; + const isWrite = writeMethods.includes(targetFuncName); + if ( + // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge. + !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) || + !(isWrite || readMethods.includes(targetFuncName))) { + return; } - return result; - }, + const method = async function (storeName, ...args) { + // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :( + const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly'); + let target = tx.store; + if (useIndex) + target = target.index(args.shift()); + // Must reject if op rejects. + // If it's a write operation, must reject if tx.done rejects. + // Must reject with op rejection first. + // Must resolve with op value. + // Must handle both promises (no unhandled rejections) + return (await Promise.all([ + target[targetFuncName](...args), + isWrite && tx.done, + ]))[0]; + }; + cachedMethods.set(prop, method); + return method; +} +replaceTraps((oldTraps) => ({ + ...oldTraps, + get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver), + has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop), +})); - /** The expressions library is testing for own properties for safety. - * I need to defeat that for the renaming I want to do. - * @param {Object} target; - * @param {string} prop; - */ - getOwnPropertyDescriptor(target, prop) { - if (prop.startsWith("$")) { - return Object.getOwnPropertyDescriptor(target.states, prop); - } else if (prop.startsWith("_")) { - return Object.getOwnPropertyDescriptor(target.data, prop.slice(1)); - } else { - return Object.getOwnPropertyDescriptor(Functions, prop); - } - }, +const advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance']; +const methodMap = {}; +const advanceResults = new WeakMap(); +const ittrProxiedCursorToOriginalProxy = new WeakMap(); +const cursorIteratorTraps = { + get(target, prop) { + if (!advanceMethodProps.includes(prop)) + return target[prop]; + let cachedFunc = methodMap[prop]; + if (!cachedFunc) { + cachedFunc = methodMap[prop] = function (...args) { + advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args)); + }; + } + return cachedFunc; + }, }; - -/** - * Compile an expression returning the function or an error - * @param {string} expression - * @returns {[ ((context?:Object)=>any ) | undefined, Error | undefined ]} - * - * */ -function compileExpression(expression) { - const te = translate(expression); - try { - const exp = main.compile(te); - /** @param {EvalContext} context */ - return [ - (context = {}) => { - let states = - "states" in context - ? { ...Globals.state.values, ...context.states } - : Globals.state.values; - let data = context.data ?? []; - const r = exp( - new Proxy( - { - Functions, - states, - data, - }, - variableHandler, - ), - ); - return r; - }, - undefined, - ]; - } catch (e) { - return [undefined, e]; - } +async function* iterate(...args) { + // tslint:disable-next-line:no-this-assignment + let cursor = this; + if (!(cursor instanceof IDBCursor)) { + cursor = await cursor.openCursor(...args); + } + if (!cursor) + return; + cursor = cursor; + const proxiedCursor = new Proxy(cursor, cursorIteratorTraps); + ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor); + // Map this double-proxy back to the original, so other cursor methods work. + reverseTransformCache.set(proxiedCursor, unwrap(cursor)); + while (cursor) { + yield proxiedCursor; + // If one of the advancing methods was not called, call continue(). + cursor = await (advanceResults.get(proxiedCursor) || cursor.continue()); + advanceResults.delete(proxiedCursor); + } +} +function isIteratorProp(target, prop) { + return ((prop === Symbol.asyncIterator && + instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) || + (prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore]))); } +replaceTraps((oldTraps) => ({ + ...oldTraps, + get(target, prop, receiver) { + if (isIteratorProp(target, prop)) + return iterate; + return oldTraps.get(target, prop, receiver); + }, + has(target, prop) { + return isIteratorProp(target, prop) || oldTraps.has(target, prop); + }, +})); -/* - * Bang color names from http://www.procato.com/rgb+index/?csv - */ -const ColorNames = { - white: "#ffffff", - red: "#ff0000", - green: "#00ff00", - blue: "#0000ff", - yellow: "#ffff00", - magenta: "#ff00ff", - cyan: "#00ffff", - black: "#000000", - "pinkish white": "#fff6f6", - "very pale pink": "#ffe2e2", - "pale pink": "#ffc2c2", - "light pink": "#ff9e9e", - "light brilliant red": "#ff6565", - "luminous vivid red": "#ff0000", - "pinkish gray": "#e7dada", - "pale grayish pink": "#e7b8b8", - pink: "#e78b8b", - "brilliant red": "#e75151", - "vivid red": "#e70000", - "reddish gray": "#a89c9c", - "grayish red": "#a87d7d", - "moderate red": "#a84a4a", - "strong red": "#a80000", - "reddish brownish gray": "#595353", - "dark grayish reddish brown": "#594242", - "reddish brown": "#592727", - "deep reddish brown": "#590000", - "reddish brownish black": "#1d1a1a", - "very reddish brown": "#1d1111", - "very deep reddish brown": "#1d0000", - "pale scarlet": "#ffc9c2", - "very light scarlet": "#ffaa9e", - "light brilliant scarlet": "#ff7865", - "luminous vivid scarlet": "#ff2000", - "light scarlet": "#e7968b", - "brilliant scarlet": "#e76451", - "vivid scarlet": "#e71d00", - "moderate scarlet": "#a8554a", - "strong scarlet": "#a81500", - "dark scarlet": "#592d27", - "deep scarlet": "#590b00", - "very pale vermilion": "#ffe9e2", - "pale vermilion": "#ffd1c2", - "very light vermilion": "#ffb69e", - "light brilliant vermilion": "#ff8b65", - "luminous vivid vermilion": "#ff4000", - "pale, light grayish vermilion": "#e7c4b8", - "light vermilion": "#e7a28b", - "brilliant vermilion": "#e77751", - "vivid vermilion": "#e73a00", - "grayish vermilion": "#a8887d", - "moderate vermilion": "#a8614a", - "strong vermilion": "#a82a00", - "dark grayish vermilion": "#594842", - "dark vermilion": "#593427", - "deep vermilion": "#591600", - "pale tangelo": "#ffd9c2", - "very light tangelo": "#ffc29e", - "light brilliant tangelo": "#ff9f65", - "luminous vivid tangelo": "#ff6000", - "light tangelo": "#e7ae8b", - "brilliant tangelo": "#e78951", - "vivid tangelo": "#e75700", - "moderate tangelo": "#a86d4a", - "strong tangelo": "#a83f00", - "dark tangelo": "#593a27", - "deep tangelo": "#592100", - "very pale orange": "#fff0e2", - "pale orange": "#ffe0c2", - "very light orange": "#ffcf9e", - "light brilliant orange": "#ffb265", - "luminous vivid orange": "#ff8000", - "pale, light grayish brown": "#e7d0b8", - "light orange": "#e7b98b", - "brilliant orange": "#e79c51", - "vivid orange": "#e77400", - "grayish brown": "#a8937d", - "moderate orange": "#a8794a", - "strong orange": "#a85400", - "dark grayish brown": "#594e42", - brown: "#594027", - "deep brown": "#592d00", - "very brown": "#1d1711", - "very deep brown": "#1d0e00", - "pale gamboge": "#ffe8c2", - "very light gamboge": "#ffdb9e", - "light brilliant gamboge": "#ffc565", - "luminous vivid gamboge": "#ff9f00", - "light gamboge": "#e7c58b", - "brilliant gamboge": "#e7af51", - "vivid gamboge": "#e79100", - "moderate gamboge": "#a8854a", - "strong gamboge": "#a86900", - "dark gamboge": "#594627", - "deep gamboge": "#593800", - "very pale amber": "#fff8e2", - "pale amber": "#fff0c2", - "very light amber": "#ffe79e", - "light brilliant amber": "#ffd865", - "luminous vivid amber": "#ffbf00", - "pale, light grayish amber": "#e7dcb8", - "light amber": "#e7d08b", - "brilliant amber": "#e7c251", - "vivid amber": "#e7ae00", - "grayish amber": "#a89e7d", - "moderate amber": "#a8914a", - "strong amber": "#a87e00", - "dark grayish amber": "#595442", - "dark amber": "#594d27", - "deep amber": "#594300", - "pale gold": "#fff7c2", - "very light gold": "#fff39e", - "light brilliant gold": "#ffec65", - "luminous vivid gold": "#ffdf00", - "light gold": "#e7dc8b", - "brilliant gold": "#e7d551", - "vivid gold": "#e7ca00", - "moderate gold": "#a89c4a", - "strong gold": "#a89300", - "dark gold": "#595327", - "deep gold": "#594e00", - "yellowish white": "#fffff6", - "very pale yellow": "#ffffe2", - "pale yellow": "#ffffc2", - "very light yellow": "#ffff9e", - "light brilliant yellow": "#ffff65", - "luminous vivid yellow": "#ffff00", - "light yellowish gray": "#e7e7da", - "pale, light grayish olive": "#e7e7b8", - "light yellow": "#e7e78b", - "brilliant yellow": "#e7e751", - "vivid yellow": "#e7e700", - "yellowish gray": "#a8a89c", - "grayish olive": "#a8a87d", - "moderate olive": "#a8a84a", - "strong olive": "#a8a800", - "dark olivish gray": "#595953", - "dark grayish olive": "#595942", - "dark olive": "#595927", - "deep olive": "#595900", - "yellowish black": "#1d1d1a", - "very dark olive": "#1d1d11", - "very deep olive": "#1d1d00", - "pale apple green": "#f7ffc2", - "very light apple green": "#f3ff9e", - "light brilliant apple green": "#ecff65", - "luminous vivid apple green": "#dfff00", - "light apple green": "#dce78b", - "brilliant apple green": "#d5e751", - "vivid apple green": "#cae700", - "moderate apple green": "#9ca84a", - "strong apple green": "#93a800", - "dark apple green": "#535927", - "deep apple green": "#4e5900", - "very pale lime green": "#f8ffe2", - "pale lime green": "#f0ffc2", - "very light lime green": "#e7ff9e", - "light brilliant lime green": "#d8ff65", - "luminous vivid lime green": "#bfff00", - "pale, light grayish lime green": "#dce7b8", - "light lime green": "#d0e78b", - "brilliant lime green": "#c2e751", - "vivid lime green": "#aee700", - "grayish lime green": "#9ea87d", - "moderate lime green": "#91a84a", - "strong lime green": "#7ea800", - "dark grayish lime green": "#545942", - "dark lime green": "#4d5927", - "deep lime green": "#435900", - "pale spring bud": "#e8ffc2", - "very light spring bud": "#dbff9e", - "light brilliant spring bud": "#c5ff65", - "luminous vivid spring bud": "#9fff00", - "light spring bud": "#c5e78b", - "brilliant spring bud": "#afe751", - "vivid spring bud": "#91e700", - "moderate spring bud": "#85a84a", - "strong spring bud": "#69a800", - "dark spring bud": "#465927", - "deep spring bud": "#385900", - "very pale chartreuse green": "#f0ffe2", - "pale chartreuse green": "#e0ffc2", - "very light chartreuse green": "#cfff9e", - "light brilliant chartreuse green": "#b2ff65", - "luminous vivid chartreuse green": "#80ff00", - "pale, light grayish chartreuse green": "#d0e7b8", - "light chartreuse green": "#b9e78b", - "brilliant chartreuse green": "#9ce751", - "vivid chartreuse green": "#74e700", - "grayish chartreuse green": "#93a87d", - "moderate chartreuse green": "#79a84a", - "strong chartreuse green": "#54a800", - "dark grayish chartreuse green": "#4e5942", - "dark chartreuse green": "#405927", - "deep chartreuse green": "#2d5900", - "very dark chartreuse green": "#171d11", - "very deep chartreuse green": "#0e1d00", - "pale pistachio": "#d9ffc2", - "very light pistachio": "#c2ff9e", - "light brilliant pistachio": "#9fff65", - "luminous vivid pistachio": "#60ff00", - "light pistachio": "#aee78b", - "brilliant pistachio": "#89e751", - "vivid pistachio": "#57e700", - "moderate pistachio": "#6da84a", - "strong pistachio": "#3fa800", - "dark pistachio": "#3a5927", - "deep pistachio": "#215900", - "very pale harlequin": "#e9ffe2", - "pale harlequin": "#d1ffc2", - "very light harlequin": "#b6ff9e", - "light brilliant harlequin": "#8bff65", - "luminous vivid harlequin": "#40ff00", - "pale, light grayish harlequin": "#c4e7b8", - "light harlequin": "#a2e78b", - "brilliant harlequin": "#77e751", - "vivid harlequin": "#3ae700", - "grayish harlequin": "#88a87d", - "moderate harlequin": "#61a84a", - "strong harlequin": "#2aa800", - "dark grayish harlequin": "#485942", - "dark harlequin": "#345927", - "deep harlequin": "#165900", - "pale sap green": "#c9ffc2", - "very light sap green": "#aaff9e", - "light brilliant sap green": "#78ff65", - "luminous vivid sap green": "#20ff00", - "light sap green": "#96e78b", - "brilliant sap green": "#64e751", - "vivid sap green": "#1de700", - "moderate sap green": "#55a84a", - "strong sap green": "#15a800", - "dark sap green": "#2d5927", - "deep sap green": "#0b5900", - "greenish white": "#f6fff6", - "very pale green": "#e2ffe2", - "pale green": "#c2ffc2", - "very light green": "#9eff9e", - "light brilliant green": "#65ff65", - "luminous vivid green": "#00ff00", - "light greenish gray": "#dae7da", - "pale, light grayish green": "#b8e7b8", - "light green": "#8be78b", - "brilliant green": "#51e751", - "vivid green": "#00e700", - "greenish gray": "#9ca89c", - "grayish green": "#7da87d", - "moderate green": "#4aa84a", - "strong green": "#00a800", - "dark greenish gray": "#535953", - "dark grayish green": "#425942", - "dark green": "#275927", - "deep green": "#005900", - "greenish black": "#1a1d1a", - "very dark green": "#111d11", - "very deep green": "#001d00", - "pale emerald green": "#c2ffc9", - "very light emerald green": "#9effaa", - "light brilliant emerald green": "#65ff78", - "luminous vivid emerald green": "#00ff20", - "light emerald green": "#8be796", - "brilliant emerald green": "#51e764", - "vivid emerald green": "#00e71d", - "moderate emerald green": "#4aa855", - "strong emerald green": "#00a815", - "dark emerald green": "#27592d", - "deep emerald green": "#00590b", - "very pale malachite green": "#e2ffe9", - "pale malachite green": "#c2ffd1", - "very light malachite green": "#9effb6", - "light brilliant malachite green": "#65ff8b", - "luminous vivid malachite green": "#00ff40", - "pale, light grayish malachite green": "#b8e7c4", - "light malachite green": "#8be7a2", - "brilliant malachite green": "#51e777", - "vivid malachite green": "#00e73a", - "grayish malachite green": "#7da888", - "moderate malachite green": "#4aa861", - "strong malachite green": "#00a82a", - "dark grayish malachite green": "#425948", - "dark malachite green": "#275934", - "deep malachite green": "#005916", - "pale sea green": "#c2ffd9", - "very light sea green": "#9effc2", - "light brilliant sea green": "#65ff9f", - "luminous vivid sea green": "#00ff60", - "light sea green": "#8be7ae", - "brilliant sea green": "#51e789", - "vivid sea green": "#00e757", - "moderate sea green": "#4aa86d", - "strong sea green": "#00a83f", - "dark sea green": "#27593a", - "deep sea green": "#005921", - "very pale spring green": "#e2fff0", - "pale spring green": "#c2ffe0", - "very light spring green": "#9effcf", - "light brilliant spring green": "#65ffb2", - "luminous vivid spring green": "#00ff80", - "pale, light grayish spring green": "#b8e7d0", - "light spring green": "#8be7b9", - "brilliant spring green": "#51e79c", - "vivid spring green": "#00e774", - "grayish spring green": "#7da893", - "moderate spring green": "#4aa879", - "strong spring green": "#00a854", - "dark grayish spring green": "#42594e", - "dark spring green": "#275940", - "deep spring green": "#00592d", - "very dark spring green": "#111d17", - "very deep spring green": "#001d0e", - "pale aquamarine": "#c2ffe8", - "very light aquamarine": "#9effdb", - "light brilliant aquamarine": "#65ffc5", - "luminous vivid aquamarine": "#00ff9f", - "light aquamarine": "#8be7c5", - "brilliant aquamarine": "#51e7af", - "vivid aquamarine": "#00e791", - "moderate aquamarine": "#4aa885", - "strong aquamarine": "#00a869", - "dark aquamarine": "#275946", - "deep aquamarine": "#005938", - "very pale turquoise": "#e2fff8", - "pale turquoise": "#c2fff0", - "very light turquoise": "#9effe7", - "light brilliant turquoise": "#65ffd8", - "luminous vivid turquoise": "#00ffbf", - "pale, light grayish turquoise": "#b8e7dc", - "light turquoise": "#8be7d0", - "brilliant turquoise": "#51e7c2", - "vivid turquoise": "#00e7ae", - "grayish turquoise": "#7da89e", - "moderate turquoise": "#4aa891", - "strong turquoise": "#00a87e", - "dark grayish turquoise": "#425954", - "dark turquoise": "#27594d", - "deep turquoise": "#005943", - "pale opal": "#c2fff7", - "very light opal": "#9efff3", - "light brilliant opal": "#65ffec", - "luminous vivid opal": "#00ffdf", - "light opal": "#8be7dc", - "brilliant opal": "#51e7d5", - "vivid opal": "#00e7ca", - "moderate opal": "#4aa89c", - "strong opal": "#00a893", - "dark opal": "#275953", - "deep opal": "#00594e", - "cyanish white": "#f6ffff", - "very pale cyan": "#e2ffff", - "pale cyan": "#c2ffff", - "very light cyan": "#9effff", - "light brilliant cyan": "#65ffff", - "luminous vivid cyan": "#00ffff", - "light cyanish gray": "#dae7e7", - "pale, light grayish cyan": "#b8e7e7", - "light cyan": "#8be7e7", - "brilliant cyan": "#51e7e7", - "vivid cyan": "#00e7e7", - "cyanish gray": "#9ca8a8", - "grayish cyan": "#7da8a8", - "moderate cyan": "#4aa8a8", - "strong cyan": "#00a8a8", - "dark cyanish gray": "#535959", - "dark grayish cyan": "#425959", - "dark cyan": "#275959", - "deep cyan": "#005959", - "cyanish black": "#1a1d1d", - "very dark cyan": "#111d1d", - "very deep cyan": "#001d1d", - "pale arctic blue": "#c2f7ff", - "very light arctic blue": "#9ef3ff", - "light brilliant arctic blue": "#65ecff", - "luminous vivid arctic blue": "#00dfff", - "light arctic blue": "#8bdce7", - "brilliant arctic blue": "#51d5e7", - "vivid arctic blue": "#00cae7", - "moderate arctic blue": "#4a9ca8", - "strong arctic blue": "#0093a8", - "dark arctic blue": "#275359", - "deep arctic blue": "#004e59", - "very pale cerulean": "#e2f8ff", - "pale cerulean": "#c2f0ff", - "very light cerulean": "#9ee7ff", - "light brilliant cerulean": "#65d8ff", - "luminous vivid cerulean": "#00bfff", - "pale, light grayish cerulean": "#b8dce7", - "light cerulean": "#8bd0e7", - "brilliant cerulean": "#51c2e7", - "vivid cerulean": "#00aee7", - "grayish cerulean": "#7d9ea8", - "moderate cerulean": "#4a91a8", - "strong cerulean": "#007ea8", - "dark grayish cerulean": "#425459", - "dark cerulean": "#274d59", - "deep cerulean": "#004359", - "pale cornflower blue": "#c2e8ff", - "very light cornflower blue": "#9edbff", - "light brilliant cornflower blue": "#65c5ff", - "luminous vivid cornflower blue": "#009fff", - "light cornflower blue": "#8bc5e7", - "brilliant cornflower blue": "#51afe7", - "vivid cornflower blue": "#0091e7", - "moderate cornflower blue": "#4a85a8", - "strong cornflower blue": "#0069a8", - "dark cornflower blue": "#274659", - "deep cornflower blue": "#003859", - "very pale azure": "#e2f0ff", - "pale azure": "#c2e0ff", - "very light azure": "#9ecfff", - "light brilliant azure": "#65b2ff", - "luminous vivid azure": "#0080ff", - "pale, light grayish azure": "#b8d0e7", - "light azure": "#8bb9e7", - "brilliant azure": "#519ce7", - "vivid azure": "#0074e7", - "grayish azure": "#7d93a8", - "moderate azure": "#4a79a8", - "strong azure": "#0054a8", - "dark grayish azure": "#424e59", - "dark azure": "#274059", - "deep azure": "#002d59", - "very dark azure": "#11171d", - "very deep azure": "#000e1d", - "pale cobalt blue": "#c2d9ff", - "very light cobalt blue": "#9ec2ff", - "light brilliant cobalt blue": "#659fff", - "luminous vivid cobalt blue": "#0060ff", - "light cobalt blue": "#8baee7", - "brilliant cobalt blue": "#5189e7", - "vivid cobalt blue": "#0057e7", - "moderate cobalt blue": "#4a6da8", - "strong cobalt blue": "#003fa8", - "dark cobalt blue": "#273a59", - "deep cobalt blue": "#002159", - "very pale sapphire blue": "#e2e9ff", - "pale sapphire blue": "#c2d1ff", - "very light sapphire blue": "#9eb6ff", - "light brilliant sapphire blue": "#658bff", - "luminous vivid sapphire blue": "#0040ff", - "pale, light grayish sapphire blue": "#b8c4e7", - "light sapphire blue": "#8ba2e7", - "brilliant sapphire blue": "#5177e7", - "vivid sapphire blue": "#003ae7", - "grayish sapphire blue": "#7d88a8", - "moderate sapphire blue": "#4a61a8", - "strong sapphire blue": "#002aa8", - "dark grayish sapphire blue": "#424859", - "dark sapphire blue": "#273459", - "deep sapphire blue": "#001659", - "pale phthalo blue": "#c2c9ff", - "very light phthalo blue": "#9eaaff", - "light brilliant phthalo blue": "#6578ff", - "luminous vivid phthalo blue": "#0020ff", - "light phthalo blue": "#8b96e7", - "brilliant phthalo blue": "#5164e7", - "vivid phthalo blue": "#001de7", - "moderate phthalo blue": "#4a55a8", - "strong phthalo blue": "#0015a8", - "dark phthalo blue": "#272d59", - "deep phthalo blue": "#000b59", - "bluish white": "#f6f6ff", - "very pale blue": "#e2e2ff", - "pale blue": "#c2c2ff", - "very light blue": "#9e9eff", - "light brilliant blue": "#6565ff", - "luminous vivid blue": "#0000ff", - "light bluish gray": "#dadae7", - "pale, light grayish blue": "#b8b8e7", - "light blue": "#8b8be7", - "brilliant blue": "#5151e7", - "vivid blue": "#0000e7", - "bluish gray": "#9c9ca8", - "grayish blue": "#7d7da8", - "moderate blue": "#4a4aa8", - "strong blue": "#0000a8", - "dark bluish gray": "#535359", - "dark grayish blue": "#424259", - "dark blue": "#272759", - "deep blue": "#000059", - "bluish black": "#1a1a1d", - "very dark blue": "#11111d", - "very deep blue": "#00001d", - "pale persian blue": "#c9c2ff", - "very light persian blue": "#aa9eff", - "light brilliant persian blue": "#7865ff", - "luminous vivid persian blue": "#2000ff", - "light persian blue": "#968be7", - "brilliant persian blue": "#6451e7", - "vivid persian blue": "#1d00e7", - "moderate persian blue": "#554aa8", - "strong persian blue": "#1500a8", - "dark persian blue": "#2d2759", - "deep persian blue": "#0b0059", - "very pale indigo": "#e9e2ff", - "pale indigo": "#d1c2ff", - "very light indigo": "#b69eff", - "light brilliant indigo": "#8b65ff", - "luminous vivid indigo": "#4000ff", - "pale, light grayish indigo": "#c4b8e7", - "light indigo": "#a28be7", - "brilliant indigo": "#7751e7", - "vivid indigo": "#3a00e7", - "grayish indigo": "#887da8", - "moderate indigo": "#614aa8", - "strong indigo": "#2a00a8", - "dark grayish indigo": "#484259", - "dark indigo": "#342759", - "deep indigo": "#160059", - "pale blue violet": "#d9c2ff", - "very light blue violet": "#c29eff", - "light brilliant blue violet": "#9f65ff", - "luminous vivid blue violet": "#6000ff", - "light blue violet": "#ae8be7", - "brilliant blue violet": "#8951e7", - "vivid blue violet": "#5700e7", - "moderate blue violet": "#6d4aa8", - "strong blue violet": "#3f00a8", - "dark blue violet": "#3a2759", - "deep blue violet": "#210059", - "very pale violet": "#f0e2ff", - "pale violet": "#e0c2ff", - "very light violet": "#cf9eff", - "light brilliant violet": "#b265ff", - "luminous vivid violet": "#8000ff", - "pale, light grayish violet": "#d0b8e7", - "light violet": "#b98be7", - "brilliant violet": "#9c51e7", - "vivid violet": "#7400e7", - "grayish violet": "#937da8", - "moderate violet": "#794aa8", - "strong violet": "#5400a8", - "dark grayish violet": "#4e4259", - "dark violet": "#402759", - "deep violet": "#2d0059", - "very dark violet": "#17111d", - "very deep violet": "#0e001d", - "pale purple": "#e8c2ff", - "very light purple": "#db9eff", - "light brilliant purple": "#c565ff", - "luminous vivid purple": "#9f00ff", - "light purple": "#c58be7", - "brilliant purple": "#af51e7", - "vivid purple": "#9100e7", - "moderate purple": "#854aa8", - "strong purple": "#6900a8", - "dark purple": "#462759", - "deep purple": "#380059", - "very pale mulberry": "#f8e2ff", - "pale mulberry": "#f0c2ff", - "very light mulberry": "#e79eff", - "light brilliant mulberry": "#d865ff", - "luminous vivid mulberry": "#bf00ff", - "pale, light grayish mulberry": "#dcb8e7", - "light mulberry": "#d08be7", - "brilliant mulberry": "#c251e7", - "vivid mulberry": "#ae00e7", - "grayish mulberry": "#9e7da8", - "moderate mulberry": "#914aa8", - "strong mulberry": "#7e00a8", - "dark grayish mulberry": "#544259", - "dark mulberry": "#4d2759", - "deep mulberry": "#430059", - "pale heliotrope": "#f7c2ff", - "very light heliotrope": "#f39eff", - "light brilliant heliotrope": "#ec65ff", - "luminous vivid heliotrope": "#df00ff", - "light heliotrope": "#dc8be7", - "brilliant heliotrope": "#d551e7", - "vivid heliotrope": "#ca00e7", - "moderate heliotrope": "#9c4aa8", - "strong heliotrope": "#9300a8", - "dark heliotrope": "#532759", - "deep heliotrope": "#4e0059", - "magentaish white": "#fff6ff", - "very pale magenta": "#ffe2ff", - "pale magenta": "#ffc2ff", - "very light magenta": "#ff9eff", - "light brilliant magenta": "#ff65ff", - "luminous vivid magenta": "#ff00ff", - "light magentaish gray": "#e7dae7", - "pale, light grayish magenta": "#e7b8e7", - "light magenta": "#e78be7", - "brilliant magenta": "#e751e7", - "vivid magenta": "#e700e7", - "magentaish gray": "#a89ca8", - "grayish magenta": "#a87da8", - "moderate magenta": "#a84aa8", - "strong magenta": "#a800a8", - "dark magentaish gray": "#595359", - "dark grayish magenta": "#594259", - "dark magenta": "#592759", - "deep magenta": "#590059", - "magentaish black": "#1d1a1d", - "very dark magenta": "#1d111d", - "very deep magenta": "#1d001d", - "pale orchid": "#ffc2f7", - "very light orchid": "#ff9ef3", - "light brilliant orchid": "#ff65ec", - "luminous vivid orchid": "#ff00df", - "light orchid": "#e78bdc", - "brilliant orchid": "#e751d5", - "vivid orchid": "#e700ca", - "moderate orchid": "#a84a9c", - "strong orchid": "#a80093", - "dark orchid": "#592753", - "deep orchid": "#59004e", - "very pale fuchsia": "#ffe2f8", - "pale fuchsia": "#ffc2f0", - "very light fuchsia": "#ff9ee7", - "light brilliant fuchsia": "#ff65d8", - "luminous vivid fuchsia": "#ff00bf", - "pale, light grayish fuchsia": "#e7b8dc", - "light fuchsia": "#e78bd0", - "brilliant fuchsia": "#e751c2", - "vivid fuchsia": "#e700ae", - "grayish fuchsia": "#a87d9e", - "moderate fuchsia": "#a84a91", - "strong fuchsia": "#a8007e", - "dark grayish fuchsia": "#594254", - "dark fuchsia": "#59274d", - "deep fuchsia": "#590043", - "pale cerise": "#ffc2e8", - "very light cerise": "#ff9edb", - "light brilliant cerise": "#ff65c5", - "luminous vivid cerise": "#ff009f", - "light cerise": "#e78bc5", - "brilliant cerise": "#e751af", - "vivid cerise": "#e70091", - "moderate cerise": "#a84a85", - "strong cerise": "#a80069", - "dark cerise": "#592746", - "deep cerise": "#590038", - "very pale rose": "#ffe2f0", - "pale rose": "#ffc2e0", - "very light rose": "#ff9ecf", - "light brilliant rose": "#ff65b2", - "luminous vivid rose": "#ff0080", - "pale, light grayish rose": "#e7b8d0", - "light rose": "#e78bb9", - "brilliant rose": "#e7519c", - "vivid rose": "#e70074", - "grayish rose": "#a87d93", - "moderate rose": "#a84a79", - "strong rose": "#a80054", - "dark grayish rose": "#59424e", - "dark rose": "#592740", - "deep rose": "#59002d", - "very dark rose": "#1d1117", - "very deep rose": "#1d000e", - "pale raspberry": "#ffc2d9", - "very light raspberry": "#ff9ec2", - "light brilliant raspberry": "#ff659f", - "luminous vivid raspberry": "#ff0060", - "light raspberry": "#e78bae", - "brilliant raspberry": "#e75189", - "vivid raspberry": "#e70057", - "moderate raspberry": "#a84a6d", - "strong raspberry": "#a8003f", - "dark raspberry": "#59273a", - "deep raspberry": "#590021", - "very pale crimson": "#ffe2e9", - "pale crimson": "#ffc2d1", - "very light crimson": "#ff9eb6", - "light brilliant crimson": "#ff658b", - "luminous vivid crimson": "#ff0040", - "pale, light grayish crimson": "#e7b8c4", - "light crimson": "#e78ba2", - "brilliant crimson": "#e75177", - "vivid crimson": "#e7003a", - "grayish crimson": "#a87d88", - "moderate crimson": "#a84a61", - "strong crimson": "#a8002a", - "dark grayish crimson": "#594248", - "dark crimson": "#592734", - "deep crimson": "#590016", - "pale amaranth": "#ffc2c9", - "very light amaranth": "#ff9eaa", - "light brilliant amaranth": "#ff6578", - "luminous vivid amaranth": "#ff0020", - "light amaranth": "#e78b96", - "brilliant amaranth": "#e75164", - "vivid amaranth": "#e7001d", - "moderate amaranth": "#a84a55", - "strong amaranth": "#a80015", - "dark amaranth": "#59272d", - "deep amaranth": "#59000b", -}; - -/** @param {string} strColor */ -function isValidColor(strColor) { - if (strColor.length == 0 || strColor in ColorNames) { - return true; - } - var s = new Option().style; - s.color = strColor; - - // return 'false' if color wasn't assigned - return s.color !== ""; -} - -/** @param {string} name */ -function getColor(name) { - return ColorNames[name] || name; -} - -/** @param {Partial} style */ -function normalizeStyle(style) { - return Object.fromEntries( - Object.entries(style) - .filter(([_, value]) => value && value.toString().length) - .map(([key, value]) => - key.toLowerCase().indexOf("color") >= 0 - ? [key, getColor(/** @type {string} */ (value))] - : [key, value && value.toString()], - ), - ); -} - -/** @param {Partial} styles */ -function styleString(styles) { - return Object.entries(normalizeStyle(styles)).reduce( - (acc, [key, value]) => - acc + - key - .split(/(?=[A-Z])/) - .join("-") - .toLowerCase() + - ":" + - value + - ";", - "", - ); -} - -function colorNamesDataList() { - return html` - ${Object.keys(ColorNames).map((name) => html``; -} - -/* Thinking about better properties */ - - -/** - * @typedef {Object} PropOptions - * @property {boolean} [hiddenLabel] - * @property {string} [placeholder] - * @property {string} [title] - * @property {string} [label] - * @property {string} [defaultValue] - * @property {string} [group] - * @property {string} [language] - * @property {any} [valueWhenEmpty] - * @property {string} [pattern] - * @property {function(string):string} [validate] - * @property {string} [inputmode] - * @property {string} [datalist] - * @property {number} [min] - * @property {number} [max] - */ - -/** - * @template {number|boolean|string} T - */ -class Prop { - label = ""; - /** @type {T} */ - _value; - - /** true if this is a formula without leading = */ - isFormulaByDefault = false; - - /** If the entered value starts with = treat it as an expression and store it here */ - formula = ""; - - /** @type {((context?:EvalContext)=>any) | undefined} compiled expression if any */ - compiled = undefined; - - // Each prop gets a unique id based on the id of its container - id = ""; - - /** @type {TreeBase} */ - container; - - /** attach the prop to its containing TreeBase component - * @param {string} name - * @param {any} value - * @param {TreeBase} container - * */ - initialize(name, value, container) { - // create id from the container id - this.id = `${container.id}-${name}`; - // link to the container - this.container = container; - // set the value if provided - if (value != undefined) { - this.set(value); - } - // create a label if it has none - this.label = - this.label || - name // convert from camelCase to Camel Case - .replace(/(?!^)([A-Z])/g, " $1") - .replace(/^./, (s) => s.toUpperCase()); - } - - /** @type {PropOptions} */ - options = {}; - - /** - * @param {T} value - * @param {PropOptions} options */ - constructor(value, options = {}) { - this._value = value; - this.options = options; - if (options.label) { - this.label = options.label; - } - } - validate = debounce( - (/** @type {string} */ value, /** @type {HTMLInputElement} */ input) => { - input.setCustomValidity(""); - if (this.isFormulaByDefault || value.startsWith("=")) { - const [compiled, error] = compileExpression(value); - if (error) { - let message = error.message.replace(/^\[.*?\]/, ""); - message = message.split("\n")[0]; - input.setCustomValidity(message); - } else if (compiled && this.options.validate) - input.setCustomValidity(this.options.validate("" + compiled({}))); - } else if (this.options.validate) { - input.setCustomValidity(this.options.validate(value)); - } - input.reportValidity(); - }, - 100, - ); - - input() { - const text = this.text; - return this.labeled( - html`${this.showValue()}`, - ); - } - onkeydown = (/** @type {KeyboardEvent} */ event) => { - // restore the input on Escape - const { key, target } = event; - if (key == "Escape" && target instanceof HTMLInputElement) { - const text = this.text; - this.validate(text, target); - event.preventDefault(); - target.value = text; - } - }; - oninput = (/** @type {InputEvent} */ event) => { - // validate on each character - if (event.target instanceof HTMLInputElement) { - this.validate(event.target.value, event.target); - event.target.style.width = `${event.target.value.length + 1}ch`; - } - }; - onchange = (/** @type {InputEvent} */ event) => { - if ( - event.target instanceof HTMLInputElement && - event.target.checkValidity() - ) { - this.set(event.target.value); - this.update(); - } - }; - onfocus = (/** @type {FocusEvent}*/ event) => { - if (this.formula && event.target instanceof HTMLInputElement) { - const span = event.target.nextElementSibling; - if (span instanceof HTMLSpanElement) { - const value = this.value; - const type = typeof value; - let text = ""; - if (type === "string" || type === "number" || type === "boolean") { - text = "" + value; - } - span.innerText = text; - } - } - }; - - showValue() { - return this.formula ? [html``] : []; - } - - /** @param {Hole} body */ - labeled(body) { - return html` - - `; - } - - /** @param {HTMLInputElement} inputElement */ - setValidity(inputElement) { - if (inputElement instanceof HTMLInputElement) { - if (this.error) { - console.log("scv", this.error.message); - inputElement.setCustomValidity(this.error.message); - inputElement.reportValidity(); - } else { - console.log("csv"); - inputElement.setCustomValidity(""); - inputElement.reportValidity(); - } - } else { - console.log("not found", inputElement); - } - } - - /** @param {any} value - * @returns {T} - * */ - cast(value) { - return value; - } - - /** - * @param {any} value - */ - set(value) { - this.compiled = undefined; - this.formula = ""; - if ( - typeof value == "string" && - (this.isFormulaByDefault || value.startsWith("=")) - ) { - // compile it here - let error; - [this.compiled, error] = compileExpression(value); - if (error) { - console.error("set error", this.label, value, error.message); - } else { - this.formula = value; - } - } else { - this._value = this.cast(value); - } - } - - /** - * extract the value to save - * returns {string} - */ - get text() { - if (this.formula || this.isFormulaByDefault) return this.formula; - return "" + this._value; - } - - /** @returns {T} */ - get value() { - if (this.compiled) { - if (!this.formula) { - console.log(this.options); - this._value = this.options.valueWhenEmpty ?? ""; - } else { - const v = this.compiled(); - this._value = this.cast(v); - } - } - return this._value; - } - - /** @param {EvalContext} context - The context - * @returns {T} */ - valueInContext(context = {}) { - if (this.compiled) { - if (!this.formula) { - this._value = this.options.valueWhenEmpty ?? ""; - } else { - const v = this.compiled(context); - this._value = this.cast(v); - } - } - return this._value; - } - - update() { - this.container.update(); - } - - /** @param {Error} [error] */ - setError(error = undefined) { - this.error = error; - } -} - -/** @param {string[] | Map | function():Map} arrayOrMap - * @returns Map - */ -function toMap(arrayOrMap) { - if (arrayOrMap instanceof Function) { - return arrayOrMap(); - } - if (Array.isArray(arrayOrMap)) { - return new Map(arrayOrMap.map((item) => [item, item])); - } - return arrayOrMap; -} - -/** @extends {Prop} */ -class Select extends Prop { - /** - * @param {string[] | Map | function():Map} choices - * @param {PropOptions} options - */ - constructor(choices = [], options = {}) { - super("", options); - this.choices = choices; - this._value = options.defaultValue || ""; - } - - /** @param {Map | null} choices */ - input(choices = null) { - if (!choices) { - choices = toMap(this.choices); - } - this._value = this._value || this.options.defaultValue || ""; - return this.labeled( - html``, - ); - } - - /** @param {any} value */ - set(value) { - this._value = value; - } -} - -class Field extends Select { - /** - * @param {PropOptions} options - */ - constructor(options = {}) { - super( - () => toMap([...Globals.data.allFields, "#ComponentName"].sort()), - options, - ); - } -} - -let Cue$1 = class Cue extends Select { - /** - * @param {PropOptions} options - */ - constructor(options = {}) { - super(() => Globals.cues.cueMap, options); - } -}; - -class Pattern extends Select { - /** - * @param {PropOptions} options - */ - constructor(options = {}) { - super(() => Globals.patterns.patternMap, options); - } -} - -class TypeSelect extends Select { - update() { - /* Magic happens here! The replace method on a TreeBaseSwitchable replaces the - * node with a new one to allow type switching in place - * */ - if (this.container instanceof TreeBaseSwitchable) - this.container.replace(this._value); - } -} - -/** @extends {Prop} */ -let String$1 = class String extends Prop {}; - -/* Allow entering a key name by first pressing Enter than pressing a single key - */ -/** @extends {Prop} */ -class KeyName extends Prop { - /** - * @param {string} value - * @param {PropOptions} options - */ - constructor(value = "", options = {}) { - super(value, options); - } - - input() { - /** @param {string} key */ - function mapKey(key) { - if (key == " ") return "Space"; - return key; - } - return this.labeled( - html` { - const target = event.target; - if (!(target instanceof HTMLInputElement)) return; - if (target.hasAttribute("readonly") && event.key == "Enter") { - target.removeAttribute("readonly"); - target.select(); - } else if (!target.hasAttribute("readonly")) { - event.stopPropagation(); - event.preventDefault(); - this._value = event.key; - target.value = mapKey(event.key); - target.setAttribute("readonly", ""); - } - }} - title="Press Enter to change then press a single key to set" - placeholder=${this.options.placeholder} - />`, - ); - } -} - -/** @extends {Prop} */ -class TextArea extends Prop { - /** - * @param {string} value - * @param {PropOptions} options - */ - constructor(value = "", options = {}) { - super(value, options); - this.validate = this.options.validate || ((_) => ""); - } - - input() { - return this.labeled( - html` - - -
${this.errors.join("\n")}
- `, - ); - } - - /** @param {string} value */ - set(value) { - this._value = value; - this.editCSS(); - } -} - -/** @extends {Prop} */ -class Color extends Prop { - /** - * @param {string} value - * @param {PropOptions} options - */ - constructor(value = "white", options = {}) { - options = { - /** @param {string} value */ - validate: (value) => { - if (isValidColor(value)) { - const swatch = document.querySelector(`#${this.id}~div`); - if (swatch instanceof HTMLDivElement) { - swatch.style.backgroundColor = getColor(value); - } - return ""; - } - return "invalid color"; - }, - datalist: "ColorNames", - ...options, - }; - super(value, options); - } - - showValue() { - return [ - html`
`, - ]; - } -} - -/** @extends {Prop} */ -class Voice extends Prop { - /** @param {string} value - * @param {PropOptions} options - */ - constructor(value = "", options = {}) { - super(value, options); - } - - input() { - return this.labeled( - html``, - ); - } -} -/** @extends {Prop} */ -class ADate extends Prop { - /** @param {string} value - * @param {PropOptions} options - */ - constructor(value = "", options = {}) { - super(value, options); - } - - input() { - return this.labeled( - html` { - this._value = event.target.value; - this.update(); - }} - />`, - ); - } -} - -/** - * @template {unknown[]} T - * @param {(...args: T)=>void} callback - * @param {number} wait - * @returns {(...args: T)=>void} - * */ -const debounce = (callback, wait) => { - let timeoutId = null; - return (...args) => { - window.clearTimeout(timeoutId); - timeoutId = window.setTimeout(() => { - callback(...args); - }, wait); - }; -}; - -const treebase = ''; - -/*! (c) Andrea Giammarchi */ - -const {iterator: iterator$1} = Symbol; - -const noop$1 = () => {}; - -/** - * A Map extend that transparently uses WeakRef around its values, - * providing a way to observe their collection at distance. - * @extends {Map} - */ -class WeakValue extends Map { - #delete = (key, ref) => { - super.delete(key); - this.#registry.unregister(ref); - }; - - #get = (key, [ref, onValueCollected]) => { - const value = ref.deref(); - if (!value) { - this.#delete(key, ref); - onValueCollected(key, this); - } - return value; - } - - #registry = new FinalizationRegistry(key => { - const pair = super.get(key); - if (pair) { - super.delete(key); - pair[1](key, this); - } - }); - - constructor(iterable) { - super(); - if (iterable) - for (const [key, value] of iterable) - this.set(key, value); - } - - clear() { - for (const [_, [ref]] of super[iterator$1]()) - this.#registry.unregister(ref); - super.clear(); - } - - delete(key) { - const pair = super.get(key); - return !!pair && !this.#delete(key, pair[0]); - } - - forEach(callback, context) { - for (const [key, value] of this) - callback.call(context, value, key, this); - } - - get(key) { - const pair = super.get(key); - return pair && this.#get(key, pair); - } - - has(key) { - return !!this.get(key); - } - - set(key, value, onValueCollected = noop$1) { - super.delete(key); - const ref = new WeakRef(value); - this.#registry.register(value, key, ref); - return super.set(key, [ref, onValueCollected]); - } - - *[iterator$1]() { - for (const [key, pair] of super[iterator$1]()) { - const value = this.#get(key, pair); - if (value) - yield [key, value]; - } - } - - *entries() { - yield *this[iterator$1](); - } - - *values() { - for (const [_, value] of this[iterator$1]()) - yield value; - } -} - -/** - * Create an object that is persisted to sessionStorage - * - * @template {Object} T - * @param {string} key - * @param {T} initial - * @returns {T} - same type as the initial value - */ -function session(key, initial) { - // import values from storage if present - const json = window.sessionStorage.getItem(key); - if (json) { - const values = JSON.parse(json); - if (!(values instanceof Object)) throw TypeError(); - // validate the value from storage - if (sameObjectShape(initial, values)) initial = values; - } - if (!(initial instanceof Object)) throw TypeError(); - return new Proxy(initial, { - set(obj, prop, value) { - const r = Reflect.set(obj, prop, value); - const json = JSON.stringify(obj); - window.sessionStorage.setItem(key, json); - return r; - }, - }); -} - -/** - * Compare objects to see if they have the same keys and types - * @param {Object} a - * @param {Object} b - * @returns {boolean} - */ -function sameObjectShape(a, b) { - for (const key of Object.keys(a)) { - if (typeof a[key] !== typeof b[key]) return false; - } - return true; -} - -/** - * Provide user friendly names for the components - */ - -/** - * Map the classname into the Menu name and the Help Wiki page name - */ -const namesMap = { - Action: ["Action", "Actions"], - ActionCondition: ["Condition", "Actions#Condition"], - Actions: ["Actions", "Actions"], - ActionUpdate: ["Update", "Actions#Update"], - Audio: ["Audio", "Audio"], - Button: ["Button", "Button"], - Content: ["Content", "Content"], - CueCircle: ["Circle", "Cues"], - CueCss: ["CSS", "Cues#CSS"], - CueFill: ["Fill", "Cues#Fill"], - CueList: ["Cues", "Cues"], - CueOverlay: ["Overlay", "Cues#Overlay"], - Customize: ["Customize", "Customize"], - Designer: ["Designer", "Designer"], - Display: ["Display", "Display"], - Filter: ["Filter", "Patterns#Filter"], - Gap: ["Gap", "Gap"], - Grid: ["Grid", "Grid"], - GridFilter: ["Filter", "Grid#Filter"], - GroupBy: ["Group By", "Patterns#Group By"], - HandlerCondition: ["Condition", "Methods#Condition"], - HandlerKeyCondition: ["Key Condition", "Methods#Key Condition"], - HandlerResponse: ["Response", "Methods#Response"], - HeadMouse: ["Head Mouse", "Head Mouse"], - KeyHandler: ["Key Handler", "Methods#Key Handler"], - Layout: ["Layout", "Layout"], - Logger: ["Logger", "Logger"], - Method: ["Method", "Methods"], - MethodChooser: ["Methods", "Methods"], - ModalDialog: ["Modal Dialog", "Modal Dialog"], - Option: ["Option", "Radio#Option"], - OrderBy: ["Order By", "Patterns#Order By"], - Page: ["Page", "Page"], - PatternGroup: ["Group", "Patterns"], - PatternList: ["Patterns", "Patterns"], - PatternManager: ["Pattern", "Patterns"], - PatternSelector: ["Selector", "Patterns"], - PointerHandler: ["Pointer Handler", "Methods#Pointer Handler"], - Radio: ["Radio", "Radio"], - ResponderActivate: ["Activate", "Methods#Activate"], - ResponderCue: ["Cue", "Methods#Cue"], - ResponderClearCue: ["Clear Cue", "Methods#Clear Cue"], - ResponderEmit: ["Emit", "Methods#Emit"], - ResponderNext: ["Next", "Methods#Next"], - ResponderStartTimer: ["Start Timer", "Methods"], - SocketHandler: ["Socket Handler", "Methods#Socket Handler"], - Speech: ["Speech", "Speech"], - Stack: ["Stack", "Stack"], - TabControl: ["Tab Control", "Tab Control"], - TabPanel: ["Tab", "Tab"], - Timer: ["Timer", "Methods#Timer"], - TimerHandler: ["Timer Handler", "Methods#Timer Handler"], - VSD: ["VSD", "VSD"], -}; - -/** - * Get the name for a menu item from the class name - * @param {string} className - */ -function friendlyName(className) { - return className in namesMap ? namesMap[className][0] : className; -} - -/** - * Get the Wiki name from the class name - * @param {string} className - */ -function wikiName(className) { - return namesMap[className][1].replace(" ", "-"); -} - -class TreeBase { - /** @type {TreeBase[]} */ - children = []; - /** @type {TreeBase | null} */ - parent = null; - /** @type {string[]} */ - allowedChildren = []; - allowDelete = true; - - // every component has a unique id - static treeBaseCounter = 0; - id = `TreeBase-${TreeBase.treeBaseCounter++}`; - - // values here are stored in sessionStorage - persisted = session(this.id, { - settingsDetailsOpen: false, - }); - - // map from id to the component - static idMap = new WeakValue(); - - /** @param {string} id - * @returns {TreeBase | null} */ - static componentFromId(id) { - // strip off any added bits of the id - const match = id.match(/TreeBase-\d+/); - if (match) { - return this.idMap.get(match[0]); - } - return null; - } - - designer = {}; - - /** A mapping from the external class name to the class */ - static nameToClass = new Map(); - /** A mapping from the class to the external class name */ - static classToName = new Map(); - - /** @param {typeof TreeBase} cls - * @param {string} externalName - * */ - static register(cls, externalName) { - this.nameToClass.set(externalName, cls); - this.classToName.set(cls, externalName); - } - - get className() { - return TreeBase.classToName.get(this.constructor); - } - - /** - * Extract the class fields that are Props and return their values as an Object - * @returns {Object} - */ - get propsAsObject() { - return Object.fromEntries( - Object.entries(this) - .filter(([_, prop]) => prop instanceof Prop) - .map(([name, prop]) => [name, prop.value]), - ); - } - - /** - * Extract the values of the fields that are Props - * @returns {Object} - */ - get props() { - return Object.fromEntries( - Object.entries(this).filter(([_, prop]) => prop instanceof Prop), - ); - } - - /** - * Prepare a TreeBase tree for external storage by converting to simple objects and arrays - * @param {Object} [options] - * @param {string[]} options.omittedProps - class names of props to omit - * @returns {Object} - * */ - toObject(options = { omittedProps: [] }) { - const props = Object.fromEntries( - Object.entries(this) - .filter( - ([_, prop]) => - prop instanceof Prop && - !options.omittedProps.includes(prop.constructor.name), - ) - .map(([name, prop]) => [name, prop.text]), - ); - const children = this.children.map((child) => child.toObject(options)); - const result = { - className: this.className, - props, - children, - }; - return result; - } - - /** - * An opportunity for the component to initialize itself. This is - * called in fromObject after the children have been added. If you - * call create directly you should call init afterward. - */ - init() { - /** Make sure OnOfGroup is enforced */ - for (const child of this.children) { - const props = child.props; - for (const instance of Object.values(props)) { - if (instance instanceof OneOfGroup && instance._value) { - instance.clearPeers(); - break; - } - } - } - } - - /** - * Create a TreeBase object - * @template {TreeBase} TB - * @param {string|(new()=>TB)} constructorOrName - * @param {TreeBase | null} parent - * @param {Object} props - * @returns {TB} - * */ - static create(constructorOrName, parent = null, props = {}) { - const constructor = - typeof constructorOrName == "string" - ? TreeBase.nameToClass.get(constructorOrName) - : constructorOrName; - /** @type {TB} */ - const result = new constructor(); - - // initialize the props - for (const [name, prop] of Object.entries(result.props)) { - prop.initialize(name, props[name], result); - } - - // link it to its parent - if (parent) { - result.parent = parent; - parent.children.push(result); - } - - // remember the relationship between id and component - TreeBase.idMap.set(result.id, result); - - return result; - } - - /** - * Instantiate a TreeBase tree from its external representation - * @param {Object} obj - * @param {TreeBase | null} parent - * @returns {TreeBase} - should be {this} but that isn't supported for some reason - * */ - static fromObject(obj, parent = null) { - // Get the constructor from the class map - if (!obj) console.trace("fromObject", obj); - const className = obj.className; - const constructor = this.nameToClass.get(className); - if (!constructor) { - console.trace("className not found", className, obj); - throw new Error("className not found"); - } - - // Create the object and link it to its parent - const result = this.create(constructor, parent, obj.props); - - // Link in the children - for (const childObj of obj.children) { - if (childObj instanceof TreeBase) { - childObj.parent = result; - result.children.push(childObj); - } else { - TreeBase.fromObject(childObj, result); - } - } - - // allow the component to initialize itself - result.init(); - - // Validate the type is what was expected - if (result instanceof this) return result; - - // Die if not - console.error("expected", this); - console.error("got", result); - throw new Error(`fromObject failed`); - } - - /** - * Signal nodes above that something has been updated - */ - update() { - let start = this; - /** @type {TreeBase | null} */ - let p = start; - while (p) { - p.onUpdate(start); - p = p.parent; - } - } - - /** - * Called when something below is updated - * @param {TreeBase} _start - */ - onUpdate(_start) {} - - /** - * Render the designer interface and return the resulting Hole - * @returns {Hole} - */ - settings() { - const detailsId = this.id + "-details"; - const settingsId = this.id + "-settings"; - return html`
-
- (this.persisted.settingsDetailsOpen = target.open)} - > - ${this.settingsSummary()} - ${this.settingsDetails()} -
- ${this.settingsChildren()} -
`; - } - - /** - * Render the summary of a components settings - * @returns {Hole} - */ - settingsSummary() { - const name = Object.hasOwn(this, "name") ? this["name"].value : ""; - return html`

${friendlyName(this.className)} ${name}

`; - } - - /** - * Render the details of a components settings - * @returns {Hole[]} - */ - settingsDetails() { - const props = this.props; - const inputs = Object.values(props).map((prop) => prop.input()); - return inputs; - } - - /** - * @returns {Hole} - */ - settingsChildren() { - return this.orderedChildren(); - } - - /** - * Render the user interface and return the resulting Hole - * @returns {Hole} - */ - template() { - return html`
`; - } - - /** - * Render the user interface catching errors and return the resulting Hole - * @returns {Hole} - */ - safeTemplate() { - try { - return this.template(); - } catch (error) { - errorHandler(error, ` safeTemplate ${this.className}`); - let classes = [this.className.toLowerCase()]; - classes.push("error"); - return html`
ERROR
`; - } - } - - /** @typedef {Object} ComponentAttrs - * @property {string[]} [classes] - * @property {Object} [style] - */ - - /** - * Wrap the body of a component - * - * @param {ComponentAttrs} attrs - * @param {Hole} body - * @returns {Hole} - */ - component(attrs, body) { - attrs = { style: {}, ...attrs }; - let classes = [this.className.toLowerCase()]; - if ("classes" in attrs) { - classes = classes.concat(attrs.classes); - } - return html`
- ${body} -
`; - } - - /** - * Swap two of my children - * @param {number} i - * @param {number} j - */ - swap(i, j) { - const A = this.children; - [A[i], A[j]] = [A[j], A[i]]; - } - - /** - * Move me to given position in my parent - * @param {number} i - */ - moveTo(i) { - const peers = this.parent?.children || []; - peers.splice(this.index, 1); - peers.splice(i, 0, this); - } - - /** - * Move me up or down by 1 position if possible - * @param {boolean} up - */ - moveUpDown(up) { - const parent = this.parent; - if (!parent) return; - const peers = parent.children; - if (peers.length > 1) { - const index = this.index; - const step = up ? -1 : 1; - if ((up && index > 0) || (!up && index < peers.length - 1)) { - parent.swap(index, index + step); - } - } - } - - /** - * Get the index of this component in its parent - * @returns {number} - */ - get index() { - return (this.parent && this.parent.children.indexOf(this)) || 0; - } - - /** - * * Remove this child from their parent and return the id of the child to receive focus - * @returns {string} - * */ - remove() { - if (!this.parent) return ""; - const peers = this.parent.children; - const index = peers.indexOf(this); - const parent = this.parent; - this.parent = null; - peers.splice(index, 1); - if (peers.length > index) { - return peers[index].id; - } else if (peers.length > 0) { - return peers[peers.length - 1].id; - } else { - return parent.id; - } - } - - /** - * Create HTML LI nodes from the children - */ - listChildren(children = this.children) { - return children.map((child) => html`
  • ${child.settings()}
  • `); - } - - /** - * Create an HTML ordered list from the children - */ - orderedChildren(children = this.children) { - return html`
      - ${this.listChildren(children)} -
    `; - } - - /** - * Create an HTML unordered list from the children - * */ - unorderedChildren(children = this.children) { - return html`
      - ${this.listChildren(children)} -
    `; - } - - /** - * Return the nearest parent of the given type - * @template T - * @param {new() => T} type - * @returns {T} - * */ - nearestParent(type) { - let p = this.parent; - while (p) { - if (p instanceof type) { - return p; - } - p = p.parent; - } - throw new Error("No such parent"); - } +// DEFLATE is a complex format; to read this code, you should probably check the RFC first: +// https://tools.ietf.org/html/rfc1951 +// You may also wish to take a look at the guide I made about this program: +// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad +// Some of the following code is similar to that of UZIP.js: +// https://github.com/photopea/UZIP.js +// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size. +// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint +// is better for memory in most engines (I *think*). - /** - * Filter children by their type - * @template T - * @param {new() => T} type - * @returns {T[]} - */ - filterChildren(type) { - /** @type {T[]} */ - const result = []; - for (const child of this.children) { - if (child instanceof type) { - result.push(child); - } +// aliases for shorter compressed code (most minifers don't do this) +var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array; +// fixed length extra bits +var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]); +// fixed distance extra bits +var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]); +// code length index map +var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); +// get base, reverse index map from extra bits +var freb = function (eb, start) { + var b = new u16(31); + for (var i = 0; i < 31; ++i) { + b[i] = start += 1 << eb[i - 1]; } - return result; - } - - /** @param {string[]} classes - * @returns {string} - */ - CSSClasses(...classes) { - return classes.join(" "); - } -} - -/** - * A variant of TreeBase that allows replacing a node with one of a similar type - */ -class TreeBaseSwitchable extends TreeBase { - init() { - super.init(); - // find the TypeSelect property and set its value - for (const prop of Object.values(this.props)) { - if (prop instanceof TypeSelect) { - if (!prop.value) { - prop.set(this.className); + // numbers here are at max 18 bits + var r = new i32(b[30]); + for (var i = 1; i < 30; ++i) { + for (var j = b[i]; j < b[i + 1]; ++j) { + r[j] = ((j - b[i]) << 5) | i; } - } - } - } - - /** Replace this node with one of a compatible type - * @param {string} className */ - replace(className) { - if (!this.parent) return; - if (this.className == className) return; - // extract the values of the old props - const props = Object.fromEntries( - Object.entries(this) - .filter(([_, prop]) => prop instanceof Prop) - .map(([name, prop]) => [name, prop.value]), - ); - const replacement = TreeBase.create(className, null, props); - replacement.init(); - const index = this.parent.children.indexOf(this); - this.parent.children[index] = replacement; - replacement.parent = this.parent; - this.update(); - } -} - -class Messages extends TreeBase { - /** @type {string[]} */ - messages = []; - - template() { - if (this.messages.length) { - const result = html`
    - ${this.messages.map((message) => html`

    ${message}

    `)} -
    `; - this.messages = []; - return result; - } else { - return html`
    `; } - } - - report(message = "") { - console.log({ message }); - this.messages.push(message); - } -} - -/** Display an error message for user feedback - * @param {string} msg - the error message - * @param {string[]} trace - stack trace - */ -function reportInternalError(msg, trace) { - const result = document.createElement("div"); - result.id = "ErrorReport"; - render( - result, - html`
    -

    Internal Error

    -

    - Your browser has detected an internal error in OS-DPI. It was very - likely caused by our program bug. We hope you will help us by sending a - report of the information below. Simply click this button - - and then paste into an email to - gb@cs.unc.edu. - -

    -
    -

    Error Report

    -

    ${msg}

    -

    Stack Trace

    -
      - ${trace.map((s) => html`
    • ${s}
    • `)} -
    -
    -
    `, - ); - document.body.prepend(result); + return { b: b, r: r }; +}; +var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r; +// we can ignore the fact that the other numbers are wrong; they never happen anyway +fl[28] = 258, revfl[258] = 28; +var _b = freb(fdeb, 0), fd = _b.b, revfd = _b.r; +// map of value to reverse (assuming 16 bits) +var rev = new u16(32768); +for (var i = 0; i < 32768; ++i) { + // reverse table algorithm from SO + var x = ((i & 0xAAAA) >> 1) | ((i & 0x5555) << 1); + x = ((x & 0xCCCC) >> 2) | ((x & 0x3333) << 2); + x = ((x & 0xF0F0) >> 4) | ((x & 0x0F0F) << 4); + rev[i] = (((x & 0xFF00) >> 8) | ((x & 0x00FF) << 8)) >> 1; } - -/** @param {string} msg - * @param {string} _file - * @param {number} _line - * @param {number} _col - * @param {Error} error - */ -window.onerror = async function (msg, _file, _line, _col, error) { - console.error("onerror", msg, error); - if (error instanceof Error) { - try { - const frames = await stacktraceExports.fromError(error); - const trace = frames.map((frame) => `${frame.toString()}`); - reportInternalError(msg.toString(), trace); - } catch (e) { - const msg2 = `Caught an error trying to report an error. - The original message was "${msg.toString()}". - With file=${_file} line=${_line} column=${_col} - error=${error.toString()}`; - reportInternalError(msg2, []); +// create huffman tree from u8 "map": index -> code length for code index +// mb (max bits) must be at most 15 +// TODO: optimize/split up? +var hMap = (function (cd, mb, r) { + var s = cd.length; + // index + var i = 0; + // u16 "map": index -> # of codes with bit length = index + var l = new u16(mb); + // length of cd must be 288 (total # of codes) + for (; i < s; ++i) { + if (cd[i]) + ++l[cd[i] - 1]; } - } + // u16 "map": index -> minimum code for bit length = index + var le = new u16(mb); + for (i = 1; i < mb; ++i) { + le[i] = (le[i - 1] + l[i - 1]) << 1; + } + var co; + if (r) { + // u16 "map": index -> number of actual bits, symbol for code + co = new u16(1 << mb); + // bits to remove for reverser + var rvb = 15 - mb; + for (i = 0; i < s; ++i) { + // ignore 0 lengths + if (cd[i]) { + // num encoding both symbol and bits read + var sv = (i << 4) | cd[i]; + // free bits + var r_1 = mb - cd[i]; + // start value + var v = le[cd[i] - 1]++ << r_1; + // m is end value + for (var m = v | ((1 << r_1) - 1); v <= m; ++v) { + // every 16 bit value starting with the code yields the same result + co[rev[v] >> rvb] = sv; + } + } + } + } + else { + co = new u16(s); + for (i = 0; i < s; ++i) { + if (cd[i]) { + co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]); + } + } + } + return co; +}); +// fixed length tree +var flt = new u8(288); +for (var i = 0; i < 144; ++i) + flt[i] = 8; +for (var i = 144; i < 256; ++i) + flt[i] = 9; +for (var i = 256; i < 280; ++i) + flt[i] = 7; +for (var i = 280; i < 288; ++i) + flt[i] = 8; +// fixed distance tree +var fdt = new u8(32); +for (var i = 0; i < 32; ++i) + fdt[i] = 5; +// fixed length map +var flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1); +// fixed distance map +var fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1); +// find max of array +var max = function (a) { + var m = a[0]; + for (var i = 1; i < a.length; ++i) { + if (a[i] > m) + m = a[i]; + } + return m; +}; +// read d, starting at bit p and mask with m +var bits = function (d, p, m) { + var o = (p / 8) | 0; + return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m; +}; +// read d, starting at bit p continuing for at least 16 bits +var bits16 = function (d, p) { + var o = (p / 8) | 0; + return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7)); +}; +// get end of byte +var shft = function (p) { return ((p + 7) / 8) | 0; }; +// typed array slice - allows garbage collector to free original reference, +// while being more compatible than .slice +var slc = function (v, s, e) { + if (s == null || s < 0) + s = 0; + if (e == null || e > v.length) + e = v.length; + // can't use .constructor in case user-supplied + return new u8(v.subarray(s, e)); +}; +// error codes +var ec = [ + 'unexpected EOF', + 'invalid block type', + 'invalid length/literal', + 'invalid distance', + 'stream finished', + 'no stream handler', + , + 'no callback', + 'invalid UTF-8 data', + 'extra field too long', + 'date not in range 1980-2099', + 'filename too long', + 'stream finishing', + 'invalid zip data' + // determined by unknown compression method +]; +var err = function (ind, msg, nt) { + var e = new Error(msg || ec[ind]); + e.code = ind; + if (Error.captureStackTrace) + Error.captureStackTrace(e, err); + if (!nt) + throw e; + return e; +}; +// expands raw DEFLATE data +var inflt = function (dat, st, buf, dict) { + // source length dict length + var sl = dat.length, dl = dict ? dict.length : 0; + if (!sl || st.f && !st.l) + return buf || new u8(0); + var noBuf = !buf; + // have to estimate size + var resize = noBuf || st.i != 2; + // no state + var noSt = st.i; + // Assumes roughly 33% compression ratio average + if (noBuf) + buf = new u8(sl * 3); + // ensure buffer can fit at least l elements + var cbuf = function (l) { + var bl = buf.length; + // need to increase size to fit + if (l > bl) { + // Double or set to necessary, whichever is greater + var nbuf = new u8(Math.max(bl * 2, l)); + nbuf.set(buf); + buf = nbuf; + } + }; + // last chunk bitpos bytes + var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n; + // total bits + var tbts = sl * 8; + do { + if (!lm) { + // BFINAL - this is only 1 when last chunk is next + final = bits(dat, pos, 1); + // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman + var type = bits(dat, pos + 1, 3); + pos += 3; + if (!type) { + // go to end of byte boundary + var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l; + if (t > sl) { + if (noSt) + err(0); + break; + } + // ensure size + if (resize) + cbuf(bt + l); + // Copy over uncompressed data + buf.set(dat.subarray(s, t), bt); + // Get new bitpos, update byte count + st.b = bt += l, st.p = pos = t * 8, st.f = final; + continue; + } + else if (type == 1) + lm = flrm, dm = fdrm, lbt = 9, dbt = 5; + else if (type == 2) { + // literal lengths + var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4; + var tl = hLit + bits(dat, pos + 5, 31) + 1; + pos += 14; + // length+distance tree + var ldt = new u8(tl); + // code length tree + var clt = new u8(19); + for (var i = 0; i < hcLen; ++i) { + // use index map to get real code + clt[clim[i]] = bits(dat, pos + i * 3, 7); + } + pos += hcLen * 3; + // code lengths bits + var clb = max(clt), clbmsk = (1 << clb) - 1; + // code lengths map + var clm = hMap(clt, clb, 1); + for (var i = 0; i < tl;) { + var r = clm[bits(dat, pos, clbmsk)]; + // bits read + pos += r & 15; + // symbol + var s = r >> 4; + // code length to copy + if (s < 16) { + ldt[i++] = s; + } + else { + // copy count + var c = 0, n = 0; + if (s == 16) + n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1]; + else if (s == 17) + n = 3 + bits(dat, pos, 7), pos += 3; + else if (s == 18) + n = 11 + bits(dat, pos, 127), pos += 7; + while (n--) + ldt[i++] = c; + } + } + // length tree distance tree + var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit); + // max length bits + lbt = max(lt); + // max dist bits + dbt = max(dt); + lm = hMap(lt, lbt, 1); + dm = hMap(dt, dbt, 1); + } + else + err(1); + if (pos > tbts) { + if (noSt) + err(0); + break; + } + } + // Make sure the buffer can hold this + the largest possible addition + // Maximum chunk size (practically, theoretically infinite) is 2^17 + if (resize) + cbuf(bt + 131072); + var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; + var lpos = pos; + for (;; lpos = pos) { + // bits read, code + var c = lm[bits16(dat, pos) & lms], sym = c >> 4; + pos += c & 15; + if (pos > tbts) { + if (noSt) + err(0); + break; + } + if (!c) + err(2); + if (sym < 256) + buf[bt++] = sym; + else if (sym == 256) { + lpos = pos, lm = null; + break; + } + else { + var add = sym - 254; + // no extra bits needed if less + if (sym > 264) { + // index + var i = sym - 257, b = fleb[i]; + add = bits(dat, pos, (1 << b) - 1) + fl[i]; + pos += b; + } + // dist + var d = dm[bits16(dat, pos) & dms], dsym = d >> 4; + if (!d) + err(3); + pos += d & 15; + var dt = fd[dsym]; + if (dsym > 3) { + var b = fdeb[dsym]; + dt += bits16(dat, pos) & (1 << b) - 1, pos += b; + } + if (pos > tbts) { + if (noSt) + err(0); + break; + } + if (resize) + cbuf(bt + 131072); + var end = bt + add; + if (bt < dt) { + var shift = dl - dt, dend = Math.min(dt, end); + if (shift + bt < 0) + err(3); + for (; bt < dend; ++bt) + buf[bt] = dict[shift + bt]; + } + for (; bt < end; ++bt) + buf[bt] = buf[bt - dt]; + } + } + st.l = lm, st.p = lpos, st.b = bt, st.f = final; + if (lm) + final = 1, st.m = lbt, st.d = dm, st.n = dbt; + } while (!final); + // don't reallocate for streams or user buffers + return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt); }; - -/** @param {Error} error */ -function errorHandler(error, extra = "") { - let stack = []; - let cause = `${error.name}${extra}`; - if (error.stack) { - const errorLines = error.stack.split("\n"); - stack = errorLines.slice(1); - cause = errorLines[0] + extra; - } - reportInternalError(cause, stack); -} -/** @param {PromiseRejectionEvent} error */ -window.onunhandledrejection = function (error) { - console.error("onunhandlederror", error); - error.preventDefault(); - reportInternalError( - error.reason.message, - error.reason.stack?.split("\n") || ["no stack"], - ); +// starting at p, write the minimum number of bits that can hold v to d +var wbits = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >> 8; }; - -const gridfilter = ''; - -class GridFilter extends TreeBase { - field = new Field({ hiddenLabel: true }); - operator = new Select(Object.keys(comparators), { hiddenLabel: true }); - value = new Expression("", { hiddenLabel: true }); - - /** move my parent instead of me. - * @param {boolean} up - */ - moveUpDown(up) { - this.parent?.moveUpDown(up); - } - - /** Format the settings - * @param {GridFilter[]} filters - * @return {Hole} - */ - static FilterSettings(filters) { - /** @type {Hole} */ - let table; - if (filters.length > 0) { - table = html` - - - - - - - - - - - ${filters.map( - (filter, index) => html` - - - - - - - `, - )} - -
    #FieldOperatorValue
    ${index + 1}${filter.field.input()}${filter.operator.input()}${filter.value.input()}
    - `; - } else { - table = html`
    `; - } - return html`
    - Filters - ${table} -
    `; - } - - /** Convert from Props to values for data module - * @param {GridFilter[]} filters - */ - static toContentFilters(filters) { - return filters.map((child) => ({ - field: child.field.value, - operator: child.operator.value, - value: child.value.value, - })); - } -} -TreeBase.register(GridFilter, "GridFilter"); - -const collator = new Intl.Collator("en", { sensitivity: "base" }); -const collatorNumber = new Intl.Collator("en", { numeric: true }); - -/** Implement comparison operators - * @typedef {function(string, string): boolean} Comparator - * - * @type {Object} - */ -const comparators = { - equals: (f, v) => collator.compare(f, v) === 0 || f === "*" || v === "*", - "starts with": (f, v) => f.toUpperCase().startsWith(v.toUpperCase()), - empty: (f) => !f, - "not empty": (f) => !!f, - "less than": (f, v) => collatorNumber.compare(f, v) < 0, - "greater than": (f, v) => collatorNumber.compare(f, v) > 0, - "less or equal": (f, v) => collatorNumber.compare(f, v) <= 0, - "greater or equal": (f, v) => collatorNumber.compare(f, v) >= 0, +// starting at p, write the minimum number of bits (>8) that can hold v to d +var wbits16 = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >> 8; + d[o + 2] |= v >> 16; }; - -/** Test a row with a filter - * @param {ContentFilter} filter - * @param {Row} row - * @returns {boolean} - */ -function match(filter, row) { - const field = row[filter.field.slice(1)] || ""; - let value = filter.value || ""; - const comparator = comparators[filter.operator]; - if (!comparator) return true; - let r = comparator(field.toString(), value.toString()); - return r; -} - -class Data { - /** @param {Rows} rows - rows coming from the spreadsheet */ - constructor(rows) { - this.contentRows = (Array.isArray(rows) && rows) || []; - this.allrows = this.contentRows; - /** @type {Set} */ - this.allFields = new Set(); - this.updateAllFields(); - this.loadTime = new Date(); - } - - updateAllFields() { - this.allFields = this.contentRows.reduce((previous, current) => { - for (const field of Object.keys(current)) { - previous.add("#" + field); - } - return previous; - }, new Set()); - this.clearFields = {}; - for (const field of this.allFields) { - this.clearFields[field.slice(1)] = null; +// creates code lengths from a frequency table +var hTree = function (d, mb) { + // Need extra info to make a tree + var t = []; + for (var i = 0; i < d.length; ++i) { + if (d[i]) + t.push({ s: i, f: d[i] }); + } + var s = t.length; + var t2 = t.slice(); + if (!s) + return { t: et, l: 0 }; + if (s == 1) { + var v = new u8(t[0].s + 1); + v[t[0].s] = 1; + return { t: v, l: 1 }; + } + t.sort(function (a, b) { return a.f - b.f; }); + // after i2 reaches last ind, will be stopped + // freq must be greater than largest possible number of symbols + t.push({ s: -1, f: 25001 }); + var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2; + t[0] = { s: -1, f: l.f + r.f, l: l, r: r }; + // efficient algorithm from UZIP.js + // i0 is lookbehind, i2 is lookahead - after processing two low-freq + // symbols that combined have high freq, will start processing i2 (high-freq, + // non-composite) symbols instead + // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/ + while (i1 != s - 1) { + l = t[t[i0].f < t[i2].f ? i0++ : i2++]; + r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++]; + t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r }; } - } - - /** - * Extract rows with the given filters - * - * @param {GridFilter[]} filters - each filter must return true - * @param {boolean} [clearFields] - return null for undefined fields - * @return {Rows} Rows that pass the filters - */ - getMatchingRows(filters, clearFields = true) { - // all the filters must match the row - const boundFilters = filters.map((filter) => ({ - field: filter.field.value, - operator: filter.operator.value, - value: filter.value.value, - })); - let result = this.allrows.filter((row) => - boundFilters.every((filter) => match(filter, row)), - ); - if (clearFields) - result = result.map((row) => ({ ...this.clearFields, ...row })); - return result; - } - - /** - * Test if any rows exist after filtering - * - * @param {GridFilter[]} filters - * @param {EvalContext} context - * @return {Boolean} true if tag combination occurs - */ - hasMatchingRows(filters, context) { - const boundFilters = filters.map((filter) => ({ - field: filter.field.value, - operator: filter.operator.value, - value: filter.value.valueInContext(context), - })); - const result = this.allrows.some((row) => - boundFilters.every((filter) => match(filter, row)), - ); - return result; - } - - /** - * Add rows from the socket interface - * @param {Rows} rows - */ - setDynamicRows(rows) { - if (!Array.isArray(rows)) return; - this.allrows = rows.concat(this.contentRows); - this.updateAllFields(); - this.loadTime = new Date(); - } -} - -const e$1=Object.assign||((e,t)=>(t&&Object.keys(t).forEach(o=>e[o]=t[o]),e)),t$1=(e,r,s)=>{const c=typeof s;if(s&&"object"===c)if(Array.isArray(s))for(const o of s)r=t$1(e,r,o);else for(const c of Object.keys(s)){const f=s[c];"function"==typeof f?r[c]=f(r[c],o$1):void 0===f?e&&!isNaN(c)?r.splice(c,1):delete r[c]:null===f||"object"!=typeof f||Array.isArray(f)?r[c]=f:"object"==typeof r[c]?r[c]=f===r[c]?f:o$1(r[c],f):r[c]=t$1(!1,{},f);}else "function"===c&&(r=s(r,o$1));return r},o$1=(o,...r)=>{const s=Array.isArray(o);return t$1(s,s?o.slice():e$1({},o),r)}; - -let State$1 = class State { - constructor(persistKey = "") { - this.persistKey = persistKey; - /** @type {Set} */ - this.listeners = new Set(); - /** @type {Object} */ - this.values = {}; - /** @type {Set} */ - this.updated = new Set(); - if (this.persistKey) { - /* persistence */ - const persist = window.sessionStorage.getItem(this.persistKey); - if (persist) { - this.values = JSON.parse(persist); - } + var maxSym = t2[0].s; + for (var i = 1; i < s; ++i) { + if (t2[i].s > maxSym) + maxSym = t2[i].s; } - } - - /** unified interface to state - * @param {string} [name] - possibly dotted path to a value - * @param {any} defaultValue - * @returns {any} - */ - get(name, defaultValue = "") { - if (name && name.length) { - return name - .split(".") - .reduce((o, p) => (o ? o[p] : defaultValue), this.values); - } else { - return undefined; + // code lengths + var tr = new u16(maxSym + 1); + // max bits in tree + var mbt = ln(t[i1 - 1], tr, 0); + if (mbt > mb) { + // more algorithms from UZIP.js + // TODO: find out how this code works (debt) + // ind debt + var i = 0, dt = 0; + // left cost + var lft = mbt - mb, cst = 1 << lft; + t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); + for (; i < s; ++i) { + var i2_1 = t2[i].s; + if (tr[i2_1] > mb) { + dt += cst - (1 << (mbt - tr[i2_1])); + tr[i2_1] = mb; + } + else + break; + } + dt >>= lft; + while (dt > 0) { + var i2_2 = t2[i].s; + if (tr[i2_2] < mb) + dt -= 1 << (mb - tr[i2_2]++ - 1); + else + ++i; + } + for (; i >= 0 && dt; --i) { + var i2_3 = t2[i].s; + if (tr[i2_3] == mb) { + --tr[i2_3]; + ++dt; + } + } + mbt = mb; } - } - - /** - * update the state with a patch and invoke any listeners - * - * @param {Object} patch - the changes to make to the state - * @return {void} - */ - update(patch = {}) { - for (const key in patch) { - this.updated.add(key); + return { t: new u8(tr), l: mbt }; +}; +// get the max length and assign length codes +var ln = function (n, l, d) { + return n.s == -1 + ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) + : (l[n.s] = d); +}; +// length codes generation +var lc = function (c) { + var s = c.length; + // Note that the semicolon was intentional + while (s && !c[--s]) + ; + var cl = new u16(++s); + // ind num streak + var cli = 0, cln = c[0], cls = 1; + var w = function (v) { cl[cli++] = v; }; + for (var i = 1; i <= s; ++i) { + if (c[i] == cln && i != s) + ++cls; + else { + if (!cln && cls > 2) { + for (; cls > 138; cls -= 138) + w(32754); + if (cls > 2) { + w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305); + cls = 0; + } + } + else if (cls > 3) { + w(cln), --cls; + for (; cls > 6; cls -= 6) + w(8304); + if (cls > 2) + w(((cls - 3) << 5) | 8208), cls = 0; + } + while (cls--) + w(cln); + cls = 1; + cln = c[i]; + } + } + return { c: cl.subarray(0, cli), n: s }; +}; +// calculate the length of output from tree, code lengths +var clen = function (cf, cl) { + var l = 0; + for (var i = 0; i < cl.length; ++i) + l += cf[i] * cl[i]; + return l; +}; +// writes a fixed block +// returns the new bit pos +var wfblk = function (out, pos, dat) { + // no need to write 00 as type: TypedArray defaults to 0 + var s = dat.length; + var o = shft(pos + 2); + out[o] = s & 255; + out[o + 1] = s >> 8; + out[o + 2] = out[o] ^ 255; + out[o + 3] = out[o + 1] ^ 255; + for (var i = 0; i < s; ++i) + out[o + i + 4] = dat[i]; + return (o + 4 + s) * 8; +}; +// writes a block +var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { + wbits(out, p++, final); + ++lf[256]; + var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l; + var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l; + var _c = lc(dlt), lclt = _c.c, nlc = _c.n; + var _d = lc(ddt), lcdt = _d.c, ndc = _d.n; + var lcfreq = new u16(19); + for (var i = 0; i < lclt.length; ++i) + ++lcfreq[lclt[i] & 31]; + for (var i = 0; i < lcdt.length; ++i) + ++lcfreq[lcdt[i] & 31]; + var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l; + var nlcc = 19; + for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) + ; + var flen = (bl + 5) << 3; + var ftlen = clen(lf, flt) + clen(df, fdt) + eb; + var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]; + if (bs >= 0 && flen <= ftlen && flen <= dtlen) + return wfblk(out, p, dat.subarray(bs, bs + bl)); + var lm, ll, dm, dl; + wbits(out, p, 1 + (dtlen < ftlen)), p += 2; + if (dtlen < ftlen) { + lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt; + var llm = hMap(lct, mlcb, 0); + wbits(out, p, nlc - 257); + wbits(out, p + 5, ndc - 1); + wbits(out, p + 10, nlcc - 4); + p += 14; + for (var i = 0; i < nlcc; ++i) + wbits(out, p + 3 * i, lct[clim[i]]); + p += 3 * nlcc; + var lcts = [lclt, lcdt]; + for (var it = 0; it < 2; ++it) { + var clct = lcts[it]; + for (var i = 0; i < clct.length; ++i) { + var len = clct[i] & 31; + wbits(out, p, llm[len]), p += lct[len]; + if (len > 15) + wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12; + } + } } - this.values = o$1(this.values, patch); - for (const callback of this.listeners) { - callback(); + else { + lm = flm, ll = flt, dm = fdm, dl = fdt; } - - if (this.persistKey) { - const persist = JSON.stringify(this.values); - window.sessionStorage.setItem(this.persistKey, persist); - // console.trace("persist $tabControl", this.values["$tabControl"]); + for (var i = 0; i < li; ++i) { + var sym = syms[i]; + if (sym > 255) { + var len = (sym >> 18) & 31; + wbits16(out, p, lm[len + 257]), p += ll[len + 257]; + if (len > 7) + wbits(out, p, (sym >> 23) & 31), p += fleb[len]; + var dst = sym & 31; + wbits16(out, p, dm[dst]), p += dl[dst]; + if (dst > 3) + wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst]; + } + else { + wbits16(out, p, lm[sym]), p += ll[sym]; + } } - } - - /** - * return a new state with the patch applied - * @param {Object} patch - changes to apply - * @return {State} - new independent state - */ - clone(patch = {}) { - const result = new State(); - result.values = o$1(this.values, patch); - return result; - } - - /** clear - reset the state - */ - clear() { - const userState = Object.keys(this.values).filter((name) => - name.startsWith("$"), - ); - const patch = Object.fromEntries( - userState.map((name) => [name, undefined]), - ); - this.update(patch); - } - - /** observe - call this function when the state updates - * @param {Function} callback - */ - observe(callback) { - this.listeners.add(callback); - } - - /** return true if the given state has been upated on this cycle - * @param {string} stateName - * @returns boolean - */ - hasBeenUpdated(stateName) { - return this.updated.has(stateName); - } - - /** clear updated for the next cycle - */ - clearUpdated() { - this.updated.clear(); - } - - /** define - add a named state to the global system state - * @param {String} name - name of the state - * @param {any} defaultValue - value if not already defined - */ - define(name, defaultValue) { - if (typeof this.values[name] === "undefined") { - this.values[name] = defaultValue; + wbits16(out, p, lm[256]); + return p + ll[256]; +}; +// deflate options (nice << 13) | chain +var deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); +// empty +var et = /*#__PURE__*/ new u8(0); +// compresses data into a raw DEFLATE buffer +var dflt = function (dat, lvl, plvl, pre, post, st) { + var s = st.z || dat.length; + var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); + // writing to this writes to the output buffer + var w = o.subarray(pre, o.length - post); + var lst = st.l; + var pos = (st.r || 0) & 7; + if (lvl) { + if (pos) + w[0] = st.r >> 3; + var opt = deo[lvl - 1]; + var n = opt >> 13, c = opt & 8191; + var msk_1 = (1 << plvl) - 1; + // prev 2-byte val map curr 2-byte val map + var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1); + var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; + var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; + // 24576 is an arbitrary number of maximum symbols per block + // 424 buffer for last block + var syms = new i32(25000); + // length/literal freq distance freq + var lf = new u16(288), df = new u16(32); + // l/lcnt exbits index l/lind waitdx blkpos + var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0; + for (; i + 2 < s; ++i) { + // hash value + var hv = hsh(i); + // index mod 32768 previous index mod + var imod = i & 32767, pimod = head[hv]; + prev[imod] = pimod; + head[hv] = imod; + // We always should modify head and prev, but only add symbols if + // this data is not yet processed ("wait" for wait index) + if (wi <= i) { + // bytes remaining + var rem = s - i; + if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) { + pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); + li = lc_1 = eb = 0, bs = i; + for (var j = 0; j < 286; ++j) + lf[j] = 0; + for (var j = 0; j < 30; ++j) + df[j] = 0; + } + // len dist chain + var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767; + if (rem > 2 && hv == hsh(i - dif)) { + var maxn = Math.min(n, rem) - 1; + var maxd = Math.min(32767, i); + // max possible length + // not capped at dif because decompressors implement "rolling" index population + var ml = Math.min(258, rem); + while (dif <= maxd && --ch_1 && imod != pimod) { + if (dat[i + l] == dat[i + l - dif]) { + var nl = 0; + for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl) + ; + if (nl > l) { + l = nl, d = dif; + // break out early when we reach "nice" (we are satisfied enough) + if (nl > maxn) + break; + // now, find the rarest 2-byte sequence within this + // length of literals and search for that instead. + // Much faster than just using the start + var mmd = Math.min(dif, nl - 2); + var md = 0; + for (var j = 0; j < mmd; ++j) { + var ti = i - dif + j & 32767; + var pti = prev[ti]; + var cd = ti - pti & 32767; + if (cd > md) + md = cd, pimod = ti; + } + } + } + // check the previous match + imod = pimod, pimod = prev[imod]; + dif += imod - pimod & 32767; + } + } + // d will be nonzero only when a match was found + if (d) { + // store both dist and len data in one int32 + // Make sure this is recognized as a len/dist with 28th bit (2^28) + syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; + var lin = revfl[l] & 31, din = revfd[d] & 31; + eb += fleb[lin] + fdeb[din]; + ++lf[257 + lin]; + ++df[din]; + wi = i + l; + ++lc_1; + } + else { + syms[li++] = dat[i]; + ++lf[dat[i]]; + } + } + } + for (i = Math.max(i, wi); i < s; ++i) { + syms[li++] = dat[i]; + ++lf[dat[i]]; + } + pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); + if (!lst) { + st.r = (pos & 7) | w[(pos / 8) | 0] << 3; + // shft(pos) now 1 less if pos & 7 != 0 + pos -= 7; + st.h = head, st.p = prev, st.i = i, st.w = wi; + } } - } - /** interpolate - * @param {string} input - * @returns {string} input with $name replaced by values from the state - */ - interpolate(input) { - let result = input.replace(/(\$[a-zA-Z0-9_.]+)/g, (_, name) => - this.get(name), - ); - result = result.replace(/\$\{([a-zA-Z0-9_.]+)}/g, (_, name) => - this.get("$" + name), - ); - return result; - } + else { + for (var i = st.w || 0; i < s + lst; i += 65535) { + // end + var e = i + 65535; + if (e >= s) { + // write final block + w[(pos / 8) | 0] = lst; + e = s; + } + pos = wfblk(w, pos + 1, dat.subarray(i, e)); + } + st.i = s; + } + return slc(o, 0, pre + shft(pos) + post); }; - -const stack = ''; - -class Stack extends TreeBase { - direction = new Select(["row", "column"], { defaultValue: "column" }); - background = new Color(""); - scale = new Float(1); - - allowedChildren = [ - "Stack", - "Gap", - "Grid", - "Display", - "Radio", - "TabControl", - "VSD", - "Button", - ]; - - /** @returns {Hole} */ - template() { - /** return the scale of the child making sure it isn't zero or undefined. - * @param {TreeBase } child - * @returns {number} - */ - function getScale(child) { - const SCALE_MIN = 0.0; - let scale = +child["scale"]?.value; - if (!scale || scale < SCALE_MIN) { - scale = SCALE_MIN; - } - return scale; +// CRC32 table +var crct = /*#__PURE__*/ (function () { + var t = new Int32Array(256); + for (var i = 0; i < 256; ++i) { + var c = i, k = 9; + while (--k) + c = ((c & 1) && -306674912) ^ (c >>> 1); + t[i] = c; } - const scaleSum = this.children.reduce( - (sum, child) => sum + getScale(child), - 0, - ); - const empty = this.children.length && scaleSum ? "" : "empty"; - const direction = this.direction.value; - const dimension = direction == "row" ? "width" : "height"; - - return this.component( - { - classes: [this.CSSClasses(direction, empty)], - style: { - backgroundColor: this.background.value, + return t; +})(); +// CRC32 +var crc = function () { + var c = -1; + return { + p: function (d) { + // closures have awful performance + var cr = c; + for (var i = 0; i < d.length; ++i) + cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8); + c = cr; }, - }, - html`${this.children.map( - (child) => - html`
    - ${child.safeTemplate()} -
    `, - )}`, - ); - } -} -TreeBase.register(Stack, "Stack"); - -class Page extends Stack { - // you can't delete the page - allowDelete = false; - - constructor() { - super(); - this.allowedChildren = this.allowedChildren.concat( - "Speech", - "Audio", - "Logger", - "ModalDialog", - "Customize", - "HeadMouse" - ); - } -} -Stack.register(Page, "Page"); - -/** Gather Slots code into one module - * - * Slots are coded in strings like $$ kind = value $$ where kind is used - * to access the Content for choices and value is the initial and default value. - * - */ - - -/** Slot descriptor - * @typedef {Object} Slot - * @property {string} name - the name of the slot list - * @property {string} value - the current value - */ - -/** Editor state - * @typedef {Object} Editor - * @property {"editor"} type - * @property {string} message - the message text - * @property {Slot[]} slots - slots if any - * @property {number} slotIndex - slot being edited - * @property {string} slotName - current slot type - * @property {string} value - value stripped of any markup - */ - -/** - * Edit slots markup to replace with highlighed values - * @param {string|Editor} msg - the string possibly containing $$ kind = value $$ markup - * @returns {Hole[]} - formatted string - */ -function formatSlottedString(msg) { - if (typeof msg === "string") { - return msg.split(/(\$\$.*?\$\$)/).map((part) => { - const m = part.match(/\$\$(?.*?)=(?.*?)\$\$/); - if (m) { - return html`${m.groups?.value || ""}`; - } else { - return html`${part}`; - } - }); - } else if (typeof msg === "object" && msg.type === "editor") { - let editor = msg; - // otherwise it is an editor object - // highlight the current slot - let i = 0; - return editor.message.split(/(\$\$.*?\$\$)/).map((part) => { - const m = part.match(/\$\$(?.*?)=(?.*?)\$\$/); - if (m) { - if (i === editor.slotIndex) { - // highlight the current slot - return html`${editor.slots[i++].value}`; - } else { - return html`${editor.slots[i++].value.replace(/^\*/, "")}`; + d: function () { return ~c; } + }; +}; +// deflate with opts +var dopt = function (dat, opt, pre, post, st) { + if (!st) { + st = { l: 1 }; + if (opt.dictionary) { + var dict = opt.dictionary.subarray(-32768); + var newDat = new u8(dict.length + dat.length); + newDat.set(dict); + newDat.set(dat, dict.length); + dat = newDat; + st.w = dict.length; } - } - return html`${part}`; - }); - } else { - return []; - } -} - -/** Edit slots markup to replace with values - * @param {string|Editor} value - * @returns {string} - */ -function toString(value) { - value ??= ""; - if (typeof value === "string") { - // strip any slot markup - value = value.replaceAll(/\$\$(?.*?)=(?.*?)\$\$/g, "$2"); - return value; - } else if (typeof value === "object" && value.type === "editor") { - let editor = value; - // otherwise it is an editor object - let i = 0; - const parts = editor.message.split(/(\$\$.*?\$\$)/).map((part) => { - const m = part.match(/\$\$(?.*?)=(?.*?)\$\$/); - if (m) { - return editor.slots[i++].value.replace(/^\*/, ""); - } - return part; - }); - return parts.join(""); - } - return value.toString(); -} - -/** We need to keep some additional state around to enable editing slotted messages. - * - * These functions are used in Updates to manipulate the state. - */ - -/** Test if this message has slots - * @param {string|Editor} message - * @returns {boolean} + } + return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, st); +}; +// Walmart object spread +var mrg = function (a, b) { + var o = {}; + for (var k in a) + o[k] = a[k]; + for (var k in b) + o[k] = b[k]; + return o; +}; +// read 2 bytes +var b2 = function (d, b) { return d[b] | (d[b + 1] << 8); }; +// read 4 bytes +var b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; }; +var b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); }; +// write bytes +var wbytes = function (d, b, v) { + for (; v; ++b) + d[b] = v, v >>>= 8; +}; +/** + * Compresses data with DEFLATE without any wrapper + * @param data The data to compress + * @param opts The compression options + * @returns The deflated version of the data */ -function hasSlots(message) { - if (message instanceof Object && message.type === "editor") { - return message.slots.length > 0; - } else if (typeof message == "string") return message.indexOf("$$") >= 0; - return false; +function deflateSync(data, opts) { + return dopt(data, opts || {}, 0, 0); } - -/** initialize the editor - * @param {String} message - * @returns Editor +/** + * Expands DEFLATE data with no wrapper + * @param data The data to decompress + * @param opts The decompression options + * @returns The decompressed version of the data */ -function init(message) { - message = message || ""; - const slots = Array.from( - message.matchAll(/\$\$(?.*?)=(?.*?)\$\$/g), - ).map((m) => m.groups); - let result = { - type: "editor", - message, - slots, - slotIndex: 0, - slotName: (slots[0] && slots[0].name) || "", - }; - return result; +function inflateSync(data, opts) { + return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary); } - -/** cancel slot editing - * @returns Editor - */ -function cancel() { - return { - type: "editor", - message: "", - slots: [], - slotIndex: 0, - slotName: "", - }; +// flatten a directory structure +var fltn = function (d, p, t, o) { + for (var k in d) { + var val = d[k], n = p + k, op = o; + if (Array.isArray(val)) + op = mrg(o, val[1]), val = val[0]; + if (val instanceof u8) + t[n] = [val, op]; + else { + t[n += '/'] = [new u8(0), op]; + fltn(val, n, t, o); + } + } +}; +// text encoder +var te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder(); +// text decoder +var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder(); +// text decoder stream +var tds = 0; +try { + td.decode(et, { stream: true }); + tds = 1; } - -/** update the value of the current slot - * @param {String} message +catch (e) { } +// decode UTF8 +var dutf8 = function (d) { + for (var r = '', i = 0;;) { + var c = d[i++]; + var eb = (c > 127) + (c > 223) + (c > 239); + if (i + eb > d.length) + return { s: r, r: slc(d, i - 1) }; + if (!eb) + r += String.fromCharCode(c); + else if (eb == 3) { + c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536, + r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023)); + } + else if (eb & 1) + r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63)); + else + r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)); + } +}; +/** + * Converts a string into a Uint8Array for use with compression/decompression methods + * @param str The string to encode + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless decoding a binary string. + * @returns The string encoded in UTF-8/Latin-1 binary */ -function update(message) { - message ??= ""; - /** @param {Editor} old - * @returns {Editor|string} - */ - return (old) => { - // copy the slots from the old value - if (!old || !old.slots) { - return ""; +function strToU8(str, latin1) { + if (latin1) { + var ar_1 = new u8(str.length); + for (var i = 0; i < str.length; ++i) + ar_1[i] = str.charCodeAt(i); + return ar_1; } - const slots = [...old.slots]; - let slotIndex = old.slotIndex; - // replace the current one - if (message.startsWith("*")) { - slots[slotIndex].value = message; - } else { - if (slots[slotIndex].value.startsWith("*")) { - slots[slotIndex].value = `${slots[slotIndex].value} ${message}`; - } else { - slots[slotIndex].value = message; - } - slotIndex++; - if (slotIndex >= slots.length) { - Globals.actions.queueEvent("okSlot", "press"); - } + if (te) + return te.encode(str); + var l = str.length; + var ar = new u8(str.length + (str.length >> 1)); + var ai = 0; + var w = function (v) { ar[ai++] = v; }; + for (var i = 0; i < l; ++i) { + if (ai + 5 > ar.length) { + var n = new u8(ai + 8 + ((l - i) << 1)); + n.set(ar); + ar = n; + } + var c = str.charCodeAt(i); + if (c < 128 || latin1) + w(c); + else if (c < 2048) + w(192 | (c >> 6)), w(128 | (c & 63)); + else if (c > 55295 && c < 57344) + c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023), + w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); + else + w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); } - return o$1(old, { - slots, - slotIndex, - slotName: slots[slotIndex]?.name, - }); - }; + return slc(ar, 0, ai); } - -/** advance to the next slot +/** + * Converts a Uint8Array to a string + * @param dat The data to decode to string + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless encoding to binary string. + * @returns The original UTF-8/Latin-1 string */ -function nextSlot() { - /** @param {Editor} old - */ - return (old) => { - if (!old || !old.slots) return; - const slotIndex = old.slotIndex + 1; - if (slotIndex >= old.slots.length) { - Globals.actions.queueEvent("okSlot", "press"); +function strFromU8(dat, latin1) { + if (latin1) { + var r = ''; + for (var i = 0; i < dat.length; i += 16384) + r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384)); + return r; + } + else if (td) { + return td.decode(dat); + } + else { + var _a = dutf8(dat), s = _a.s, r = _a.r; + if (r.length) + err(8); + return s; } - return o$1(old, { slotIndex, slotName: old.slots[slotIndex]?.name }); - }; } - -/** duplicate the current slot +// skip local zip header +var slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); }; +// read zip header +var zh = function (d, b, z) { + var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20); + var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2]; + return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off]; +}; +// read zip64 extra field +var z64e = function (d, b) { + for (; b2(d, b) != 1; b += 4 + b2(d, b + 2)) + ; + return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)]; +}; +// extra field length +var exfl = function (ex) { + var le = 0; + if (ex) { + for (var k in ex) { + var l = ex[k].length; + if (l > 65535) + err(9); + le += l + 4; + } + } + return le; +}; +// write zip header +var wzh = function (d, b, f, fn, u, c, ce, co) { + var fl = fn.length, ex = f.extra, col = co && co.length; + var exl = exfl(ex); + wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4; + if (ce != null) + d[b++] = 20, d[b++] = f.os; + d[b] = 20, b += 2; // spec compliance? what's that? + d[b++] = (f.flag << 1) | (c < 0 && 8), d[b++] = u && 8; + d[b++] = f.compression & 255, d[b++] = f.compression >> 8; + var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980; + if (y < 0 || y > 119) + err(10); + wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >> 1)), b += 4; + if (c != -1) { + wbytes(d, b, f.crc); + wbytes(d, b + 4, c < 0 ? -c - 2 : c); + wbytes(d, b + 8, f.size); + } + wbytes(d, b + 12, fl); + wbytes(d, b + 14, exl), b += 16; + if (ce != null) { + wbytes(d, b, col); + wbytes(d, b + 6, f.attrs); + wbytes(d, b + 10, ce), b += 14; + } + d.set(fn, b); + b += fl; + if (exl) { + for (var k in ex) { + var exf = ex[k], l = exf.length; + wbytes(d, b, +k); + wbytes(d, b + 2, l); + d.set(exf, b + 4), b += 4 + l; + } + } + if (col) + d.set(co, b), b += col; + return b; +}; +// write zip footer (end of central directory) +var wzf = function (o, b, c, d, e) { + wbytes(o, b, 0x6054B50); // skip disk + wbytes(o, b + 8, c); + wbytes(o, b + 10, c); + wbytes(o, b + 12, d); + wbytes(o, b + 16, e); +}; +/** + * Synchronously creates a ZIP file. Prefer using `zip` for better performance + * with more than one file. + * @param data The directory structure for the ZIP archive + * @param opts The main options, merged with per-file options + * @returns The generated ZIP archive */ -function duplicate() { - /** @param {Editor} old - */ - return (old) => { - if (!old || !old.slots) return; - const matches = Array.from( - old.message.matchAll(/\$\$(?.*?)=(?.*?)\$\$/g), - ); - const current = matches[old.slotIndex]; - if (current !== undefined && current.index !== undefined) { - const message = - old.message.slice(0, current.index) + - current[0] + - " and " + - current[0] + - old.message.slice(current.index + current[0].length); - const slots = [ - ...old.slots.slice(0, old.slotIndex + 1), - { ...old.slots[old.slotIndex] }, // copy it - ...old.slots.slice(old.slotIndex + 1), - ]; - return o$1(old, { - message, - slots, - }); - } else { - return old; +function zipSync(data, opts) { + if (!opts) + opts = {}; + var r = {}; + var files = []; + fltn(data, '', r, opts); + var o = 0; + var tot = 0; + for (var fn in r) { + var _a = r[fn], file = _a[0], p = _a[1]; + var compression = p.level == 0 ? 0 : 8; + var f = strToU8(fn), s = f.length; + var com = p.comment, m = com && strToU8(com), ms = m && m.length; + var exl = exfl(p.extra); + if (s > 65535) + err(11); + var d = compression ? deflateSync(file, p) : file, l = d.length; + var c = crc(); + c.p(file); + files.push(mrg(p, { + size: file.length, + crc: c.d(), + c: d, + f: f, + m: m, + u: s != fn.length || (m && (com.length != ms)), + o: o, + compression: compression + })); + o += 30 + s + exl + l; + tot += 76 + 2 * (s + exl) + (ms || 0) + l; + } + var out = new u8(tot + 22), oe = o, cdl = tot - o; + for (var i = 0; i < files.length; ++i) { + var f = files[i]; + wzh(out, f.o, f, f.f, f.u, f.c.length); + var badd = 30 + f.f.length + exfl(f.extra); + out.set(f.c, f.o + badd); + wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0); } - }; + wzf(out, o, files.length, cdl, oe); + return out; } - -/** Get the slot name - * @param {string|Editor} message - * @returns string; +/** + * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better + * performance with more than one file. + * @param data The raw compressed ZIP file + * @param opts The ZIP extraction options + * @returns The decompressed files */ -function slotName(message) { - if (typeof message === "object" && message.type === "editor") { - return message.slotName; - } - return ""; -} -{ - Functions["slots"] = { - init, - cancel, - update, - hasSlots, - duplicate, - nextSlot, - slotName, - toString, - }; +function unzipSync(data, opts) { + var files = {}; + var e = data.length - 22; + for (; b4(data, e) != 0x6054B50; --e) { + if (!e || data.length - e > 65558) + err(13); + } + var c = b2(data, e + 8); + if (!c) + return {}; + var o = b4(data, e + 16); + var z = o == 4294967295 || c == 65535; + if (z) { + var ze = b4(data, e - 12); + z = b4(data, ze) == 0x6064B50; + if (z) { + c = b4(data, ze + 32); + o = b4(data, ze + 48); + } + } + var fltr = opts && opts.filter; + for (var i = 0; i < c; ++i) { + var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); + o = no; + if (!fltr || fltr({ + name: fn, + size: sc, + originalSize: su, + compression: c_2 + })) { + if (!c_2) + files[fn] = slc(data, b, b + sc); + else if (c_2 == 8) + files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) }); + else + err(14, 'unknown compression type ' + c_2); + } + } + return files; } -const grid = ''; +const e$1=(()=>{if("undefined"==typeof self)return !1;if("top"in self&&self!==top)try{top.window.document._=0;}catch(e){return !1}return "showOpenFilePicker"in self})(),t$1=e$1?Promise.resolve().then(function(){return l}):Promise.resolve().then(function(){return v});async function n(...e){return (await t$1).default(...e)}e$1?Promise.resolve().then(function(){return y}):Promise.resolve().then(function(){return b});const a=e$1?Promise.resolve().then(function(){return m}):Promise.resolve().then(function(){return k});async function o$1(...e){return (await a).default(...e)}const s=async e=>{const t=await e.getFile();return t.handle=e,t};var c=async(e=[{}])=>{Array.isArray(e)||(e=[e]);const t=[];e.forEach((e,n)=>{t[n]={description:e.description||"Files",accept:{}},e.mimeTypes?e.mimeTypes.map(r=>{t[n].accept[r]=e.extensions||[];}):t[n].accept["*/*"]=e.extensions||[];});const n=await window.showOpenFilePicker({id:e[0].id,startIn:e[0].startIn,types:t,multiple:e[0].multiple||!1,excludeAcceptAllOption:e[0].excludeAcceptAllOption||!1}),r=await Promise.all(n.map(s));return e[0].multiple?r:r[0]},l={__proto__:null,default:c};function u(e){function t(e){if(Object(e)!==e)return Promise.reject(new TypeError(e+" is not an object."));var t=e.done;return Promise.resolve(e.value).then(function(e){return {value:e,done:t}})}return u=function(e){this.s=e,this.n=e.next;},u.prototype={s:null,n:null,next:function(){return t(this.n.apply(this.s,arguments))},return:function(e){var n=this.s.return;return void 0===n?Promise.resolve({value:e,done:!0}):t(n.apply(this.s,arguments))},throw:function(e){var n=this.s.return;return void 0===n?Promise.reject(e):t(n.apply(this.s,arguments))}},new u(e)}const p=async(e,t,n=e.name,r)=>{const i=[],a=[];var o,s=!1,c=!1;try{for(var l,d=function(e){var t,n,r,i=2;for("undefined"!=typeof Symbol&&(n=Symbol.asyncIterator,r=Symbol.iterator);i--;){if(n&&null!=(t=e[n]))return t.call(e);if(r&&null!=(t=e[r]))return new u(t.call(e));n="@@asyncIterator",r="@@iterator";}throw new TypeError("Object is not async iterable")}(e.values());s=!(l=await d.next()).done;s=!1){const o=l.value,s=`${n}/${o.name}`;"file"===o.kind?a.push(o.getFile().then(t=>(t.directoryHandle=e,t.handle=o,Object.defineProperty(t,"webkitRelativePath",{configurable:!0,enumerable:!0,get:()=>s})))):"directory"!==o.kind||!t||r&&r(o)||i.push(p(o,t,s,r));}}catch(e){c=!0,o=e;}finally{try{s&&null!=d.return&&await d.return();}finally{if(c)throw o}}return [...(await Promise.all(i)).flat(),...await Promise.all(a)]};var d=async(e={})=>{e.recursive=e.recursive||!1,e.mode=e.mode||"read";const t=await window.showDirectoryPicker({id:e.id,startIn:e.startIn,mode:e.mode});return (await(await t.values()).next()).done?[t]:p(t,e.recursive,void 0,e.skipDirectory)},y={__proto__:null,default:d},f=async(e,t=[{}],n=null,r=!1,i=null)=>{Array.isArray(t)||(t=[t]),t[0].fileName=t[0].fileName||"Untitled";const a=[];let o=null;if(e instanceof Blob&&e.type?o=e.type:e.headers&&e.headers.get("content-type")&&(o=e.headers.get("content-type")),t.forEach((e,t)=>{a[t]={description:e.description||"Files",accept:{}},e.mimeTypes?(0===t&&o&&e.mimeTypes.push(o),e.mimeTypes.map(n=>{a[t].accept[n]=e.extensions||[];})):o?a[t].accept[o]=e.extensions||[]:a[t].accept["*/*"]=e.extensions||[];}),n)try{await n.getFile();}catch(e){if(n=null,r)throw e}const s=n||await window.showSaveFilePicker({suggestedName:t[0].fileName,id:t[0].id,startIn:t[0].startIn,types:a,excludeAcceptAllOption:t[0].excludeAcceptAllOption||!1});!n&&i&&i(s);const c=await s.createWritable();if("stream"in e){const t=e.stream();return await t.pipeTo(c),s}return "body"in e?(await e.body.pipeTo(c),s):(await c.write(await e),await c.close(),s)},m={__proto__:null,default:f},w=async(e=[{}])=>(Array.isArray(e)||(e=[e]),new Promise((t,n)=>{const r=document.createElement("input");r.type="file";const i=[...e.map(e=>e.mimeTypes||[]),...e.map(e=>e.extensions||[])].join();r.multiple=e[0].multiple||!1,r.accept=i||"",r.style.display="none",document.body.append(r);const a=e=>{"function"==typeof o&&o(),t(e);},o=e[0].legacySetup&&e[0].legacySetup(a,()=>o(n),r),s=()=>{window.removeEventListener("focus",s),r.remove();};r.addEventListener("click",()=>{window.addEventListener("focus",s);}),r.addEventListener("change",()=>{window.removeEventListener("focus",s),r.remove(),a(r.multiple?Array.from(r.files):r.files[0]);}),"showPicker"in HTMLInputElement.prototype?r.showPicker():r.click();})),v={__proto__:null,default:w},h=async(e=[{}])=>(Array.isArray(e)||(e=[e]),e[0].recursive=e[0].recursive||!1,new Promise((t,n)=>{const r=document.createElement("input");r.type="file",r.webkitdirectory=!0;const i=e=>{"function"==typeof a&&a(),t(e);},a=e[0].legacySetup&&e[0].legacySetup(i,()=>a(n),r);r.addEventListener("change",()=>{let t=Array.from(r.files);e[0].recursive?e[0].recursive&&e[0].skipDirectory&&(t=t.filter(t=>t.webkitRelativePath.split("/").every(t=>!e[0].skipDirectory({name:t,kind:"directory"})))):t=t.filter(e=>2===e.webkitRelativePath.split("/").length),i(t);}),"showPicker"in HTMLInputElement.prototype?r.showPicker():r.click();})),b={__proto__:null,default:h},P=async(e,t={})=>{Array.isArray(t)&&(t=t[0]);const n=document.createElement("a");let r=e;"body"in e&&(r=await async function(e,t){const n=e.getReader(),r=new ReadableStream({start:e=>async function t(){return n.read().then(({done:n,value:r})=>{if(!n)return e.enqueue(r),t();e.close();})}()}),i=new Response(r),a=await i.blob();return n.releaseLock(),new Blob([a],{type:t})}(e.body,e.headers.get("content-type"))),n.download=t.fileName||"Untitled",n.href=URL.createObjectURL(await r);const i=()=>{"function"==typeof a&&a();},a=t.legacySetup&&t.legacySetup(i,()=>a(),n);return n.addEventListener("click",()=>{setTimeout(()=>URL.revokeObjectURL(n.href),3e4),i();}),n.click(),null},k={__proto__:null,default:P}; -/** - * Return an image or video element given the name + parameters - * like "foo.mp4 autoplay loop". - * @param {string} src - * @param {string} title - * @param {null|function():void} onload - * @returns {Hole} - */ -function imageOrVideo(src, title, onload = null) { - const match = /(?.*\.(?:mp4|webm))(?.*$)/.exec(src); +class DB { + constructor() { + this.dbPromise = openDB("os-dpi", 5, { + async upgrade(db, oldVersion, _newVersion, transaction) { + let store5 = db.createObjectStore("store5", { + keyPath: ["name", "type"], + }); + store5.createIndex("by-name", "name"); + if (oldVersion == 4) { + // copy data from old store to new + const store4 = transaction.objectStore("store"); + for await (const cursor of store4) { + const record4 = cursor.value; + store5.put(record4); + } + db.deleteObjectStore("store"); + // add an etag index to url store + transaction.objectStore("url").createIndex("by-etag", "etag"); + } else if (oldVersion < 4) { + db.createObjectStore("media"); + let savedStore = db.createObjectStore("saved", { + keyPath: "name", + }); + savedStore.createIndex("by-etag", "etag"); + // track etags for urls + const urlStore = db.createObjectStore("url", { + keyPath: "url", + }); + // add an etag index to the url store + urlStore.createIndex("by-etag", "etag"); + } + }, + blocked(currentVersion, blockedVersion, event) { + console.log("blocked", { currentVersion, blockedVersion, event }); + }, + blocking(_currentVersion, _blockedVersion, _event) { + window.location.reload(); + }, + terminated() { + console.log("terminated"); + }, + }); + this.updateListeners = []; + this.designName = ""; + this.fileName = ""; + this.fileHandle = null; + this.fileVersion = 0.0; + this.fileUid = ""; + } - if (match && match.groups) { - // video - const options = match.groups.options; - const vsrc = match.groups.src; - return html`