From c282d204c07e47702a50049ce157f7a6bdfd16a3 Mon Sep 17 00:00:00 2001 From: Gary Bishop Date: Sat, 27 Jan 2024 10:09:14 -0500 Subject: [PATCH] deploy --- index.css | 11 +- index.js | 13157 ++++++++++++++++++++-------------------- index.js.map | 2 +- service-worker.js | 2 +- service-worker.js.map | 2 +- 5 files changed, 6739 insertions(+), 6435 deletions(-) diff --git a/index.css b/index.css index 9c028a71..8866c00b 100644 --- a/index.css +++ b/index.css @@ -443,6 +443,7 @@ body:not(.designing) video[dbsrc]:not([src]) { } .tabcontrol .panels { display: flex; + min-height: 0; } .tabcontrol .buttons { display: flex; @@ -632,8 +633,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 { diff --git a/index.js b/index.js index 48c99b05..0ae83b5e 100644 --- a/index.js +++ b/index.js @@ -3146,290 +3146,6 @@ 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} - */ -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(); - /* 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; -}; - -/** - * @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; } - /* c8 ignore start */ - remove() { remove(this, false); } - /* c8 ignore stop */ - 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 * @@ -3588,11 +3304,169 @@ 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 {Map | WeakMap} map + * @param {any} key + * @param {T} value + * @returns {T} + */ +const set = (map, key, value) => { + map.set(key, value); + return value; +}; + +/** + * Return a descriptor, if any, for the referenced *Element* + * @param {Element} ref + * @param {string} prop + * @returns + */ +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; + +/** + * @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(); + /* 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; +}; + +/** + * @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 +); + +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 @@ -3610,8 +3484,6 @@ const aria = (element, value) => { return value; }; -const arrayComment = () => array; - let listeners; /** @@ -3635,33 +3507,31 @@ const at = (element, value, name) => { /** * @template T - * @this {import("./literals.js").HoleDetails} + * @this {import("./literals.js").Detail} * @param {Node} node * @param {T} value * @returns {T} */ function hole(node, value) { - const n = this.n || (this.n = node); + let { n: hole } = this, nullish = false; 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); + if (value !== null) { + (hole || node).replaceWith((this.n = value.valueOf())); + break; + } + case 'undefined': + nullish = true; + default: + node.data = nullish ? '' : value; + if (hole) { + this.n = null; + hole.replaceWith(node); + } break; - } } return value; } -const boundComment = () => hole.bind(comment()); - /** * @template T * @param {Element} element @@ -3775,13 +3645,29 @@ const toggle = (element, value, name) => ( * @param {Node[]} prev * @returns {Node[]} */ -const array = (node, value, _, prev) => udomdiff( - node.parentNode, - prev, - value.length ? value : empty, - diffFragment, - node -); +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], @@ -3829,139 +3715,336 @@ const text = (element, value) => ( value ); -let template = document.createElement('template'), svg, range; +/** @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 */ /** - * @param {string} text - * @param {boolean} xml - * @returns {DocumentFragment} + * @typedef {Object} Entry + * @property {number[]} path + * @property {function} update + * @property {string} name */ -const createContent = (text, xml) => { - if (xml) { - if (!svg) { - svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); - range = newRange(); - range.selectNodeContents(svg); - } - return range.createContextualFragment(text); - } - template.innerHTML = text; - const { content } = template; - template = template.cloneNode(false); - return content; -}; - -/** @typedef {import("./literals.js").Entry} Entry */ /** - * @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 + * @returns */ +const cel = (f, e, d) => ({ f, e, d }); /** - * @param {Element} node - * @returns {number[]} + * @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 */ -const createPath = node => { - const path = []; - let parentNode; - while ((parentNode = node.parentNode)) { - path.push(path.indexOf.call(parentNode.childNodes, node)); - node = parentNode; - } - return path; -}; /** - * @param {TemplateStringsArray} template - * @param {boolean} xml - * @returns {Resolved} + * @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 attribute name, if any, or `null` + * @returns {Detail} */ -const resolve = (template, values, xml) => { - const content = createContent(parser$1(template, prefix, xml), xml); - const { length } = template; - let asArray = false, entries = empty; - if (length > 1) { - const tw = document.createTreeWalker(content, 1 | 128); - const replace = []; - let i = 0, search = `${prefix}${i++}`; - entries = []; - while (i < length) { - const node = tw.nextNode(); - 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)); - search = `${prefix}${i++}`; - } - } - else { - let path; - 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)); - removeAttribute(node, search); - search = `${prefix}${i++}`; - } - if ( - TEXT_ELEMENTS.test(node.localName) && - node.textContent.trim() === `` - ) { - entries.push(entry(TEXT_NODE, path || createPath(node), text)); - search = `${prefix}${i++}`; - } - } - } - for (i = 0; i < replace.length; i++) - replace[i].replaceWith(document.createTextNode('')); - } - const l = content.childNodes.length; - return set(cache, template, cel(content, entries, l === 1 && asArray ? 0 : l)); -}; - -/** @type {WeakMap} */ -const cache = new WeakMap; -const prefix = 'isµ'; +const detail = (v, u, t, n) => ({ v, u, t, n }); /** - * @param {boolean} xml - * @returns {(template: TemplateStringsArray, values: any[]) => Resolved} + * @param {number[]} p the path to retrieve the node + * @param {function} u the update function + * @param {string?} n the attribute name, if any, or `null` + * @returns {Entry} */ -const parser = xml => (template, values) => cache.get(template) || resolve(template, values, xml); +const entry = (p, u, n) => ({ p, u, n }); -const parseHTML = create(parser(false)); -const parseSVG = create(parser(true)); +/** + * @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 {import("./literals.js").Cache} cache - * @param {Hole} hole - * @returns {Node} + * @param {Cache[]} s the cache stack + * @returns {Cache} */ -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 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} + */ +const parsed = (n, d) => ({ n, d }); + +/** + * @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 { f: fragment, e: entries, d: direct } = parse(template, values); + const root = fragment.cloneNode(true); + let current, prev, details = entries === empty ? empty : []; + for (let i = 0; i < entries.length; i++) { + const { p: path, u: update, n: name } = entries[i]; + const node = path === prev ? current : (current = find(root, (prev = path))); + details[i] = detail(empty, update, node, name); + } + return parsed( + 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; + +/** + * @param {string} text + * @param {boolean} xml + * @returns {DocumentFragment} + */ +const createContent = (text, xml) => { + if (xml) { + if (!svg) { + svg = document.createElementNS(SVG_NAMESPACE, 'svg'); + range = newRange(); + range.selectNodeContents(svg); + } + return range.createContextualFragment(text); + } + template.innerHTML = text; + const { content } = template; + template = template.cloneNode(false); + return content; +}; + +/** @typedef {import("./literals.js").Entry} Entry */ + +/** + * @typedef {Object} Resolved + * @property {DocumentFragment} content + * @property {Entry[]} entries + * @property {function[]} updates + * @property {number} length + */ + +/** + * @param {Element} node + * @returns {number[]} + */ +const createPath = node => { + const path = []; + let parentNode; + while ((parentNode = node.parentNode)) { + path.push(path.indexOf.call(parentNode.childNodes, node)); + node = parentNode; + } + return path; +}; + +const textNode = () => document.createTextNode(''); + +/** + * @param {TemplateStringsArray} template + * @param {boolean} xml + * @returns {Resolved} + */ +const resolve = (template, values, xml) => { + const content = createContent(parser$1(template, prefix, xml), xml); + const { length } = template; + let entries = empty; + if (length > 1) { + 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) { + // ⚠️ once array, always array! + const update = isArray$3(values[i - 1]) ? array : hole; + if (update === hole) replace.push(node); + entries.push(entry(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(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(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(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()); + } + // 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, cel(content, entries, len === 1)); +}; + +/** @type {WeakMap} */ +const cache = new WeakMap; +const prefix = 'isµ'; + +/** + * @param {boolean} xml + * @returns {(template: TemplateStringsArray, values: any[]) => Resolved} + */ +const parser = xml => (template, values) => cache.get(template) || resolve(template, values, xml); + +const parseHTML = create(parser(false)); +const parseSVG = create(parser(true)); + +const createCache = ({ u }) => ( + u === array ? + cache$1([]) : ( + u === hole ? + cache$1(empty) : + null + ) +); + +/** + * @param {import("./literals.js").Cache} cache + * @param {Hole} hole + * @returns {Node} + */ +const unroll = (cache, { s, t, v }) => { + let i = 0, { d: details, s: stack } = cache; + if (cache.t !== t) { + const { n, d } = (s ? parseSVG : parseHTML)(t, v); + cache.t = t; + cache.n = n; + cache.d = (details = d); + if (v.length) cache.s = (stack = d.map(createCache)); + } + for (; i < details.length; i++) { + const value = v[i]; + const detail = details[i]; + const { v: previous, u: update, t: target, n: name } = detail; + switch (update) { + case array: + detail.v = array( + target, + unrollValues(stack[i], value), + previous + ); + break; + case hole: + const current = value instanceof Hole ? + unroll(stack[i], value) : + value + ; + if (current !== previous) + detail.v = hole.call(detail, target, current); + break; + default: + if (value !== previous) + detail.v = update(target, value, name, previous); + break; } } return cache.n; @@ -3973,18 +4056,15 @@ const unroll = (cache, { s: svg, t: template, v: 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; - } + let i = 0, { length } = values; if (length < stack.length) stack.splice(length); - return length; + for (; i < length; i++) { + const value = values[i]; + const asHole = value instanceof Hole; + const cache = stack[i] || (stack[i] = asHole ? cache$1(empty) : null); + if (asHole) values[i] = unroll(cache, value); + } + return values; }; /** @@ -4017,13 +4097,12 @@ const known = new WeakMap; 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); + where.replaceChildren(info.n.valueOf()); return where; }; /*! (c) Andrea Giammarchi - MIT */ - /** @typedef {import("./literals.js").Value} Value */ const tag = svg => (template, ...values) => new Hole(svg, template, values); @@ -4231,8 +4310,8 @@ function setHashKey(obj, h) { } ``` */ -function noop$2() {} -noop$2.$inject = []; +function noop$1() {} +noop$1.$inject = []; /** * @ngdoc function @@ -5797,7 +5876,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_( @@ -6390,7 +6469,7 @@ ASTInterpreter.prototype = { }); var fn = ast.body.length === 0 - ? noop$2 + ? noop$1 : ast.body.length === 1 ? expressions[0] : function (scope, locals) { @@ -6818,15 +6897,20 @@ var parse = parse$1; var filters = {}; var Lexer = parse.Lexer; var Parser = parse.Parser; + /** * Compiles src and returns a function that executes src on a target object. - * The compiled function is cached under compile.cache[src] to speed up further calls. + * To speed up further calls the compiled function is cached under compile.cache[src] if options.filters is not present. * * @param {string} src + * @param {object | undefined} options * @returns {function} */ -function compile(src, lexerOptions) { - lexerOptions = lexerOptions || {}; +function compile(src, options) { + options = options || {}; + var localFilters = options.filters || filters; + var cache = options.filters ? options.cache || {} : compile.cache; + var lexerOptions = options; var cached; @@ -6836,34 +6920,37 @@ function compile(src, lexerOptions) { ); } var parserOptions = { - csp: false, // noUnsafeEval, - literals: { - // defined at: function $ParseProvider() { - true: true, - false: false, - null: null, - /*eslint no-undefined: 0*/ - undefined: undefined, - /* eslint: no-undefined: 1 */ - }, + csp: options.csp != null ? options.scp : false, // noUnsafeEval, + literals: + options.literals != null + ? options.literals + : { + // defined at: function $ParseProvider() { + true: true, + false: false, + null: null, + /*eslint no-undefined: 0*/ + undefined: undefined, + /* eslint: no-undefined: 1 */ + }, }; var lexer = new Lexer(lexerOptions); var parser = new Parser( lexer, function getFilter(name) { - return filters[name]; + return localFilters[name]; }, parserOptions ); - if (!compile.cache) { + if (!cache) { return parser.parse(src); } - cached = compile.cache[src]; + cached = cache[src]; if (!cached) { - cached = compile.cache[src] = parser.parse(src); + cached = cache[src] = parser.parse(src); } return cached; @@ -6888,7 +6975,6 @@ 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 @@ -6904,6295 +6990,6227 @@ 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 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); - } - }), - 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), +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; + }, }; - -/** - * 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); - } - if (result === undefined || result === null) { - result = ""; +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; } - return result; - }, - - /** 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); + 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; } - }, -}; + 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), +})); -/** - * 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]; - } +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; + }, +}; +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, - ); +// 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*). - 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(); +// 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]; } - }; - 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; + // 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; } - 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); + 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; +} +// 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]; } - } - - /** - * 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); - } + // 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; } - 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); - } - } else if (this.isFormulaByDefault) { - this._value = this.options.valueWhenEmpty ?? ""; + 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; + } + } + } } - 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); + else { + co = new u16(s); + for (i = 0; i < s; ++i) { + if (cd[i]) { + co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]); + } + } } - 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); - } + 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); }; - -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); +// 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; +}; +// 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; +}; +// 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] }); } - } -} - -/** @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; + 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 }; } - 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 ""; + 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)); } - return "invalid color"; - }, - datalist: "ColorNames", - ...options, + st.i = s; + } + return slc(o, 0, pre + shft(pos) + post); +}; +// 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; + } + 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; + }, + d: function () { return ~c; } }; - 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); - }; }; - -/*! (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]; +// 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; + } } - } - - *entries() { - yield *this[iterator$1](); - } - - *values() { - for (const [_, value] of this[iterator$1]()) - yield value; - } -} - + 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; +}; /** - * Provide user friendly names for the components + * 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 deflateSync(data, opts) { + return dopt(data, opts || {}, 0, 0); +} /** - * Map the classname into the Menu name and the Help Wiki page name + * Expands DEFLATE data with no wrapper + * @param data The data to decompress + * @param opts The decompression options + * @returns The decompressed version of the data */ -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"], +function inflateSync(data, opts) { + return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary); +} +// 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); + } + } }; - -/** - * 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; +// 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; } - +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)); + } +}; /** - * Get the Wiki name from the class name - * @param {string} className + * 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 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++}`; - - 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]); +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; + } + 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 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; + return slc(ar, 0, ai); +} +/** + * 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 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; + } +} +// 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; } - } } - } - - /** - * 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); + 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); } - - // link it to its parent - if (parent) { - result.parent = parent; - parent.children.push(result); + 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; } - - // 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"); + 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 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; } - - // 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); - } + 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); } - - // 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; + wzf(out, o, files.length, cdl, oe); + return out; +} +/** + * 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 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; +} - /** - * Called when something below is updated - * @param {TreeBase} _start - */ - onUpdate(_start) {} +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}; - /** - * Render the designer interface and return the resulting Hole - * @returns {Hole} - */ - settings() { - const detailsId = this.id + "-details"; - const settingsId = this.id + "-settings"; - let focused = false; // suppress toggle when not focused - return html`
-
{ - if ( - !focused && - event.target instanceof HTMLElement && - event.target.parentElement instanceof HTMLDetailsElement && - event.target.parentElement.open && - event.pointerId >= 0 // not from the keyboard - ) { - /* When we click on the summary bar of a details element that is not focused, - * only focus it and prevent the toggle */ - event.preventDefault(); +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); } - }} - @toggle=${(/** @type {Event} */ event) => { - if (event.target instanceof HTMLDetailsElement) - this.settingsDetailsOpen = event.target.open; - }} - > - { - /** Record if the summary was focused before we clicked */ - focused = event.target == document.activeElement; - }} - > - ${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(); + 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 = ""; } - /** - * Render the user interface and return the resulting Hole - * @returns {Hole} + /** set the name for the current design + * @param {string} name */ - template() { - return html`
`; + setDesignName(name) { + this.designName = name; + document.title = name; } - /** - * Render the user interface catching errors and return the resulting Hole - * @returns {Hole} + /** rename the design + * @param {string} newName */ - safeTemplate() { - try { - return this.template(); - } catch (error) { - errorHandler(error, ` safeTemplate ${this.className}`); - let classes = [this.className.toLowerCase()]; - classes.push("error"); - return html`
ERROR
`; + async renameDesign(newName) { + const db = await this.dbPromise; + newName = await this.uniqueName(newName); + const tx = db.transaction(["store5", "media", "saved"], "readwrite"); + const index = tx.objectStore("store5").index("by-name"); + for await (const cursor of index.iterate(this.designName)) { + const record = { ...cursor.value, name: newName }; + cursor.delete(); + tx.objectStore("store5").put(record); + } + const mst = tx.objectStore("media"); + for await (const cursor of mst.iterate()) { + if (cursor && cursor.key[0] == this.designName) { + const record = { ...cursor.value }; + const key = cursor.key; + cursor.delete(); + key[0] = newName; + mst.put(record, key); + } + } + const cursor = await tx.objectStore("saved").openCursor(this.designName); + if (cursor) { + cursor.delete(); } - } + await tx.done; + this.fileHandle = null; + this.fileName = ""; - /** @typedef {Object} ComponentAttrs - * @property {string[]} [classes] - * @property {Object} [style] - */ + this.notify({ action: "rename", name: this.designName, newName }); + this.designName = newName; + } /** - * Wrap the body of a component - * - * @param {ComponentAttrs} attrs - * @param {Hole} body - * @returns {Hole} + * return list of names of designs in the db + * @returns {Promise} */ - component(attrs, body) { - attrs = { style: {}, ...attrs }; - let classes = [this.className.toLowerCase()]; - if ("classes" in attrs) { - classes = classes.concat(attrs.classes); + async names() { + const db = await this.dbPromise; + const index = db.transaction("store5", "readonly").store.index("by-name"); + const result = []; + for await (const cursor of index.iterate(null, "nextunique")) { + result.push(/** @type {string} */ (cursor.key)); } - return html`
- ${body} -
`; + return result; } /** - * Swap two of my children - * @param {number} i - * @param {number} j + * return list of names of saved designs in the db + * @returns {Promise} */ - swap(i, j) { - const A = this.children; - [A[i], A[j]] = [A[j], A[i]]; + async saved() { + const db = await this.dbPromise; + const result = []; + for (const key of await db.getAllKeys("saved")) { + result.push(key.toString()); + } + return result; } /** - * Move me to given position in my parent - * @param {number} i + * Create a unique name for new design + * @param {string} name - the desired name + * @returns {Promise} */ - moveTo(i) { - const peers = this.parent?.children || []; - peers.splice(this.index, 1); - peers.splice(i, 0, this); + async uniqueName(name = "new") { + // strip off any suffix + name = name.replace(/\.osdpi$|\.zip$/, ""); + // strip any -number off the end of base + name = name.replace(/-\d+$/, "") || name; + // replace characters we don't want with _ + name = name.replaceAll(/[^a-zA-Z0-9]/g, "_"); + // replace multiple _ with one + name = name.replaceAll(/_+/g, "_"); + // remove trailing _ + name = name.replace(/_+$/, ""); + // remove leading _ + name = name.replace(/^_+/, ""); + // if we're left with nothing the call it noname + name = name || "noname"; + const allNames = await this.names(); + if (allNames.indexOf(name) < 0) return name; + const base = name; + for (let i = 1; ; i++) { + const name = `${base}-${i}`; + if (allNames.indexOf(name) < 0) return name; + } } - /** - * Move me up or down by 1 position if possible - * @param {boolean} up + /** Return the most recent record for the type + * @param {string} type + * @param {any} defaultValue + * @returns {Promise} */ - 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); - } - } + async read(type, defaultValue = {}) { + const db = await this.dbPromise; + const record = await db.get("store5", [this.designName, type]); + const data = record ? record.data : defaultValue; + return data; } /** - * Get the index of this component in its parent - * @returns {number} + * Read all records of the given type + * + * @param {string} type + * @returns {Promise} */ - get index() { - return (this.parent && this.parent.children.indexOf(this)) || 0; + async readAll(type) { + return [this.read(type)]; } - /** - * * 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; - } + /** Add a new record + * @param {string} type + * @param {Object} data + */ + async write(type, data) { + const db = await this.dbPromise; + // do all this in a transaction + const tx = db.transaction(["store5", "saved"], "readwrite"); + // note that this design has been updated + await tx.objectStore("saved").delete(this.designName); + // add the record to the store + const store = tx.objectStore("store5"); + await store.put({ name: this.designName, type, data }); + await tx.done; + + this.notify({ action: "update", name: this.designName }); } /** - * Create HTML LI nodes from the children + * delete records of this type + * + * @param {string} type + * @returns {Promise} */ - listChildren(children = this.children) { - return children.map((child) => html`
  • ${child.settings()}
  • `); + async clear(type) { + const db = await this.dbPromise; + return db.delete("store5", [this.designName, type]); } - /** - * Create an HTML ordered list from the children + /** Undo by deleting the most recent record + * @param {string} type */ - orderedChildren(children = this.children) { - return html`
      - ${this.listChildren(children)} -
    `; + async undo(type) { + if (type == "content") return; + const db = await this.dbPromise; + const index = db + .transaction("store5", "readwrite") + .store.index("by-name-type"); + const cursor = await index.openCursor([this.designName, type], "prev"); + if (cursor) await cursor.delete(); + await db.delete("saved", this.designName); + this.notify({ action: "update", name: this.designName }); } - /** - * Create an HTML unordered list from the children - * */ - unorderedChildren(children = this.children) { - return html`
      - ${this.listChildren(children)} -
    `; + /** Read a design from a local file + * @param {import("browser-fs-access").FileWithHandle} file + */ + async readDesignFromFile(file) { + // keep the handle so we can save to it later + this.fileHandle = file.handle; + return this.readDesignFromBlob(file, file.name); } - /** - * 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; + /** Read a design from a URL + * @param {string} url + * @param {string} [name] + * @returns {Promise} + */ + async readDesignFromURL(url, name = "") { + let design_url = url; + + // allow for the url to point to HTML that contains the link + if (!url.match(/.*\.(osdpi|zip)$/)) { + const response = await fetch("https://gb.cs.unc.edu/cors/", { + headers: { "Target-URL": url }, + }); + if (!response.ok) { + throw new Error(`Fetching the URL (${url}) failed: ${response.status}`); } - p = p.parent; + const html = await response.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + // find the first link that matches the name + const link = + doc.querySelector(`a[href$="${name}.zip"]`) || + doc.querySelector(`a[href$="${name}.osdpi"]`); + if (link instanceof HTMLAnchorElement) { + design_url = link.href; + } else { + throw new Error(`Invalid URL ${url}`); + } + } + const db = await this.dbPromise; + // have we seen this url before? + const urlRecord = await db.get("url", design_url); + /** @type {HeadersInit} */ + const headers = {}; // for the fetch + if (urlRecord) { + /** @type {string} */ + const etag = urlRecord.etag; + // do we have any saved designs with this etag? + const savedKey = await db.getKeyFromIndex("saved", "by-etag", etag); + if (savedKey) { + // yes we have a previously saved design from this url + // set the headers to check if it has changed + headers["If-None-Match"] = etag; + name = savedKey.toString(); + } + } + headers["Target-URL"] = design_url; + + const response = await fetch("https://gb.cs.unc.edu/cors/", { headers }); + if (response.status == 304) { + // we already have it + this.designName = name; + return false; + } + if (!response.ok) { + throw new Error(`Fetching the URL (${url}) failed: ${response.status}`); } - throw new Error("No such parent"); - } - /** - * 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); + const etag = response.headers.get("ETag") || ""; + await db.put("url", { url: design_url, page_url: url, etag }); + + if (!name) { + const urlParts = new URL(design_url, window.location.origin); + const pathParts = urlParts.pathname.split("/"); + if ( + pathParts.length > 0 && + (pathParts[pathParts.length - 1].endsWith(".osdpi") || + pathParts[pathParts.length - 1].endsWith(".zip")) + ) { + name = pathParts[pathParts.length - 1]; + } else { + throw new Error(`Design files should have .osdpi suffix`); } } - return result; + + const blob = await response.blob(); + + // parse the URL + return this.readDesignFromBlob(blob, name, etag); } - /** @param {string[]} classes - * @returns {string} + /** + * Reload the design from a URL if and only if: + * 1. It was loaded from a URL + * 2. It has not been edited + * 3. The ETag has changed */ - CSSClasses(...classes) { - return classes.join(" "); - } -} + async reloadDesignFromOriginalURL() { + const db = await this.dbPromise; -/** - * 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); + const name = this.designName; + + // check saved + const savedRecord = await db.get("saved", name); + if (savedRecord && savedRecord.etag && savedRecord.etag != "none") { + // lookup the URL + const etag = savedRecord.etag; + const urlRecord = await db.getFromIndex("url", "by-etag", etag); + if (urlRecord) { + const url = urlRecord.page_url; + if (await this.readDesignFromURL(url)) { + Globals.restart(); } } } } - /** Replace this node with one of a compatible type - * @param {string} className - * @param {Object} [props] - used in undo to reset the props - * */ - replace(className, props) { - if (!this.parent) return; - if (this.className == className) return; + /** Read a design from a zip file + * @param {Blob} blob + * @param {string} filename + * @param {string} etag + * @returns {Promise} + */ + async readDesignFromBlob(blob, filename, etag = "") { + const db = await this.dbPromise; + this.fileName = filename; - let update = true; - // extract the values of the old props - if (!props) { - props = Object.fromEntries( - Object.entries(this) - .filter(([_, prop]) => prop instanceof Prop) - .map(([name, prop]) => [name, prop.value]), - ); + const zippedBuf = await readAsArrayBuffer(blob); + const zippedArray = new Uint8Array(zippedBuf); + const unzipped = unzipSync(zippedArray); + + // normalize the fileName to make the design name + let name = this.fileName; + // make sure it is unique + if (!etag) { + name = await this.uniqueName(name); } else { - update = false; - } - const replacement = TreeBase.create(className, null, props); - replacement.init(); - if (!(replacement instanceof TreeBaseSwitchable)) { - throw new Error( - `Invalid TreeBaseSwitchable replacement ${this.className} ${replacement.className}`, - ); - } - const index = this.parent.children.indexOf(this); - this.parent.children[index] = replacement; - replacement.parent = this.parent; - if (update) { - console.log("update"); - this.update(); + name = name.replace(/\.(zip|osdpi)$/, ""); } - } -} -class Messages extends TreeBase { - /** @type {string[]} */ - messages = []; + this.designName = name; - template() { - if (this.messages.length) { - const result = html`
    - ${this.messages.map((message) => html`

    ${message}

    `)} -
    `; - this.messages = []; - return result; - } else { - return html`
    `; + for (const fname in unzipped) { + const mimetype = mime(fname) || "application/octet-stream"; + if (mimetype == "application/json") { + const text = strFromU8(unzipped[fname]); + let obj = {}; + try { + obj = JSON.parse(text); + } catch (e) { + obj = {}; + console.trace(e); + } + const type = fname.split(".")[0]; + await this.write(type, obj); + } else if ( + mimetype.startsWith("image") || + mimetype.startsWith("audio") || + mimetype.startsWith("video") + ) { + const blob = new Blob([unzipped[fname]], { + type: mimetype, + }); + await db.put( + "media", + { + name: fname, + content: blob, + }, + [name, fname], + ); + } } + await db.put("saved", { name: this.designName, etag }); + this.notify({ action: "update", name: this.designName }); + return true; } - report(message = "") { - console.log({ message }); - this.messages.push(message); - } -} + // do this part async to avoid file picker timeout + async convertDesignToBlob() { + const db = await this.dbPromise; + // collect the parts of the design + const layout = Globals.layout.toObject(); + const actions = Globals.actions.toObject(); + const content = await this.read("content"); + const method = Globals.method.toObject(); + const pattern = Globals.patterns.toObject(); + const cues = Globals.cues.toObject(); -/** 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); -} + const zipargs = { + "layout.json": strToU8(JSON.stringify(layout)), + "actions.json": strToU8(JSON.stringify(actions)), + "content.json": strToU8(JSON.stringify(content)), + "method.json": strToU8(JSON.stringify(method)), + "pattern.json": strToU8(JSON.stringify(pattern)), + "cues.json": strToU8(JSON.stringify(cues)), + }; -/** @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, []); + const mediaKeys = (await db.getAllKeys("media")).filter((pair) => + Object.values(pair).includes(this.designName), + ); + + // add the encoded image to the zipargs + for (const key of mediaKeys) { + const record = await db.get("media", key); + if (record) { + const contentBuf = await record.content.arrayBuffer(); + const contentArray = new Uint8Array(contentBuf); + zipargs[key[1]] = contentArray; + } } - } -}; -/** @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; + // zip it + const zip = zipSync(zipargs); + // create a blob from the zipped result + const blob = new Blob([zip], { type: "application/octet-stream" }); + return blob; } - 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"], - ); -}; - -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 + /** Save a design into a zip file */ - moveUpDown(up) { - this.parent?.moveUpDown(up); + async saveDesign() { + const db = await this.dbPromise; + + const options = { + fileName: this.fileName || this.designName + ".osdpi", + extensions: [".osdpi", ".zip"], + id: "osdpi", + }; + try { + await o$1(this.convertDesignToBlob(), options, this.fileHandle); + await db.put("saved", { name: this.designName }); + } catch (error) { + console.error("Export failed"); + console.error(error); + } } - /** Format the settings - * @param {GridFilter[]} filters - * @return {Hole} + /** Unload a design from the database + * @param {string} name - the name of the design to delete */ - 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`
    `; + async unload(name) { + const db = await this.dbPromise; + const tx = db.transaction("store5", "readwrite"); + const index = tx.store.index("by-name"); + for await (const cursor of index.iterate(name)) { + cursor.delete(); } - return html`
    - Filters - ${table} -
    `; + await tx.done; + // delete media + const txm = db.transaction("media", "readwrite"); + const mediaKeys = (await txm.store.getAllKeys()).filter( + (pair) => Object.values(pair)[0] == name, + ); + + // delete the media + for (const key of mediaKeys) { + await txm.store.delete(key); + } + await txm.done; + await db.delete("saved", name); + this.notify({ action: "unload", name }); } - /** Convert from Props to values for data module - * @param {GridFilter[]} filters + /** Return an image from the database + * @param {string} name + * @returns {Promise} */ - static toContentFilters(filters) { - return filters.map((child) => ({ - field: child.field.value, - operator: child.operator.value, - value: child.value.value, - })); + async getImage(name) { + const db = await this.dbPromise; + const record = await db.get("media", [this.designName, name]); + const img = new Image(); + if (record) { + img.src = URL.createObjectURL(record.content); + } + img.title = record.name; + return img; } -} -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, -}; - -/** 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(); + /** Return an audio file from the database + * @param {string} name + * @returns {Promise} + */ + async getAudio(name) { + const db = await this.dbPromise; + const record = await db.get("media", [this.designName, name]); + const audio = new Audio(); + if (record) { + audio.src = URL.createObjectURL(record.content); + } + return audio; } - 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; - } + /** Return an image URL from the database + * @param {string} name + * @returns {Promise} + */ + async getMediaURL(name) { + const db = await this.dbPromise; + const record = await db.get("media", [this.designName, name]); + if (record) return URL.createObjectURL(record.content); + else return ""; } - /** - * 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 + /** Add media to the database + * @param {Blob} blob + * @param {string} name */ - 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)), + async addMedia(blob, name) { + const db = await this.dbPromise; + return await db.put( + "media", + { + name: name, + content: blob, + }, + [this.designName, name], ); - 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)), + /** List media entries from a given store + * @returns {Promise} + * */ + async listMedia() { + const db = await this.dbPromise; + const keys = (await db.getAllKeys("media")).filter( + (key) => key[0] == this.designName, //only show resources from this design ); + const result = []; + for (const key of keys) { + result.push(key[1].toString()); + } return result; } - /** - * Add rows from the socket interface - * @param {Rows} rows + /** delete media files + * @param {string[]} names */ - 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); + async deleteMedia(...names) { + const db = await this.dbPromise; + const tx = db.transaction(["media", "saved"], "readwrite"); + const mst = tx.objectStore("media"); + for await (const cursor of mst.iterate()) { + if ( + cursor && + cursor.key[0] == this.designName && + names.includes(cursor.key[1]) + ) { + cursor.delete(); } } + const cursor = await tx.objectStore("saved").openCursor(this.designName); + if (cursor) { + cursor.delete(); + } + await tx.done; } - /** unified interface to state - * @param {string} [name] - possibly dotted path to a value - * @param {any} defaultValue - * @returns {any} + /** Listen for database update + * @param {(message: UpdateNotification) =>void} callback */ - get(name, defaultValue = "") { - if (name && name.length) { - return name - .split(".") - .reduce((o, p) => (o ? o[p] : defaultValue), this.values); - } else { - return undefined; - } + addUpdateListener(callback) { + this.updateListeners.push(callback); } - /** - * update the state with a patch and invoke any listeners - * - * @param {Object} patch - the changes to make to the state - * @return {void} + /** Notify listeners of database update + * @param {UpdateNotification} message */ - update(patch = {}) { - for (const key in patch) { - this.updated.add(key); - } - this.values = o$1(this.values, patch); - for (const callback of this.listeners) { - callback(); + notify(message) { + for (const listener of this.updateListeners) { + listener(message); } + } +} - if (this.persistKey) { - const persist = JSON.stringify(this.values); - window.sessionStorage.setItem(this.persistKey, persist); - // console.trace("persist $tabControl", this.values["$tabControl"]); +const db = new DB(); + +/** Convert a blob into an array buffer + * @param {Blob} blob */ +function readAsArrayBuffer(blob) { + return new Promise((resolve) => { + const fr = new FileReader(); + fr.onloadend = () => fr.result instanceof ArrayBuffer && resolve(fr.result); + fr.readAsArrayBuffer(blob); + }); +} + +const mimetypes = { + ".json": "application/json", + ".aac": "audio/aac", + ".mp3": "audio/mpeg", + ".mp4": "audio/mp4", + ".oga": "audio/ogg", + ".wav": "audio/wav", + ".weba": "audio/webm", + ".webm": "video/webm", + ".avif": "image/avif", + ".bmp": "image/bmp", + ".gif": "image/gif", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".jfif": "image/jpeg", + ".png": "image/png", + ".svg": "image/svg+xml", + ".tif": "image/tiff", + ".tiff": "image/tiff", + ".webp": "image/webp", +}; +/** Map filenames to mimetypes for unpacking the zip file + * @param {string} fname + */ +function mime(fname) { + const extension = /\.[-a-zA-Z0-9]+$/.exec(fname); + if (!extension) return false; + return mimetypes[extension] || false; +} + +/** @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); + }; + }; +} +/** @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); + }; + }; +} +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); } - } + }), + 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), + reload_design: () => { + db.reloadDesignFromOriginalURL(); + return "reloaded"; + }, +}; - /** - * 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; - } +/** + * 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(/(? - name.startsWith("$"), - ); - const patch = Object.fromEntries( - userState.map((name) => [name, undefined]), - ); - this.update(patch); - } +/** + * Cleanup access to state and data + * + * @param {State} state + * @param {Row} data + * @returns {function(string): any} + */ +function access(state, data) { + return function (name) { + if (!name) return ""; + if (state && name.startsWith("$")) { + return state.get(name); + } + if (data && name.startsWith("#")) { + const r = data[name.slice(1)]; + if (r == null) return ""; + return r; + } + return ""; + }; +} - /** observe - call this function when the state updates - * @param {Function} callback +/** 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 */ - observe(callback) { - this.listeners.add(callback); - } + 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); + } + if (result === undefined || result === null) { + result = ""; + } + return result; + }, - /** return true if the given state has been upated on this cycle - * @param {string} stateName - * @returns boolean + /** 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; */ - hasBeenUpdated(stateName) { - return this.updated.has(stateName); - } + 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); + } + }, +}; - /** clear updated for the next cycle - */ - clearUpdated() { - this.updated.clear(); +/** + * 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]; } +} - /** 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; - } - } - /** 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; - } +/* + * 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", }; -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; - } - 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, - }, - }, - 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(/^\*/, "")}`; - } - } - 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(""); +/** @param {string} strColor */ +function isValidColor(strColor) { + if (strColor.length == 0 || strColor in ColorNames) { + return true; } - 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. - */ + var s = new Option().style; + s.color = strColor; -/** Test if this message has slots - * @param {string|Editor} message - * @returns {boolean} - */ -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; + // return 'false' if color wasn't assigned + return s.color !== ""; } -/** initialize the editor - * @param {String} message - * @returns Editor - */ -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; +/** @param {string} name */ +function getColor(name) { + return ColorNames[name] || name; } -/** cancel slot editing - * @returns Editor - */ -function cancel() { - return { - type: "editor", - message: "", - slots: [], - slotIndex: 0, - slotName: "", - }; +/** @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()], + ), + ); } -/** update the value of the current slot - * @param {String} message - */ -function update(message) { - message ??= ""; - /** @param {Editor} old - * @returns {Editor|string} - */ - return (old) => { - // copy the slots from the old value - if (!old || !old.slots) { - return ""; - } - 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"); - } - } - return o$1(old, { - slots, - slotIndex, - slotName: slots[slotIndex]?.name, - }); - }; +/** @param {Partial} styles */ +function styleString(styles) { + return Object.entries(normalizeStyle(styles)).reduce( + (acc, [key, value]) => + acc + + key + .split(/(?=[A-Z])/) + .join("-") + .toLowerCase() + + ":" + + value + + ";", + "", + ); } -/** advance to the next slot - */ -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"); - } - return o$1(old, { slotIndex, slotName: old.slots[slotIndex]?.name }); - }; +function colorNamesDataList() { + return html` + ${Object.keys(ColorNames).map((name) => html``; } -/** duplicate the current slot - */ -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; - } - }; -} +/* Thinking about better properties */ -/** Get the slot name - * @param {string|Editor} message - * @returns string; + +/** + * @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] */ -function slotName(message) { - if (typeof message === "object" && message.type === "editor") { - return message.slotName; - } - return ""; -} -{ - Functions["slots"] = { - init, - cancel, - update, - hasSlots, - duplicate, - nextSlot, - slotName, - toString, - }; -} /** - * 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} + * @template {number|boolean|string} T */ -function imageOrVideo(src, title, onload = null) { - const match = /(?.*\.(?:mp4|webm))(?.*$)/.exec(src); +class Prop { + label = ""; + /** @type {T} */ + _value; - if (match && match.groups) { - // video - const options = match.groups.options; - const vsrc = match.groups.src; - return html`