const module = ""; const CONSTANTS = { MODULE_NAME: "sequencer", FLAG_NAME: "effects", COLOR: { PRIMARY: 15615023, SECONDARY: 6298186, TERTIARY: 6298186 }, SHAPES: { POLY: "polygon", RECT: "rectangle", CIRC: "circle", ELIP: "ellipse", RREC: "roundedRect" }, FEET_REGEX: new RegExp(/\.[0-9]+ft\.*/g), ARRAY_REGEX: new RegExp(/\.[0-9]$/g), STATUS: { READY: 0, RUNNING: 1, COMPLETE: 2, SKIPPED: 3, ABORTED: 4 } }; CONSTANTS.INTEGRATIONS = {}; CONSTANTS.INTEGRATIONS.ISOMETRIC = {}; CONSTANTS.INTEGRATIONS.ISOMETRIC.ACTIVE = false; CONSTANTS.INTEGRATIONS.ISOMETRIC.MODULE_NAME = "grape_juice-isometrics"; CONSTANTS.INTEGRATIONS.ISOMETRIC.SCENE_ENABLED = `flags.${CONSTANTS.INTEGRATIONS.ISOMETRIC.MODULE_NAME}.is_isometric`; CONSTANTS.INTEGRATIONS.ISOMETRIC.PROJECTION_FLAG = `flags.${CONSTANTS.INTEGRATIONS.ISOMETRIC.MODULE_NAME}.original_image_projection_type`; CONSTANTS.INTEGRATIONS.ISOMETRIC.PROJECTION_TYPES = { TOPDOWN: "topdown", TRUE: "true_isometric", DIAMETRIC: "diametric" }; CONSTANTS.INTEGRATIONS.ISOMETRIC.ISOMETRIC_CONVERSION = Math.sqrt(3); CONSTANTS.INTEGRATIONS.ISOMETRIC.DIMETRIC_CONVERSION = 2 / CONSTANTS.INTEGRATIONS.ISOMETRIC.ISOMETRIC_CONVERSION; CONSTANTS.INTEGRATIONS.ISOMETRIC.DUNGEON_BUILDER_CONVERSION = 278 / 154 / CONSTANTS.INTEGRATIONS.ISOMETRIC.ISOMETRIC_CONVERSION; CONSTANTS.EFFECTS_FLAG = `flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`; function registerEase(easeName, easeFunction, overwrite = false) { if (typeof easeName !== "string") throw custom_error("registerEase", "easeName must be of type string"); if (!is_function$1(easeFunction)) throw custom_error( "registerEase", "easeFunction must be of type function" ); if (easeFunctions[easeName] !== void 0 && !overwrite) return; debug(`registerEase | Registered ease function: ${easeName}`); easeFunctions[easeName] = easeFunction; } const easeFunctions = { linear, easeInSine, easeOutSine, easeInOutSine, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeInQuint, easeOutQuint, easeInOutQuint, easeInExpo, easeOutExpo, easeInOutExpo, easeInCirc, easeOutCirc, easeInOutCirc, easeInBack, easeOutBack, easeInOutBack, easeInElastic, easeOutElastic, easeInOutElastic, easeInBounce, easeOutBounce, easeInOutBounce }; const EASE = { LINEAR: "linear", SINE_IN: "easeInSine", SINE_OUT: "easeOutSine", SINE_IN_OUT: "easeInOutSine", QUAD_IN: "easeInQuad", QUAD_OUT: "easeOutQuad", QUAD_IN_OUT: "easeInOutQuad", CUBIC_IN: "easeInCubic", CUBIC_OUT: "easeOutCubic", CUBIC_IN_OUT: "easeInOutCubic", QUART_IN: "easeInQuart", QUART_OUT: "easeOutQuart", QUART_IN_OUT: "easeInOutQuart", QUINT_IN: "easeInQuint", QUINT_OUT: "easeOutQuint", QUINT_IN_OUT: "easeInOutQuint", EXPO_IN: "easeInExpo", EXPO_OUT: "easeOutExpo", EXPO_IN_OUT: "easeInOutExpo", CIRC_IN: "easeInCirc", CIRC_OUT: "easeOutCirc", CIRC_IN_OUT: "easeInOutCirc", BACK_IN: "easeInBack", BACK_OUT: "easeOutBack", BACK_IN_OUT: "easeInOutBack", ELASTIC_IN: "easeInElastic", ELASTIC_OUT: "easeOutElastic", ELASTIC_IN_OUT: "easeInOutElastic", BOUNCE_IN: "easeInBounce", BOUNCE_OUT: "easeOutBounce", BOUNCE_IN_OUT: "easeInOutBounce" }; function linear(x) { return x; } function easeInSine(x) { return 1 - Math.cos(x * Math.PI / 2); } function easeOutSine(x) { return Math.sin(x * Math.PI / 2); } function easeInOutSine(x) { return -(Math.cos(Math.PI * x) - 1) / 2; } function easeInQuad(x) { return x * x; } function easeOutQuad(x) { return 1 - (1 - x) * (1 - x); } function easeInOutQuad(x) { return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; } function easeInCubic(x) { return x * x * x; } function easeOutCubic(x) { return 1 - Math.pow(1 - x, 3); } function easeInOutCubic(x) { return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; } function easeInQuart(x) { return x * x * x * x; } function easeOutQuart(x) { return 1 - Math.pow(1 - x, 4); } function easeInOutQuart(x) { return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2; } function easeInQuint(x) { return x * x * x * x * x; } function easeOutQuint(x) { return 1 - Math.pow(1 - x, 5); } function easeInOutQuint(x) { return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; } function easeInExpo(x) { return x === 0 ? 0 : Math.pow(2, 10 * x - 10); } function easeOutExpo(x) { return x === 1 ? 1 : 1 - Math.pow(2, -10 * x); } function easeInOutExpo(x) { return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 : (2 - Math.pow(2, -20 * x + 10)) / 2; } function easeInCirc(x) { return 1 - Math.sqrt(1 - Math.pow(x, 2)); } function easeOutCirc(x) { return Math.sqrt(1 - Math.pow(x - 1, 2)); } function easeInOutCirc(x) { return x < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2; } function easeInBack(x) { const c1 = 1.70158; const c3 = c1 + 1; return c3 * x * x * x - c1 * x * x; } function easeOutBack(x) { const c1 = 1.70158; const c3 = c1 + 1; return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); } function easeInOutBack(x) { const c1 = 1.70158; const c2 = c1 * 1.525; return x < 0.5 ? Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2) / 2 : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; } function easeInElastic(x) { const c4 = 2 * Math.PI / 3; return x === 0 ? 0 : x === 1 ? 1 : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4); } function easeOutElastic(x) { const c4 = 2 * Math.PI / 3; return x === 0 ? 0 : x === 1 ? 1 : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1; } function easeInOutElastic(x) { const c5 = 2 * Math.PI / 4.5; return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2 : Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5) / 2 + 1; } function easeInBounce(x) { return 1 - easeOutBounce(1 - x); } function easeOutBounce(x) { const n1 = 7.5625; const d1 = 2.75; if (x < 1 / d1) { return n1 * x * x; } else if (x < 2 / d1) { return n1 * (x -= 1.5 / d1) * x + 0.75; } else if (x < 2.5 / d1) { return n1 * (x -= 2.25 / d1) * x + 0.9375; } else { return n1 * (x -= 2.625 / d1) * x + 0.984375; } } function easeInOutBounce(x) { return x < 0.5 ? (1 - easeOutBounce(1 - 2 * x)) / 2 : (1 + easeOutBounce(2 * x - 1)) / 2; } async function getFiles(inFile, { applyWildCard = false, softFail = false } = {}) { let source = "data"; const browseOptions = { wildcard: applyWildCard }; if (/\.s3\./.test(inFile)) { source = "s3"; const { bucket, keyPrefix } = FilePicker.parseS3URL(inFile); if (bucket) { browseOptions.bucket = bucket; inFile = keyPrefix; } } try { return (await FilePicker.browse(source, inFile, browseOptions)).files; } catch (err) { if (softFail) return false; throw custom_error("Sequencer", `getFiles | ${err}`); } } function interpolate(p1, p2, t, ease = "linear") { const easeFunction = is_function$1(ease) ? ease : easeFunctions[ease]; return p1 + (p2 - p1) * easeFunction(t); } function random_float_between(min, max, twister = false) { const random = twister ? twister.random() : Math.random(); const _max = Math.max(max, min); const _min = Math.min(max, min); return random * (_max - _min) + _min; } function random_int_between(min, max, twister = false) { return Math.floor(random_float_between(min, max, twister)); } function flip_negate(num_1, num_2) { if (num_1 > 0) { num_1 -= num_2; } else { num_1 += num_2; } return num_1; } function shuffle_array(inArray, twister = false) { let shuffled = [...inArray]; const randomMethod = twister?.random ?? Math.random; for (let i = shuffled.length - 1; i > 0; i--) { let j = Math.floor(randomMethod() * (i + 1)); let temp = shuffled[i]; shuffled[i] = shuffled[j]; shuffled[j] = temp; } return shuffled; } function is_function$1(inFunc) { return inFunc && ({}.toString.call(inFunc) === "[object Function]" || {}.toString.call(inFunc) === "[object AsyncFunction]"); } function random_array_element(inArray, { recurse = false, twister = false, index = false } = {}) { const chosenIndex = random_int_between(0, inArray.length, twister); let choice = inArray[chosenIndex]; if (recurse && Array.isArray(choice)) { return random_array_element(choice, { recurse, twister, index }); } if (index) { return chosenIndex; } return choice; } function random_object_element(inObject, { recurse = false, twister = false } = {}) { let keys = Object.keys(inObject).filter((k) => !k.startsWith("_")); let choice = inObject[random_array_element(keys, { twister })]; if (typeof choice === "object" && recurse) { return random_object_element(choice, { recurse: true }); } return choice; } function is_real_number(inNumber) { return !isNaN(inNumber) && typeof inNumber === "number" && isFinite(inNumber); } function deep_get(obj, path) { if (!Array.isArray(path)) path = path.split("."); try { let i; for (i = 0; i < path.length - 1; i++) { obj = obj[path[i]]; } return obj[path[i]]; } catch (err) { } } function deep_set(obj, path, value) { if (!Array.isArray(path)) path = path.split("."); try { let i; for (i = 0; i < path.length - 1; i++) { obj = obj[path[i]]; } if (is_function$1(obj[path[i]])) { obj[path[i]](value); } else { obj[path[i]] = value; } } catch (err) { } } function flatten_object(obj) { let toReturn = []; for (let i in obj) { if (i.startsWith("_")) continue; if (!obj.hasOwnProperty(i)) continue; if (typeof obj[i] == "object") { let flatObject = flatten_object(obj[i]); for (let x in flatObject) { if (!flatObject.hasOwnProperty(x)) continue; toReturn[i + "." + x] = flatObject[x]; } } else { toReturn[i] = obj[i]; } } return toReturn; } function wait$1(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function clamp(num, min, max) { const _max = Math.max(min, max); const _min = Math.min(min, max); return Math.max(_min, Math.min(_max, num)); } function is_UUID(inId) { return typeof inId === "string" && (inId.startsWith("Scene") || inId.startsWith("Actor") || inId.startsWith("Item")) && (inId.match(/\./g) || []).length && !inId.endsWith("."); } function get_object_from_scene(inObjectId, inSceneId = game.user.viewedScene) { let tryUUID = is_UUID(inObjectId); if (tryUUID) { const obj = fromUuidSync(inObjectId); if (obj) return obj; tryUUID = false; } return get_all_documents_from_scene(inSceneId).find((obj) => { return get_object_identifier(obj, tryUUID) === inObjectId; }); } function get_all_documents_from_scene(inSceneId = false) { const scene = inSceneId ? game.scenes.get(inSceneId) : game.scenes.get(game.user?.viewedScene); if (!scene) return []; return [ ...canvas.templates?.preview?.children ?? [], ...Array.from(scene?.tokens ?? []), ...Array.from(scene?.lights ?? []), ...Array.from(scene?.sounds ?? []), ...Array.from(scene?.templates ?? []), ...Array.from(scene?.tiles ?? []), ...Array.from(scene?.walls ?? []), ...Array.from(scene?.drawings ?? []) ].deepFlatten().filter(Boolean); } function validate_document(inObject) { const document2 = inObject?.document ?? inObject; return is_UUID(document2?.uuid) ? document2 : inObject; } function get_object_identifier(inObject, tryUUID = true) { const uuid = tryUUID && is_UUID(inObject?.uuid) ? inObject?.uuid : void 0; return uuid ?? inObject?.id ?? inObject?.document?.name ?? inObject?.name ?? (inObject?.tag !== "" ? inObject?.tag : void 0) ?? (inObject?.label !== "" ? inObject?.label : void 0); } function make_array_unique(inArray) { return Array.from(new Set(inArray)); } function debug(msg, args = "") { if (game.settings.get(CONSTANTS.MODULE_NAME, "debug")) console.log(`DEBUG | Sequencer | ${msg}`, args); } function debug_error(msg, args) { if (game.settings.get(CONSTANTS.MODULE_NAME, "debug")) console.error(`DEBUG | Sequencer | ${msg}`, args); } function custom_warning(inClassName, warning, notify = false) { inClassName = inClassName !== "Sequencer" ? "Sequencer | Module: " + inClassName : inClassName; warning = `${inClassName} | ${warning}`; if (notify) ui.notifications.warn(warning, { console: false }); console.warn(warning.replace("
", "\n")); } const throttledWarnings = {}; function throttled_custom_warning(inClassName, warning, delay = 1e4, notify = false) { inClassName = inClassName !== "Sequencer" ? "Sequencer | Module: " + inClassName : inClassName; warning = `${inClassName} | ${warning}`; if (throttledWarnings[warning]) return; throttledWarnings[warning] = true; if (notify) ui.notifications.warn(warning, { console: false }); console.warn(warning.replace("
", "\n")); setTimeout(() => { delete throttledWarnings[warning]; }, delay); } function custom_error(inClassName, error, notify = true) { inClassName = inClassName !== "Sequencer" ? "Sequencer | Module: " + inClassName : inClassName; error = `${inClassName} | ${error}`; if (notify) ui.notifications.error(error, { console: false }); return new Error(error.replace("
", "\n")); } function user_can_do(inSetting) { return game.user.role > game.settings.get(CONSTANTS.MODULE_NAME, inSetting); } function group_by(xs, key) { return xs.reduce(function(acc, obj) { let property = getProperty(obj, key); acc[property] = acc[property] || []; acc[property].push(obj); return acc; }, {}); } function objHasProperty(obj, prop) { return obj.constructor.prototype.hasOwnProperty(prop); } function sequence_proxy_wrap(inSequence) { return new Proxy(inSequence, { get: function(target, prop) { if (!objHasProperty(target, prop)) { if (Sequencer.SectionManager.externalSections[prop] === void 0) { const section = target.sections[target.sections.length - 1]; if (section && objHasProperty(section, prop)) { const targetProperty = Reflect.get(section, prop); return is_function$1(targetProperty) ? targetProperty.bind(section) : targetProperty; } return Reflect.get(target, prop); } target.sectionToCreate = Sequencer.SectionManager.externalSections[prop]; return Reflect.get(target, "_createCustomSection"); } return Reflect.get(target, prop); } }); } function section_proxy_wrap(inClass) { return new Proxy(inClass, { get: function(target, prop) { if (!objHasProperty(target, prop) && objHasProperty(target.sequence, prop)) { const targetProperty = Reflect.get(target.sequence, prop); return is_function$1(targetProperty) ? targetProperty.bind(target.sequence) : targetProperty; } return Reflect.get(target, prop); } }); } function str_to_search_regex_str(str) { return str.trim().replace(/[^A-Za-z0-9 .*_-]/g, "").replace(/\*+/g, ".*?"); } function safe_str(str) { return str.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); } function get_hash(input) { let hash2 = 0; const len = input.length; for (let i = 0; i < len; i++) { hash2 = (hash2 << 5) - hash2 + input.charCodeAt(i); hash2 |= 0; } return hash2; } function parseColor(inColor) { return { hexadecimal: is_real_number(inColor) ? inColor.toString(16) : inColor, decimal: typeof inColor === "string" && inColor.startsWith("#") ? parseInt(inColor.slice(1), 16) : inColor }; } function getCanvasMouse() { return game.release.generation === 11 ? canvas.app.renderer.plugins.interaction.pointer : canvas.app.renderer.plugins.interaction.mouse; } function noop() { } const identity = (x) => x; function assign(tar, src) { for (const k in src) tar[k] = src[k]; return tar; } function run(fn) { return fn(); } function blank_object() { return /* @__PURE__ */ Object.create(null); } function run_all(fns) { fns.forEach(run); } function is_function(thing) { return typeof thing === "function"; } function safe_not_equal(a, b) { return a != a ? b == b : a !== b || (a && typeof a === "object" || typeof a === "function"); } let src_url_equal_anchor; function src_url_equal(element_src, url) { if (!src_url_equal_anchor) { src_url_equal_anchor = document.createElement("a"); } src_url_equal_anchor.href = url; return element_src === src_url_equal_anchor.href; } function is_empty(obj) { return Object.keys(obj).length === 0; } function subscribe(store, ...callbacks) { if (store == null) { return noop; } const unsub = store.subscribe(...callbacks); return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; } function get_store_value(store) { let value; subscribe(store, (_) => value = _)(); return value; } function component_subscribe(component, store, callback) { component.$$.on_destroy.push(subscribe(store, callback)); } function create_slot(definition, ctx, $$scope, fn) { if (definition) { const slot_ctx = get_slot_context(definition, ctx, $$scope, fn); return definition[0](slot_ctx); } } function get_slot_context(definition, ctx, $$scope, fn) { return definition[1] && fn ? assign($$scope.ctx.slice(), definition[1](fn(ctx))) : $$scope.ctx; } function get_slot_changes(definition, $$scope, dirty, fn) { if (definition[2] && fn) { const lets = definition[2](fn(dirty)); if ($$scope.dirty === void 0) { return lets; } if (typeof lets === "object") { const merged = []; const len = Math.max($$scope.dirty.length, lets.length); for (let i = 0; i < len; i += 1) { merged[i] = $$scope.dirty[i] | lets[i]; } return merged; } return $$scope.dirty | lets; } return $$scope.dirty; } function update_slot_base(slot, slot_definition, ctx, $$scope, slot_changes, get_slot_context_fn) { if (slot_changes) { const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn); slot.p(slot_context, slot_changes); } } function get_all_dirty_from_scope($$scope) { if ($$scope.ctx.length > 32) { const dirty = []; const length = $$scope.ctx.length / 32; for (let i = 0; i < length; i++) { dirty[i] = -1; } return dirty; } return -1; } function exclude_internal_props(props) { const result = {}; for (const k in props) if (k[0] !== "$") result[k] = props[k]; return result; } function compute_slots(slots) { const result = {}; for (const key in slots) { result[key] = true; } return result; } function set_store_value(store, ret, value) { store.set(value); return ret; } function action_destroyer(action_result) { return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; } const is_client = typeof window !== "undefined"; let now = is_client ? () => window.performance.now() : () => Date.now(); let raf = is_client ? (cb) => requestAnimationFrame(cb) : noop; const tasks = /* @__PURE__ */ new Set(); function run_tasks(now2) { tasks.forEach((task) => { if (!task.c(now2)) { tasks.delete(task); task.f(); } }); if (tasks.size !== 0) raf(run_tasks); } function loop(callback) { let task; if (tasks.size === 0) raf(run_tasks); return { promise: new Promise((fulfill) => { tasks.add(task = { c: callback, f: fulfill }); }), abort() { tasks.delete(task); } }; } function append(target, node) { target.appendChild(node); } function get_root_for_style(node) { if (!node) return document; const root = node.getRootNode ? node.getRootNode() : node.ownerDocument; if (root && root.host) { return root; } return node.ownerDocument; } function append_empty_stylesheet(node) { const style_element = element("style"); append_stylesheet(get_root_for_style(node), style_element); return style_element.sheet; } function append_stylesheet(node, style) { append(node.head || node, style); return style.sheet; } function insert(target, node, anchor) { target.insertBefore(node, anchor || null); } function detach(node) { if (node.parentNode) { node.parentNode.removeChild(node); } } function destroy_each(iterations, detaching) { for (let i = 0; i < iterations.length; i += 1) { if (iterations[i]) iterations[i].d(detaching); } } function element(name) { return document.createElement(name); } function svg_element(name) { return document.createElementNS("http://www.w3.org/2000/svg", name); } function text$1(data) { return document.createTextNode(data); } function space() { return text$1(" "); } function empty() { return text$1(""); } function listen(node, event, handler, options) { node.addEventListener(event, handler, options); return () => node.removeEventListener(event, handler, options); } function prevent_default(fn) { return function(event) { event.preventDefault(); return fn.call(this, event); }; } function stop_propagation(fn) { return function(event) { event.stopPropagation(); return fn.call(this, event); }; } function attr(node, attribute, value) { if (value == null) node.removeAttribute(attribute); else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value); } function to_number(value) { return value === "" ? null : +value; } function children(element2) { return Array.from(element2.childNodes); } function set_data(text2, data) { data = "" + data; if (text2.wholeText !== data) text2.data = data; } function set_input_value(input, value) { input.value = value == null ? "" : value; } function set_style(node, key, value, important) { if (value === null) { node.style.removeProperty(key); } else { node.style.setProperty(key, value, important ? "important" : ""); } } function select_option(select, value) { for (let i = 0; i < select.options.length; i += 1) { const option = select.options[i]; if (option.__value === value) { option.selected = true; return; } } select.selectedIndex = -1; } function select_options(select, value) { for (let i = 0; i < select.options.length; i += 1) { const option = select.options[i]; option.selected = ~value.indexOf(option.__value); } } function select_value(select) { const selected_option = select.querySelector(":checked") || select.options[0]; return selected_option && selected_option.__value; } function select_multiple_value(select) { return [].map.call(select.querySelectorAll(":checked"), (option) => option.__value); } function toggle_class(element2, name, toggle) { element2.classList[toggle ? "add" : "remove"](name); } function custom_event(type, detail, { bubbles = false, cancelable = false } = {}) { const e = document.createEvent("CustomEvent"); e.initCustomEvent(type, bubbles, cancelable, detail); return e; } class HtmlTag { constructor(is_svg = false) { this.is_svg = false; this.is_svg = is_svg; this.e = this.n = null; } c(html) { this.h(html); } m(html, target, anchor = null) { if (!this.e) { if (this.is_svg) this.e = svg_element(target.nodeName); else this.e = element(target.nodeName); this.t = target; this.c(html); } this.i(anchor); } h(html) { this.e.innerHTML = html; this.n = Array.from(this.e.childNodes); } i(anchor) { for (let i = 0; i < this.n.length; i += 1) { insert(this.t, this.n[i], anchor); } } p(html) { this.d(); this.h(html); this.i(this.a); } d() { this.n.forEach(detach); } } function construct_svelte_component(component, props) { return new component(props); } const managed_styles = /* @__PURE__ */ new Map(); let active = 0; function hash(str) { let hash2 = 5381; let i = str.length; while (i--) hash2 = (hash2 << 5) - hash2 ^ str.charCodeAt(i); return hash2 >>> 0; } function create_style_information(doc, node) { const info = { stylesheet: append_empty_stylesheet(node), rules: {} }; managed_styles.set(doc, info); return info; } function create_rule(node, a, b, duration, delay, ease, fn, uid = 0) { const step = 16.666 / duration; let keyframes = "{\n"; for (let p = 0; p <= 1; p += step) { const t = a + (b - a) * ease(p); keyframes += p * 100 + `%{${fn(t, 1 - t)}} `; } const rule = keyframes + `100% {${fn(b, 1 - b)}} }`; const name = `__svelte_${hash(rule)}_${uid}`; const doc = get_root_for_style(node); const { stylesheet, rules } = managed_styles.get(doc) || create_style_information(doc, node); if (!rules[name]) { rules[name] = true; stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); } const animation2 = node.style.animation || ""; node.style.animation = `${animation2 ? `${animation2}, ` : ""}${name} ${duration}ms linear ${delay}ms 1 both`; active += 1; return name; } function delete_rule(node, name) { const previous = (node.style.animation || "").split(", "); const next = previous.filter( name ? (anim) => anim.indexOf(name) < 0 : (anim) => anim.indexOf("__svelte") === -1 // remove all Svelte animations ); const deleted = previous.length - next.length; if (deleted) { node.style.animation = next.join(", "); active -= deleted; if (!active) clear_rules(); } } function clear_rules() { raf(() => { if (active) return; managed_styles.forEach((info) => { const { ownerNode } = info.stylesheet; if (ownerNode) detach(ownerNode); }); managed_styles.clear(); }); } let current_component; function set_current_component(component) { current_component = component; } function get_current_component() { if (!current_component) throw new Error("Function called outside component initialization"); return current_component; } function onMount(fn) { get_current_component().$$.on_mount.push(fn); } function afterUpdate(fn) { get_current_component().$$.after_update.push(fn); } function onDestroy(fn) { get_current_component().$$.on_destroy.push(fn); } function createEventDispatcher() { const component = get_current_component(); return (type, detail, { cancelable = false } = {}) => { const callbacks = component.$$.callbacks[type]; if (callbacks) { const event = custom_event(type, detail, { cancelable }); callbacks.slice().forEach((fn) => { fn.call(component, event); }); return !event.defaultPrevented; } return true; }; } function setContext(key, context) { get_current_component().$$.context.set(key, context); return context; } function getContext(key) { return get_current_component().$$.context.get(key); } const dirty_components = []; const binding_callbacks = []; const render_callbacks = []; const flush_callbacks = []; const resolved_promise = Promise.resolve(); let update_scheduled = false; function schedule_update() { if (!update_scheduled) { update_scheduled = true; resolved_promise.then(flush); } } function add_render_callback(fn) { render_callbacks.push(fn); } function add_flush_callback(fn) { flush_callbacks.push(fn); } const seen_callbacks = /* @__PURE__ */ new Set(); let flushidx = 0; function flush() { if (flushidx !== 0) { return; } const saved_component = current_component; do { try { while (flushidx < dirty_components.length) { const component = dirty_components[flushidx]; flushidx++; set_current_component(component); update(component.$$); } } catch (e) { dirty_components.length = 0; flushidx = 0; throw e; } set_current_component(null); dirty_components.length = 0; flushidx = 0; while (binding_callbacks.length) binding_callbacks.pop()(); for (let i = 0; i < render_callbacks.length; i += 1) { const callback = render_callbacks[i]; if (!seen_callbacks.has(callback)) { seen_callbacks.add(callback); callback(); } } render_callbacks.length = 0; } while (dirty_components.length); while (flush_callbacks.length) { flush_callbacks.pop()(); } update_scheduled = false; seen_callbacks.clear(); set_current_component(saved_component); } function update($$) { if ($$.fragment !== null) { $$.update(); run_all($$.before_update); const dirty = $$.dirty; $$.dirty = [-1]; $$.fragment && $$.fragment.p($$.ctx, dirty); $$.after_update.forEach(add_render_callback); } } let promise; function wait() { if (!promise) { promise = Promise.resolve(); promise.then(() => { promise = null; }); } return promise; } function dispatch(node, direction, kind) { node.dispatchEvent(custom_event(`${direction ? "intro" : "outro"}${kind}`)); } const outroing = /* @__PURE__ */ new Set(); let outros; function group_outros() { outros = { r: 0, c: [], p: outros // parent group }; } function check_outros() { if (!outros.r) { run_all(outros.c); } outros = outros.p; } function transition_in(block, local) { if (block && block.i) { outroing.delete(block); block.i(local); } } function transition_out(block, local, detach2, callback) { if (block && block.o) { if (outroing.has(block)) return; outroing.add(block); outros.c.push(() => { outroing.delete(block); if (callback) { if (detach2) block.d(1); callback(); } }); block.o(local); } else if (callback) { callback(); } } const null_transition = { duration: 0 }; function create_in_transition(node, fn, params) { const options = { direction: "in" }; let config = fn(node, params, options); let running = false; let animation_name; let task; let uid = 0; function cleanup() { if (animation_name) delete_rule(node, animation_name); } function go() { const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition; if (css) animation_name = create_rule(node, 0, 1, duration, delay, easing, css, uid++); tick(0, 1); const start_time = now() + delay; const end_time = start_time + duration; if (task) task.abort(); running = true; add_render_callback(() => dispatch(node, true, "start")); task = loop((now2) => { if (running) { if (now2 >= end_time) { tick(1, 0); dispatch(node, true, "end"); cleanup(); return running = false; } if (now2 >= start_time) { const t = easing((now2 - start_time) / duration); tick(t, 1 - t); } } return running; }); } let started = false; return { start() { if (started) return; started = true; delete_rule(node); if (is_function(config)) { config = config(options); wait().then(go); } else { go(); } }, invalidate() { started = false; }, end() { if (running) { cleanup(); running = false; } } }; } function create_out_transition(node, fn, params) { const options = { direction: "out" }; let config = fn(node, params, options); let running = true; let animation_name; const group = outros; group.r += 1; function go() { const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition; if (css) animation_name = create_rule(node, 1, 0, duration, delay, easing, css); const start_time = now() + delay; const end_time = start_time + duration; add_render_callback(() => dispatch(node, false, "start")); loop((now2) => { if (running) { if (now2 >= end_time) { tick(0, 1); dispatch(node, false, "end"); if (!--group.r) { run_all(group.c); } return false; } if (now2 >= start_time) { const t = easing((now2 - start_time) / duration); tick(1 - t, t); } } return running; }); } if (is_function(config)) { wait().then(() => { config = config(options); go(); }); } else { go(); } return { end(reset) { if (reset && config.tick) { config.tick(1, 0); } if (running) { if (animation_name) delete_rule(node, animation_name); running = false; } } }; } function destroy_block(block, lookup) { block.d(1); lookup.delete(block.key); } function outro_and_destroy_block(block, lookup) { transition_out(block, 1, 1, () => { lookup.delete(block.key); }); } function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list, lookup, node, destroy, create_each_block2, next, get_context) { let o = old_blocks.length; let n = list.length; let i = o; const old_indexes = {}; while (i--) old_indexes[old_blocks[i].key] = i; const new_blocks = []; const new_lookup = /* @__PURE__ */ new Map(); const deltas = /* @__PURE__ */ new Map(); i = n; while (i--) { const child_ctx = get_context(ctx, list, i); const key = get_key(child_ctx); let block = lookup.get(key); if (!block) { block = create_each_block2(key, child_ctx); block.c(); } else if (dynamic) { block.p(child_ctx, dirty); } new_lookup.set(key, new_blocks[i] = block); if (key in old_indexes) deltas.set(key, Math.abs(i - old_indexes[key])); } const will_move = /* @__PURE__ */ new Set(); const did_move = /* @__PURE__ */ new Set(); function insert2(block) { transition_in(block, 1); block.m(node, next); lookup.set(block.key, block); next = block.first; n--; } while (o && n) { const new_block = new_blocks[n - 1]; const old_block = old_blocks[o - 1]; const new_key = new_block.key; const old_key = old_block.key; if (new_block === old_block) { next = new_block.first; o--; n--; } else if (!new_lookup.has(old_key)) { destroy(old_block, lookup); o--; } else if (!lookup.has(new_key) || will_move.has(new_key)) { insert2(new_block); } else if (did_move.has(old_key)) { o--; } else if (deltas.get(new_key) > deltas.get(old_key)) { did_move.add(new_key); insert2(new_block); } else { will_move.add(old_key); o--; } } while (o--) { const old_block = old_blocks[o]; if (!new_lookup.has(old_block.key)) destroy(old_block, lookup); } while (n) insert2(new_blocks[n - 1]); return new_blocks; } function get_spread_update(levels, updates) { const update2 = {}; const to_null_out = {}; const accounted_for = { $$scope: 1 }; let i = levels.length; while (i--) { const o = levels[i]; const n = updates[i]; if (n) { for (const key in o) { if (!(key in n)) to_null_out[key] = 1; } for (const key in n) { if (!accounted_for[key]) { update2[key] = n[key]; accounted_for[key] = 1; } } levels[i] = n; } else { for (const key in o) { accounted_for[key] = 1; } } } for (const key in to_null_out) { if (!(key in update2)) update2[key] = void 0; } return update2; } function get_spread_object(spread_props) { return typeof spread_props === "object" && spread_props !== null ? spread_props : {}; } function bind(component, name, callback) { const index = component.$$.props[name]; if (index !== void 0) { component.$$.bound[index] = callback; callback(component.$$.ctx[index]); } } function create_component(block) { block && block.c(); } function mount_component(component, target, anchor, customElement) { const { fragment, after_update } = component.$$; fragment && fragment.m(target, anchor); if (!customElement) { add_render_callback(() => { const new_on_destroy = component.$$.on_mount.map(run).filter(is_function); if (component.$$.on_destroy) { component.$$.on_destroy.push(...new_on_destroy); } else { run_all(new_on_destroy); } component.$$.on_mount = []; }); } after_update.forEach(add_render_callback); } function destroy_component(component, detaching) { const $$ = component.$$; if ($$.fragment !== null) { run_all($$.on_destroy); $$.fragment && $$.fragment.d(detaching); $$.on_destroy = $$.fragment = null; $$.ctx = []; } } function make_dirty(component, i) { if (component.$$.dirty[0] === -1) { dirty_components.push(component); schedule_update(); component.$$.dirty.fill(0); } component.$$.dirty[i / 31 | 0] |= 1 << i % 31; } function init(component, options, instance2, create_fragment2, not_equal, props, append_styles, dirty = [-1]) { const parent_component = current_component; set_current_component(component); const $$ = component.$$ = { fragment: null, ctx: [], // state props, update: noop, not_equal, bound: blank_object(), // lifecycle on_mount: [], on_destroy: [], on_disconnect: [], before_update: [], after_update: [], context: new Map(options.context || (parent_component ? parent_component.$$.context : [])), // everything else callbacks: blank_object(), dirty, skip_bound: false, root: options.target || parent_component.$$.root }; append_styles && append_styles($$.root); let ready = false; $$.ctx = instance2 ? instance2(component, options.props || {}, (i, ret, ...rest) => { const value = rest.length ? rest[0] : ret; if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value); if (ready) make_dirty(component, i); } return ret; }) : []; $$.update(); ready = true; run_all($$.before_update); $$.fragment = create_fragment2 ? create_fragment2($$.ctx) : false; if (options.target) { if (options.hydrate) { const nodes = children(options.target); $$.fragment && $$.fragment.l(nodes); nodes.forEach(detach); } else { $$.fragment && $$.fragment.c(); } if (options.intro) transition_in(component.$$.fragment); mount_component(component, options.target, options.anchor, options.customElement); flush(); } set_current_component(parent_component); } class SvelteComponent { $destroy() { destroy_component(this, 1); this.$destroy = noop; } $on(type, callback) { if (!is_function(callback)) { return noop; } const callbacks = this.$$.callbacks[type] || (this.$$.callbacks[type] = []); callbacks.push(callback); return () => { const index = callbacks.indexOf(callback); if (index !== -1) callbacks.splice(index, 1); }; } $set($$props) { if (this.$$set && !is_empty($$props)) { this.$$.skip_bound = true; this.$$set($$props); this.$$.skip_bound = false; } } } const s_TAG_OBJECT = "[object Object]"; function deepMerge(target = {}, ...sourceObj) { if (Object.prototype.toString.call(target) !== s_TAG_OBJECT) { throw new TypeError(`deepMerge error: 'target' is not an 'object'.`); } for (let cntr = 0; cntr < sourceObj.length; cntr++) { if (Object.prototype.toString.call(sourceObj[cntr]) !== s_TAG_OBJECT) { throw new TypeError(`deepMerge error: 'sourceObj[${cntr}]' is not an 'object'.`); } } return _deepMerge(target, ...sourceObj); } function isIterable(value) { if (value === null || value === void 0 || typeof value !== "object") { return false; } return typeof value[Symbol.iterator] === "function"; } function isObject(value) { return value !== null && typeof value === "object"; } function isPlainObject(value) { if (Object.prototype.toString.call(value) !== s_TAG_OBJECT) { return false; } const prototype = Object.getPrototypeOf(value); return prototype === null || prototype === Object.prototype; } function safeAccess(data, accessor, defaultValue = void 0) { if (typeof data !== "object") { return defaultValue; } if (typeof accessor !== "string") { return defaultValue; } const access = accessor.split("."); for (let cntr = 0; cntr < access.length; cntr++) { if (typeof data[access[cntr]] === "undefined" || data[access[cntr]] === null) { return defaultValue; } data = data[access[cntr]]; } return data; } function safeSet(data, accessor, value, operation = "set", createMissing = true) { if (typeof data !== "object") { throw new TypeError(`safeSet Error: 'data' is not an 'object'.`); } if (typeof accessor !== "string") { throw new TypeError(`safeSet Error: 'accessor' is not a 'string'.`); } const access = accessor.split("."); for (let cntr = 0; cntr < access.length; cntr++) { if (Array.isArray(data)) { const number = +access[cntr]; if (!Number.isInteger(number) || number < 0) { return false; } } if (cntr === access.length - 1) { switch (operation) { case "add": data[access[cntr]] += value; break; case "div": data[access[cntr]] /= value; break; case "mult": data[access[cntr]] *= value; break; case "set": data[access[cntr]] = value; break; case "set-undefined": if (typeof data[access[cntr]] === "undefined") { data[access[cntr]] = value; } break; case "sub": data[access[cntr]] -= value; break; } } else { if (createMissing && typeof data[access[cntr]] === "undefined") { data[access[cntr]] = {}; } if (data[access[cntr]] === null || typeof data[access[cntr]] !== "object") { return false; } data = data[access[cntr]]; } } return true; } function _deepMerge(target = {}, ...sourceObj) { for (let cntr = 0; cntr < sourceObj.length; cntr++) { const obj = sourceObj[cntr]; for (const prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { if (prop.startsWith("-=")) { delete target[prop.slice(2)]; continue; } target[prop] = Object.prototype.hasOwnProperty.call(target, prop) && target[prop]?.constructor === Object && obj[prop]?.constructor === Object ? _deepMerge({}, target[prop], obj[prop]) : obj[prop]; } } } return target; } class A11yHelper { /** * Apply focus to the HTMLElement targets in a given A11yFocusSource data object. An iterable list `options.focusEl` * can contain HTMLElements or selector strings. If multiple focus targets are provided in a list then the first * valid target found will be focused. If focus target is a string then a lookup via `document.querySelector` is * performed. In this case you should provide a unique selector for the desired focus target. * * Note: The body of this method is postponed to the next clock tick to allow any changes in the DOM to occur that * might alter focus targets before applying. * * @param {A11yFocusSource|{ focusSource: A11yFocusSource }} options - The focus options instance to apply. */ static applyFocusSource(options) { if (!isObject(options)) { return; } const focusOpts = isObject(options?.focusSource) ? options.focusSource : options; setTimeout(() => { const debug2 = typeof focusOpts.debug === "boolean" ? focusOpts.debug : false; if (isIterable(focusOpts.focusEl)) { if (debug2) { console.debug(`A11yHelper.applyFocusSource debug - Attempting to apply focus target: `, focusOpts.focusEl); } for (const target of focusOpts.focusEl) { if (target instanceof HTMLElement && target.isConnected) { target.focus(); if (debug2) { console.debug(`A11yHelper.applyFocusSource debug - Applied focus to target: `, target); } break; } else if (typeof target === "string") { const element2 = document.querySelector(target); if (element2 instanceof HTMLElement && element2.isConnected) { element2.focus(); if (debug2) { console.debug(`A11yHelper.applyFocusSource debug - Applied focus to target: `, element2); } break; } else if (debug2) { console.debug(`A11yHelper.applyFocusSource debug - Could not query selector: `, target); } } } } else if (debug2) { console.debug(`A11yHelper.applyFocusSource debug - No focus targets defined.`); } }, 0); } /** * Returns first focusable element within a specified element. * * @param {HTMLElement|Document} [element=document] - Optional element to start query. * * @param {object} [options] - Optional parameters. * * @param {Iterable} [options.ignoreClasses] - Iterable list of classes to ignore elements. * * @param {Set} [options.ignoreElements] - Set of elements to ignore. * * @returns {HTMLElement} First focusable child element */ static getFirstFocusableElement(element2 = document, options) { const focusableElements = this.getFocusableElements(element2, options); return focusableElements.length > 0 ? focusableElements[0] : void 0; } /** * Returns all focusable elements within a specified element. * * @param {HTMLElement|Document} [element=document] Optional element to start query. * * @param {object} [options] - Optional parameters. * * @param {boolean} [options.anchorHref=true] - When true anchors must have an HREF. * * @param {Iterable} [options.ignoreClasses] - Iterable list of classes to ignore elements. * * @param {Set} [options.ignoreElements] - Set of elements to ignore. * * @param {string} [options.selectors] - Custom list of focusable selectors for `querySelectorAll`. * * @returns {Array} Child keyboard focusable */ static getFocusableElements(element2 = document, { anchorHref = true, ignoreClasses, ignoreElements, selectors } = {}) { if (!(element2 instanceof HTMLElement) && !(element2 instanceof Document)) { throw new TypeError(`'element' is not a HTMLElement or Document instance.`); } if (typeof anchorHref !== "boolean") { throw new TypeError(`'anchorHref' is not a boolean.`); } if (ignoreClasses !== void 0 && !isIterable(ignoreClasses)) { throw new TypeError(`'ignoreClasses' is not an iterable list.`); } if (ignoreElements !== void 0 && !(ignoreElements instanceof Set)) { throw new TypeError(`'ignoreElements' is not a Set.`); } if (selectors !== void 0 && typeof selectors !== "string") { throw new TypeError(`'selectors' is not a string.`); } const selectorQuery = selectors ?? this.#getFocusableSelectors(anchorHref); const allElements = [...element2.querySelectorAll(selectorQuery)]; if (ignoreElements && ignoreClasses) { return allElements.filter((el) => { let hasIgnoreClass = false; for (const ignoreClass of ignoreClasses) { if (el.classList.contains(ignoreClass)) { hasIgnoreClass = true; break; } } return !hasIgnoreClass && !ignoreElements.has(el) && el.style.display !== "none" && el.style.visibility !== "hidden" && !el.hasAttribute("disabled") && !el.hasAttribute("inert") && el.getAttribute("aria-hidden") !== "true"; }); } else if (ignoreClasses) { return allElements.filter((el) => { let hasIgnoreClass = false; for (const ignoreClass of ignoreClasses) { if (el.classList.contains(ignoreClass)) { hasIgnoreClass = true; break; } } return !hasIgnoreClass && el.style.display !== "none" && el.style.visibility !== "hidden" && !el.hasAttribute("disabled") && !el.hasAttribute("inert") && el.getAttribute("aria-hidden") !== "true"; }); } else if (ignoreElements) { return allElements.filter((el) => { return !ignoreElements.has(el) && el.style.display !== "none" && el.style.visibility !== "hidden" && !el.hasAttribute("disabled") && !el.hasAttribute("inert") && el.getAttribute("aria-hidden") !== "true"; }); } else { return allElements.filter((el) => { return el.style.display !== "none" && el.style.visibility !== "hidden" && !el.hasAttribute("disabled") && !el.hasAttribute("inert") && el.getAttribute("aria-hidden") !== "true"; }); } } /** * Returns the default focusable selectors query. * * @param {boolean} [anchorHref=true] - When true anchors must have an HREF. * * @returns {string} Focusable selectors for `querySelectorAll`. */ static #getFocusableSelectors(anchorHref = true) { return `button, [contenteditable=""], [contenteditable="true"], details summary:not([tabindex="-1"]), embed, a${anchorHref ? "[href]" : ""}, iframe, object, input:not([type=hidden]), select, textarea, [tabindex]:not([tabindex="-1"])`; } /** * Gets a A11yFocusSource object from the given DOM event allowing for optional X / Y screen space overrides. * Browsers (Firefox / Chrome) forwards a mouse event for the context menu keyboard button. Provides detection of * when the context menu event is from the keyboard. Firefox as of (1/23) does not provide the correct screen space * coordinates, so for keyboard context menu presses coordinates are generated from the centroid point of the * element. * * A default fallback element or selector string may be provided to provide the focus target. If the event comes from * the keyboard however the source focused element is inserted as the target with the fallback value appended to the * list of focus targets. When A11yFocusSource is applied by {@link A11yHelper.applyFocusSource} the target focus * list is iterated through until a connected target is found and focus applied. * * @param {object} options - Options * * @param {KeyboardEvent|MouseEvent} [options.event] - The source DOM event. * * @param {boolean} [options.debug] - When true {@link A11yHelper.applyFocusSource} logs focus target data. * * @param {HTMLElement|string} [options.focusEl] - A specific HTMLElement or selector string as the focus target. * * @param {number} [options.x] - Used when an event isn't provided; integer of event source in screen space. * * @param {number} [options.y] - Used when an event isn't provided; integer of event source in screen space. * * @returns {A11yFocusSource} A A11yFocusSource object. * * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1426671 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=314314 * * TODO: Evaluate / test against touch input devices. */ static getFocusSource({ event, x, y, focusEl, debug: debug2 = false }) { if (focusEl !== void 0 && !(focusEl instanceof HTMLElement) && typeof focusEl !== "string") { throw new TypeError( `A11yHelper.getFocusSource error: 'focusEl' is not a HTMLElement or string.` ); } if (debug2 !== void 0 && typeof debug2 !== "boolean") { throw new TypeError(`A11yHelper.getFocusSource error: 'debug' is not a boolean.`); } if (event === void 0) { if (typeof x !== "number") { throw new TypeError(`A11yHelper.getFocusSource error: 'event' not defined and 'x' is not a number.`); } if (typeof y !== "number") { throw new TypeError(`A11yHelper.getFocusSource error: 'event' not defined and 'y' is not a number.`); } return { debug: debug2, focusEl: focusEl !== void 0 ? [focusEl] : void 0, x, y }; } if (!(event instanceof KeyboardEvent) && !(event instanceof MouseEvent)) { throw new TypeError(`A11yHelper.getFocusSource error: 'event' is not a KeyboardEvent or MouseEvent.`); } if (x !== void 0 && !Number.isInteger(x)) { throw new TypeError(`A11yHelper.getFocusSource error: 'x' is not a number.`); } if (y !== void 0 && !Number.isInteger(y)) { throw new TypeError(`A11yHelper.getFocusSource error: 'y' is not a number.`); } const targetEl = event.target; if (!(targetEl instanceof HTMLElement)) { throw new TypeError(`A11yHelper.getFocusSource error: 'event.target' is not an HTMLElement.`); } const result = { debug: debug2 }; if (event instanceof MouseEvent) { if (event?.button !== 2 && event.type === "contextmenu") { const rect = targetEl.getBoundingClientRect(); result.x = x ?? rect.left + rect.width / 2; result.y = y ?? rect.top + rect.height / 2; result.focusEl = focusEl !== void 0 ? [targetEl, focusEl] : [targetEl]; result.source = "keyboard"; } else { result.x = x ?? event.pageX; result.y = y ?? event.pageY; result.focusEl = focusEl !== void 0 ? [focusEl] : void 0; } } else { const rect = targetEl.getBoundingClientRect(); result.x = x ?? rect.left + rect.width / 2; result.y = y ?? rect.top + rect.height / 2; result.focusEl = focusEl !== void 0 ? [targetEl, focusEl] : [targetEl]; result.source = "keyboard"; } return result; } /** * Returns first focusable element within a specified element. * * @param {HTMLElement|Document} [element=document] - Optional element to start query. * * @param {object} [options] - Optional parameters. * * @param {Iterable} [options.ignoreClasses] - Iterable list of classes to ignore elements. * * @param {Set} [options.ignoreElements] - Set of elements to ignore. * * @returns {HTMLElement} First focusable child element */ static getLastFocusableElement(element2 = document, options) { const focusableElements = this.getFocusableElements(element2, options); return focusableElements.length > 0 ? focusableElements[focusableElements.length - 1] : void 0; } /** * Tests if the given element is focusable. * * @param {HTMLElement} [el] - Element to test. * * @param {object} [options] - Optional parameters. * * @param {boolean} [options.anchorHref=true] - When true anchors must have an HREF. * * @param {Iterable} [options.ignoreClasses] - Iterable list of classes to ignore elements. * * @returns {boolean} Element is focusable. */ static isFocusable(el, { anchorHref = true, ignoreClasses } = {}) { if (el === void 0 || el === null || !(el instanceof HTMLElement) || el?.hidden || !el?.isConnected) { return false; } if (typeof anchorHref !== "boolean") { throw new TypeError(`'anchorHref' is not a boolean.`); } if (ignoreClasses !== void 0 && !isIterable(ignoreClasses)) { throw new TypeError(`'ignoreClasses' is not an iterable list.`); } const contenteditableAttr = el.getAttribute("contenteditable"); const contenteditableFocusable = typeof contenteditableAttr === "string" && (contenteditableAttr === "" || contenteditableAttr === "true"); const tabindexAttr = el.getAttribute("tabindex"); const tabindexFocusable = typeof tabindexAttr === "string" && tabindexAttr !== "-1"; const isAnchor = el instanceof HTMLAnchorElement; if (contenteditableFocusable || tabindexFocusable || isAnchor || el instanceof HTMLButtonElement || el instanceof HTMLDetailsElement || el instanceof HTMLEmbedElement || el instanceof HTMLIFrameElement || el instanceof HTMLInputElement || el instanceof HTMLObjectElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) { if (isAnchor && anchorHref && typeof el.getAttribute("href") !== "string") { return false; } return el.style.display !== "none" && el.style.visibility !== "hidden" && !el.hasAttribute("disabled") && !el.hasAttribute("inert") && el.getAttribute("aria-hidden") !== "true"; } return false; } /** * Convenience method to check if the given data is a valid focus source. * * @param {HTMLElement|string} data - Either an HTMLElement or selector string. * * @returns {boolean} Is valid focus source. */ static isFocusSource(data) { return data instanceof HTMLElement || typeof data === "string"; } } class StyleManager { /** @type {CSSStyleRule} */ #cssRule; /** @type {string} */ #docKey; /** @type {string} */ #selector; /** @type {HTMLStyleElement} */ #styleElement; /** @type {number} */ #version; /** * * @param {object} opts - Options. * * @param {string} opts.docKey - Required key providing a link to a specific style sheet element. * * @param {string} [opts.selector=:root] - Selector element. * * @param {Document} [opts.document] - Target document to load styles into. * * @param {number} [opts.version] - An integer representing the version / level of styles being managed. * */ constructor({ docKey, selector = ":root", document: document2 = globalThis.document, version } = {}) { if (typeof docKey !== "string") { throw new TypeError(`StyleManager error: 'docKey' is not a string.`); } if (typeof selector !== "string") { throw new TypeError(`StyleManager error: 'selector' is not a string.`); } if (version !== void 0 && !Number.isSafeInteger(version) && version < 1) { throw new TypeError(`StyleManager error: 'version' is defined and is not a positive integer >= 1.`); } this.#selector = selector; this.#docKey = docKey; this.#version = version; if (document2[this.#docKey] === void 0) { this.#styleElement = document2.createElement("style"); document2.head.append(this.#styleElement); this.#styleElement._STYLE_MANAGER_VERSION = version; this.#styleElement.sheet.insertRule(`${selector} {}`, 0); this.#cssRule = this.#styleElement.sheet.cssRules[0]; document2[docKey] = this.#styleElement; } else { this.#styleElement = document2[docKey]; this.#cssRule = this.#styleElement.sheet.cssRules[0]; if (version) { const existingVersion = this.#styleElement._STYLE_MANAGER_VERSION ?? 0; if (version > existingVersion) { this.#cssRule.style.cssText = ""; } } } } /** * @returns {string} Provides an accessor to get the `cssText` for the style sheet. */ get cssText() { return this.#cssRule.style.cssText; } /** * @returns {number} Returns the version of this instance. */ get version() { return this.#version; } /** * Provides a copy constructor to duplicate an existing StyleManager instance into a new document. * * Note: This is used to support the `PopOut` module. * * @param {Document} [document] Target browser document to clone into. * * @returns {StyleManager} New style manager instance. */ clone(document2 = globalThis.document) { const newStyleManager = new StyleManager({ selector: this.#selector, docKey: this.#docKey, document: document2, version: this.#version }); newStyleManager.#cssRule.style.cssText = this.#cssRule.style.cssText; return newStyleManager; } get() { const cssText = this.#cssRule.style.cssText; const result = {}; if (cssText !== "") { for (const entry of cssText.split(";")) { if (entry !== "") { const values = entry.split(":"); result[values[0].trim()] = values[1]; } } } return result; } /** * Gets a particular CSS variable. * * @param {string} key - CSS variable property key. * * @returns {string} Returns CSS variable value. */ getProperty(key) { if (typeof key !== "string") { throw new TypeError(`StyleManager error: 'key' is not a string.`); } return this.#cssRule.style.getPropertyValue(key); } /** * Set rules by property / value; useful for CSS variables. * * @param {Object} rules - An object with property / value string pairs to load. * * @param {boolean} [overwrite=true] - When true overwrites any existing values. */ setProperties(rules, overwrite = true) { if (!isObject(rules)) { throw new TypeError(`StyleManager error: 'rules' is not an object.`); } if (typeof overwrite !== "boolean") { throw new TypeError(`StyleManager error: 'overwrite' is not a boolean.`); } if (overwrite) { for (const [key, value] of Object.entries(rules)) { this.#cssRule.style.setProperty(key, value); } } else { for (const [key, value] of Object.entries(rules)) { if (this.#cssRule.style.getPropertyValue(key) === "") { this.#cssRule.style.setProperty(key, value); } } } } /** * Sets a particular property. * * @param {string} key - CSS variable property key. * * @param {string} value - CSS variable value. * * @param {boolean} [overwrite=true] - Overwrite any existing value. */ setProperty(key, value, overwrite = true) { if (typeof key !== "string") { throw new TypeError(`StyleManager error: 'key' is not a string.`); } if (typeof value !== "string") { throw new TypeError(`StyleManager error: 'value' is not a string.`); } if (typeof overwrite !== "boolean") { throw new TypeError(`StyleManager error: 'overwrite' is not a boolean.`); } if (overwrite) { this.#cssRule.style.setProperty(key, value); } else { if (this.#cssRule.style.getPropertyValue(key) === "") { this.#cssRule.style.setProperty(key, value); } } } /** * Removes the property keys specified. If `keys` is an iterable list then all property keys in the list are removed. * * @param {Iterable} keys - The property keys to remove. */ removeProperties(keys) { if (!isIterable(keys)) { throw new TypeError(`StyleManager error: 'keys' is not an iterable list.`); } for (const key of keys) { if (typeof key === "string") { this.#cssRule.style.removeProperty(key); } } } /** * Removes a particular CSS variable. * * @param {string} key - CSS variable property key. * * @returns {string} CSS variable value when removed. */ removeProperty(key) { if (typeof key !== "string") { throw new TypeError(`StyleManager error: 'key' is not a string.`); } return this.#cssRule.style.removeProperty(key); } } const s_REGEX = /(\d+)\s*px/; function styleParsePixels(value) { if (typeof value !== "string") { return void 0; } const isPixels = s_REGEX.test(value); const number = parseInt(value); return isPixels && Number.isFinite(number) ? number : void 0; } const applicationShellContract = ["elementRoot"]; Object.freeze(applicationShellContract); function isApplicationShell(component) { if (component === null || component === void 0) { return false; } let compHasContract = true; let protoHasContract = true; for (const accessor of applicationShellContract) { const descriptor = Object.getOwnPropertyDescriptor(component, accessor); if (descriptor === void 0 || descriptor.get === void 0 || descriptor.set === void 0) { compHasContract = false; } } const prototype = Object.getPrototypeOf(component); for (const accessor of applicationShellContract) { const descriptor = Object.getOwnPropertyDescriptor(prototype, accessor); if (descriptor === void 0 || descriptor.get === void 0 || descriptor.set === void 0) { protoHasContract = false; } } return compHasContract || protoHasContract; } function isHMRProxy(comp) { const instanceName = comp?.constructor?.name; if (typeof instanceName === "string" && (instanceName.startsWith("Proxy<") || instanceName === "ProxyComponent")) { return true; } const prototypeName = comp?.prototype?.constructor?.name; return typeof prototypeName === "string" && (prototypeName.startsWith("Proxy<") || prototypeName === "ProxyComponent"); } function isSvelteComponent(comp) { if (comp === null || comp === void 0 || typeof comp !== "function") { return false; } const prototypeName = comp?.prototype?.constructor?.name; if (typeof prototypeName === "string" && (prototypeName.startsWith("Proxy<") || prototypeName === "ProxyComponent")) { return true; } return typeof window !== void 0 ? typeof comp.prototype.$destroy === "function" && typeof comp.prototype.$on === "function" : ( // client-side typeof comp.render === "function" ); } async function outroAndDestroy(instance2) { return new Promise((resolve) => { if (instance2.$$.fragment && instance2.$$.fragment.o) { group_outros(); transition_out(instance2.$$.fragment, 0, 0, () => { instance2.$destroy(); resolve(); }); check_outros(); } else { instance2.$destroy(); resolve(); } }); } function parseSvelteConfig(config, thisArg = void 0) { if (typeof config !== "object") { throw new TypeError(`parseSvelteConfig - 'config' is not an object: ${JSON.stringify(config)}.`); } if (!isSvelteComponent(config.class)) { throw new TypeError( `parseSvelteConfig - 'class' is not a Svelte component constructor for config: ${JSON.stringify(config)}.` ); } if (config.hydrate !== void 0 && typeof config.hydrate !== "boolean") { throw new TypeError( `parseSvelteConfig - 'hydrate' is not a boolean for config: ${JSON.stringify(config)}.` ); } if (config.intro !== void 0 && typeof config.intro !== "boolean") { throw new TypeError( `parseSvelteConfig - 'intro' is not a boolean for config: ${JSON.stringify(config)}.` ); } if (config.target !== void 0 && typeof config.target !== "string" && !(config.target instanceof HTMLElement) && !(config.target instanceof ShadowRoot) && !(config.target instanceof DocumentFragment)) { throw new TypeError( `parseSvelteConfig - 'target' is not a string, HTMLElement, ShadowRoot, or DocumentFragment for config: ${JSON.stringify(config)}.` ); } if (config.anchor !== void 0 && typeof config.anchor !== "string" && !(config.anchor instanceof HTMLElement) && !(config.anchor instanceof ShadowRoot) && !(config.anchor instanceof DocumentFragment)) { throw new TypeError( `parseSvelteConfig - 'anchor' is not a string, HTMLElement, ShadowRoot, or DocumentFragment for config: ${JSON.stringify(config)}.` ); } if (config.context !== void 0 && typeof config.context !== "function" && !(config.context instanceof Map) && typeof config.context !== "object") { throw new TypeError( `parseSvelteConfig - 'context' is not a Map, function or object for config: ${JSON.stringify(config)}.` ); } if (config.selectorTarget !== void 0 && typeof config.selectorTarget !== "string") { throw new TypeError( `parseSvelteConfig - 'selectorTarget' is not a string for config: ${JSON.stringify(config)}.` ); } if (config.options !== void 0 && typeof config.options !== "object") { throw new TypeError( `parseSvelteConfig - 'options' is not an object for config: ${JSON.stringify(config)}.` ); } if (config.options !== void 0) { if (config.options.injectApp !== void 0 && typeof config.options.injectApp !== "boolean") { throw new TypeError( `parseSvelteConfig - 'options.injectApp' is not a boolean for config: ${JSON.stringify(config)}.` ); } if (config.options.injectEventbus !== void 0 && typeof config.options.injectEventbus !== "boolean") { throw new TypeError( `parseSvelteConfig - 'options.injectEventbus' is not a boolean for config: ${JSON.stringify(config)}.` ); } if (config.options.selectorElement !== void 0 && typeof config.options.selectorElement !== "string") { throw new TypeError( `parseSvelteConfig - 'selectorElement' is not a string for config: ${JSON.stringify(config)}.` ); } } const svelteConfig = { ...config }; delete svelteConfig.options; let externalContext = {}; if (typeof svelteConfig.context === "function") { const contextFunc = svelteConfig.context; delete svelteConfig.context; const result = contextFunc.call(thisArg); if (isObject(result)) { externalContext = { ...result }; } else { throw new Error(`parseSvelteConfig - 'context' is a function that did not return an object for config: ${JSON.stringify(config)}`); } } else if (svelteConfig.context instanceof Map) { externalContext = Object.fromEntries(svelteConfig.context); delete svelteConfig.context; } else if (isObject(svelteConfig.context)) { externalContext = svelteConfig.context; delete svelteConfig.context; } svelteConfig.props = s_PROCESS_PROPS(svelteConfig.props, thisArg, config); if (Array.isArray(svelteConfig.children)) { const children2 = []; for (let cntr = 0; cntr < svelteConfig.children.length; cntr++) { const child = svelteConfig.children[cntr]; if (!isSvelteComponent(child.class)) { throw new Error(`parseSvelteConfig - 'class' is not a Svelte component for child[${cntr}] for config: ${JSON.stringify(config)}`); } child.props = s_PROCESS_PROPS(child.props, thisArg, config); children2.push(child); } if (children2.length > 0) { externalContext.children = children2; } delete svelteConfig.children; } else if (isObject(svelteConfig.children)) { if (!isSvelteComponent(svelteConfig.children.class)) { throw new Error(`parseSvelteConfig - 'class' is not a Svelte component for children object for config: ${JSON.stringify(config)}`); } svelteConfig.children.props = s_PROCESS_PROPS(svelteConfig.children.props, thisArg, config); externalContext.children = [svelteConfig.children]; delete svelteConfig.children; } if (!(svelteConfig.context instanceof Map)) { svelteConfig.context = /* @__PURE__ */ new Map(); } svelteConfig.context.set("#external", externalContext); return svelteConfig; } function s_PROCESS_PROPS(props, thisArg, config) { if (typeof props === "function") { const result = props.call(thisArg); if (isObject(result)) { return result; } else { throw new Error(`parseSvelteConfig - 'props' is a function that did not return an object for config: ${JSON.stringify(config)}`); } } else if (isObject(props)) { return props; } else if (props !== void 0) { throw new Error( `parseSvelteConfig - 'props' is not a function or an object for config: ${JSON.stringify(config)}` ); } return {}; } function hasGetter(object, accessor) { if (object === null || object === void 0) { return false; } const iDescriptor = Object.getOwnPropertyDescriptor(object, accessor); if (iDescriptor !== void 0 && iDescriptor.get !== void 0) { return true; } for (let o = Object.getPrototypeOf(object); o; o = Object.getPrototypeOf(o)) { const descriptor = Object.getOwnPropertyDescriptor(o, accessor); if (descriptor !== void 0 && descriptor.get !== void 0) { return true; } } return false; } const subscriber_queue = []; function readable(value, start) { return { subscribe: writable$1(value, start).subscribe }; } function writable$1(value, start = noop) { let stop; const subscribers = /* @__PURE__ */ new Set(); function set(new_value) { if (safe_not_equal(value, new_value)) { value = new_value; if (stop) { const run_queue = !subscriber_queue.length; for (const subscriber of subscribers) { subscriber[1](); subscriber_queue.push(subscriber, value); } if (run_queue) { for (let i = 0; i < subscriber_queue.length; i += 2) { subscriber_queue[i][0](subscriber_queue[i + 1]); } subscriber_queue.length = 0; } } } } function update2(fn) { set(fn(value)); } function subscribe2(run2, invalidate = noop) { const subscriber = [run2, invalidate]; subscribers.add(subscriber); if (subscribers.size === 1) { stop = start(set) || noop; } run2(value); return () => { subscribers.delete(subscriber); if (subscribers.size === 0) { stop(); stop = null; } }; } return { set, update: update2, subscribe: subscribe2 }; } function derived(stores, fn, initial_value) { const single = !Array.isArray(stores); const stores_array = single ? [stores] : stores; const auto = fn.length < 2; return readable(initial_value, (set) => { let inited = false; const values = []; let pending = 0; let cleanup = noop; const sync = () => { if (pending) { return; } cleanup(); const result = fn(single ? values[0] : values, set); if (auto) { set(result); } else { cleanup = is_function(result) ? result : noop; } }; const unsubscribers = stores_array.map((store, i) => subscribe(store, (value) => { values[i] = value; pending &= ~(1 << i); if (inited) { sync(); } }, () => { pending |= 1 << i; })); inited = true; sync(); return function stop() { run_all(unsubscribers); cleanup(); }; }); } function isSimpleDeriver(deriver) { return deriver.length < 2; } function generator(storage2) { function readable2(key, value, start) { return { subscribe: writable2(key, value, start).subscribe }; } function writable2(key, value, start = noop) { function wrap_start(ogSet) { return start(function wrap_set(new_value) { if (storage2) { storage2.setItem(key, JSON.stringify(new_value)); } return ogSet(new_value); }); } if (storage2) { const storageValue = storage2.getItem(key); try { if (storageValue) { value = JSON.parse(storageValue); } } catch (err) { } storage2.setItem(key, JSON.stringify(value)); } const ogStore = writable$1(value, start ? wrap_start : void 0); function set(new_value) { if (storage2) { storage2.setItem(key, JSON.stringify(new_value)); } ogStore.set(new_value); } function update2(fn) { set(fn(get_store_value(ogStore))); } function subscribe2(run2, invalidate = noop) { return ogStore.subscribe(run2, invalidate); } return { set, update: update2, subscribe: subscribe2 }; } function derived2(key, stores, fn, initial_value) { const single = !Array.isArray(stores); const stores_array = single ? [stores] : stores; if (storage2 && storage2.getItem(key)) { try { initial_value = JSON.parse(storage2.getItem(key)); } catch (err) { } } return readable2(key, initial_value, (set) => { let inited = false; const values = []; let pending = 0; let cleanup = noop; const sync = () => { if (pending) { return; } cleanup(); const input = single ? values[0] : values; if (isSimpleDeriver(fn)) { set(fn(input)); } else { const result = fn(input, set); cleanup = is_function(result) ? result : noop; } }; const unsubscribers = stores_array.map((store, i) => store.subscribe((value) => { values[i] = value; pending &= ~(1 << i); if (inited) { sync(); } }, () => { pending |= 1 << i; })); inited = true; sync(); return function stop() { run_all(unsubscribers); cleanup(); }; }); } return { readable: readable2, writable: writable2, derived: derived2, get: get_store_value }; } var storage = typeof window !== "undefined" ? window.sessionStorage : void 0; var g = generator(storage); var writable = g.writable; class TJSSessionStorage { /** * @type {Map} */ #stores = /* @__PURE__ */ new Map(); /** * Creates a new store for the given key. * * @param {string} key - Key to lookup in stores map. * * @param {boolean} [defaultValue] - A default value to set for the store. * * @returns {import('svelte/store').Writable} The new store. */ static #createStore(key, defaultValue = void 0) { try { const value = sessionStorage.getItem(key); if (value !== null) { defaultValue = value === "undefined" ? void 0 : JSON.parse(value); } } catch (err) { } return writable(key, defaultValue); } /** * Gets a store from the `stores` Map or creates a new store for the key and a given default value. * * @param {string} key - Key to lookup in stores map. * * @param {boolean} [defaultValue] - A default value to set for the store. * * @returns {import('svelte/store').Writable} The store for the given key. */ #getStore(key, defaultValue = void 0) { let store = this.#stores.get(key); if (store === void 0) { store = TJSSessionStorage.#createStore(key, defaultValue); this.#stores.set(key, store); } return store; } /** * Get value from the sessionStorage. * * @param {string} key - Key to lookup in sessionStorage. * * @param {*} [defaultValue] - A default value to return if key not present in session storage. * * @returns {*} Value from session storage or if not defined any default value provided. */ getItem(key, defaultValue) { let value = defaultValue; const storageValue = sessionStorage.getItem(key); if (storageValue !== null) { try { value = storageValue === "undefined" ? void 0 : JSON.parse(storageValue); } catch (err) { value = defaultValue; } } else if (defaultValue !== void 0) { try { const newValue = JSON.stringify(defaultValue); sessionStorage.setItem(key, newValue === "undefined" ? void 0 : newValue); } catch (err) { } } return value; } /** * Returns the backing Svelte store for the given key; potentially sets a default value if the key * is not already set. * * @param {string} key - Key to lookup in sessionStorage. * * @param {*} [defaultValue] - A default value to return if key not present in session storage. * * @returns {import('svelte/store').Writable} The Svelte store for this key. */ getStore(key, defaultValue) { return this.#getStore(key, defaultValue); } /** * Sets the value for the given key in sessionStorage. * * @param {string} key - Key to lookup in sessionStorage. * * @param {*} value - A value to set for this key. */ setItem(key, value) { const store = this.#getStore(key); store.set(value); } /** * Convenience method to swap a boolean value stored in session storage. * * @param {string} key - Key to lookup in sessionStorage. * * @param {boolean} [defaultValue] - A default value to return if key not present in session storage. * * @returns {boolean} The boolean swap for the given key. */ swapItemBoolean(key, defaultValue) { const store = this.#getStore(key, defaultValue); let currentValue = false; try { currentValue = !!JSON.parse(sessionStorage.getItem(key)); } catch (err) { } const newValue = typeof currentValue === "boolean" ? !currentValue : false; store.set(newValue); return newValue; } } function isUpdatableStore(store) { if (store === null || store === void 0) { return false; } switch (typeof store) { case "function": case "object": return typeof store.subscribe === "function" && typeof store.update === "function"; } return false; } function subscribeIgnoreFirst(store, update2) { let firedFirst = false; return store.subscribe((value) => { if (!firedFirst) { firedFirst = true; } else { update2(value); } }); } function writableDerived(origins, derive, reflect, initial) { var childDerivedSetter, originValues, blockNextDerive = false; var reflectOldValues = reflect.length >= 2; var wrappedDerive = (got, set) => { childDerivedSetter = set; if (reflectOldValues) { originValues = got; } if (!blockNextDerive) { let returned = derive(got, set); if (derive.length < 2) { set(returned); } else { return returned; } } blockNextDerive = false; }; var childDerived = derived(origins, wrappedDerive, initial); var singleOrigin = !Array.isArray(origins); function doReflect(reflecting) { var setWith = reflect(reflecting, originValues); if (singleOrigin) { blockNextDerive = true; origins.set(setWith); } else { setWith.forEach((value, i) => { blockNextDerive = true; origins[i].set(value); }); } blockNextDerive = false; } var tryingSet = false; function update2(fn) { var isUpdated, mutatedBySubscriptions, oldValue, newValue; if (tryingSet) { newValue = fn(get_store_value(childDerived)); childDerivedSetter(newValue); return; } var unsubscribe = childDerived.subscribe((value) => { if (!tryingSet) { oldValue = value; } else if (!isUpdated) { isUpdated = true; } else { mutatedBySubscriptions = true; } }); newValue = fn(oldValue); tryingSet = true; childDerivedSetter(newValue); unsubscribe(); tryingSet = false; if (mutatedBySubscriptions) { newValue = get_store_value(childDerived); } if (isUpdated) { doReflect(newValue); } } return { subscribe: childDerived.subscribe, set(value) { update2(() => value); }, update: update2 }; } function propertyStore(origin, propName) { if (!Array.isArray(propName)) { return writableDerived( origin, (object) => object[propName], (reflecting, object) => { object[propName] = reflecting; return object; } ); } else { let props = propName.concat(); return writableDerived( origin, (value) => { for (let i = 0; i < props.length; ++i) { value = value[props[i]]; } return value; }, (reflecting, object) => { let target = object; for (let i = 0; i < props.length - 1; ++i) { target = target[props[i]]; } target[props[props.length - 1]] = reflecting; return object; } ); } } const storeState = writable$1(void 0); ({ subscribe: storeState.subscribe, get: () => game }); Hooks.once("ready", () => storeState.set(game)); function cubicOut(t) { const f = t - 1; return f * f * f + 1; } function lerp$5(start, end, amount) { return (1 - amount) * start + amount * end; } function degToRad(deg) { return deg * (Math.PI / 180); } var EPSILON = 1e-6; var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array; var RANDOM = Math.random; if (!Math.hypot) Math.hypot = function() { var y = 0, i = arguments.length; while (i--) { y += arguments[i] * arguments[i]; } return Math.sqrt(y); }; function create$6() { var out = new ARRAY_TYPE(9); if (ARRAY_TYPE != Float32Array) { out[1] = 0; out[2] = 0; out[3] = 0; out[5] = 0; out[6] = 0; out[7] = 0; } out[0] = 1; out[4] = 1; out[8] = 1; return out; } function create$5() { var out = new ARRAY_TYPE(16); if (ARRAY_TYPE != Float32Array) { out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; } out[0] = 1; out[5] = 1; out[10] = 1; out[15] = 1; return out; } function clone$5(a) { var out = new ARRAY_TYPE(16); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; } function copy$5(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; } function fromValues$5(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) { var out = new ARRAY_TYPE(16); out[0] = m00; out[1] = m01; out[2] = m02; out[3] = m03; out[4] = m10; out[5] = m11; out[6] = m12; out[7] = m13; out[8] = m20; out[9] = m21; out[10] = m22; out[11] = m23; out[12] = m30; out[13] = m31; out[14] = m32; out[15] = m33; return out; } function set$5(out, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) { out[0] = m00; out[1] = m01; out[2] = m02; out[3] = m03; out[4] = m10; out[5] = m11; out[6] = m12; out[7] = m13; out[8] = m20; out[9] = m21; out[10] = m22; out[11] = m23; out[12] = m30; out[13] = m31; out[14] = m32; out[15] = m33; return out; } function identity$2(out) { out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; } function transpose(out, a) { if (out === a) { var a01 = a[1], a02 = a[2], a03 = a[3]; var a12 = a[6], a13 = a[7]; var a23 = a[11]; out[1] = a[4]; out[2] = a[8]; out[3] = a[12]; out[4] = a01; out[6] = a[9]; out[7] = a[13]; out[8] = a02; out[9] = a12; out[11] = a[14]; out[12] = a03; out[13] = a13; out[14] = a23; } else { out[0] = a[0]; out[1] = a[4]; out[2] = a[8]; out[3] = a[12]; out[4] = a[1]; out[5] = a[5]; out[6] = a[9]; out[7] = a[13]; out[8] = a[2]; out[9] = a[6]; out[10] = a[10]; out[11] = a[14]; out[12] = a[3]; out[13] = a[7]; out[14] = a[11]; out[15] = a[15]; } return out; } function invert$2(out, a) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; var b00 = a00 * a11 - a01 * a10; var b01 = a00 * a12 - a02 * a10; var b02 = a00 * a13 - a03 * a10; var b03 = a01 * a12 - a02 * a11; var b04 = a01 * a13 - a03 * a11; var b05 = a02 * a13 - a03 * a12; var b06 = a20 * a31 - a21 * a30; var b07 = a20 * a32 - a22 * a30; var b08 = a20 * a33 - a23 * a30; var b09 = a21 * a32 - a22 * a31; var b10 = a21 * a33 - a23 * a31; var b11 = a22 * a33 - a23 * a32; var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; if (!det) { return null; } det = 1 / det; out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; return out; } function adjoint(out, a) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; out[0] = a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22); out[1] = -(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22)); out[2] = a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12); out[3] = -(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12)); out[4] = -(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22)); out[5] = a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22); out[6] = -(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12)); out[7] = a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12); out[8] = a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21); out[9] = -(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21)); out[10] = a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11); out[11] = -(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11)); out[12] = -(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21)); out[13] = a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21); out[14] = -(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11)); out[15] = a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11); return out; } function determinant(a) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; var b00 = a00 * a11 - a01 * a10; var b01 = a00 * a12 - a02 * a10; var b02 = a00 * a13 - a03 * a10; var b03 = a01 * a12 - a02 * a11; var b04 = a01 * a13 - a03 * a11; var b05 = a02 * a13 - a03 * a12; var b06 = a20 * a31 - a21 * a30; var b07 = a20 * a32 - a22 * a30; var b08 = a20 * a33 - a23 * a30; var b09 = a21 * a32 - a22 * a31; var b10 = a21 * a33 - a23 * a31; var b11 = a22 * a33 - a23 * a32; return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; } function multiply$5(out, a, b) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; return out; } function translate$1(out, a, v) { var x = v[0], y = v[1], z = v[2]; var a00, a01, a02, a03; var a10, a11, a12, a13; var a20, a21, a22, a23; if (a === out) { out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; } else { a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03; out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13; out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23; out[12] = a00 * x + a10 * y + a20 * z + a[12]; out[13] = a01 * x + a11 * y + a21 * z + a[13]; out[14] = a02 * x + a12 * y + a22 * z + a[14]; out[15] = a03 * x + a13 * y + a23 * z + a[15]; } return out; } function scale$5(out, a, v) { var x = v[0], y = v[1], z = v[2]; out[0] = a[0] * x; out[1] = a[1] * x; out[2] = a[2] * x; out[3] = a[3] * x; out[4] = a[4] * y; out[5] = a[5] * y; out[6] = a[6] * y; out[7] = a[7] * y; out[8] = a[8] * z; out[9] = a[9] * z; out[10] = a[10] * z; out[11] = a[11] * z; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; } function rotate$1(out, a, rad, axis) { var x = axis[0], y = axis[1], z = axis[2]; var len = Math.hypot(x, y, z); var s, c, t; var a00, a01, a02, a03; var a10, a11, a12, a13; var a20, a21, a22, a23; var b00, b01, b02; var b10, b11, b12; var b20, b21, b22; if (len < EPSILON) { return null; } len = 1 / len; x *= len; y *= len; z *= len; s = Math.sin(rad); c = Math.cos(rad); t = 1 - c; a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; b00 = x * x * t + c; b01 = y * x * t + z * s; b02 = z * x * t - y * s; b10 = x * y * t - z * s; b11 = y * y * t + c; b12 = z * y * t + x * s; b20 = x * z * t + y * s; b21 = y * z * t - x * s; b22 = z * z * t + c; out[0] = a00 * b00 + a10 * b01 + a20 * b02; out[1] = a01 * b00 + a11 * b01 + a21 * b02; out[2] = a02 * b00 + a12 * b01 + a22 * b02; out[3] = a03 * b00 + a13 * b01 + a23 * b02; out[4] = a00 * b10 + a10 * b11 + a20 * b12; out[5] = a01 * b10 + a11 * b11 + a21 * b12; out[6] = a02 * b10 + a12 * b11 + a22 * b12; out[7] = a03 * b10 + a13 * b11 + a23 * b12; out[8] = a00 * b20 + a10 * b21 + a20 * b22; out[9] = a01 * b20 + a11 * b21 + a21 * b22; out[10] = a02 * b20 + a12 * b21 + a22 * b22; out[11] = a03 * b20 + a13 * b21 + a23 * b22; if (a !== out) { out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } return out; } function rotateX$3(out, a, rad) { var s = Math.sin(rad); var c = Math.cos(rad); var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; if (a !== out) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[4] = a10 * c + a20 * s; out[5] = a11 * c + a21 * s; out[6] = a12 * c + a22 * s; out[7] = a13 * c + a23 * s; out[8] = a20 * c - a10 * s; out[9] = a21 * c - a11 * s; out[10] = a22 * c - a12 * s; out[11] = a23 * c - a13 * s; return out; } function rotateY$3(out, a, rad) { var s = Math.sin(rad); var c = Math.cos(rad); var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; if (a !== out) { out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[0] = a00 * c - a20 * s; out[1] = a01 * c - a21 * s; out[2] = a02 * c - a22 * s; out[3] = a03 * c - a23 * s; out[8] = a00 * s + a20 * c; out[9] = a01 * s + a21 * c; out[10] = a02 * s + a22 * c; out[11] = a03 * s + a23 * c; return out; } function rotateZ$3(out, a, rad) { var s = Math.sin(rad); var c = Math.cos(rad); var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; if (a !== out) { out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[0] = a00 * c + a10 * s; out[1] = a01 * c + a11 * s; out[2] = a02 * c + a12 * s; out[3] = a03 * c + a13 * s; out[4] = a10 * c - a00 * s; out[5] = a11 * c - a01 * s; out[6] = a12 * c - a02 * s; out[7] = a13 * c - a03 * s; return out; } function fromTranslation$1(out, v) { out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0; out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; out[15] = 1; return out; } function fromScaling(out, v) { out[0] = v[0]; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = v[1]; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = v[2]; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; } function fromRotation$1(out, rad, axis) { var x = axis[0], y = axis[1], z = axis[2]; var len = Math.hypot(x, y, z); var s, c, t; if (len < EPSILON) { return null; } len = 1 / len; x *= len; y *= len; z *= len; s = Math.sin(rad); c = Math.cos(rad); t = 1 - c; out[0] = x * x * t + c; out[1] = y * x * t + z * s; out[2] = z * x * t - y * s; out[3] = 0; out[4] = x * y * t - z * s; out[5] = y * y * t + c; out[6] = z * y * t + x * s; out[7] = 0; out[8] = x * z * t + y * s; out[9] = y * z * t - x * s; out[10] = z * z * t + c; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; } function fromXRotation(out, rad) { var s = Math.sin(rad); var c = Math.cos(rad); out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = c; out[6] = s; out[7] = 0; out[8] = 0; out[9] = -s; out[10] = c; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; } function fromYRotation(out, rad) { var s = Math.sin(rad); var c = Math.cos(rad); out[0] = c; out[1] = 0; out[2] = -s; out[3] = 0; out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0; out[8] = s; out[9] = 0; out[10] = c; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; } function fromZRotation(out, rad) { var s = Math.sin(rad); var c = Math.cos(rad); out[0] = c; out[1] = s; out[2] = 0; out[3] = 0; out[4] = -s; out[5] = c; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; } function fromRotationTranslation$1(out, q, v) { var x = q[0], y = q[1], z = q[2], w = q[3]; var x2 = x + x; var y2 = y + y; var z2 = z + z; var xx = x * x2; var xy = x * y2; var xz = x * z2; var yy = y * y2; var yz = y * z2; var zz = z * z2; var wx = w * x2; var wy = w * y2; var wz = w * z2; out[0] = 1 - (yy + zz); out[1] = xy + wz; out[2] = xz - wy; out[3] = 0; out[4] = xy - wz; out[5] = 1 - (xx + zz); out[6] = yz + wx; out[7] = 0; out[8] = xz + wy; out[9] = yz - wx; out[10] = 1 - (xx + yy); out[11] = 0; out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; out[15] = 1; return out; } function fromQuat2(out, a) { var translation = new ARRAY_TYPE(3); var bx = -a[0], by = -a[1], bz = -a[2], bw = a[3], ax = a[4], ay = a[5], az = a[6], aw = a[7]; var magnitude = bx * bx + by * by + bz * bz + bw * bw; if (magnitude > 0) { translation[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2 / magnitude; translation[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2 / magnitude; translation[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2 / magnitude; } else { translation[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2; translation[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2; translation[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2; } fromRotationTranslation$1(out, a, translation); return out; } function getTranslation$1(out, mat) { out[0] = mat[12]; out[1] = mat[13]; out[2] = mat[14]; return out; } function getScaling(out, mat) { var m11 = mat[0]; var m12 = mat[1]; var m13 = mat[2]; var m21 = mat[4]; var m22 = mat[5]; var m23 = mat[6]; var m31 = mat[8]; var m32 = mat[9]; var m33 = mat[10]; out[0] = Math.hypot(m11, m12, m13); out[1] = Math.hypot(m21, m22, m23); out[2] = Math.hypot(m31, m32, m33); return out; } function getRotation(out, mat) { var scaling = new ARRAY_TYPE(3); getScaling(scaling, mat); var is1 = 1 / scaling[0]; var is2 = 1 / scaling[1]; var is3 = 1 / scaling[2]; var sm11 = mat[0] * is1; var sm12 = mat[1] * is2; var sm13 = mat[2] * is3; var sm21 = mat[4] * is1; var sm22 = mat[5] * is2; var sm23 = mat[6] * is3; var sm31 = mat[8] * is1; var sm32 = mat[9] * is2; var sm33 = mat[10] * is3; var trace = sm11 + sm22 + sm33; var S = 0; if (trace > 0) { S = Math.sqrt(trace + 1) * 2; out[3] = 0.25 * S; out[0] = (sm23 - sm32) / S; out[1] = (sm31 - sm13) / S; out[2] = (sm12 - sm21) / S; } else if (sm11 > sm22 && sm11 > sm33) { S = Math.sqrt(1 + sm11 - sm22 - sm33) * 2; out[3] = (sm23 - sm32) / S; out[0] = 0.25 * S; out[1] = (sm12 + sm21) / S; out[2] = (sm31 + sm13) / S; } else if (sm22 > sm33) { S = Math.sqrt(1 + sm22 - sm11 - sm33) * 2; out[3] = (sm31 - sm13) / S; out[0] = (sm12 + sm21) / S; out[1] = 0.25 * S; out[2] = (sm23 + sm32) / S; } else { S = Math.sqrt(1 + sm33 - sm11 - sm22) * 2; out[3] = (sm12 - sm21) / S; out[0] = (sm31 + sm13) / S; out[1] = (sm23 + sm32) / S; out[2] = 0.25 * S; } return out; } function fromRotationTranslationScale(out, q, v, s) { var x = q[0], y = q[1], z = q[2], w = q[3]; var x2 = x + x; var y2 = y + y; var z2 = z + z; var xx = x * x2; var xy = x * y2; var xz = x * z2; var yy = y * y2; var yz = y * z2; var zz = z * z2; var wx = w * x2; var wy = w * y2; var wz = w * z2; var sx = s[0]; var sy = s[1]; var sz = s[2]; out[0] = (1 - (yy + zz)) * sx; out[1] = (xy + wz) * sx; out[2] = (xz - wy) * sx; out[3] = 0; out[4] = (xy - wz) * sy; out[5] = (1 - (xx + zz)) * sy; out[6] = (yz + wx) * sy; out[7] = 0; out[8] = (xz + wy) * sz; out[9] = (yz - wx) * sz; out[10] = (1 - (xx + yy)) * sz; out[11] = 0; out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; out[15] = 1; return out; } function fromRotationTranslationScaleOrigin(out, q, v, s, o) { var x = q[0], y = q[1], z = q[2], w = q[3]; var x2 = x + x; var y2 = y + y; var z2 = z + z; var xx = x * x2; var xy = x * y2; var xz = x * z2; var yy = y * y2; var yz = y * z2; var zz = z * z2; var wx = w * x2; var wy = w * y2; var wz = w * z2; var sx = s[0]; var sy = s[1]; var sz = s[2]; var ox = o[0]; var oy = o[1]; var oz = o[2]; var out0 = (1 - (yy + zz)) * sx; var out1 = (xy + wz) * sx; var out2 = (xz - wy) * sx; var out4 = (xy - wz) * sy; var out5 = (1 - (xx + zz)) * sy; var out6 = (yz + wx) * sy; var out8 = (xz + wy) * sz; var out9 = (yz - wx) * sz; var out10 = (1 - (xx + yy)) * sz; out[0] = out0; out[1] = out1; out[2] = out2; out[3] = 0; out[4] = out4; out[5] = out5; out[6] = out6; out[7] = 0; out[8] = out8; out[9] = out9; out[10] = out10; out[11] = 0; out[12] = v[0] + ox - (out0 * ox + out4 * oy + out8 * oz); out[13] = v[1] + oy - (out1 * ox + out5 * oy + out9 * oz); out[14] = v[2] + oz - (out2 * ox + out6 * oy + out10 * oz); out[15] = 1; return out; } function fromQuat(out, q) { var x = q[0], y = q[1], z = q[2], w = q[3]; var x2 = x + x; var y2 = y + y; var z2 = z + z; var xx = x * x2; var yx = y * x2; var yy = y * y2; var zx = z * x2; var zy = z * y2; var zz = z * z2; var wx = w * x2; var wy = w * y2; var wz = w * z2; out[0] = 1 - yy - zz; out[1] = yx + wz; out[2] = zx - wy; out[3] = 0; out[4] = yx - wz; out[5] = 1 - xx - zz; out[6] = zy + wx; out[7] = 0; out[8] = zx + wy; out[9] = zy - wx; out[10] = 1 - xx - yy; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; } function frustum(out, left, right, bottom, top, near, far) { var rl = 1 / (right - left); var tb = 1 / (top - bottom); var nf = 1 / (near - far); out[0] = near * 2 * rl; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = near * 2 * tb; out[6] = 0; out[7] = 0; out[8] = (right + left) * rl; out[9] = (top + bottom) * tb; out[10] = (far + near) * nf; out[11] = -1; out[12] = 0; out[13] = 0; out[14] = far * near * 2 * nf; out[15] = 0; return out; } function perspectiveNO(out, fovy, aspect, near, far) { var f = 1 / Math.tan(fovy / 2), nf; out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[11] = -1; out[12] = 0; out[13] = 0; out[15] = 0; if (far != null && far !== Infinity) { nf = 1 / (near - far); out[10] = (far + near) * nf; out[14] = 2 * far * near * nf; } else { out[10] = -1; out[14] = -2 * near; } return out; } var perspective = perspectiveNO; function perspectiveZO(out, fovy, aspect, near, far) { var f = 1 / Math.tan(fovy / 2), nf; out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[11] = -1; out[12] = 0; out[13] = 0; out[15] = 0; if (far != null && far !== Infinity) { nf = 1 / (near - far); out[10] = far * nf; out[14] = far * near * nf; } else { out[10] = -1; out[14] = -near; } return out; } function perspectiveFromFieldOfView(out, fov, near, far) { var upTan = Math.tan(fov.upDegrees * Math.PI / 180); var downTan = Math.tan(fov.downDegrees * Math.PI / 180); var leftTan = Math.tan(fov.leftDegrees * Math.PI / 180); var rightTan = Math.tan(fov.rightDegrees * Math.PI / 180); var xScale = 2 / (leftTan + rightTan); var yScale = 2 / (upTan + downTan); out[0] = xScale; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = yScale; out[6] = 0; out[7] = 0; out[8] = -((leftTan - rightTan) * xScale * 0.5); out[9] = (upTan - downTan) * yScale * 0.5; out[10] = far / (near - far); out[11] = -1; out[12] = 0; out[13] = 0; out[14] = far * near / (near - far); out[15] = 0; return out; } function orthoNO(out, left, right, bottom, top, near, far) { var lr = 1 / (left - right); var bt = 1 / (bottom - top); var nf = 1 / (near - far); out[0] = -2 * lr; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = -2 * bt; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 2 * nf; out[11] = 0; out[12] = (left + right) * lr; out[13] = (top + bottom) * bt; out[14] = (far + near) * nf; out[15] = 1; return out; } var ortho = orthoNO; function orthoZO(out, left, right, bottom, top, near, far) { var lr = 1 / (left - right); var bt = 1 / (bottom - top); var nf = 1 / (near - far); out[0] = -2 * lr; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = -2 * bt; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = nf; out[11] = 0; out[12] = (left + right) * lr; out[13] = (top + bottom) * bt; out[14] = near * nf; out[15] = 1; return out; } function lookAt(out, eye, center, up) { var x0, x1, x2, y0, y1, y2, z0, z1, z2, len; var eyex = eye[0]; var eyey = eye[1]; var eyez = eye[2]; var upx = up[0]; var upy = up[1]; var upz = up[2]; var centerx = center[0]; var centery = center[1]; var centerz = center[2]; if (Math.abs(eyex - centerx) < EPSILON && Math.abs(eyey - centery) < EPSILON && Math.abs(eyez - centerz) < EPSILON) { return identity$2(out); } z0 = eyex - centerx; z1 = eyey - centery; z2 = eyez - centerz; len = 1 / Math.hypot(z0, z1, z2); z0 *= len; z1 *= len; z2 *= len; x0 = upy * z2 - upz * z1; x1 = upz * z0 - upx * z2; x2 = upx * z1 - upy * z0; len = Math.hypot(x0, x1, x2); if (!len) { x0 = 0; x1 = 0; x2 = 0; } else { len = 1 / len; x0 *= len; x1 *= len; x2 *= len; } y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0; len = Math.hypot(y0, y1, y2); if (!len) { y0 = 0; y1 = 0; y2 = 0; } else { len = 1 / len; y0 *= len; y1 *= len; y2 *= len; } out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0; out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0; out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0; out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez); out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez); out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez); out[15] = 1; return out; } function targetTo(out, eye, target, up) { var eyex = eye[0], eyey = eye[1], eyez = eye[2], upx = up[0], upy = up[1], upz = up[2]; var z0 = eyex - target[0], z1 = eyey - target[1], z2 = eyez - target[2]; var len = z0 * z0 + z1 * z1 + z2 * z2; if (len > 0) { len = 1 / Math.sqrt(len); z0 *= len; z1 *= len; z2 *= len; } var x0 = upy * z2 - upz * z1, x1 = upz * z0 - upx * z2, x2 = upx * z1 - upy * z0; len = x0 * x0 + x1 * x1 + x2 * x2; if (len > 0) { len = 1 / Math.sqrt(len); x0 *= len; x1 *= len; x2 *= len; } out[0] = x0; out[1] = x1; out[2] = x2; out[3] = 0; out[4] = z1 * x2 - z2 * x1; out[5] = z2 * x0 - z0 * x2; out[6] = z0 * x1 - z1 * x0; out[7] = 0; out[8] = z0; out[9] = z1; out[10] = z2; out[11] = 0; out[12] = eyex; out[13] = eyey; out[14] = eyez; out[15] = 1; return out; } function str$5(a) { return "mat4(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ", " + a[8] + ", " + a[9] + ", " + a[10] + ", " + a[11] + ", " + a[12] + ", " + a[13] + ", " + a[14] + ", " + a[15] + ")"; } function frob(a) { return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); } function add$5(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; out[2] = a[2] + b[2]; out[3] = a[3] + b[3]; out[4] = a[4] + b[4]; out[5] = a[5] + b[5]; out[6] = a[6] + b[6]; out[7] = a[7] + b[7]; out[8] = a[8] + b[8]; out[9] = a[9] + b[9]; out[10] = a[10] + b[10]; out[11] = a[11] + b[11]; out[12] = a[12] + b[12]; out[13] = a[13] + b[13]; out[14] = a[14] + b[14]; out[15] = a[15] + b[15]; return out; } function subtract$3(out, a, b) { out[0] = a[0] - b[0]; out[1] = a[1] - b[1]; out[2] = a[2] - b[2]; out[3] = a[3] - b[3]; out[4] = a[4] - b[4]; out[5] = a[5] - b[5]; out[6] = a[6] - b[6]; out[7] = a[7] - b[7]; out[8] = a[8] - b[8]; out[9] = a[9] - b[9]; out[10] = a[10] - b[10]; out[11] = a[11] - b[11]; out[12] = a[12] - b[12]; out[13] = a[13] - b[13]; out[14] = a[14] - b[14]; out[15] = a[15] - b[15]; return out; } function multiplyScalar(out, a, b) { out[0] = a[0] * b; out[1] = a[1] * b; out[2] = a[2] * b; out[3] = a[3] * b; out[4] = a[4] * b; out[5] = a[5] * b; out[6] = a[6] * b; out[7] = a[7] * b; out[8] = a[8] * b; out[9] = a[9] * b; out[10] = a[10] * b; out[11] = a[11] * b; out[12] = a[12] * b; out[13] = a[13] * b; out[14] = a[14] * b; out[15] = a[15] * b; return out; } function multiplyScalarAndAdd(out, a, b, scale2) { out[0] = a[0] + b[0] * scale2; out[1] = a[1] + b[1] * scale2; out[2] = a[2] + b[2] * scale2; out[3] = a[3] + b[3] * scale2; out[4] = a[4] + b[4] * scale2; out[5] = a[5] + b[5] * scale2; out[6] = a[6] + b[6] * scale2; out[7] = a[7] + b[7] * scale2; out[8] = a[8] + b[8] * scale2; out[9] = a[9] + b[9] * scale2; out[10] = a[10] + b[10] * scale2; out[11] = a[11] + b[11] * scale2; out[12] = a[12] + b[12] * scale2; out[13] = a[13] + b[13] * scale2; out[14] = a[14] + b[14] * scale2; out[15] = a[15] + b[15] * scale2; return out; } function exactEquals$5(a, b) { return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8] && a[9] === b[9] && a[10] === b[10] && a[11] === b[11] && a[12] === b[12] && a[13] === b[13] && a[14] === b[14] && a[15] === b[15]; } function equals$5(a, b) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; var a4 = a[4], a5 = a[5], a6 = a[6], a7 = a[7]; var a8 = a[8], a9 = a[9], a10 = a[10], a11 = a[11]; var a12 = a[12], a13 = a[13], a14 = a[14], a15 = a[15]; var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; var b4 = b[4], b5 = b[5], b6 = b[6], b7 = b[7]; var b8 = b[8], b9 = b[9], b10 = b[10], b11 = b[11]; var b12 = b[12], b13 = b[13], b14 = b[14], b15 = b[15]; return Math.abs(a0 - b0) <= EPSILON * Math.max(1, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= EPSILON * Math.max(1, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= EPSILON * Math.max(1, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= EPSILON * Math.max(1, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= EPSILON * Math.max(1, Math.abs(a7), Math.abs(b7)) && Math.abs(a8 - b8) <= EPSILON * Math.max(1, Math.abs(a8), Math.abs(b8)) && Math.abs(a9 - b9) <= EPSILON * Math.max(1, Math.abs(a9), Math.abs(b9)) && Math.abs(a10 - b10) <= EPSILON * Math.max(1, Math.abs(a10), Math.abs(b10)) && Math.abs(a11 - b11) <= EPSILON * Math.max(1, Math.abs(a11), Math.abs(b11)) && Math.abs(a12 - b12) <= EPSILON * Math.max(1, Math.abs(a12), Math.abs(b12)) && Math.abs(a13 - b13) <= EPSILON * Math.max(1, Math.abs(a13), Math.abs(b13)) && Math.abs(a14 - b14) <= EPSILON * Math.max(1, Math.abs(a14), Math.abs(b14)) && Math.abs(a15 - b15) <= EPSILON * Math.max(1, Math.abs(a15), Math.abs(b15)); } var mul$5 = multiply$5; var sub$3 = subtract$3; const mat4 = /* @__PURE__ */ Object.freeze({ __proto__: null, add: add$5, adjoint, clone: clone$5, copy: copy$5, create: create$5, determinant, equals: equals$5, exactEquals: exactEquals$5, frob, fromQuat, fromQuat2, fromRotation: fromRotation$1, fromRotationTranslation: fromRotationTranslation$1, fromRotationTranslationScale, fromRotationTranslationScaleOrigin, fromScaling, fromTranslation: fromTranslation$1, fromValues: fromValues$5, fromXRotation, fromYRotation, fromZRotation, frustum, getRotation, getScaling, getTranslation: getTranslation$1, identity: identity$2, invert: invert$2, lookAt, mul: mul$5, multiply: multiply$5, multiplyScalar, multiplyScalarAndAdd, ortho, orthoNO, orthoZO, perspective, perspectiveFromFieldOfView, perspectiveNO, perspectiveZO, rotate: rotate$1, rotateX: rotateX$3, rotateY: rotateY$3, rotateZ: rotateZ$3, scale: scale$5, set: set$5, str: str$5, sub: sub$3, subtract: subtract$3, targetTo, translate: translate$1, transpose }); function create$4() { var out = new ARRAY_TYPE(3); if (ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; out[2] = 0; } return out; } function clone$4(a) { var out = new ARRAY_TYPE(3); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; } function length$4(a) { var x = a[0]; var y = a[1]; var z = a[2]; return Math.hypot(x, y, z); } function fromValues$4(x, y, z) { var out = new ARRAY_TYPE(3); out[0] = x; out[1] = y; out[2] = z; return out; } function copy$4(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; } function set$4(out, x, y, z) { out[0] = x; out[1] = y; out[2] = z; return out; } function add$4(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; out[2] = a[2] + b[2]; return out; } function subtract$2(out, a, b) { out[0] = a[0] - b[0]; out[1] = a[1] - b[1]; out[2] = a[2] - b[2]; return out; } function multiply$4(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; out[2] = a[2] * b[2]; return out; } function divide$2(out, a, b) { out[0] = a[0] / b[0]; out[1] = a[1] / b[1]; out[2] = a[2] / b[2]; return out; } function ceil$2(out, a) { out[0] = Math.ceil(a[0]); out[1] = Math.ceil(a[1]); out[2] = Math.ceil(a[2]); return out; } function floor$2(out, a) { out[0] = Math.floor(a[0]); out[1] = Math.floor(a[1]); out[2] = Math.floor(a[2]); return out; } function min$2(out, a, b) { out[0] = Math.min(a[0], b[0]); out[1] = Math.min(a[1], b[1]); out[2] = Math.min(a[2], b[2]); return out; } function max$2(out, a, b) { out[0] = Math.max(a[0], b[0]); out[1] = Math.max(a[1], b[1]); out[2] = Math.max(a[2], b[2]); return out; } function round$2(out, a) { out[0] = Math.round(a[0]); out[1] = Math.round(a[1]); out[2] = Math.round(a[2]); return out; } function scale$4(out, a, b) { out[0] = a[0] * b; out[1] = a[1] * b; out[2] = a[2] * b; return out; } function scaleAndAdd$2(out, a, b, scale2) { out[0] = a[0] + b[0] * scale2; out[1] = a[1] + b[1] * scale2; out[2] = a[2] + b[2] * scale2; return out; } function distance$2(a, b) { var x = b[0] - a[0]; var y = b[1] - a[1]; var z = b[2] - a[2]; return Math.hypot(x, y, z); } function squaredDistance$2(a, b) { var x = b[0] - a[0]; var y = b[1] - a[1]; var z = b[2] - a[2]; return x * x + y * y + z * z; } function squaredLength$4(a) { var x = a[0]; var y = a[1]; var z = a[2]; return x * x + y * y + z * z; } function negate$2(out, a) { out[0] = -a[0]; out[1] = -a[1]; out[2] = -a[2]; return out; } function inverse$2(out, a) { out[0] = 1 / a[0]; out[1] = 1 / a[1]; out[2] = 1 / a[2]; return out; } function normalize$4(out, a) { var x = a[0]; var y = a[1]; var z = a[2]; var len = x * x + y * y + z * z; if (len > 0) { len = 1 / Math.sqrt(len); } out[0] = a[0] * len; out[1] = a[1] * len; out[2] = a[2] * len; return out; } function dot$4(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } function cross$2(out, a, b) { var ax = a[0], ay = a[1], az = a[2]; var bx = b[0], by = b[1], bz = b[2]; out[0] = ay * bz - az * by; out[1] = az * bx - ax * bz; out[2] = ax * by - ay * bx; return out; } function lerp$4(out, a, b, t) { var ax = a[0]; var ay = a[1]; var az = a[2]; out[0] = ax + t * (b[0] - ax); out[1] = ay + t * (b[1] - ay); out[2] = az + t * (b[2] - az); return out; } function hermite(out, a, b, c, d, t) { var factorTimes2 = t * t; var factor1 = factorTimes2 * (2 * t - 3) + 1; var factor2 = factorTimes2 * (t - 2) + t; var factor3 = factorTimes2 * (t - 1); var factor4 = factorTimes2 * (3 - 2 * t); out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4; out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4; out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4; return out; } function bezier(out, a, b, c, d, t) { var inverseFactor = 1 - t; var inverseFactorTimesTwo = inverseFactor * inverseFactor; var factorTimes2 = t * t; var factor1 = inverseFactorTimesTwo * inverseFactor; var factor2 = 3 * t * inverseFactorTimesTwo; var factor3 = 3 * factorTimes2 * inverseFactor; var factor4 = factorTimes2 * t; out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4; out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4; out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4; return out; } function random$3(out, scale2) { scale2 = scale2 || 1; var r = RANDOM() * 2 * Math.PI; var z = RANDOM() * 2 - 1; var zScale = Math.sqrt(1 - z * z) * scale2; out[0] = Math.cos(r) * zScale; out[1] = Math.sin(r) * zScale; out[2] = z * scale2; return out; } function transformMat4$2(out, a, m) { var x = a[0], y = a[1], z = a[2]; var w = m[3] * x + m[7] * y + m[11] * z + m[15]; w = w || 1; out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w; out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w; out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w; return out; } function transformMat3$1(out, a, m) { var x = a[0], y = a[1], z = a[2]; out[0] = x * m[0] + y * m[3] + z * m[6]; out[1] = x * m[1] + y * m[4] + z * m[7]; out[2] = x * m[2] + y * m[5] + z * m[8]; return out; } function transformQuat$1(out, a, q) { var qx = q[0], qy = q[1], qz = q[2], qw = q[3]; var x = a[0], y = a[1], z = a[2]; var uvx = qy * z - qz * y, uvy = qz * x - qx * z, uvz = qx * y - qy * x; var uuvx = qy * uvz - qz * uvy, uuvy = qz * uvx - qx * uvz, uuvz = qx * uvy - qy * uvx; var w2 = qw * 2; uvx *= w2; uvy *= w2; uvz *= w2; uuvx *= 2; uuvy *= 2; uuvz *= 2; out[0] = x + uvx + uuvx; out[1] = y + uvy + uuvy; out[2] = z + uvz + uuvz; return out; } function rotateX$2(out, a, b, rad) { var p = [], r = []; p[0] = a[0] - b[0]; p[1] = a[1] - b[1]; p[2] = a[2] - b[2]; r[0] = p[0]; r[1] = p[1] * Math.cos(rad) - p[2] * Math.sin(rad); r[2] = p[1] * Math.sin(rad) + p[2] * Math.cos(rad); out[0] = r[0] + b[0]; out[1] = r[1] + b[1]; out[2] = r[2] + b[2]; return out; } function rotateY$2(out, a, b, rad) { var p = [], r = []; p[0] = a[0] - b[0]; p[1] = a[1] - b[1]; p[2] = a[2] - b[2]; r[0] = p[2] * Math.sin(rad) + p[0] * Math.cos(rad); r[1] = p[1]; r[2] = p[2] * Math.cos(rad) - p[0] * Math.sin(rad); out[0] = r[0] + b[0]; out[1] = r[1] + b[1]; out[2] = r[2] + b[2]; return out; } function rotateZ$2(out, a, b, rad) { var p = [], r = []; p[0] = a[0] - b[0]; p[1] = a[1] - b[1]; p[2] = a[2] - b[2]; r[0] = p[0] * Math.cos(rad) - p[1] * Math.sin(rad); r[1] = p[0] * Math.sin(rad) + p[1] * Math.cos(rad); r[2] = p[2]; out[0] = r[0] + b[0]; out[1] = r[1] + b[1]; out[2] = r[2] + b[2]; return out; } function angle$1(a, b) { var ax = a[0], ay = a[1], az = a[2], bx = b[0], by = b[1], bz = b[2], mag1 = Math.sqrt(ax * ax + ay * ay + az * az), mag2 = Math.sqrt(bx * bx + by * by + bz * bz), mag = mag1 * mag2, cosine = mag && dot$4(a, b) / mag; return Math.acos(Math.min(Math.max(cosine, -1), 1)); } function zero$2(out) { out[0] = 0; out[1] = 0; out[2] = 0; return out; } function str$4(a) { return "vec3(" + a[0] + ", " + a[1] + ", " + a[2] + ")"; } function exactEquals$4(a, b) { return a[0] === b[0] && a[1] === b[1] && a[2] === b[2]; } function equals$4(a, b) { var a0 = a[0], a1 = a[1], a2 = a[2]; var b0 = b[0], b1 = b[1], b2 = b[2]; return Math.abs(a0 - b0) <= EPSILON * Math.max(1, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1, Math.abs(a2), Math.abs(b2)); } var sub$2 = subtract$2; var mul$4 = multiply$4; var div$2 = divide$2; var dist$2 = distance$2; var sqrDist$2 = squaredDistance$2; var len$4 = length$4; var sqrLen$4 = squaredLength$4; var forEach$2 = function() { var vec = create$4(); return function(a, stride, offset2, count, fn, arg) { var i, l; if (!stride) { stride = 3; } if (!offset2) { offset2 = 0; } if (count) { l = Math.min(count * stride + offset2, a.length); } else { l = a.length; } for (i = offset2; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i + 1]; vec[2] = a[i + 2]; fn(vec, vec, arg); a[i] = vec[0]; a[i + 1] = vec[1]; a[i + 2] = vec[2]; } return a; }; }(); const vec3 = /* @__PURE__ */ Object.freeze({ __proto__: null, add: add$4, angle: angle$1, bezier, ceil: ceil$2, clone: clone$4, copy: copy$4, create: create$4, cross: cross$2, dist: dist$2, distance: distance$2, div: div$2, divide: divide$2, dot: dot$4, equals: equals$4, exactEquals: exactEquals$4, floor: floor$2, forEach: forEach$2, fromValues: fromValues$4, hermite, inverse: inverse$2, len: len$4, length: length$4, lerp: lerp$4, max: max$2, min: min$2, mul: mul$4, multiply: multiply$4, negate: negate$2, normalize: normalize$4, random: random$3, rotateX: rotateX$2, rotateY: rotateY$2, rotateZ: rotateZ$2, round: round$2, scale: scale$4, scaleAndAdd: scaleAndAdd$2, set: set$4, sqrDist: sqrDist$2, sqrLen: sqrLen$4, squaredDistance: squaredDistance$2, squaredLength: squaredLength$4, str: str$4, sub: sub$2, subtract: subtract$2, transformMat3: transformMat3$1, transformMat4: transformMat4$2, transformQuat: transformQuat$1, zero: zero$2 }); function create$3() { var out = new ARRAY_TYPE(4); if (ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 0; } return out; } function normalize$3(out, a) { var x = a[0]; var y = a[1]; var z = a[2]; var w = a[3]; var len = x * x + y * y + z * z + w * w; if (len > 0) { len = 1 / Math.sqrt(len); } out[0] = x * len; out[1] = y * len; out[2] = z * len; out[3] = w * len; return out; } (function() { var vec = create$3(); return function(a, stride, offset2, count, fn, arg) { var i, l; if (!stride) { stride = 4; } if (!offset2) { offset2 = 0; } if (count) { l = Math.min(count * stride + offset2, a.length); } else { l = a.length; } for (i = offset2; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i + 1]; vec[2] = a[i + 2]; vec[3] = a[i + 3]; fn(vec, vec, arg); a[i] = vec[0]; a[i + 1] = vec[1]; a[i + 2] = vec[2]; a[i + 3] = vec[3]; } return a; }; })(); function create$2() { var out = new ARRAY_TYPE(4); if (ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; out[2] = 0; } out[3] = 1; return out; } function setAxisAngle(out, axis, rad) { rad = rad * 0.5; var s = Math.sin(rad); out[0] = s * axis[0]; out[1] = s * axis[1]; out[2] = s * axis[2]; out[3] = Math.cos(rad); return out; } function slerp(out, a, b, t) { var ax = a[0], ay = a[1], az = a[2], aw = a[3]; var bx = b[0], by = b[1], bz = b[2], bw = b[3]; var omega, cosom, sinom, scale0, scale1; cosom = ax * bx + ay * by + az * bz + aw * bw; if (cosom < 0) { cosom = -cosom; bx = -bx; by = -by; bz = -bz; bw = -bw; } if (1 - cosom > EPSILON) { omega = Math.acos(cosom); sinom = Math.sin(omega); scale0 = Math.sin((1 - t) * omega) / sinom; scale1 = Math.sin(t * omega) / sinom; } else { scale0 = 1 - t; scale1 = t; } out[0] = scale0 * ax + scale1 * bx; out[1] = scale0 * ay + scale1 * by; out[2] = scale0 * az + scale1 * bz; out[3] = scale0 * aw + scale1 * bw; return out; } function fromMat3(out, m) { var fTrace = m[0] + m[4] + m[8]; var fRoot; if (fTrace > 0) { fRoot = Math.sqrt(fTrace + 1); out[3] = 0.5 * fRoot; fRoot = 0.5 / fRoot; out[0] = (m[5] - m[7]) * fRoot; out[1] = (m[6] - m[2]) * fRoot; out[2] = (m[1] - m[3]) * fRoot; } else { var i = 0; if (m[4] > m[0]) i = 1; if (m[8] > m[i * 3 + i]) i = 2; var j = (i + 1) % 3; var k = (i + 2) % 3; fRoot = Math.sqrt(m[i * 3 + i] - m[j * 3 + j] - m[k * 3 + k] + 1); out[i] = 0.5 * fRoot; fRoot = 0.5 / fRoot; out[3] = (m[j * 3 + k] - m[k * 3 + j]) * fRoot; out[j] = (m[j * 3 + i] + m[i * 3 + j]) * fRoot; out[k] = (m[k * 3 + i] + m[i * 3 + k]) * fRoot; } return out; } var normalize$2 = normalize$3; (function() { var tmpvec3 = create$4(); var xUnitVec3 = fromValues$4(1, 0, 0); var yUnitVec3 = fromValues$4(0, 1, 0); return function(out, a, b) { var dot = dot$4(a, b); if (dot < -0.999999) { cross$2(tmpvec3, xUnitVec3, a); if (len$4(tmpvec3) < 1e-6) cross$2(tmpvec3, yUnitVec3, a); normalize$4(tmpvec3, tmpvec3); setAxisAngle(out, tmpvec3, Math.PI); return out; } else if (dot > 0.999999) { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 1; return out; } else { cross$2(tmpvec3, a, b); out[0] = tmpvec3[0]; out[1] = tmpvec3[1]; out[2] = tmpvec3[2]; out[3] = 1 + dot; return normalize$2(out, out); } }; })(); (function() { var temp1 = create$2(); var temp2 = create$2(); return function(out, a, b, c, d, t) { slerp(temp1, a, d, t); slerp(temp2, b, c, t); slerp(out, temp1, temp2, 2 * t * (1 - t)); return out; }; })(); (function() { var matr = create$6(); return function(out, view, right, up) { matr[0] = right[0]; matr[3] = right[1]; matr[6] = right[2]; matr[1] = up[0]; matr[4] = up[1]; matr[7] = up[2]; matr[2] = -view[0]; matr[5] = -view[1]; matr[8] = -view[2]; return normalize$2(out, fromMat3(out, matr)); }; })(); function create() { var out = new ARRAY_TYPE(2); if (ARRAY_TYPE != Float32Array) { out[0] = 0; out[1] = 0; } return out; } (function() { var vec = create(); return function(a, stride, offset2, count, fn, arg) { var i, l; if (!stride) { stride = 2; } if (!offset2) { offset2 = 0; } if (count) { l = Math.min(count * stride + offset2, a.length); } else { l = a.length; } for (i = offset2; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i + 1]; fn(vec, vec, arg); a[i] = vec[0]; a[i + 1] = vec[1]; } return a; }; })(); class AnimationControl { /** @type {object} */ #animationData; /** @type {Promise} */ #finishedPromise; #willFinish; /** * Defines a static empty / void animation control. * * @type {AnimationControl} */ static #voidControl = new AnimationControl(null); /** * Provides a static void / undefined AnimationControl that is automatically resolved. * * @returns {AnimationControl} Void AnimationControl */ static get voidControl() { return this.#voidControl; } /** * @param {object|null} [animationData] - Animation data from {@link AnimationAPI}. * * @param {boolean} [willFinish] - Promise that tracks animation finished state. */ constructor(animationData, willFinish = false) { this.#animationData = animationData; this.#willFinish = willFinish; if (isObject(animationData)) { animationData.control = this; } } /** * Get a promise that resolves when animation is finished. * * @returns {Promise} */ get finished() { if (!(this.#finishedPromise instanceof Promise)) { this.#finishedPromise = this.#willFinish ? new Promise((resolve) => this.#animationData.resolve = resolve) : Promise.resolve(); } return this.#finishedPromise; } /** * Returns whether this animation is currently active / animating. * * Note: a delayed animation may not be started / active yet. Use {@link AnimationControl.isFinished} to determine * if an animation is actually finished. * * @returns {boolean} Animation active state. */ get isActive() { return this.#animationData.active; } /** * Returns whether this animation is completely finished. * * @returns {boolean} Animation finished state. */ get isFinished() { return this.#animationData.finished; } /** * Cancels the animation. */ cancel() { const animationData = this.#animationData; if (animationData === null || animationData === void 0) { return; } animationData.cancelled = true; } } class AnimationManager { /** * @type {object[]} */ static activeList = []; /** * @type {object[]} */ static newList = []; /** * @type {number} */ static current; /** * Add animation data. * * @param {object} data - */ static add(data) { const now2 = performance.now(); data.start = now2 + (AnimationManager.current - now2); AnimationManager.newList.push(data); } /** * Manage all animation */ static animate() { const current = AnimationManager.current = performance.now(); if (AnimationManager.activeList.length === 0 && AnimationManager.newList.length === 0) { globalThis.requestAnimationFrame(AnimationManager.animate); return; } if (AnimationManager.newList.length) { for (let cntr = AnimationManager.newList.length; --cntr >= 0; ) { const data = AnimationManager.newList[cntr]; if (data.cancelled) { AnimationManager.newList.splice(cntr, 1); data.cleanup(data); } if (data.active) { AnimationManager.newList.splice(cntr, 1); AnimationManager.activeList.push(data); } } } for (let cntr = AnimationManager.activeList.length; --cntr >= 0; ) { const data = AnimationManager.activeList[cntr]; if (data.cancelled || data.el !== void 0 && !data.el.isConnected) { AnimationManager.activeList.splice(cntr, 1); data.cleanup(data); continue; } data.current = current - data.start; if (data.current >= data.duration) { for (let dataCntr = data.keys.length; --dataCntr >= 0; ) { const key = data.keys[dataCntr]; data.newData[key] = data.destination[key]; } data.position.set(data.newData); AnimationManager.activeList.splice(cntr, 1); data.cleanup(data); continue; } const easedTime = data.ease(data.current / data.duration); for (let dataCntr = data.keys.length; --dataCntr >= 0; ) { const key = data.keys[dataCntr]; data.newData[key] = data.interpolate(data.initial[key], data.destination[key], easedTime); } data.position.set(data.newData); } globalThis.requestAnimationFrame(AnimationManager.animate); } /** * Cancels all animations for given Position instance. * * @param {Position} position - Position instance. */ static cancel(position) { for (let cntr = AnimationManager.activeList.length; --cntr >= 0; ) { const data = AnimationManager.activeList[cntr]; if (data.position === position) { AnimationManager.activeList.splice(cntr, 1); data.cancelled = true; data.cleanup(data); } } for (let cntr = AnimationManager.newList.length; --cntr >= 0; ) { const data = AnimationManager.newList[cntr]; if (data.position === position) { AnimationManager.newList.splice(cntr, 1); data.cancelled = true; data.cleanup(data); } } } /** * Cancels all active and delayed animations. */ static cancelAll() { for (let cntr = AnimationManager.activeList.length; --cntr >= 0; ) { const data = AnimationManager.activeList[cntr]; data.cancelled = true; data.cleanup(data); } for (let cntr = AnimationManager.newList.length; --cntr >= 0; ) { const data = AnimationManager.newList[cntr]; data.cancelled = true; data.cleanup(data); } AnimationManager.activeList.length = 0; AnimationManager.newList.length = 0; } /** * Gets all {@link AnimationControl} instances for a given Position instance. * * @param {Position} position - Position instance. * * @returns {AnimationControl[]} All scheduled AnimationControl instances for the given Position instance. */ static getScheduled(position) { const results = []; for (let cntr = AnimationManager.activeList.length; --cntr >= 0; ) { const data = AnimationManager.activeList[cntr]; if (data.position === position) { results.push(data.control); } } for (let cntr = AnimationManager.newList.length; --cntr >= 0; ) { const data = AnimationManager.newList[cntr]; if (data.position === position) { results.push(data.control); } } return results; } } AnimationManager.animate(); const animateKeys = /* @__PURE__ */ new Set([ // Main keys "left", "top", "maxWidth", "maxHeight", "minWidth", "minHeight", "width", "height", "rotateX", "rotateY", "rotateZ", "scale", "translateX", "translateY", "translateZ", "zIndex", // Aliases "rotation" ]); const transformKeys = ["rotateX", "rotateY", "rotateZ", "scale", "translateX", "translateY", "translateZ"]; Object.freeze(transformKeys); const relativeRegex = /^([-+*])=(-?[\d]*\.?[\d]+)$/; const numericDefaults = { // Other keys height: 0, left: 0, maxHeight: null, maxWidth: null, minHeight: null, minWidth: null, top: 0, transformOrigin: null, width: 0, zIndex: null, rotateX: 0, rotateY: 0, rotateZ: 0, scale: 1, translateX: 0, translateY: 0, translateZ: 0, rotation: 0 }; Object.freeze(numericDefaults); function setNumericDefaults(data) { if (data.rotateX === null) { data.rotateX = 0; } if (data.rotateY === null) { data.rotateY = 0; } if (data.rotateZ === null) { data.rotateZ = 0; } if (data.translateX === null) { data.translateX = 0; } if (data.translateY === null) { data.translateY = 0; } if (data.translateZ === null) { data.translateZ = 0; } if (data.scale === null) { data.scale = 1; } if (data.rotation === null) { data.rotation = 0; } } const transformKeysBitwise = { rotateX: 1, rotateY: 2, rotateZ: 4, scale: 8, translateX: 16, translateY: 32, translateZ: 64 }; Object.freeze(transformKeysBitwise); const transformOriginDefault = "top left"; const transformOrigins = [ "top left", "top center", "top right", "center left", "center", "center right", "bottom left", "bottom center", "bottom right" ]; Object.freeze(transformOrigins); function convertRelative(positionData, position) { for (const key in positionData) { if (animateKeys.has(key)) { const value = positionData[key]; if (typeof value !== "string") { continue; } if (value === "auto" || value === "inherit") { continue; } const regexResults = relativeRegex.exec(value); if (!regexResults) { throw new Error( `convertRelative error: malformed relative key (${key}) with value (${value})` ); } const current = position[key]; switch (regexResults[1]) { case "-": positionData[key] = current - parseFloat(regexResults[2]); break; case "+": positionData[key] = current + parseFloat(regexResults[2]); break; case "*": positionData[key] = current * parseFloat(regexResults[2]); break; } } } } class AnimationAPI { /** @type {PositionData} */ #data; /** @type {Position} */ #position; /** * Tracks the number of animation control instances that are active. * * @type {number} */ #instanceCount = 0; /** * Provides a bound function to pass as data to AnimationManager to invoke * * @type {Function} * @see {AnimationAPI.#cleanupInstance} */ #cleanup; constructor(position, data) { this.#position = position; this.#data = data; this.#cleanup = this.#cleanupInstance.bind(this); } /** * Returns whether there are scheduled animations whether active or delayed for this Position. * * @returns {boolean} Are there active animation instances. */ get isScheduled() { return this.#instanceCount > 0; } /** * Adds / schedules an animation w/ the AnimationManager. This contains the final steps common to all tweens. * * @param {object} initial - * * @param {object} destination - * * @param {number} duration - * * @param {HTMLElement} el - * * @param {number} delay - * * @param {Function} ease - * * @param {Function} interpolate - * * @returns {AnimationControl} The associated animation control. */ #addAnimation(initial, destination, duration, el, delay, ease, interpolate2) { setNumericDefaults(initial); setNumericDefaults(destination); for (const key in initial) { if (!Number.isFinite(initial[key])) { delete initial[key]; } } const keys = Object.keys(initial); const newData = Object.assign({ immediateElementUpdate: true }, initial); if (keys.length === 0) { return AnimationControl.voidControl; } const animationData = { active: true, cleanup: this.#cleanup, cancelled: false, control: void 0, current: 0, destination, duration: duration * 1e3, // Internally the AnimationManager works in ms. ease, el, finished: false, initial, interpolate: interpolate2, keys, newData, position: this.#position, resolve: void 0, start: void 0 }; if (delay > 0) { animationData.active = false; setTimeout(() => { if (!animationData.cancelled) { animationData.active = true; const now2 = performance.now(); animationData.start = now2 + (AnimationManager.current - now2); } }, delay * 1e3); } this.#instanceCount++; AnimationManager.add(animationData); return new AnimationControl(animationData, true); } /** * Cancels all animation instances for this Position instance. */ cancel() { AnimationManager.cancel(this.#position); } /** * Cleans up an animation instance. * * @param {object} data - Animation data for an animation instance. */ #cleanupInstance(data) { this.#instanceCount--; data.active = false; data.finished = true; if (typeof data.resolve === "function") { data.resolve(data.cancelled); } } /** * Returns all currently scheduled AnimationControl instances for this Position instance. * * @returns {AnimationControl[]} All currently scheduled animation controls for this Position instance. */ getScheduled() { return AnimationManager.getScheduled(this.#position); } /** * Provides a tween from given position data to the current position. * * @param {PositionDataExtended} fromData - The starting position. * * @param {object} [opts] - Optional parameters. * * @param {number} [opts.delay=0] - Delay in seconds before animation starts. * * @param {number} [opts.duration=1] - Duration in seconds. * * @param {Function} [opts.ease=cubicOut] - Easing function. * * @param {Function} [opts.interpolate=lerp] - Interpolation function. * * @returns {AnimationControl} A control object that can cancel animation and provides a `finished` Promise. */ from(fromData, { delay = 0, duration = 1, ease = cubicOut, interpolate: interpolate2 = lerp$5 } = {}) { if (!isObject(fromData)) { throw new TypeError(`AnimationAPI.from error: 'fromData' is not an object.`); } const position = this.#position; const parent = position.parent; if (parent !== void 0 && typeof parent?.options?.positionable === "boolean" && !parent?.options?.positionable) { return AnimationControl.voidControl; } const targetEl = parent instanceof HTMLElement ? parent : parent?.elementTarget; const el = targetEl instanceof HTMLElement && targetEl.isConnected ? targetEl : void 0; if (!Number.isFinite(delay) || delay < 0) { throw new TypeError(`AnimationAPI.from error: 'delay' is not a positive number.`); } if (!Number.isFinite(duration) || duration < 0) { throw new TypeError(`AnimationAPI.from error: 'duration' is not a positive number.`); } if (typeof ease !== "function") { throw new TypeError(`AnimationAPI.from error: 'ease' is not a function.`); } if (typeof interpolate2 !== "function") { throw new TypeError(`AnimationAPI.from error: 'interpolate' is not a function.`); } const initial = {}; const destination = {}; const data = this.#data; for (const key in fromData) { if (data[key] !== void 0 && fromData[key] !== data[key]) { initial[key] = fromData[key]; destination[key] = data[key]; } } convertRelative(initial, data); return this.#addAnimation(initial, destination, duration, el, delay, ease, interpolate2); } /** * Provides a tween from given position data to the current position. * * @param {PositionDataExtended} fromData - The starting position. * * @param {PositionDataExtended} toData - The ending position. * * @param {object} [opts] - Optional parameters. * * @param {number} [opts.delay=0] - Delay in seconds before animation starts. * * @param {number} [opts.duration=1] - Duration in seconds. * * @param {Function} [opts.ease=cubicOut] - Easing function. * * @param {Function} [opts.interpolate=lerp] - Interpolation function. * * @returns {AnimationControl} A control object that can cancel animation and provides a `finished` Promise. */ fromTo(fromData, toData, { delay = 0, duration = 1, ease = cubicOut, interpolate: interpolate2 = lerp$5 } = {}) { if (!isObject(fromData)) { throw new TypeError(`AnimationAPI.fromTo error: 'fromData' is not an object.`); } if (!isObject(toData)) { throw new TypeError(`AnimationAPI.fromTo error: 'toData' is not an object.`); } const parent = this.#position.parent; if (parent !== void 0 && typeof parent?.options?.positionable === "boolean" && !parent?.options?.positionable) { return AnimationControl.voidControl; } const targetEl = parent instanceof HTMLElement ? parent : parent?.elementTarget; const el = targetEl instanceof HTMLElement && targetEl.isConnected ? targetEl : void 0; if (!Number.isFinite(delay) || delay < 0) { throw new TypeError(`AnimationAPI.fromTo error: 'delay' is not a positive number.`); } if (!Number.isFinite(duration) || duration < 0) { throw new TypeError(`AnimationAPI.fromTo error: 'duration' is not a positive number.`); } if (typeof ease !== "function") { throw new TypeError(`AnimationAPI.fromTo error: 'ease' is not a function.`); } if (typeof interpolate2 !== "function") { throw new TypeError(`AnimationAPI.fromTo error: 'interpolate' is not a function.`); } const initial = {}; const destination = {}; const data = this.#data; for (const key in fromData) { if (toData[key] === void 0) { console.warn( `AnimationAPI.fromTo warning: key ('${key}') from 'fromData' missing in 'toData'; skipping this key.` ); continue; } if (data[key] !== void 0) { initial[key] = fromData[key]; destination[key] = toData[key]; } } convertRelative(initial, data); convertRelative(destination, data); return this.#addAnimation(initial, destination, duration, el, delay, ease, interpolate2); } /** * Provides a tween to given position data from the current position. * * @param {PositionDataExtended} toData - The destination position. * * @param {object} [opts] - Optional parameters. * * @param {number} [opts.delay=0] - Delay in seconds before animation starts. * * @param {number} [opts.duration=1] - Duration in seconds. * * @param {Function} [opts.ease=cubicOut] - Easing function. * * @param {Function} [opts.interpolate=lerp] - Interpolation function. * * @returns {AnimationControl} A control object that can cancel animation and provides a `finished` Promise. */ to(toData, { delay = 0, duration = 1, ease = cubicOut, interpolate: interpolate2 = lerp$5 } = {}) { if (!isObject(toData)) { throw new TypeError(`AnimationAPI.to error: 'toData' is not an object.`); } const parent = this.#position.parent; if (parent !== void 0 && typeof parent?.options?.positionable === "boolean" && !parent?.options?.positionable) { return AnimationControl.voidControl; } const targetEl = parent instanceof HTMLElement ? parent : parent?.elementTarget; const el = targetEl instanceof HTMLElement && targetEl.isConnected ? targetEl : void 0; if (!Number.isFinite(delay) || delay < 0) { throw new TypeError(`AnimationAPI.to error: 'delay' is not a positive number.`); } if (!Number.isFinite(duration) || duration < 0) { throw new TypeError(`AnimationAPI.to error: 'duration' is not a positive number.`); } if (typeof ease !== "function") { throw new TypeError(`AnimationAPI.to error: 'ease' is not a function.`); } if (typeof interpolate2 !== "function") { throw new TypeError(`AnimationAPI.to error: 'interpolate' is not a function.`); } const initial = {}; const destination = {}; const data = this.#data; for (const key in toData) { if (data[key] !== void 0 && toData[key] !== data[key]) { destination[key] = toData[key]; initial[key] = data[key]; } } convertRelative(destination, data); return this.#addAnimation(initial, destination, duration, el, delay, ease, interpolate2); } /** * Returns a function that provides an optimized way to constantly update a to-tween. * * @param {Iterable} keys - The keys for quickTo. * * @param {object} [opts] - Optional parameters. * * @param {number} [opts.duration=1] - Duration in seconds. * * @param {Function} [opts.ease=cubicOut] - Easing function. * * @param {Function} [opts.interpolate=lerp] - Interpolation function. * * @returns {quickToCallback} quick-to tween function. */ quickTo(keys, { duration = 1, ease = cubicOut, interpolate: interpolate2 = lerp$5 } = {}) { if (!isIterable(keys)) { throw new TypeError(`AnimationAPI.quickTo error: 'keys' is not an iterable list.`); } const parent = this.#position.parent; if (parent !== void 0 && typeof parent?.options?.positionable === "boolean" && !parent?.options?.positionable) { throw new Error(`AnimationAPI.quickTo error: 'parent' is not positionable.`); } if (!Number.isFinite(duration) || duration < 0) { throw new TypeError(`AnimationAPI.quickTo error: 'duration' is not a positive number.`); } if (typeof ease !== "function") { throw new TypeError(`AnimationAPI.quickTo error: 'ease' is not a function.`); } if (typeof interpolate2 !== "function") { throw new TypeError(`AnimationAPI.quickTo error: 'interpolate' is not a function.`); } const initial = {}; const destination = {}; const data = this.#data; for (const key of keys) { if (typeof key !== "string") { throw new TypeError(`AnimationAPI.quickTo error: key is not a string.`); } if (!animateKeys.has(key)) { throw new Error(`AnimationAPI.quickTo error: key ('${key}') is not animatable.`); } if (data[key] !== void 0) { destination[key] = data[key]; initial[key] = data[key]; } } const keysArray = [...keys]; Object.freeze(keysArray); const newData = Object.assign({ immediateElementUpdate: true }, initial); const animationData = { active: true, cleanup: this.#cleanup, cancelled: false, control: void 0, current: 0, destination, duration: duration * 1e3, // Internally the AnimationManager works in ms. ease, el: void 0, finished: true, // Note: start in finished state to add to AnimationManager on first callback. initial, interpolate: interpolate2, keys, newData, position: this.#position, resolve: void 0, start: void 0 }; const quickToCB = (...args) => { const argsLength = args.length; if (argsLength === 0) { return; } for (let cntr = keysArray.length; --cntr >= 0; ) { const key = keysArray[cntr]; if (data[key] !== void 0) { initial[key] = data[key]; } } if (isObject(args[0])) { const objData = args[0]; for (const key in objData) { if (destination[key] !== void 0) { destination[key] = objData[key]; } } } else { for (let cntr = 0; cntr < argsLength && cntr < keysArray.length; cntr++) { const key = keysArray[cntr]; if (destination[key] !== void 0) { destination[key] = args[cntr]; } } } convertRelative(destination, data); setNumericDefaults(initial); setNumericDefaults(destination); const targetEl = parent instanceof HTMLElement ? parent : parent?.elementTarget; animationData.el = targetEl instanceof HTMLElement && targetEl.isConnected ? targetEl : void 0; if (animationData.finished) { animationData.finished = false; animationData.active = true; animationData.current = 0; this.#instanceCount++; AnimationManager.add(animationData); } else { const now2 = performance.now(); animationData.start = now2 + (AnimationManager.current - now2); animationData.current = 0; } }; quickToCB.keys = keysArray; quickToCB.options = ({ duration: duration2, ease: ease2, interpolate: interpolate3 } = {}) => { if (duration2 !== void 0 && (!Number.isFinite(duration2) || duration2 < 0)) { throw new TypeError(`AnimationAPI.quickTo.options error: 'duration' is not a positive number.`); } if (ease2 !== void 0 && typeof ease2 !== "function") { throw new TypeError(`AnimationAPI.quickTo.options error: 'ease' is not a function.`); } if (interpolate3 !== void 0 && typeof interpolate3 !== "function") { throw new TypeError(`AnimationAPI.quickTo.options error: 'interpolate' is not a function.`); } if (duration2 >= 0) { animationData.duration = duration2 * 1e3; } if (ease2) { animationData.ease = ease2; } if (interpolate3) { animationData.interpolate = interpolate3; } return quickToCB; }; return quickToCB; } } class AnimationGroupControl { /** @type {AnimationControl[]} */ #animationControls; /** @type {Promise[]>} */ #finishedPromise; /** * Defines a static empty / void animation control. * * @type {AnimationGroupControl} */ static #voidControl = new AnimationGroupControl(null); /** * Provides a static void / undefined AnimationGroupControl that is automatically resolved. * * @returns {AnimationGroupControl} Void AnimationGroupControl */ static get voidControl() { return this.#voidControl; } /** * @param {AnimationControl[]} animationControls - An array of AnimationControl instances. */ constructor(animationControls) { this.#animationControls = animationControls; } /** * Get a promise that resolves when all animations are finished. * * @returns {Promise[]>|Promise} Finished Promise for all animations. */ get finished() { const animationControls = this.#animationControls; if (animationControls === null || animationControls === void 0) { return Promise.resolve(); } if (!(this.#finishedPromise instanceof Promise)) { const promises = []; for (let cntr = animationControls.length; --cntr >= 0; ) { promises.push(animationControls[cntr].finished); } this.#finishedPromise = Promise.all(promises); } return this.#finishedPromise; } /** * Returns whether there are active animation instances for this group. * * Note: a delayed animation may not be started / active yet. Use {@link AnimationGroupControl.isFinished} to * determine if all animations in the group are finished. * * @returns {boolean} Are there active animation instances. */ get isActive() { const animationControls = this.#animationControls; if (animationControls === null || animationControls === void 0) { return false; } for (let cntr = animationControls.length; --cntr >= 0; ) { if (animationControls[cntr].isActive) { return true; } } return false; } /** * Returns whether all animations in the group are finished. * * @returns {boolean} Are all animation instances finished. */ get isFinished() { const animationControls = this.#animationControls; if (animationControls === null || animationControls === void 0) { return true; } for (let cntr = animationControls.length; --cntr >= 0; ) { if (!animationControls[cntr].isFinished) { return false; } } return false; } /** * Cancels the all animations. */ cancel() { const animationControls = this.#animationControls; if (animationControls === null || animationControls === void 0) { return; } for (let cntr = this.#animationControls.length; --cntr >= 0; ) { this.#animationControls[cntr].cancel(); } } } class AnimationGroupAPI { /** * Checks of the given object is a Position instance by checking for AnimationAPI. * * @param {*} object - Any data. * * @returns {boolean} Is Position. */ static #isPosition(object) { return isObject(object) && object.animate instanceof AnimationAPI; } /** * Cancels any animation for given Position data. * * @param {Position|{position: Position}|Iterable|Iterable<{position: Position}>} position - */ static cancel(position) { if (isIterable(position)) { let index = -1; for (const entry of position) { index++; const actualPosition = this.#isPosition(entry) ? entry : entry.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.cancel warning: No Position instance found at index: ${index}.`); continue; } AnimationManager.cancel(actualPosition); } } else { const actualPosition = this.#isPosition(position) ? position : position.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.cancel warning: No Position instance found.`); return; } AnimationManager.cancel(actualPosition); } } /** * Cancels all Position animation. */ static cancelAll() { AnimationManager.cancelAll(); } /** * Gets all animation controls for the given position data. * * @param {Position|{position: Position}|Iterable|Iterable<{position: Position}>} position - * * @returns {{position: Position, data: object|void, controls: AnimationControl[]}[]} Results array. */ static getScheduled(position) { const results = []; if (isIterable(position)) { let index = -1; for (const entry of position) { index++; const isPosition = this.#isPosition(entry); const actualPosition = isPosition ? entry : entry.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.getScheduled warning: No Position instance found at index: ${index}.`); continue; } const controls = AnimationManager.getScheduled(actualPosition); results.push({ position: actualPosition, data: isPosition ? void 0 : entry, controls }); } } else { const isPosition = this.#isPosition(position); const actualPosition = isPosition ? position : position.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.getScheduled warning: No Position instance found.`); return results; } const controls = AnimationManager.getScheduled(actualPosition); results.push({ position: actualPosition, data: isPosition ? void 0 : position, controls }); } return results; } /** * Provides the `from` animation tween for one or more Position instances as a group. * * @param {Position|{position: Position}|Iterable|Iterable<{position: Position}>} position - * * @param {object|Function} fromData - * * @param {object|Function} options - * * @returns {TJSBasicAnimation} Basic animation control. */ static from(position, fromData, options) { if (!isObject(fromData) && typeof fromData !== "function") { throw new TypeError(`AnimationGroupAPI.from error: 'fromData' is not an object or function.`); } if (options !== void 0 && !isObject(options) && typeof options !== "function") { throw new TypeError(`AnimationGroupAPI.from error: 'options' is not an object or function.`); } const animationControls = []; let index = -1; let callbackOptions; const hasDataCallback = typeof fromData === "function"; const hasOptionCallback = typeof options === "function"; const hasCallback = hasDataCallback || hasOptionCallback; if (hasCallback) { callbackOptions = { index, position: void 0, data: void 0 }; } let actualFromData = fromData; let actualOptions = options; if (isIterable(position)) { for (const entry of position) { index++; const isPosition = this.#isPosition(entry); const actualPosition = isPosition ? entry : entry.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.from warning: No Position instance found at index: ${index}.`); continue; } if (hasCallback) { callbackOptions.index = index; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : entry; } if (hasDataCallback) { actualFromData = fromData(callbackOptions); if (actualFromData === null || actualFromData === void 0) { continue; } if (typeof actualFromData !== "object") { throw new TypeError(`AnimationGroupAPI.from error: fromData callback function iteration(${index}) failed to return an object.`); } } if (hasOptionCallback) { actualOptions = options(callbackOptions); if (actualOptions === null || actualOptions === void 0) { continue; } if (typeof actualOptions !== "object") { throw new TypeError(`AnimationGroupAPI.from error: options callback function iteration(${index}) failed to return an object.`); } } animationControls.push(actualPosition.animate.from(actualFromData, actualOptions)); } } else { const isPosition = this.#isPosition(position); const actualPosition = isPosition ? position : position.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.from warning: No Position instance found.`); return AnimationGroupControl.voidControl; } if (hasCallback) { callbackOptions.index = 0; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : position; } if (hasDataCallback) { actualFromData = fromData(callbackOptions); if (typeof actualFromData !== "object") { throw new TypeError( `AnimationGroupAPI.from error: fromData callback function failed to return an object.` ); } } if (hasOptionCallback) { actualOptions = options(callbackOptions); if (typeof actualOptions !== "object") { throw new TypeError( `AnimationGroupAPI.from error: options callback function failed to return an object.` ); } } animationControls.push(actualPosition.animate.from(actualFromData, actualOptions)); } return new AnimationGroupControl(animationControls); } /** * Provides the `fromTo` animation tween for one or more Position instances as a group. * * @param {Position|{position: Position}|Iterable|Iterable<{position: Position}>} position - * * @param {object|Function} fromData - * * @param {object|Function} toData - * * @param {object|Function} options - * * @returns {TJSBasicAnimation} Basic animation control. */ static fromTo(position, fromData, toData, options) { if (!isObject(fromData) && typeof fromData !== "function") { throw new TypeError(`AnimationGroupAPI.fromTo error: 'fromData' is not an object or function.`); } if (!isObject(toData) && typeof toData !== "function") { throw new TypeError(`AnimationGroupAPI.fromTo error: 'toData' is not an object or function.`); } if (options !== void 0 && !isObject(options) && typeof options !== "function") { throw new TypeError(`AnimationGroupAPI.fromTo error: 'options' is not an object or function.`); } const animationControls = []; let index = -1; let callbackOptions; const hasFromCallback = typeof fromData === "function"; const hasToCallback = typeof toData === "function"; const hasOptionCallback = typeof options === "function"; const hasCallback = hasFromCallback || hasToCallback || hasOptionCallback; if (hasCallback) { callbackOptions = { index, position: void 0, data: void 0 }; } let actualFromData = fromData; let actualToData = toData; let actualOptions = options; if (isIterable(position)) { for (const entry of position) { index++; const isPosition = this.#isPosition(entry); const actualPosition = isPosition ? entry : entry.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.fromTo warning: No Position instance found at index: ${index}.`); continue; } if (hasCallback) { callbackOptions.index = index; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : entry; } if (hasFromCallback) { actualFromData = fromData(callbackOptions); if (actualFromData === null || actualFromData === void 0) { continue; } if (typeof actualFromData !== "object") { throw new TypeError(`AnimationGroupAPI.fromTo error: fromData callback function iteration(${index}) failed to return an object.`); } } if (hasToCallback) { actualToData = toData(callbackOptions); if (actualToData === null || actualToData === void 0) { continue; } if (typeof actualToData !== "object") { throw new TypeError(`AnimationGroupAPI.fromTo error: toData callback function iteration(${index}) failed to return an object.`); } } if (hasOptionCallback) { actualOptions = options(callbackOptions); if (actualOptions === null || actualOptions === void 0) { continue; } if (typeof actualOptions !== "object") { throw new TypeError(`AnimationGroupAPI.fromTo error: options callback function iteration(${index}) failed to return an object.`); } } animationControls.push(actualPosition.animate.fromTo(actualFromData, actualToData, actualOptions)); } } else { const isPosition = this.#isPosition(position); const actualPosition = isPosition ? position : position.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.fromTo warning: No Position instance found.`); return AnimationGroupControl.voidControl; } if (hasCallback) { callbackOptions.index = 0; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : position; } if (hasFromCallback) { actualFromData = fromData(callbackOptions); if (typeof actualFromData !== "object") { throw new TypeError( `AnimationGroupAPI.fromTo error: fromData callback function failed to return an object.` ); } } if (hasToCallback) { actualToData = toData(callbackOptions); if (typeof actualToData !== "object") { throw new TypeError( `AnimationGroupAPI.fromTo error: toData callback function failed to return an object.` ); } } if (hasOptionCallback) { actualOptions = options(callbackOptions); if (typeof actualOptions !== "object") { throw new TypeError( `AnimationGroupAPI.fromTo error: options callback function failed to return an object.` ); } } animationControls.push(actualPosition.animate.fromTo(actualFromData, actualToData, actualOptions)); } return new AnimationGroupControl(animationControls); } /** * Provides the `to` animation tween for one or more Position instances as a group. * * @param {Position|{position: Position}|Iterable|Iterable<{position: Position}>} position - * * @param {object|Function} toData - * * @param {object|Function} options - * * @returns {TJSBasicAnimation} Basic animation control. */ static to(position, toData, options) { if (!isObject(toData) && typeof toData !== "function") { throw new TypeError(`AnimationGroupAPI.to error: 'toData' is not an object or function.`); } if (options !== void 0 && !isObject(options) && typeof options !== "function") { throw new TypeError(`AnimationGroupAPI.to error: 'options' is not an object or function.`); } const animationControls = []; let index = -1; let callbackOptions; const hasDataCallback = typeof toData === "function"; const hasOptionCallback = typeof options === "function"; const hasCallback = hasDataCallback || hasOptionCallback; if (hasCallback) { callbackOptions = { index, position: void 0, data: void 0 }; } let actualToData = toData; let actualOptions = options; if (isIterable(position)) { for (const entry of position) { index++; const isPosition = this.#isPosition(entry); const actualPosition = isPosition ? entry : entry.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.to warning: No Position instance found at index: ${index}.`); continue; } if (hasCallback) { callbackOptions.index = index; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : entry; } if (hasDataCallback) { actualToData = toData(callbackOptions); if (actualToData === null || actualToData === void 0) { continue; } if (typeof actualToData !== "object") { throw new TypeError(`AnimationGroupAPI.to error: toData callback function iteration(${index}) failed to return an object.`); } } if (hasOptionCallback) { actualOptions = options(callbackOptions); if (actualOptions === null || actualOptions === void 0) { continue; } if (typeof actualOptions !== "object") { throw new TypeError(`AnimationGroupAPI.to error: options callback function iteration(${index}) failed to return an object.`); } } animationControls.push(actualPosition.animate.to(actualToData, actualOptions)); } } else { const isPosition = this.#isPosition(position); const actualPosition = isPosition ? position : position.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.to warning: No Position instance found.`); return AnimationGroupControl.voidControl; } if (hasCallback) { callbackOptions.index = 0; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : position; } if (hasDataCallback) { actualToData = toData(callbackOptions); if (typeof actualToData !== "object") { throw new TypeError( `AnimationGroupAPI.to error: toData callback function failed to return an object.` ); } } if (hasOptionCallback) { actualOptions = options(callbackOptions); if (typeof actualOptions !== "object") { throw new TypeError( `AnimationGroupAPI.to error: options callback function failed to return an object.` ); } } animationControls.push(actualPosition.animate.to(actualToData, actualOptions)); } return new AnimationGroupControl(animationControls); } /** * Provides the `to` animation tween for one or more Position instances as a group. * * @param {Position|{position: Position}|Iterable|Iterable<{position: Position}>} position - * * @param {Iterable} keys - * * @param {object|Function} options - * * @returns {quickToCallback} Basic animation control. */ static quickTo(position, keys, options) { if (!isIterable(keys)) { throw new TypeError(`AnimationGroupAPI.quickTo error: 'keys' is not an iterable list.`); } if (options !== void 0 && !isObject(options) && typeof options !== "function") { throw new TypeError(`AnimationGroupAPI.quickTo error: 'options' is not an object or function.`); } const quickToCallbacks = []; let index = -1; const hasOptionCallback = typeof options === "function"; const callbackOptions = { index, position: void 0, data: void 0 }; let actualOptions = options; if (isIterable(position)) { for (const entry of position) { index++; const isPosition = this.#isPosition(entry); const actualPosition = isPosition ? entry : entry.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.quickTo warning: No Position instance found at index: ${index}.`); continue; } callbackOptions.index = index; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : entry; if (hasOptionCallback) { actualOptions = options(callbackOptions); if (actualOptions === null || actualOptions === void 0) { continue; } if (typeof actualOptions !== "object") { throw new TypeError(`AnimationGroupAPI.quickTo error: options callback function iteration(${index}) failed to return an object.`); } } quickToCallbacks.push(actualPosition.animate.quickTo(keys, actualOptions)); } } else { const isPosition = this.#isPosition(position); const actualPosition = isPosition ? position : position.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.quickTo warning: No Position instance found.`); return () => null; } callbackOptions.index = 0; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : position; if (hasOptionCallback) { actualOptions = options(callbackOptions); if (typeof actualOptions !== "object") { throw new TypeError( `AnimationGroupAPI.quickTo error: options callback function failed to return an object.` ); } } quickToCallbacks.push(actualPosition.animate.quickTo(keys, actualOptions)); } const keysArray = [...keys]; Object.freeze(keysArray); const quickToCB = (...args) => { const argsLength = args.length; if (argsLength === 0) { return; } if (typeof args[0] === "function") { const dataCallback = args[0]; index = -1; let cntr = 0; if (isIterable(position)) { for (const entry of position) { index++; const isPosition = this.#isPosition(entry); const actualPosition = isPosition ? entry : entry.position; if (!this.#isPosition(actualPosition)) { continue; } callbackOptions.index = index; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : entry; const toData = dataCallback(callbackOptions); if (toData === null || toData === void 0) { continue; } const toDataIterable = isIterable(toData); if (!Number.isFinite(toData) && !toDataIterable && typeof toData !== "object") { throw new TypeError(`AnimationGroupAPI.quickTo error: toData callback function iteration(${index}) failed to return a finite number, iterable list, or object.`); } if (toDataIterable) { quickToCallbacks[cntr++](...toData); } else { quickToCallbacks[cntr++](toData); } } } else { const isPosition = this.#isPosition(position); const actualPosition = isPosition ? position : position.position; if (!this.#isPosition(actualPosition)) { return; } callbackOptions.index = 0; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : position; const toData = dataCallback(callbackOptions); if (toData === null || toData === void 0) { return; } const toDataIterable = isIterable(toData); if (!Number.isFinite(toData) && !toDataIterable && typeof toData !== "object") { throw new TypeError(`AnimationGroupAPI.quickTo error: toData callback function iteration(${index}) failed to return a finite number, iterable list, or object.`); } if (toDataIterable) { quickToCallbacks[cntr++](...toData); } else { quickToCallbacks[cntr++](toData); } } } else { for (let cntr = quickToCallbacks.length; --cntr >= 0; ) { quickToCallbacks[cntr](...args); } } }; quickToCB.keys = keysArray; quickToCB.options = (options2) => { if (options2 !== void 0 && !isObject(options2) && typeof options2 !== "function") { throw new TypeError(`AnimationGroupAPI.quickTo error: 'options' is not an object or function.`); } if (isObject(options2)) { for (let cntr = quickToCallbacks.length; --cntr >= 0; ) { quickToCallbacks[cntr].options(options2); } } else if (typeof options2 === "function") { if (isIterable(position)) { index = -1; let cntr = 0; for (const entry of position) { index++; const isPosition = this.#isPosition(entry); const actualPosition = isPosition ? entry : entry.position; if (!this.#isPosition(actualPosition)) { console.warn( `AnimationGroupAPI.quickTo.options warning: No Position instance found at index: ${index}.` ); continue; } callbackOptions.index = index; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : entry; actualOptions = options2(callbackOptions); if (actualOptions === null || actualOptions === void 0) { continue; } if (typeof actualOptions !== "object") { throw new TypeError( `AnimationGroupAPI.quickTo.options error: options callback function iteration(${index}) failed to return an object.` ); } quickToCallbacks[cntr++].options(actualOptions); } } else { const isPosition = this.#isPosition(position); const actualPosition = isPosition ? position : position.position; if (!this.#isPosition(actualPosition)) { console.warn(`AnimationGroupAPI.quickTo.options warning: No Position instance found.`); return quickToCB; } callbackOptions.index = 0; callbackOptions.position = position; callbackOptions.data = isPosition ? void 0 : position; actualOptions = options2(callbackOptions); if (typeof actualOptions !== "object") { throw new TypeError( `AnimationGroupAPI.quickTo error: options callback function failed to return an object.` ); } quickToCallbacks[0].options(actualOptions); } } return quickToCB; }; return quickToCB; } } class Centered { /** * @type {HTMLElement} */ #element; /** * Provides a manual setting of the element height. As things go `offsetHeight` causes a browser layout and is not * performance oriented. If manually set this height is used instead of `offsetHeight`. * * @type {number} */ #height; /** * Set from an optional value in the constructor to lock accessors preventing modification. */ #lock; /** * Provides a manual setting of the element width. As things go `offsetWidth` causes a browser layout and is not * performance oriented. If manually set this width is used instead of `offsetWidth`. * * @type {number} */ #width; constructor({ element: element2, lock = false, width: width2, height } = {}) { this.element = element2; this.width = width2; this.height = height; this.#lock = typeof lock === "boolean" ? lock : false; } get element() { return this.#element; } get height() { return this.#height; } get width() { return this.#width; } set element(element2) { if (this.#lock) { return; } if (element2 === void 0 || element2 === null || element2 instanceof HTMLElement) { this.#element = element2; } else { throw new TypeError(`'element' is not a HTMLElement, undefined, or null.`); } } set height(height) { if (this.#lock) { return; } if (height === void 0 || Number.isFinite(height)) { this.#height = height; } else { throw new TypeError(`'height' is not a finite number or undefined.`); } } set width(width2) { if (this.#lock) { return; } if (width2 === void 0 || Number.isFinite(width2)) { this.#width = width2; } else { throw new TypeError(`'width' is not a finite number or undefined.`); } } setDimension(width2, height) { if (this.#lock) { return; } if (width2 === void 0 || Number.isFinite(width2)) { this.#width = width2; } else { throw new TypeError(`'width' is not a finite number or undefined.`); } if (height === void 0 || Number.isFinite(height)) { this.#height = height; } else { throw new TypeError(`'height' is not a finite number or undefined.`); } } getLeft(width2) { const boundsWidth = this.#width ?? this.#element?.offsetWidth ?? globalThis.innerWidth; return (boundsWidth - width2) / 2; } getTop(height) { const boundsHeight = this.#height ?? this.#element?.offsetHeight ?? globalThis.innerHeight; return (boundsHeight - height) / 2; } } const browserCentered = new Centered(); const positionInitial = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, Centered, browserCentered }, Symbol.toStringTag, { value: "Module" })); class PositionChangeSet { constructor() { this.left = false; this.top = false; this.width = false; this.height = false; this.maxHeight = false; this.maxWidth = false; this.minHeight = false; this.minWidth = false; this.zIndex = false; this.transform = false; this.transformOrigin = false; } hasChange() { return this.left || this.top || this.width || this.height || this.maxHeight || this.maxWidth || this.minHeight || this.minWidth || this.zIndex || this.transform || this.transformOrigin; } set(value) { this.left = value; this.top = value; this.width = value; this.height = value; this.maxHeight = value; this.maxWidth = value; this.minHeight = value; this.minWidth = value; this.zIndex = value; this.transform = value; this.transformOrigin = value; } } class PositionData { constructor({ height = null, left = null, maxHeight = null, maxWidth = null, minHeight = null, minWidth = null, rotateX = null, rotateY = null, rotateZ = null, scale: scale2 = null, translateX = null, translateY = null, translateZ = null, top = null, transformOrigin = null, width: width2 = null, zIndex = null } = {}) { this.height = height; this.left = left; this.maxHeight = maxHeight; this.maxWidth = maxWidth; this.minHeight = minHeight; this.minWidth = minWidth; this.rotateX = rotateX; this.rotateY = rotateY; this.rotateZ = rotateZ; this.scale = scale2; this.top = top; this.transformOrigin = transformOrigin; this.translateX = translateX; this.translateY = translateY; this.translateZ = translateZ; this.width = width2; this.zIndex = zIndex; Object.seal(this); } /** * Copies given data to this instance. * * @param {PositionData} data - Copy from this instance. * * @returns {PositionData} This instance. */ copy(data) { this.height = data.height; this.left = data.left; this.maxHeight = data.maxHeight; this.maxWidth = data.maxWidth; this.minHeight = data.minHeight; this.minWidth = data.minWidth; this.rotateX = data.rotateX; this.rotateY = data.rotateY; this.rotateZ = data.rotateZ; this.scale = data.scale; this.top = data.top; this.transformOrigin = data.transformOrigin; this.translateX = data.translateX; this.translateY = data.translateY; this.translateZ = data.translateZ; this.width = data.width; this.zIndex = data.zIndex; return this; } } class PositionStateAPI { /** @type {PositionData} */ #data; /** * @type {Map} */ #dataSaved = /* @__PURE__ */ new Map(); /** @type {Position} */ #position; /** @type {Transforms} */ #transforms; constructor(position, data, transforms) { this.#position = position; this.#data = data; this.#transforms = transforms; } /** * Returns any stored save state by name. * * @param {string} name - Saved data set name. * * @returns {PositionDataExtended} The saved data set. */ get({ name }) { if (typeof name !== "string") { throw new TypeError(`Position - getSave error: 'name' is not a string.`); } return this.#dataSaved.get(name); } /** * Returns any associated default data. * * @returns {PositionDataExtended} Associated default data. */ getDefault() { return this.#dataSaved.get("#defaultData"); } /** * Removes and returns any position state by name. * * @param {object} options - Options. * * @param {string} options.name - Name to remove and retrieve. * * @returns {PositionDataExtended} Saved position data. */ remove({ name }) { if (typeof name !== "string") { throw new TypeError(`Position - remove: 'name' is not a string.`); } const data = this.#dataSaved.get(name); this.#dataSaved.delete(name); return data; } /** * Resets data to default values and invokes set. * * @param {object} [opts] - Optional parameters. * * @param {boolean} [opts.keepZIndex=false] - When true keeps current z-index. * * @param {boolean} [opts.invokeSet=true] - When true invokes set method. * * @returns {boolean} Operation successful. */ reset({ keepZIndex = false, invokeSet = true } = {}) { const defaultData = this.#dataSaved.get("#defaultData"); if (typeof defaultData !== "object") { return false; } if (this.#position.animate.isScheduled) { this.#position.animate.cancel(); } const zIndex = this.#position.zIndex; const data = Object.assign({}, defaultData); if (keepZIndex) { data.zIndex = zIndex; } this.#transforms.reset(data); if (this.#position.parent?.reactive?.minimized) { this.#position.parent?.maximize?.({ animate: false, duration: 0 }); } if (invokeSet) { setTimeout(() => this.#position.set(data), 0); } return true; } /** * Restores a saved positional state returning the data. Several optional parameters are available * to control whether the restore action occurs silently (no store / inline styles updates), animates * to the stored data, or simply sets the stored data. Restoring via {@link AnimationAPI.to} allows * specification of the duration, easing, and interpolate functions along with configuring a Promise to be * returned if awaiting the end of the animation. * * @param {object} params - Parameters * * @param {string} params.name - Saved data set name. * * @param {boolean} [params.remove=false] - Remove data set. * * @param {Iterable} [params.properties] - Specific properties to set / animate. * * @param {boolean} [params.silent] - Set position data directly; no store or style updates. * * @param {boolean} [params.async=false] - If animating return a Promise that resolves with any saved data. * * @param {boolean} [params.animateTo=false] - Animate to restore data. * * @param {number} [params.duration=0.1] - Duration in seconds. * * @param {Function} [params.ease=linear] - Easing function. * * @param {Function} [params.interpolate=lerp] - Interpolation function. * * @returns {PositionDataExtended|Promise} Saved position data. */ restore({ name, remove = false, properties, silent = false, async = false, animateTo = false, duration = 0.1, ease = identity, interpolate: interpolate2 = lerp$5 }) { if (typeof name !== "string") { throw new TypeError(`Position - restore error: 'name' is not a string.`); } const dataSaved = this.#dataSaved.get(name); if (dataSaved) { if (remove) { this.#dataSaved.delete(name); } let data = dataSaved; if (isIterable(properties)) { data = {}; for (const property of properties) { data[property] = dataSaved[property]; } } if (silent) { for (const property in data) { this.#data[property] = data[property]; } return dataSaved; } else if (animateTo) { if (data.transformOrigin !== this.#position.transformOrigin) { this.#position.transformOrigin = data.transformOrigin; } if (async) { return this.#position.animate.to(data, { duration, ease, interpolate: interpolate2 }).finished.then(() => dataSaved); } else { this.#position.animate.to(data, { duration, ease, interpolate: interpolate2 }); } } else { this.#position.set(data); } } return dataSaved; } /** * Saves current position state with the opportunity to add extra data to the saved state. * * @param {object} opts - Options. * * @param {string} opts.name - name to index this saved data. * * @param {...*} [opts.extra] - Extra data to add to saved data. * * @returns {PositionData} Current position data */ save({ name, ...extra }) { if (typeof name !== "string") { throw new TypeError(`Position - save error: 'name' is not a string.`); } const data = this.#position.get(extra); this.#dataSaved.set(name, data); return data; } /** * Directly sets a position state. * * @param {object} opts - Options. * * @param {string} opts.name - name to index this saved data. * * @param {...*} [opts.data] - Position data to set. */ set({ name, ...data }) { if (typeof name !== "string") { throw new TypeError(`Position - set error: 'name' is not a string.`); } this.#dataSaved.set(name, data); } } class StyleCache { constructor() { this.el = void 0; this.computed = void 0; this.marginLeft = void 0; this.marginTop = void 0; this.maxHeight = void 0; this.maxWidth = void 0; this.minHeight = void 0; this.minWidth = void 0; this.hasWillChange = false; this.resizeObserved = { contentHeight: void 0, contentWidth: void 0, offsetHeight: void 0, offsetWidth: void 0 }; const storeResizeObserved = writable$1(this.resizeObserved); this.stores = { element: writable$1(this.el), resizeContentHeight: propertyStore(storeResizeObserved, "contentHeight"), resizeContentWidth: propertyStore(storeResizeObserved, "contentWidth"), resizeObserved: storeResizeObserved, resizeOffsetHeight: propertyStore(storeResizeObserved, "offsetHeight"), resizeOffsetWidth: propertyStore(storeResizeObserved, "offsetWidth") }; } /** * Returns the cached offsetHeight from any attached `resizeObserver` action otherwise gets the offsetHeight from * the element directly. The more optimized path is using `resizeObserver` as getting it from the element * directly is more expensive and alters the execution order of an animation frame. * * @returns {number} The element offsetHeight. */ get offsetHeight() { if (this.el instanceof HTMLElement) { return this.resizeObserved.offsetHeight !== void 0 ? this.resizeObserved.offsetHeight : this.el.offsetHeight; } throw new Error(`StyleCache - get offsetHeight error: no element assigned.`); } /** * Returns the cached offsetWidth from any attached `resizeObserver` action otherwise gets the offsetWidth from * the element directly. The more optimized path is using `resizeObserver` as getting it from the element * directly is more expensive and alters the execution order of an animation frame. * * @returns {number} The element offsetHeight. */ get offsetWidth() { if (this.el instanceof HTMLElement) { return this.resizeObserved.offsetWidth !== void 0 ? this.resizeObserved.offsetWidth : this.el.offsetWidth; } throw new Error(`StyleCache - get offsetWidth error: no element assigned.`); } /** * @param {HTMLElement} el - * * @returns {boolean} Does element match cached element. */ hasData(el) { return this.el === el; } /** * Resets the style cache. */ reset() { if (this.el instanceof HTMLElement && this.el.isConnected && !this.hasWillChange) { this.el.style.willChange = null; } this.el = void 0; this.computed = void 0; this.marginLeft = void 0; this.marginTop = void 0; this.maxHeight = void 0; this.maxWidth = void 0; this.minHeight = void 0; this.minWidth = void 0; this.hasWillChange = false; this.resizeObserved.contentHeight = void 0; this.resizeObserved.contentWidth = void 0; this.resizeObserved.offsetHeight = void 0; this.resizeObserved.offsetWidth = void 0; this.stores.element.set(void 0); } /** * Updates the style cache with new data from the given element. * * @param {HTMLElement} el - An HTML element. */ update(el) { this.el = el; this.computed = globalThis.getComputedStyle(el); this.marginLeft = styleParsePixels(el.style.marginLeft) ?? styleParsePixels(this.computed.marginLeft); this.marginTop = styleParsePixels(el.style.marginTop) ?? styleParsePixels(this.computed.marginTop); this.maxHeight = styleParsePixels(el.style.maxHeight) ?? styleParsePixels(this.computed.maxHeight); this.maxWidth = styleParsePixels(el.style.maxWidth) ?? styleParsePixels(this.computed.maxWidth); this.minHeight = styleParsePixels(el.style.minHeight) ?? styleParsePixels(this.computed.minHeight); this.minWidth = styleParsePixels(el.style.minWidth) ?? styleParsePixels(this.computed.minWidth); const willChange = el.style.willChange !== "" ? el.style.willChange : this.computed.willChange; this.hasWillChange = willChange !== "" && willChange !== "auto"; this.stores.element.set(el); } } class TransformData { constructor() { Object.seal(this); } /** * Stores the calculated bounding rectangle. * * @type {DOMRect} */ #boundingRect = new DOMRect(); /** * Stores the individual transformed corner points of the window in screenspace clockwise from: * top left -> top right -> bottom right -> bottom left. * * @type {Vector3[]} */ #corners = [vec3.create(), vec3.create(), vec3.create(), vec3.create()]; /** * Stores the current gl-matrix mat4 data. * * @type {Matrix4} */ #mat4 = mat4.create(); /** * Stores the pre & post origin translations to apply to matrix transforms. * * @type {Matrix4[]} */ #originTranslations = [mat4.create(), mat4.create()]; /** * @returns {DOMRect} The bounding rectangle. */ get boundingRect() { return this.#boundingRect; } /** * @returns {Vector3[]} The transformed corner points as vec3 in screen space. */ get corners() { return this.#corners; } /** * @returns {string} Returns the CSS style string for the transform matrix. */ get css() { return `matrix3d(${this.mat4.join(",")})`; } /** * @returns {Matrix4} The transform matrix. */ get mat4() { return this.#mat4; } /** * @returns {Matrix4[]} The pre / post translation matrices for origin translation. */ get originTranslations() { return this.#originTranslations; } } class AdapterValidators { /** @type {boolean} */ #enabled = true; /** * @type {ValidatorData[]} */ #validatorData; #mapUnsubscribe = /* @__PURE__ */ new Map(); /** * @returns {[AdapterValidators, ValidatorData[]]} Returns this and internal storage for validator adapter. */ constructor() { this.#validatorData = []; Object.seal(this); return [this, this.#validatorData]; } /** * @returns {boolean} Returns the enabled state.s */ get enabled() { return this.#enabled; } /** * @returns {number} Returns the length of the validators array. */ get length() { return this.#validatorData.length; } /** * @param {boolean} enabled - Sets enabled state. */ set enabled(enabled) { if (typeof enabled !== "boolean") { throw new TypeError(`'enabled' is not a boolean.`); } this.#enabled = enabled; } /** * Provides an iterator for validators. * * @returns {Generator} Generator / iterator of validators. * @yields {ValidatorData} */ *[Symbol.iterator]() { if (this.#validatorData.length === 0) { return; } for (const entry of this.#validatorData) { yield { ...entry }; } } /** * @param {...(ValidatorFn|ValidatorData)} validators - */ add(...validators) { for (const validator of validators) { const validatorType = typeof validator; if (validatorType !== "function" && validatorType !== "object" || validator === null) { throw new TypeError(`AdapterValidator error: 'validator' is not a function or object.`); } let data = void 0; let subscribeFn = void 0; switch (validatorType) { case "function": data = { id: void 0, validator, weight: 1 }; subscribeFn = validator.subscribe; break; case "object": if (typeof validator.validator !== "function") { throw new TypeError(`AdapterValidator error: 'validator' attribute is not a function.`); } if (validator.weight !== void 0 && typeof validator.weight !== "number" || (validator.weight < 0 || validator.weight > 1)) { throw new TypeError( `AdapterValidator error: 'weight' attribute is not a number between '0 - 1' inclusive.` ); } data = { id: validator.id !== void 0 ? validator.id : void 0, validator: validator.validator.bind(validator), weight: validator.weight || 1, instance: validator }; subscribeFn = validator.validator.subscribe ?? validator.subscribe; break; } const index = this.#validatorData.findIndex((value) => { return data.weight < value.weight; }); if (index >= 0) { this.#validatorData.splice(index, 0, data); } else { this.#validatorData.push(data); } if (typeof subscribeFn === "function") { const unsubscribe = subscribeFn(); if (typeof unsubscribe !== "function") { throw new TypeError( "AdapterValidator error: Filter has subscribe function, but no unsubscribe function is returned." ); } if (this.#mapUnsubscribe.has(data.validator)) { throw new Error( "AdapterValidator error: Filter added already has an unsubscribe function registered." ); } this.#mapUnsubscribe.set(data.validator, unsubscribe); } } } clear() { this.#validatorData.length = 0; for (const unsubscribe of this.#mapUnsubscribe.values()) { unsubscribe(); } this.#mapUnsubscribe.clear(); } /** * @param {...(ValidatorFn|ValidatorData)} validators - */ remove(...validators) { const length = this.#validatorData.length; if (length === 0) { return; } for (const data of validators) { const actualValidator = typeof data === "function" ? data : isObject(data) ? data.validator : void 0; if (!actualValidator) { continue; } for (let cntr = this.#validatorData.length; --cntr >= 0; ) { if (this.#validatorData[cntr].validator === actualValidator) { this.#validatorData.splice(cntr, 1); let unsubscribe = void 0; if (typeof (unsubscribe = this.#mapUnsubscribe.get(actualValidator)) === "function") { unsubscribe(); this.#mapUnsubscribe.delete(actualValidator); } } } } } /** * Remove validators by the provided callback. The callback takes 3 parameters: `id`, `validator`, and `weight`. * Any truthy value returned will remove that validator. * * @param {function(*, ValidatorFn, number): boolean} callback - Callback function to evaluate each validator * entry. */ removeBy(callback) { const length = this.#validatorData.length; if (length === 0) { return; } if (typeof callback !== "function") { throw new TypeError(`AdapterValidator error: 'callback' is not a function.`); } this.#validatorData = this.#validatorData.filter((data) => { const remove = callback.call(callback, { ...data }); if (remove) { let unsubscribe; if (typeof (unsubscribe = this.#mapUnsubscribe.get(data.validator)) === "function") { unsubscribe(); this.#mapUnsubscribe.delete(data.validator); } } return !remove; }); } removeById(...ids) { const length = this.#validatorData.length; if (length === 0) { return; } this.#validatorData = this.#validatorData.filter((data) => { let remove = false; for (const id of ids) { remove |= data.id === id; } if (remove) { let unsubscribe; if (typeof (unsubscribe = this.#mapUnsubscribe.get(data.validator)) === "function") { unsubscribe(); this.#mapUnsubscribe.delete(data.validator); } } return !remove; }); } } class BasicBounds { /** * When true constrains the min / max width or height to element. * * @type {boolean} */ #constrain; /** * @type {HTMLElement} */ #element; /** * When true the validator is active. * * @type {boolean} */ #enabled; /** * Provides a manual setting of the element height. As things go `offsetHeight` causes a browser layout and is not * performance oriented. If manually set this height is used instead of `offsetHeight`. * * @type {number} */ #height; /** * Set from an optional value in the constructor to lock accessors preventing modification. */ #lock; /** * Provides a manual setting of the element width. As things go `offsetWidth` causes a browser layout and is not * performance oriented. If manually set this width is used instead of `offsetWidth`. * * @type {number} */ #width; constructor({ constrain = true, element: element2, enabled = true, lock = false, width: width2, height } = {}) { this.element = element2; this.constrain = constrain; this.enabled = enabled; this.width = width2; this.height = height; this.#lock = typeof lock === "boolean" ? lock : false; } get constrain() { return this.#constrain; } get element() { return this.#element; } get enabled() { return this.#enabled; } get height() { return this.#height; } get width() { return this.#width; } set constrain(constrain) { if (this.#lock) { return; } if (typeof constrain !== "boolean") { throw new TypeError(`'constrain' is not a boolean.`); } this.#constrain = constrain; } set element(element2) { if (this.#lock) { return; } if (element2 === void 0 || element2 === null || element2 instanceof HTMLElement) { this.#element = element2; } else { throw new TypeError(`'element' is not a HTMLElement, undefined, or null.`); } } set enabled(enabled) { if (this.#lock) { return; } if (typeof enabled !== "boolean") { throw new TypeError(`'enabled' is not a boolean.`); } this.#enabled = enabled; } set height(height) { if (this.#lock) { return; } if (height === void 0 || Number.isFinite(height)) { this.#height = height; } else { throw new TypeError(`'height' is not a finite number or undefined.`); } } set width(width2) { if (this.#lock) { return; } if (width2 === void 0 || Number.isFinite(width2)) { this.#width = width2; } else { throw new TypeError(`'width' is not a finite number or undefined.`); } } setDimension(width2, height) { if (this.#lock) { return; } if (width2 === void 0 || Number.isFinite(width2)) { this.#width = width2; } else { throw new TypeError(`'width' is not a finite number or undefined.`); } if (height === void 0 || Number.isFinite(height)) { this.#height = height; } else { throw new TypeError(`'height' is not a finite number or undefined.`); } } /** * Provides a validator that respects transforms in positional data constraining the position to within the target * elements bounds. * * @param {ValidationData} valData - The associated validation data for position updates. * * @returns {PositionData} Potentially adjusted position data. */ validator(valData) { if (!this.#enabled) { return valData.position; } const boundsWidth = this.#width ?? this.#element?.offsetWidth ?? globalThis.innerWidth; const boundsHeight = this.#height ?? this.#element?.offsetHeight ?? globalThis.innerHeight; if (typeof valData.position.width === "number") { const maxW = valData.maxWidth ?? (this.#constrain ? boundsWidth : Number.MAX_SAFE_INTEGER); valData.position.width = valData.width = Math.clamped(valData.position.width, valData.minWidth, maxW); if (valData.width + valData.position.left + valData.marginLeft > boundsWidth) { valData.position.left = boundsWidth - valData.width - valData.marginLeft; } } if (typeof valData.position.height === "number") { const maxH = valData.maxHeight ?? (this.#constrain ? boundsHeight : Number.MAX_SAFE_INTEGER); valData.position.height = valData.height = Math.clamped(valData.position.height, valData.minHeight, maxH); if (valData.height + valData.position.top + valData.marginTop > boundsHeight) { valData.position.top = boundsHeight - valData.height - valData.marginTop; } } const maxL = Math.max(boundsWidth - valData.width - valData.marginLeft, 0); valData.position.left = Math.round(Math.clamped(valData.position.left, 0, maxL)); const maxT = Math.max(boundsHeight - valData.height - valData.marginTop, 0); valData.position.top = Math.round(Math.clamped(valData.position.top, 0, maxT)); return valData.position; } } const s_TRANSFORM_DATA = new TransformData(); class TransformBounds { /** * When true constrains the min / max width or height to element. * * @type {boolean} */ #constrain; /** * @type {HTMLElement} */ #element; /** * When true the validator is active. * * @type {boolean} */ #enabled; /** * Provides a manual setting of the element height. As things go `offsetHeight` causes a browser layout and is not * performance oriented. If manually set this height is used instead of `offsetHeight`. * * @type {number} */ #height; /** * Set from an optional value in the constructor to lock accessors preventing modification. */ #lock; /** * Provides a manual setting of the element width. As things go `offsetWidth` causes a browser layout and is not * performance oriented. If manually set this width is used instead of `offsetWidth`. * * @type {number} */ #width; constructor({ constrain = true, element: element2, enabled = true, lock = false, width: width2, height } = {}) { this.element = element2; this.constrain = constrain; this.enabled = enabled; this.width = width2; this.height = height; this.#lock = typeof lock === "boolean" ? lock : false; } get constrain() { return this.#constrain; } get element() { return this.#element; } get enabled() { return this.#enabled; } get height() { return this.#height; } get width() { return this.#width; } set constrain(constrain) { if (this.#lock) { return; } if (typeof constrain !== "boolean") { throw new TypeError(`'constrain' is not a boolean.`); } this.#constrain = constrain; } set element(element2) { if (this.#lock) { return; } if (element2 === void 0 || element2 === null || element2 instanceof HTMLElement) { this.#element = element2; } else { throw new TypeError(`'element' is not a HTMLElement, undefined, or null.`); } } set enabled(enabled) { if (this.#lock) { return; } if (typeof enabled !== "boolean") { throw new TypeError(`'enabled' is not a boolean.`); } this.#enabled = enabled; } set height(height) { if (this.#lock) { return; } if (height === void 0 || Number.isFinite(height)) { this.#height = height; } else { throw new TypeError(`'height' is not a finite number or undefined.`); } } set width(width2) { if (this.#lock) { return; } if (width2 === void 0 || Number.isFinite(width2)) { this.#width = width2; } else { throw new TypeError(`'width' is not a finite number or undefined.`); } } setDimension(width2, height) { if (this.#lock) { return; } if (width2 === void 0 || Number.isFinite(width2)) { this.#width = width2; } else { throw new TypeError(`'width' is not a finite number or undefined.`); } if (height === void 0 || Number.isFinite(height)) { this.#height = height; } else { throw new TypeError(`'height' is not a finite number or undefined.`); } } /** * Provides a validator that respects transforms in positional data constraining the position to within the target * elements bounds. * * @param {ValidationData} valData - The associated validation data for position updates. * * @returns {PositionData} Potentially adjusted position data. */ validator(valData) { if (!this.#enabled) { return valData.position; } const boundsWidth = this.#width ?? this.#element?.offsetWidth ?? globalThis.innerWidth; const boundsHeight = this.#height ?? this.#element?.offsetHeight ?? globalThis.innerHeight; if (typeof valData.position.width === "number") { const maxW = valData.maxWidth ?? (this.#constrain ? boundsWidth : Number.MAX_SAFE_INTEGER); valData.position.width = Math.clamped(valData.width, valData.minWidth, maxW); } if (typeof valData.position.height === "number") { const maxH = valData.maxHeight ?? (this.#constrain ? boundsHeight : Number.MAX_SAFE_INTEGER); valData.position.height = Math.clamped(valData.height, valData.minHeight, maxH); } const data = valData.transforms.getData(valData.position, s_TRANSFORM_DATA, valData); const initialX = data.boundingRect.x; const initialY = data.boundingRect.y; if (data.boundingRect.bottom + valData.marginTop > boundsHeight) { data.boundingRect.y += boundsHeight - data.boundingRect.bottom - valData.marginTop; } if (data.boundingRect.right + valData.marginLeft > boundsWidth) { data.boundingRect.x += boundsWidth - data.boundingRect.right - valData.marginLeft; } if (data.boundingRect.top - valData.marginTop < 0) { data.boundingRect.y += Math.abs(data.boundingRect.top - valData.marginTop); } if (data.boundingRect.left - valData.marginLeft < 0) { data.boundingRect.x += Math.abs(data.boundingRect.left - valData.marginLeft); } valData.position.left -= initialX - data.boundingRect.x; valData.position.top -= initialY - data.boundingRect.y; return valData.position; } } const basicWindow = new BasicBounds({ lock: true }); const transformWindow = new TransformBounds({ lock: true }); const positionValidators = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, BasicBounds, TransformBounds, basicWindow, transformWindow }, Symbol.toStringTag, { value: "Module" })); const s_SCALE_VECTOR = [1, 1, 1]; const s_TRANSLATE_VECTOR = [0, 0, 0]; const s_MAT4_RESULT = mat4.create(); const s_MAT4_TEMP = mat4.create(); const s_VEC3_TEMP = vec3.create(); class Transforms { /** * Stores the transform keys in the order added. * * @type {string[]} */ #orderList = []; constructor() { this._data = {}; } /** * @returns {boolean} Whether there are active transforms in local data. */ get isActive() { return this.#orderList.length > 0; } /** * @returns {number|undefined} Any local rotateX data. */ get rotateX() { return this._data.rotateX; } /** * @returns {number|undefined} Any local rotateY data. */ get rotateY() { return this._data.rotateY; } /** * @returns {number|undefined} Any local rotateZ data. */ get rotateZ() { return this._data.rotateZ; } /** * @returns {number|undefined} Any local rotateZ scale. */ get scale() { return this._data.scale; } /** * @returns {number|undefined} Any local translateZ data. */ get translateX() { return this._data.translateX; } /** * @returns {number|undefined} Any local translateZ data. */ get translateY() { return this._data.translateY; } /** * @returns {number|undefined} Any local translateZ data. */ get translateZ() { return this._data.translateZ; } /** * Sets the local rotateX data if the value is a finite number otherwise removes the local data. * * @param {number|null|undefined} value - A value to set. */ set rotateX(value) { if (Number.isFinite(value)) { if (this._data.rotateX === void 0) { this.#orderList.push("rotateX"); } this._data.rotateX = value; } else { if (this._data.rotateX !== void 0) { const index = this.#orderList.findIndex((entry) => entry === "rotateX"); if (index >= 0) { this.#orderList.splice(index, 1); } } delete this._data.rotateX; } } /** * Sets the local rotateY data if the value is a finite number otherwise removes the local data. * * @param {number|null|undefined} value - A value to set. */ set rotateY(value) { if (Number.isFinite(value)) { if (this._data.rotateY === void 0) { this.#orderList.push("rotateY"); } this._data.rotateY = value; } else { if (this._data.rotateY !== void 0) { const index = this.#orderList.findIndex((entry) => entry === "rotateY"); if (index >= 0) { this.#orderList.splice(index, 1); } } delete this._data.rotateY; } } /** * Sets the local rotateZ data if the value is a finite number otherwise removes the local data. * * @param {number|null|undefined} value - A value to set. */ set rotateZ(value) { if (Number.isFinite(value)) { if (this._data.rotateZ === void 0) { this.#orderList.push("rotateZ"); } this._data.rotateZ = value; } else { if (this._data.rotateZ !== void 0) { const index = this.#orderList.findIndex((entry) => entry === "rotateZ"); if (index >= 0) { this.#orderList.splice(index, 1); } } delete this._data.rotateZ; } } /** * Sets the local scale data if the value is a finite number otherwise removes the local data. * * @param {number|null|undefined} value - A value to set. */ set scale(value) { if (Number.isFinite(value)) { if (this._data.scale === void 0) { this.#orderList.push("scale"); } this._data.scale = value; } else { if (this._data.scale !== void 0) { const index = this.#orderList.findIndex((entry) => entry === "scale"); if (index >= 0) { this.#orderList.splice(index, 1); } } delete this._data.scale; } } /** * Sets the local translateX data if the value is a finite number otherwise removes the local data. * * @param {number|null|undefined} value - A value to set. */ set translateX(value) { if (Number.isFinite(value)) { if (this._data.translateX === void 0) { this.#orderList.push("translateX"); } this._data.translateX = value; } else { if (this._data.translateX !== void 0) { const index = this.#orderList.findIndex((entry) => entry === "translateX"); if (index >= 0) { this.#orderList.splice(index, 1); } } delete this._data.translateX; } } /** * Sets the local translateY data if the value is a finite number otherwise removes the local data. * * @param {number|null|undefined} value - A value to set. */ set translateY(value) { if (Number.isFinite(value)) { if (this._data.translateY === void 0) { this.#orderList.push("translateY"); } this._data.translateY = value; } else { if (this._data.translateY !== void 0) { const index = this.#orderList.findIndex((entry) => entry === "translateY"); if (index >= 0) { this.#orderList.splice(index, 1); } } delete this._data.translateY; } } /** * Sets the local translateZ data if the value is a finite number otherwise removes the local data. * * @param {number|null|undefined} value - A value to set. */ set translateZ(value) { if (Number.isFinite(value)) { if (this._data.translateZ === void 0) { this.#orderList.push("translateZ"); } this._data.translateZ = value; } else { if (this._data.translateZ !== void 0) { const index = this.#orderList.findIndex((entry) => entry === "translateZ"); if (index >= 0) { this.#orderList.splice(index, 1); } } delete this._data.translateZ; } } /** * Returns the matrix3d CSS transform for the given position / transform data. * * @param {object} [data] - Optional position data otherwise use local stored transform data. * * @returns {string} The CSS matrix3d string. */ getCSS(data = this._data) { return `matrix3d(${this.getMat4(data, s_MAT4_RESULT).join(",")})`; } /** * Returns the matrix3d CSS transform for the given position / transform data. * * @param {object} [data] - Optional position data otherwise use local stored transform data. * * @returns {string} The CSS matrix3d string. */ getCSSOrtho(data = this._data) { return `matrix3d(${this.getMat4Ortho(data, s_MAT4_RESULT).join(",")})`; } /** * Collects all data including a bounding rect, transform matrix, and points array of the given {@link PositionData} * instance with the applied local transform data. * * @param {PositionData} position - The position data to process. * * @param {TransformData} [output] - Optional TransformData output instance. * * @param {object} [validationData] - Optional validation data for adjustment parameters. * * @returns {TransformData} The output TransformData instance. */ getData(position, output = new TransformData(), validationData = {}) { const valWidth = validationData.width ?? 0; const valHeight = validationData.height ?? 0; const valOffsetTop = validationData.offsetTop ?? validationData.marginTop ?? 0; const valOffsetLeft = validationData.offsetLeft ?? validationData.offsetLeft ?? 0; position.top += valOffsetTop; position.left += valOffsetLeft; const width2 = Number.isFinite(position.width) ? position.width : valWidth; const height = Number.isFinite(position.height) ? position.height : valHeight; const rect = output.corners; if (this.hasTransform(position)) { rect[0][0] = rect[0][1] = rect[0][2] = 0; rect[1][0] = width2; rect[1][1] = rect[1][2] = 0; rect[2][0] = width2; rect[2][1] = height; rect[2][2] = 0; rect[3][0] = 0; rect[3][1] = height; rect[3][2] = 0; const matrix = this.getMat4(position, output.mat4); const translate = s_GET_ORIGIN_TRANSLATION(position.transformOrigin, width2, height, output.originTranslations); if (transformOriginDefault === position.transformOrigin) { vec3.transformMat4(rect[0], rect[0], matrix); vec3.transformMat4(rect[1], rect[1], matrix); vec3.transformMat4(rect[2], rect[2], matrix); vec3.transformMat4(rect[3], rect[3], matrix); } else { vec3.transformMat4(rect[0], rect[0], translate[0]); vec3.transformMat4(rect[0], rect[0], matrix); vec3.transformMat4(rect[0], rect[0], translate[1]); vec3.transformMat4(rect[1], rect[1], translate[0]); vec3.transformMat4(rect[1], rect[1], matrix); vec3.transformMat4(rect[1], rect[1], translate[1]); vec3.transformMat4(rect[2], rect[2], translate[0]); vec3.transformMat4(rect[2], rect[2], matrix); vec3.transformMat4(rect[2], rect[2], translate[1]); vec3.transformMat4(rect[3], rect[3], translate[0]); vec3.transformMat4(rect[3], rect[3], matrix); vec3.transformMat4(rect[3], rect[3], translate[1]); } rect[0][0] = position.left + rect[0][0]; rect[0][1] = position.top + rect[0][1]; rect[1][0] = position.left + rect[1][0]; rect[1][1] = position.top + rect[1][1]; rect[2][0] = position.left + rect[2][0]; rect[2][1] = position.top + rect[2][1]; rect[3][0] = position.left + rect[3][0]; rect[3][1] = position.top + rect[3][1]; } else { rect[0][0] = position.left; rect[0][1] = position.top; rect[1][0] = position.left + width2; rect[1][1] = position.top; rect[2][0] = position.left + width2; rect[2][1] = position.top + height; rect[3][0] = position.left; rect[3][1] = position.top + height; mat4.identity(output.mat4); } let maxX = Number.MIN_SAFE_INTEGER; let maxY = Number.MIN_SAFE_INTEGER; let minX = Number.MAX_SAFE_INTEGER; let minY = Number.MAX_SAFE_INTEGER; for (let cntr = 4; --cntr >= 0; ) { if (rect[cntr][0] > maxX) { maxX = rect[cntr][0]; } if (rect[cntr][0] < minX) { minX = rect[cntr][0]; } if (rect[cntr][1] > maxY) { maxY = rect[cntr][1]; } if (rect[cntr][1] < minY) { minY = rect[cntr][1]; } } const boundingRect = output.boundingRect; boundingRect.x = minX; boundingRect.y = minY; boundingRect.width = maxX - minX; boundingRect.height = maxY - minY; position.top -= valOffsetTop; position.left -= valOffsetLeft; return output; } /** * Creates a transform matrix based on local data applied in order it was added. * * If no data object is provided then the source is the local transform data. If another data object is supplied * then the stored local transform order is applied then all remaining transform keys are applied. This allows the * construction of a transform matrix in advance of setting local data and is useful in collision detection. * * @param {object} [data] - PositionData instance or local transform data. * * @param {Matrix4} [output] - The output mat4 instance. * * @returns {Matrix4} Transform matrix. */ getMat4(data = this._data, output = mat4.create()) { const matrix = mat4.identity(output); let seenKeys = 0; const orderList = this.#orderList; for (let cntr = 0; cntr < orderList.length; cntr++) { const key = orderList[cntr]; switch (key) { case "rotateX": seenKeys |= transformKeysBitwise.rotateX; mat4.multiply(matrix, matrix, mat4.fromXRotation(s_MAT4_TEMP, degToRad(data[key]))); break; case "rotateY": seenKeys |= transformKeysBitwise.rotateY; mat4.multiply(matrix, matrix, mat4.fromYRotation(s_MAT4_TEMP, degToRad(data[key]))); break; case "rotateZ": seenKeys |= transformKeysBitwise.rotateZ; mat4.multiply(matrix, matrix, mat4.fromZRotation(s_MAT4_TEMP, degToRad(data[key]))); break; case "scale": seenKeys |= transformKeysBitwise.scale; s_SCALE_VECTOR[0] = s_SCALE_VECTOR[1] = data[key]; mat4.multiply(matrix, matrix, mat4.fromScaling(s_MAT4_TEMP, s_SCALE_VECTOR)); break; case "translateX": seenKeys |= transformKeysBitwise.translateX; s_TRANSLATE_VECTOR[0] = data.translateX; s_TRANSLATE_VECTOR[1] = 0; s_TRANSLATE_VECTOR[2] = 0; mat4.multiply(matrix, matrix, mat4.fromTranslation(s_MAT4_TEMP, s_TRANSLATE_VECTOR)); break; case "translateY": seenKeys |= transformKeysBitwise.translateY; s_TRANSLATE_VECTOR[0] = 0; s_TRANSLATE_VECTOR[1] = data.translateY; s_TRANSLATE_VECTOR[2] = 0; mat4.multiply(matrix, matrix, mat4.fromTranslation(s_MAT4_TEMP, s_TRANSLATE_VECTOR)); break; case "translateZ": seenKeys |= transformKeysBitwise.translateZ; s_TRANSLATE_VECTOR[0] = 0; s_TRANSLATE_VECTOR[1] = 0; s_TRANSLATE_VECTOR[2] = data.translateZ; mat4.multiply(matrix, matrix, mat4.fromTranslation(s_MAT4_TEMP, s_TRANSLATE_VECTOR)); break; } } if (data !== this._data) { for (let cntr = 0; cntr < transformKeys.length; cntr++) { const key = transformKeys[cntr]; if (data[key] === null || (seenKeys & transformKeysBitwise[key]) > 0) { continue; } switch (key) { case "rotateX": mat4.multiply(matrix, matrix, mat4.fromXRotation(s_MAT4_TEMP, degToRad(data[key]))); break; case "rotateY": mat4.multiply(matrix, matrix, mat4.fromYRotation(s_MAT4_TEMP, degToRad(data[key]))); break; case "rotateZ": mat4.multiply(matrix, matrix, mat4.fromZRotation(s_MAT4_TEMP, degToRad(data[key]))); break; case "scale": s_SCALE_VECTOR[0] = s_SCALE_VECTOR[1] = data[key]; mat4.multiply(matrix, matrix, mat4.fromScaling(s_MAT4_TEMP, s_SCALE_VECTOR)); break; case "translateX": s_TRANSLATE_VECTOR[0] = data[key]; s_TRANSLATE_VECTOR[1] = 0; s_TRANSLATE_VECTOR[2] = 0; mat4.multiply(matrix, matrix, mat4.fromTranslation(s_MAT4_TEMP, s_TRANSLATE_VECTOR)); break; case "translateY": s_TRANSLATE_VECTOR[0] = 0; s_TRANSLATE_VECTOR[1] = data[key]; s_TRANSLATE_VECTOR[2] = 0; mat4.multiply(matrix, matrix, mat4.fromTranslation(s_MAT4_TEMP, s_TRANSLATE_VECTOR)); break; case "translateZ": s_TRANSLATE_VECTOR[0] = 0; s_TRANSLATE_VECTOR[1] = 0; s_TRANSLATE_VECTOR[2] = data[key]; mat4.multiply(matrix, matrix, mat4.fromTranslation(s_MAT4_TEMP, s_TRANSLATE_VECTOR)); break; } } } return matrix; } /** * Provides an orthographic enhancement to convert left / top positional data to a translate operation. * * This transform matrix takes into account that the remaining operations are , but adds any left / top attributes from passed in data to * translate X / Y. * * If no data object is provided then the source is the local transform data. If another data object is supplied * then the stored local transform order is applied then all remaining transform keys are applied. This allows the * construction of a transform matrix in advance of setting local data and is useful in collision detection. * * @param {object} [data] - PositionData instance or local transform data. * * @param {Matrix4} [output] - The output mat4 instance. * * @returns {Matrix4} Transform matrix. */ getMat4Ortho(data = this._data, output = mat4.create()) { const matrix = mat4.identity(output); s_TRANSLATE_VECTOR[0] = (data.left ?? 0) + (data.translateX ?? 0); s_TRANSLATE_VECTOR[1] = (data.top ?? 0) + (data.translateY ?? 0); s_TRANSLATE_VECTOR[2] = data.translateZ ?? 0; mat4.multiply(matrix, matrix, mat4.fromTranslation(s_MAT4_TEMP, s_TRANSLATE_VECTOR)); if (data.scale !== null) { s_SCALE_VECTOR[0] = s_SCALE_VECTOR[1] = data.scale; mat4.multiply(matrix, matrix, mat4.fromScaling(s_MAT4_TEMP, s_SCALE_VECTOR)); } if (data.rotateX === null && data.rotateY === null && data.rotateZ === null) { return matrix; } let seenKeys = 0; const orderList = this.#orderList; for (let cntr = 0; cntr < orderList.length; cntr++) { const key = orderList[cntr]; switch (key) { case "rotateX": seenKeys |= transformKeysBitwise.rotateX; mat4.multiply(matrix, matrix, mat4.fromXRotation(s_MAT4_TEMP, degToRad(data[key]))); break; case "rotateY": seenKeys |= transformKeysBitwise.rotateY; mat4.multiply(matrix, matrix, mat4.fromYRotation(s_MAT4_TEMP, degToRad(data[key]))); break; case "rotateZ": seenKeys |= transformKeysBitwise.rotateZ; mat4.multiply(matrix, matrix, mat4.fromZRotation(s_MAT4_TEMP, degToRad(data[key]))); break; } } if (data !== this._data) { for (let cntr = 0; cntr < transformKeys.length; cntr++) { const key = transformKeys[cntr]; if (data[key] === null || (seenKeys & transformKeysBitwise[key]) > 0) { continue; } switch (key) { case "rotateX": mat4.multiply(matrix, matrix, mat4.fromXRotation(s_MAT4_TEMP, degToRad(data[key]))); break; case "rotateY": mat4.multiply(matrix, matrix, mat4.fromYRotation(s_MAT4_TEMP, degToRad(data[key]))); break; case "rotateZ": mat4.multiply(matrix, matrix, mat4.fromZRotation(s_MAT4_TEMP, degToRad(data[key]))); break; } } } return matrix; } /** * Tests an object if it contains transform keys and the values are finite numbers. * * @param {object} data - An object to test for transform data. * * @returns {boolean} Whether the given PositionData has transforms. */ hasTransform(data) { for (const key of transformKeys) { if (Number.isFinite(data[key])) { return true; } } return false; } /** * Resets internal data from the given object containing valid transform keys. * * @param {object} data - An object with transform data. */ reset(data) { for (const key in data) { if (transformKeys.includes(key)) { if (Number.isFinite(data[key])) { this._data[key] = data[key]; } else { const index = this.#orderList.findIndex((entry) => entry === key); if (index >= 0) { this.#orderList.splice(index, 1); } delete this._data[key]; } } } } } function s_GET_ORIGIN_TRANSLATION(transformOrigin, width2, height, output) { const vector = s_VEC3_TEMP; switch (transformOrigin) { case "top left": vector[0] = vector[1] = 0; mat4.fromTranslation(output[0], vector); mat4.fromTranslation(output[1], vector); break; case "top center": vector[0] = -width2 * 0.5; vector[1] = 0; mat4.fromTranslation(output[0], vector); vector[0] = width2 * 0.5; mat4.fromTranslation(output[1], vector); break; case "top right": vector[0] = -width2; vector[1] = 0; mat4.fromTranslation(output[0], vector); vector[0] = width2; mat4.fromTranslation(output[1], vector); break; case "center left": vector[0] = 0; vector[1] = -height * 0.5; mat4.fromTranslation(output[0], vector); vector[1] = height * 0.5; mat4.fromTranslation(output[1], vector); break; case null: case "center": vector[0] = -width2 * 0.5; vector[1] = -height * 0.5; mat4.fromTranslation(output[0], vector); vector[0] = width2 * 0.5; vector[1] = height * 0.5; mat4.fromTranslation(output[1], vector); break; case "center right": vector[0] = -width2; vector[1] = -height * 0.5; mat4.fromTranslation(output[0], vector); vector[0] = width2; vector[1] = height * 0.5; mat4.fromTranslation(output[1], vector); break; case "bottom left": vector[0] = 0; vector[1] = -height; mat4.fromTranslation(output[0], vector); vector[1] = height; mat4.fromTranslation(output[1], vector); break; case "bottom center": vector[0] = -width2 * 0.5; vector[1] = -height; mat4.fromTranslation(output[0], vector); vector[0] = width2 * 0.5; vector[1] = height; mat4.fromTranslation(output[1], vector); break; case "bottom right": vector[0] = -width2; vector[1] = -height; mat4.fromTranslation(output[0], vector); vector[0] = width2; vector[1] = height; mat4.fromTranslation(output[1], vector); break; default: mat4.identity(output[0]); mat4.identity(output[1]); break; } return output; } class UpdateElementData { constructor() { this.data = void 0; this.dataSubscribers = new PositionData(); this.dimensionData = { width: 0, height: 0 }; this.changeSet = void 0; this.options = void 0; this.queued = false; this.styleCache = void 0; this.transforms = void 0; this.transformData = new TransformData(); this.subscriptions = void 0; this.storeDimension = writable$1(this.dimensionData); this.storeTransform = writable$1(this.transformData, () => { this.options.transformSubscribed = true; return () => this.options.transformSubscribed = false; }); this.queued = false; Object.seal(this.dimensionData); } } async function nextAnimationFrame(cntr = 1) { if (!Number.isInteger(cntr) || cntr < 1) { throw new TypeError(`nextAnimationFrame error: 'cntr' must be a positive integer greater than 0.`); } let currentTime = performance.now(); for (; --cntr >= 0; ) { currentTime = await new Promise((resolve) => requestAnimationFrame(resolve)); } return currentTime; } class UpdateElementManager { static list = []; static listCntr = 0; static updatePromise; static get promise() { return this.updatePromise; } /** * Potentially adds the given element and internal updateData instance to the list. * * @param {HTMLElement} el - An HTMLElement instance. * * @param {UpdateElementData} updateData - An UpdateElementData instance. * * @returns {Promise} The unified next frame update promise. Returns `currentTime`. */ static add(el, updateData) { if (this.listCntr < this.list.length) { const entry = this.list[this.listCntr]; entry[0] = el; entry[1] = updateData; } else { this.list.push([el, updateData]); } this.listCntr++; updateData.queued = true; if (!this.updatePromise) { this.updatePromise = this.wait(); } return this.updatePromise; } /** * Await on `nextAnimationFrame` and iterate over list map invoking callback functions. * * @returns {Promise} The next frame Promise / currentTime from nextAnimationFrame. */ static async wait() { const currentTime = await nextAnimationFrame(); this.updatePromise = void 0; for (let cntr = this.listCntr; --cntr >= 0; ) { const entry = this.list[cntr]; const el = entry[0]; const updateData = entry[1]; entry[0] = void 0; entry[1] = void 0; updateData.queued = false; if (!el.isConnected) { continue; } if (updateData.options.ortho) { s_UPDATE_ELEMENT_ORTHO(el, updateData); } else { s_UPDATE_ELEMENT(el, updateData); } if (updateData.options.calculateTransform || updateData.options.transformSubscribed) { s_UPDATE_TRANSFORM(el, updateData); } this.updateSubscribers(updateData); } this.listCntr = 0; return currentTime; } /** * Potentially immediately updates the given element. * * @param {HTMLElement} el - An HTMLElement instance. * * @param {UpdateElementData} updateData - An UpdateElementData instance. */ static immediate(el, updateData) { if (!el.isConnected) { return; } if (updateData.options.ortho) { s_UPDATE_ELEMENT_ORTHO(el, updateData); } else { s_UPDATE_ELEMENT(el, updateData); } if (updateData.options.calculateTransform || updateData.options.transformSubscribed) { s_UPDATE_TRANSFORM(el, updateData); } this.updateSubscribers(updateData); } /** * @param {UpdateElementData} updateData - Data change set. */ static updateSubscribers(updateData) { const data = updateData.data; const changeSet = updateData.changeSet; if (!changeSet.hasChange()) { return; } const output = updateData.dataSubscribers.copy(data); const subscriptions = updateData.subscriptions; if (subscriptions.length > 0) { for (let cntr = 0; cntr < subscriptions.length; cntr++) { subscriptions[cntr](output); } } if (changeSet.width || changeSet.height) { updateData.dimensionData.width = data.width; updateData.dimensionData.height = data.height; updateData.storeDimension.set(updateData.dimensionData); } changeSet.set(false); } } function s_UPDATE_ELEMENT(el, updateData) { const changeSet = updateData.changeSet; const data = updateData.data; if (changeSet.left) { el.style.left = `${data.left}px`; } if (changeSet.top) { el.style.top = `${data.top}px`; } if (changeSet.zIndex) { el.style.zIndex = typeof data.zIndex === "number" ? `${data.zIndex}` : null; } if (changeSet.width) { el.style.width = typeof data.width === "number" ? `${data.width}px` : data.width; } if (changeSet.height) { el.style.height = typeof data.height === "number" ? `${data.height}px` : data.height; } if (changeSet.transformOrigin) { el.style.transformOrigin = data.transformOrigin === "center" ? null : data.transformOrigin; } if (changeSet.transform) { el.style.transform = updateData.transforms.isActive ? updateData.transforms.getCSS() : null; } } function s_UPDATE_ELEMENT_ORTHO(el, updateData) { const changeSet = updateData.changeSet; const data = updateData.data; if (changeSet.zIndex) { el.style.zIndex = typeof data.zIndex === "number" ? `${data.zIndex}` : null; } if (changeSet.width) { el.style.width = typeof data.width === "number" ? `${data.width}px` : data.width; } if (changeSet.height) { el.style.height = typeof data.height === "number" ? `${data.height}px` : data.height; } if (changeSet.transformOrigin) { el.style.transformOrigin = data.transformOrigin === "center" ? null : data.transformOrigin; } if (changeSet.left || changeSet.top || changeSet.transform) { el.style.transform = updateData.transforms.getCSSOrtho(data); } } function s_UPDATE_TRANSFORM(el, updateData) { s_VALIDATION_DATA$1.height = updateData.data.height !== "auto" ? updateData.data.height : updateData.styleCache.offsetHeight; s_VALIDATION_DATA$1.width = updateData.data.width !== "auto" ? updateData.data.width : updateData.styleCache.offsetWidth; s_VALIDATION_DATA$1.marginLeft = updateData.styleCache.marginLeft; s_VALIDATION_DATA$1.marginTop = updateData.styleCache.marginTop; updateData.transforms.getData(updateData.data, updateData.transformData, s_VALIDATION_DATA$1); updateData.storeTransform.set(updateData.transformData); } const s_VALIDATION_DATA$1 = { height: void 0, width: void 0, marginLeft: void 0, marginTop: void 0 }; class Position { /** * @type {PositionData} */ #data = new PositionData(); /** * Provides the animation API. * * @type {AnimationAPI} */ #animate = new AnimationAPI(this, this.#data); /** * Provides a way to turn on / off the position handling. * * @type {boolean} */ #enabled = true; /** * Stores the style attributes that changed on update. * * @type {PositionChangeSet} */ #positionChangeSet = new PositionChangeSet(); /** * Stores ongoing options that are set in the constructor or by transform store subscription. * * @type {PositionOptions} */ #options = { calculateTransform: false, initialHelper: void 0, ortho: true, transformSubscribed: false }; /** * The associated parent for positional data tracking. Used in validators. * * @type {PositionParent} */ #parent; /** * @type {StorePosition} */ #stores; /** * Stores an instance of the computer styles for the target element. * * @type {StyleCache} */ #styleCache; /** * Stores the subscribers. * * @type {(function(PositionData): void)[]} */ #subscriptions = []; /** * @type {Transforms} */ #transforms = new Transforms(); /** * @type {UpdateElementData} */ #updateElementData; /** * Stores the UpdateElementManager wait promise. * * @type {Promise} */ #updateElementPromise; /** * @type {AdapterValidators} */ #validators; /** * @type {ValidatorData[]} */ #validatorData; /** * @type {PositionStateAPI} */ #state = new PositionStateAPI(this, this.#data, this.#transforms); /** * @returns {AnimationGroupAPI} Public Animation API. */ static get Animate() { return AnimationGroupAPI; } /** * @returns {{browserCentered?: Centered, Centered?: *}} Initial position helpers. */ static get Initial() { return positionInitial; } /** * Returns TransformData class / constructor. * * @returns {TransformData} TransformData class / constructor. */ static get TransformData() { return TransformData; } /** * Returns default validators. * * Note: `basicWindow` and `BasicBounds` will eventually be removed. * * @returns {{basicWindow?: BasicBounds, transformWindow?: TransformBounds, TransformBounds?: *, BasicBounds?: *}} * Available validators. */ static get Validators() { return positionValidators; } /** * Returns a duplicate of a given position instance copying any options and validators. * * // TODO: Consider more safety over options processing. * * @param {Position} position - A position instance. * * @param {PositionOptions} options - Position options. * * @returns {Position} A duplicate position instance. */ static duplicate(position, options) { if (!(position instanceof Position)) { throw new TypeError(`'position' is not an instance of Position.`); } const newPosition = new Position(options); newPosition.#options = Object.assign({}, position.#options, options); newPosition.#validators.add(...position.#validators); newPosition.set(position.#data); return newPosition; } /** * @param {PositionParent|PositionOptionsAll} [parent] - A potential parent element or object w/ `elementTarget` * getter. May also be the PositionOptions object w/ 1 argument. * * @param {PositionOptionsAll} [options] - Default values. */ constructor(parent, options) { if (isPlainObject(parent)) { options = parent; } else { this.#parent = parent; } const data = this.#data; const transforms = this.#transforms; this.#styleCache = new StyleCache(); const updateData = new UpdateElementData(); updateData.changeSet = this.#positionChangeSet; updateData.data = this.#data; updateData.options = this.#options; updateData.styleCache = this.#styleCache; updateData.subscriptions = this.#subscriptions; updateData.transforms = this.#transforms; this.#updateElementData = updateData; if (isObject(options)) { if (typeof options.calculateTransform === "boolean") { this.#options.calculateTransform = options.calculateTransform; } if (typeof options.ortho === "boolean") { this.#options.ortho = options.ortho; } if (Number.isFinite(options.height) || options.height === "auto" || options.height === "inherit" || options.height === null) { data.height = updateData.dimensionData.height = typeof options.height === "number" ? Math.round(options.height) : options.height; } if (Number.isFinite(options.left) || options.left === null) { data.left = typeof options.left === "number" ? Math.round(options.left) : options.left; } if (Number.isFinite(options.maxHeight) || options.maxHeight === null) { data.maxHeight = typeof options.maxHeight === "number" ? Math.round(options.maxHeight) : options.maxHeight; } if (Number.isFinite(options.maxWidth) || options.maxWidth === null) { data.maxWidth = typeof options.maxWidth === "number" ? Math.round(options.maxWidth) : options.maxWidth; } if (Number.isFinite(options.minHeight) || options.minHeight === null) { data.minHeight = typeof options.minHeight === "number" ? Math.round(options.minHeight) : options.minHeight; } if (Number.isFinite(options.minWidth) || options.minWidth === null) { data.minWidth = typeof options.minWidth === "number" ? Math.round(options.minWidth) : options.minWidth; } if (Number.isFinite(options.rotateX) || options.rotateX === null) { transforms.rotateX = data.rotateX = options.rotateX; } if (Number.isFinite(options.rotateY) || options.rotateY === null) { transforms.rotateY = data.rotateY = options.rotateY; } if (Number.isFinite(options.rotateZ) || options.rotateZ === null) { transforms.rotateZ = data.rotateZ = options.rotateZ; } if (Number.isFinite(options.scale) || options.scale === null) { transforms.scale = data.scale = options.scale; } if (Number.isFinite(options.top) || options.top === null) { data.top = typeof options.top === "number" ? Math.round(options.top) : options.top; } if (typeof options.transformOrigin === "string" || options.transformOrigin === null) { data.transformOrigin = transformOrigins.includes(options.transformOrigin) ? options.transformOrigin : null; } if (Number.isFinite(options.translateX) || options.translateX === null) { transforms.translateX = data.translateX = options.translateX; } if (Number.isFinite(options.translateY) || options.translateY === null) { transforms.translateY = data.translateY = options.translateY; } if (Number.isFinite(options.translateZ) || options.translateZ === null) { transforms.translateZ = data.translateZ = options.translateZ; } if (Number.isFinite(options.width) || options.width === "auto" || options.width === "inherit" || options.width === null) { data.width = updateData.dimensionData.width = typeof options.width === "number" ? Math.round(options.width) : options.width; } if (Number.isFinite(options.zIndex) || options.zIndex === null) { data.zIndex = typeof options.zIndex === "number" ? Math.round(options.zIndex) : options.zIndex; } } this.#stores = { // The main properties for manipulating Position. height: propertyStore(this, "height"), left: propertyStore(this, "left"), rotateX: propertyStore(this, "rotateX"), rotateY: propertyStore(this, "rotateY"), rotateZ: propertyStore(this, "rotateZ"), scale: propertyStore(this, "scale"), top: propertyStore(this, "top"), transformOrigin: propertyStore(this, "transformOrigin"), translateX: propertyStore(this, "translateX"), translateY: propertyStore(this, "translateY"), translateZ: propertyStore(this, "translateZ"), width: propertyStore(this, "width"), zIndex: propertyStore(this, "zIndex"), // Stores that control validation when width / height is not `auto`. maxHeight: propertyStore(this, "maxHeight"), maxWidth: propertyStore(this, "maxWidth"), minHeight: propertyStore(this, "minHeight"), minWidth: propertyStore(this, "minWidth"), // Readable stores based on updates or from resize observer changes. dimension: { subscribe: updateData.storeDimension.subscribe }, element: { subscribe: this.#styleCache.stores.element.subscribe }, resizeContentHeight: { subscribe: this.#styleCache.stores.resizeContentHeight.subscribe }, resizeContentWidth: { subscribe: this.#styleCache.stores.resizeContentWidth.subscribe }, resizeOffsetHeight: { subscribe: this.#styleCache.stores.resizeOffsetHeight.subscribe }, resizeOffsetWidth: { subscribe: this.#styleCache.stores.resizeOffsetWidth.subscribe }, transform: { subscribe: updateData.storeTransform.subscribe }, // Protected store that should only be set by resizeObserver action. resizeObserved: this.#styleCache.stores.resizeObserved }; subscribeIgnoreFirst(this.#stores.resizeObserved, (resizeData) => { const parent2 = this.#parent; const el = parent2 instanceof HTMLElement ? parent2 : parent2?.elementTarget; if (el instanceof HTMLElement && Number.isFinite(resizeData?.offsetWidth) && Number.isFinite(resizeData?.offsetHeight)) { this.set(data); } }); this.#stores.transformOrigin.values = transformOrigins; [this.#validators, this.#validatorData] = new AdapterValidators(); if (options?.initial || options?.positionInitial) { const initialHelper = options.initial ?? options.positionInitial; if (typeof initialHelper?.getLeft !== "function" || typeof initialHelper?.getTop !== "function") { throw new Error( `'options.initial' position helper does not contain 'getLeft' and / or 'getTop' functions.` ); } this.#options.initialHelper = options.initial; } if (options?.validator) { if (isIterable(options?.validator)) { this.validators.add(...options.validator); } else { this.validators.add(options.validator); } } } /** * Returns the animation API. * * @returns {AnimationAPI} Animation API. */ get animate() { return this.#animate; } /** * Returns the dimension data for the readable store. * * @returns {{width: number | 'auto', height: number | 'auto'}} Dimension data. */ get dimension() { return this.#updateElementData.dimensionData; } /** * Returns the enabled state. * * @returns {boolean} Enabled state. */ get enabled() { return this.#enabled; } /** * Returns the current HTMLElement being positioned. * * @returns {HTMLElement|undefined} Current HTMLElement being positioned. */ get element() { return this.#styleCache.el; } /** * Returns a promise that is resolved on the next element update with the time of the update. * * @returns {Promise} Promise resolved on element update. */ get elementUpdated() { return this.#updateElementPromise; } /** * Returns the associated {@link PositionParent} instance. * * @returns {PositionParent} The PositionParent instance. */ get parent() { return this.#parent; } /** * Returns the state API. * * @returns {PositionStateAPI} Position state API. */ get state() { return this.#state; } /** * Returns the derived writable stores for individual data variables. * * @returns {StorePosition} Derived / writable stores. */ get stores() { return this.#stores; } /** * Returns the transform data for the readable store. * * @returns {TransformData} Transform Data. */ get transform() { return this.#updateElementData.transformData; } /** * Returns the validators. * * @returns {AdapterValidators} validators. */ get validators() { return this.#validators; } /** * Sets the enabled state. * * @param {boolean} enabled - New enabled state. */ set enabled(enabled) { if (typeof enabled !== "boolean") { throw new TypeError(`'enabled' is not a boolean.`); } this.#enabled = enabled; } /** * Sets the associated {@link PositionParent} instance. Resets the style cache and default data. * * @param {PositionParent|void} parent - A PositionParent instance. */ set parent(parent) { if (parent !== void 0 && !(parent instanceof HTMLElement) && !isObject(parent)) { throw new TypeError(`'parent' is not an HTMLElement, object, or undefined.`); } this.#parent = parent; this.#state.remove({ name: "#defaultData" }); this.#styleCache.reset(); if (parent) { this.set(this.#data); } } // Data accessors ---------------------------------------------------------------------------------------------------- /** * @returns {number|'auto'|'inherit'|null} height */ get height() { return this.#data.height; } /** * @returns {number|null} left */ get left() { return this.#data.left; } /** * @returns {number|null} maxHeight */ get maxHeight() { return this.#data.maxHeight; } /** * @returns {number|null} maxWidth */ get maxWidth() { return this.#data.maxWidth; } /** * @returns {number|null} minHeight */ get minHeight() { return this.#data.minHeight; } /** * @returns {number|null} minWidth */ get minWidth() { return this.#data.minWidth; } /** * @returns {number|null} rotateX */ get rotateX() { return this.#data.rotateX; } /** * @returns {number|null} rotateY */ get rotateY() { return this.#data.rotateY; } /** * @returns {number|null} rotateZ */ get rotateZ() { return this.#data.rotateZ; } /** * @returns {number|null} alias for rotateZ */ get rotation() { return this.#data.rotateZ; } /** * @returns {number|null} scale */ get scale() { return this.#data.scale; } /** * @returns {number|null} top */ get top() { return this.#data.top; } /** * @returns {string} transformOrigin */ get transformOrigin() { return this.#data.transformOrigin; } /** * @returns {number|null} translateX */ get translateX() { return this.#data.translateX; } /** * @returns {number|null} translateY */ get translateY() { return this.#data.translateY; } /** * @returns {number|null} translateZ */ get translateZ() { return this.#data.translateZ; } /** * @returns {number|'auto'|'inherit'|null} width */ get width() { return this.#data.width; } /** * @returns {number|null} z-index */ get zIndex() { return this.#data.zIndex; } /** * @param {number|string|null} height - */ set height(height) { this.#stores.height.set(height); } /** * @param {number|string|null} left - */ set left(left) { this.#stores.left.set(left); } /** * @param {number|string|null} maxHeight - */ set maxHeight(maxHeight) { this.#stores.maxHeight.set(maxHeight); } /** * @param {number|string|null} maxWidth - */ set maxWidth(maxWidth) { this.#stores.maxWidth.set(maxWidth); } /** * @param {number|string|null} minHeight - */ set minHeight(minHeight) { this.#stores.minHeight.set(minHeight); } /** * @param {number|string|null} minWidth - */ set minWidth(minWidth) { this.#stores.minWidth.set(minWidth); } /** * @param {number|string|null} rotateX - */ set rotateX(rotateX) { this.#stores.rotateX.set(rotateX); } /** * @param {number|string|null} rotateY - */ set rotateY(rotateY) { this.#stores.rotateY.set(rotateY); } /** * @param {number|string|null} rotateZ - */ set rotateZ(rotateZ) { this.#stores.rotateZ.set(rotateZ); } /** * @param {number|string|null} rotateZ - alias for rotateZ */ set rotation(rotateZ) { this.#stores.rotateZ.set(rotateZ); } /** * @param {number|string|null} scale - */ set scale(scale2) { this.#stores.scale.set(scale2); } /** * @param {number|string|null} top - */ set top(top) { this.#stores.top.set(top); } /** * @param {string} transformOrigin - */ set transformOrigin(transformOrigin) { if (transformOrigins.includes(transformOrigin)) { this.#stores.transformOrigin.set(transformOrigin); } } /** * @param {number|string|null} translateX - */ set translateX(translateX) { this.#stores.translateX.set(translateX); } /** * @param {number|string|null} translateY - */ set translateY(translateY) { this.#stores.translateY.set(translateY); } /** * @param {number|string|null} translateZ - */ set translateZ(translateZ) { this.#stores.translateZ.set(translateZ); } /** * @param {number|string|null} width - */ set width(width2) { this.#stores.width.set(width2); } /** * @param {number|string|null} zIndex - */ set zIndex(zIndex) { this.#stores.zIndex.set(zIndex); } /** * Assigns current position to object passed into method. * * @param {object|PositionData} [position] - Target to assign current position data. * * @param {PositionGetOptions} [options] - Defines options for specific keys and substituting null for numeric * default values. * * @returns {PositionData} Passed in object with current position data. */ get(position = {}, options) { const keys = options?.keys; const excludeKeys = options?.exclude; const numeric = options?.numeric ?? false; if (isIterable(keys)) { if (numeric) { for (const key of keys) { position[key] = this[key] ?? numericDefaults[key]; } } else { for (const key of keys) { position[key] = this[key]; } } if (isIterable(excludeKeys)) { for (const key of excludeKeys) { delete position[key]; } } return position; } else { const data = Object.assign(position, this.#data); if (isIterable(excludeKeys)) { for (const key of excludeKeys) { delete data[key]; } } if (numeric) { setNumericDefaults(data); } return data; } } /** * @returns {PositionData} Current position data. */ toJSON() { return Object.assign({}, this.#data); } /** * All calculation and updates of position are implemented in {@link Position}. This allows position to be fully * reactive and in control of updating inline styles for the application. * * Note: the logic for updating position is improved and changes a few aspects from the default * {@link Application.setPosition}. The gate on `popOut` is removed, so to ensure no positional application occurs * popOut applications can set `this.options.positionable` to false ensuring no positional inline styles are * applied. * * The initial set call on an application with a target element will always set width / height as this is * necessary for correct calculations. * * When a target element is present updated styles are applied after validation. To modify the behavior of set * implement one or more validator functions and add them from the application via * `this.position.validators.add()`. * * Updates to any target element are decoupled from the underlying Position data. This method returns this instance * that you can then await on the target element inline style update by using {@link Position.elementUpdated}. * * @param {PositionDataExtended} [position] - Position data to set. * * @returns {Position} This Position instance. */ set(position = {}) { if (typeof position !== "object") { throw new TypeError(`Position - set error: 'position' is not an object.`); } const parent = this.#parent; if (!this.#enabled) { return this; } if (parent !== void 0 && typeof parent?.options?.positionable === "boolean" && !parent?.options?.positionable) { return this; } const immediateElementUpdate = position.immediateElementUpdate === true; const data = this.#data; const transforms = this.#transforms; const targetEl = parent instanceof HTMLElement ? parent : parent?.elementTarget; const el = targetEl instanceof HTMLElement && targetEl.isConnected ? targetEl : void 0; const changeSet = this.#positionChangeSet; const styleCache = this.#styleCache; if (el) { if (!styleCache.hasData(el)) { styleCache.update(el); if (!styleCache.hasWillChange) ; changeSet.set(true); this.#updateElementData.queued = false; } convertRelative(position, this); position = this.#updatePosition(position, parent, el, styleCache); if (position === null) { return this; } } if (Number.isFinite(position.left)) { position.left = Math.round(position.left); if (data.left !== position.left) { data.left = position.left; changeSet.left = true; } } if (Number.isFinite(position.top)) { position.top = Math.round(position.top); if (data.top !== position.top) { data.top = position.top; changeSet.top = true; } } if (Number.isFinite(position.maxHeight) || position.maxHeight === null) { position.maxHeight = typeof position.maxHeight === "number" ? Math.round(position.maxHeight) : null; if (data.maxHeight !== position.maxHeight) { data.maxHeight = position.maxHeight; changeSet.maxHeight = true; } } if (Number.isFinite(position.maxWidth) || position.maxWidth === null) { position.maxWidth = typeof position.maxWidth === "number" ? Math.round(position.maxWidth) : null; if (data.maxWidth !== position.maxWidth) { data.maxWidth = position.maxWidth; changeSet.maxWidth = true; } } if (Number.isFinite(position.minHeight) || position.minHeight === null) { position.minHeight = typeof position.minHeight === "number" ? Math.round(position.minHeight) : null; if (data.minHeight !== position.minHeight) { data.minHeight = position.minHeight; changeSet.minHeight = true; } } if (Number.isFinite(position.minWidth) || position.minWidth === null) { position.minWidth = typeof position.minWidth === "number" ? Math.round(position.minWidth) : null; if (data.minWidth !== position.minWidth) { data.minWidth = position.minWidth; changeSet.minWidth = true; } } if (Number.isFinite(position.rotateX) || position.rotateX === null) { if (data.rotateX !== position.rotateX) { data.rotateX = transforms.rotateX = position.rotateX; changeSet.transform = true; } } if (Number.isFinite(position.rotateY) || position.rotateY === null) { if (data.rotateY !== position.rotateY) { data.rotateY = transforms.rotateY = position.rotateY; changeSet.transform = true; } } if (Number.isFinite(position.rotateZ) || position.rotateZ === null) { if (data.rotateZ !== position.rotateZ) { data.rotateZ = transforms.rotateZ = position.rotateZ; changeSet.transform = true; } } if (Number.isFinite(position.scale) || position.scale === null) { position.scale = typeof position.scale === "number" ? Math.max(0, Math.min(position.scale, 1e3)) : null; if (data.scale !== position.scale) { data.scale = transforms.scale = position.scale; changeSet.transform = true; } } if (typeof position.transformOrigin === "string" && transformOrigins.includes( position.transformOrigin ) || position.transformOrigin === null) { if (data.transformOrigin !== position.transformOrigin) { data.transformOrigin = position.transformOrigin; changeSet.transformOrigin = true; } } if (Number.isFinite(position.translateX) || position.translateX === null) { if (data.translateX !== position.translateX) { data.translateX = transforms.translateX = position.translateX; changeSet.transform = true; } } if (Number.isFinite(position.translateY) || position.translateY === null) { if (data.translateY !== position.translateY) { data.translateY = transforms.translateY = position.translateY; changeSet.transform = true; } } if (Number.isFinite(position.translateZ) || position.translateZ === null) { if (data.translateZ !== position.translateZ) { data.translateZ = transforms.translateZ = position.translateZ; changeSet.transform = true; } } if (Number.isFinite(position.zIndex)) { position.zIndex = Math.round(position.zIndex); if (data.zIndex !== position.zIndex) { data.zIndex = position.zIndex; changeSet.zIndex = true; } } if (Number.isFinite(position.width) || position.width === "auto" || position.width === "inherit" || position.width === null) { position.width = typeof position.width === "number" ? Math.round(position.width) : position.width; if (data.width !== position.width) { data.width = position.width; changeSet.width = true; } } if (Number.isFinite(position.height) || position.height === "auto" || position.height === "inherit" || position.height === null) { position.height = typeof position.height === "number" ? Math.round(position.height) : position.height; if (data.height !== position.height) { data.height = position.height; changeSet.height = true; } } if (el) { const defaultData = this.#state.getDefault(); if (typeof defaultData !== "object") { this.#state.save({ name: "#defaultData", ...Object.assign({}, data) }); } if (immediateElementUpdate) { UpdateElementManager.immediate(el, this.#updateElementData); this.#updateElementPromise = Promise.resolve(performance.now()); } else if (!this.#updateElementData.queued) { this.#updateElementPromise = UpdateElementManager.add(el, this.#updateElementData); } } else { UpdateElementManager.updateSubscribers(this.#updateElementData); } return this; } /** * * @param {function(PositionData): void} handler - Callback function that is invoked on update / changes. Receives * a copy of the PositionData. * * @returns {(function(): void)} Unsubscribe function. */ subscribe(handler) { this.#subscriptions.push(handler); handler(Object.assign({}, this.#data)); return () => { const index = this.#subscriptions.findIndex((sub) => sub === handler); if (index >= 0) { this.#subscriptions.splice(index, 1); } }; } /** * @param {PositionDataExtended} opts - * * @param {number|null} opts.left - * * @param {number|null} opts.top - * * @param {number|null} opts.maxHeight - * * @param {number|null} opts.maxWidth - * * @param {number|null} opts.minHeight - * * @param {number|null} opts.minWidth - * * @param {number|'auto'|null} opts.width - * * @param {number|'auto'|null} opts.height - * * @param {number|null} opts.rotateX - * * @param {number|null} opts.rotateY - * * @param {number|null} opts.rotateZ - * * @param {number|null} opts.scale - * * @param {string} opts.transformOrigin - * * @param {number|null} opts.translateX - * * @param {number|null} opts.translateY - * * @param {number|null} opts.translateZ - * * @param {number|null} opts.zIndex - * * @param {number|null} opts.rotation - alias for rotateZ * * @param {*} opts.rest - * * @param {object} parent - * * @param {HTMLElement} el - * * @param {StyleCache} styleCache - * * @returns {null|PositionData} Updated position data or null if validation fails. */ #updatePosition({ // Directly supported parameters left, top, maxWidth, maxHeight, minWidth, minHeight, width: width2, height, rotateX, rotateY, rotateZ, scale: scale2, transformOrigin, translateX, translateY, translateZ, zIndex, // Aliased parameters rotation: rotation2, ...rest } = {}, parent, el, styleCache) { let currentPosition = s_DATA_UPDATE.copy(this.#data); if (el.style.width === "" || width2 !== void 0) { if (width2 === "auto" || currentPosition.width === "auto" && width2 !== null) { currentPosition.width = "auto"; width2 = styleCache.offsetWidth; } else if (width2 === "inherit" || currentPosition.width === "inherit" && width2 !== null) { currentPosition.width = "inherit"; width2 = styleCache.offsetWidth; } else { const newWidth = Number.isFinite(width2) ? width2 : currentPosition.width; currentPosition.width = width2 = Number.isFinite(newWidth) ? Math.round(newWidth) : styleCache.offsetWidth; } } else { width2 = Number.isFinite(currentPosition.width) ? currentPosition.width : styleCache.offsetWidth; } if (el.style.height === "" || height !== void 0) { if (height === "auto" || currentPosition.height === "auto" && height !== null) { currentPosition.height = "auto"; height = styleCache.offsetHeight; } else if (height === "inherit" || currentPosition.height === "inherit" && height !== null) { currentPosition.height = "inherit"; height = styleCache.offsetHeight; } else { const newHeight = Number.isFinite(height) ? height : currentPosition.height; currentPosition.height = height = Number.isFinite(newHeight) ? Math.round(newHeight) : styleCache.offsetHeight; } } else { height = Number.isFinite(currentPosition.height) ? currentPosition.height : styleCache.offsetHeight; } if (Number.isFinite(left)) { currentPosition.left = left; } else if (!Number.isFinite(currentPosition.left)) { currentPosition.left = typeof this.#options.initialHelper?.getLeft === "function" ? this.#options.initialHelper.getLeft(width2) : 0; } if (Number.isFinite(top)) { currentPosition.top = top; } else if (!Number.isFinite(currentPosition.top)) { currentPosition.top = typeof this.#options.initialHelper?.getTop === "function" ? this.#options.initialHelper.getTop(height) : 0; } if (Number.isFinite(maxHeight) || maxHeight === null) { currentPosition.maxHeight = Number.isFinite(maxHeight) ? Math.round(maxHeight) : null; } if (Number.isFinite(maxWidth) || maxWidth === null) { currentPosition.maxWidth = Number.isFinite(maxWidth) ? Math.round(maxWidth) : null; } if (Number.isFinite(minHeight) || minHeight === null) { currentPosition.minHeight = Number.isFinite(minHeight) ? Math.round(minHeight) : null; } if (Number.isFinite(minWidth) || minWidth === null) { currentPosition.minWidth = Number.isFinite(minWidth) ? Math.round(minWidth) : null; } if (Number.isFinite(rotateX) || rotateX === null) { currentPosition.rotateX = rotateX; } if (Number.isFinite(rotateY) || rotateY === null) { currentPosition.rotateY = rotateY; } if (rotateZ !== currentPosition.rotateZ && (Number.isFinite(rotateZ) || rotateZ === null)) { currentPosition.rotateZ = rotateZ; } else if (rotation2 !== currentPosition.rotateZ && (Number.isFinite(rotation2) || rotation2 === null)) { currentPosition.rotateZ = rotation2; } if (Number.isFinite(translateX) || translateX === null) { currentPosition.translateX = translateX; } if (Number.isFinite(translateY) || translateY === null) { currentPosition.translateY = translateY; } if (Number.isFinite(translateZ) || translateZ === null) { currentPosition.translateZ = translateZ; } if (Number.isFinite(scale2) || scale2 === null) { currentPosition.scale = typeof scale2 === "number" ? Math.max(0, Math.min(scale2, 1e3)) : null; } if (typeof transformOrigin === "string" || transformOrigin === null) { currentPosition.transformOrigin = transformOrigins.includes(transformOrigin) ? transformOrigin : null; } if (Number.isFinite(zIndex) || zIndex === null) { currentPosition.zIndex = typeof zIndex === "number" ? Math.round(zIndex) : zIndex; } const validatorData = this.#validatorData; if (this.#validators.enabled && validatorData.length) { s_VALIDATION_DATA.parent = parent; s_VALIDATION_DATA.el = el; s_VALIDATION_DATA.computed = styleCache.computed; s_VALIDATION_DATA.transforms = this.#transforms; s_VALIDATION_DATA.height = height; s_VALIDATION_DATA.width = width2; s_VALIDATION_DATA.marginLeft = styleCache.marginLeft; s_VALIDATION_DATA.marginTop = styleCache.marginTop; s_VALIDATION_DATA.maxHeight = styleCache.maxHeight ?? currentPosition.maxHeight; s_VALIDATION_DATA.maxWidth = styleCache.maxWidth ?? currentPosition.maxWidth; const isMinimized = parent?.reactive?.minimized ?? false; s_VALIDATION_DATA.minHeight = isMinimized ? currentPosition.minHeight ?? 0 : styleCache.minHeight || (currentPosition.minHeight ?? 0); s_VALIDATION_DATA.minWidth = isMinimized ? currentPosition.minWidth ?? 0 : styleCache.minWidth || (currentPosition.minWidth ?? 0); for (let cntr = 0; cntr < validatorData.length; cntr++) { s_VALIDATION_DATA.position = currentPosition; s_VALIDATION_DATA.rest = rest; currentPosition = validatorData[cntr].validator(s_VALIDATION_DATA); if (currentPosition === null) { return null; } } } return currentPosition; } } const s_DATA_UPDATE = new PositionData(); const s_VALIDATION_DATA = { position: void 0, parent: void 0, el: void 0, computed: void 0, transforms: void 0, height: void 0, width: void 0, marginLeft: void 0, marginTop: void 0, maxHeight: void 0, maxWidth: void 0, minHeight: void 0, minWidth: void 0, rest: void 0 }; Object.seal(s_VALIDATION_DATA); class ApplicationState { /** @type {ApplicationShellExt} */ #application; /** @type {Map} */ #dataSaved = /* @__PURE__ */ new Map(); /** * @param {ApplicationShellExt} application - The application. */ constructor(application) { this.#application = application; Object.seal(this); } /** * Returns current application state along with any extra data passed into method. * * @param {object} [extra] - Extra data to add to application state. * * @returns {ApplicationStateData} Passed in object with current application state. */ get(extra = {}) { return Object.assign(extra, { position: this.#application?.position?.get(), beforeMinimized: this.#application?.position?.state.get({ name: "#beforeMinimized" }), options: Object.assign({}, this.#application?.options), ui: { minimized: this.#application?.reactive?.minimized } }); } /** * Returns any stored save state by name. * * @param {string} name - Saved data set name. * * @returns {ApplicationStateData} The saved data set. */ getSave({ name }) { if (typeof name !== "string") { throw new TypeError(`ApplicationState - getSave error: 'name' is not a string.`); } return this.#dataSaved.get(name); } /** * Removes and returns any application state by name. * * @param {object} options - Options. * * @param {string} options.name - Name to remove and retrieve. * * @returns {ApplicationStateData} Saved application data. */ remove({ name }) { if (typeof name !== "string") { throw new TypeError(`ApplicationState - remove: 'name' is not a string.`); } const data = this.#dataSaved.get(name); this.#dataSaved.delete(name); return data; } /** * Restores a saved application state returning the data. Several optional parameters are available * to control whether the restore action occurs silently (no store / inline styles updates), animates * to the stored data, or simply sets the stored data. Restoring via {@link AnimationAPI.to} allows * specification of the duration, easing, and interpolate functions along with configuring a Promise to be * returned if awaiting the end of the animation. * * @param {object} params - Parameters * * @param {string} params.name - Saved data set name. * * @param {boolean} [params.remove=false] - Remove data set. * * @param {boolean} [params.async=false] - If animating return a Promise that resolves with any saved data. * * @param {boolean} [params.animateTo=false] - Animate to restore data. * * @param {number} [params.duration=0.1] - Duration in seconds. * * @param {Function} [params.ease=linear] - Easing function. * * @param {Function} [params.interpolate=lerp] - Interpolation function. * * @returns {ApplicationStateData|Promise} Saved application data. */ restore({ name, remove = false, async = false, animateTo = false, duration = 0.1, ease = identity, interpolate: interpolate2 = lerp$5 }) { if (typeof name !== "string") { throw new TypeError(`ApplicationState - restore error: 'name' is not a string.`); } const dataSaved = this.#dataSaved.get(name); if (dataSaved) { if (remove) { this.#dataSaved.delete(name); } if (async) { return this.set(dataSaved, { async, animateTo, duration, ease, interpolate: interpolate2 }).then(() => dataSaved); } else { this.set(dataSaved, { async, animateTo, duration, ease, interpolate: interpolate2 }); } } return dataSaved; } /** * Saves current application state with the opportunity to add extra data to the saved state. * * @param {object} options - Options. * * @param {string} options.name - name to index this saved data. * * @param {...*} [options.extra] - Extra data to add to saved data. * * @returns {ApplicationStateData} Current application data */ save({ name, ...extra }) { if (typeof name !== "string") { throw new TypeError(`ApplicationState - save error: 'name' is not a string.`); } const data = this.get(extra); this.#dataSaved.set(name, data); return data; } /** * Restores a saved application state returning the data. Several optional parameters are available * to control whether the restore action occurs silently (no store / inline styles updates), animates * to the stored data, or simply sets the stored data. Restoring via {@link AnimationAPI.to} allows * specification of the duration, easing, and interpolate functions along with configuring a Promise to be * returned if awaiting the end of the animation. * * Note: If serializing application state any minimized apps will use the before minimized state on initial render * of the app as it is currently not possible to render apps with Foundry VTT core API in the minimized state. * * TODO: THIS METHOD NEEDS TO BE REFACTORED WHEN TRL IS MADE INTO A STANDALONE FRAMEWORK. * * @param {ApplicationStateData} data - Saved data set name. * * @param {object} [opts] - Optional parameters * * @param {boolean} [opts.async=false] - If animating return a Promise that resolves with any saved data. * * @param {boolean} [opts.animateTo=false] - Animate to restore data. * * @param {number} [opts.duration=0.1] - Duration in seconds. * * @param {Function} [opts.ease=linear] - Easing function. * * @param {Function} [opts.interpolate=lerp] - Interpolation function. * * @returns {ApplicationShellExt|Promise} When synchronous the application or Promise when * animating resolving with application. */ set(data, { async = false, animateTo = false, duration = 0.1, ease = identity, interpolate: interpolate2 = lerp$5 } = {}) { if (!isObject(data)) { throw new TypeError(`ApplicationState - restore error: 'data' is not an object.`); } const application = this.#application; if (!isObject(data?.position)) { console.warn(`ApplicationState.set warning: 'data.position' is not an object.`); return application; } const rendered = application.rendered; if (animateTo && !rendered) { console.warn(`ApplicationState.set warning: Application is not rendered and 'animateTo' is true.`); return application; } if (animateTo) { if (data.position.transformOrigin !== application.position.transformOrigin) { application.position.transformOrigin = data.position.transformOrigin; } if (isObject(data?.ui)) { const minimized = typeof data.ui?.minimized === "boolean" ? data.ui.minimized : false; if (application?.reactive?.minimized && !minimized) { application.maximize({ animate: false, duration: 0 }); } } const promise2 = application.position.animate.to( data.position, { duration, ease, interpolate: interpolate2 } ).finished.then((cancelled) => { if (cancelled) { return application; } if (isObject(data?.options)) { application?.reactive.mergeOptions(data.options); } if (isObject(data?.ui)) { const minimized = typeof data.ui?.minimized === "boolean" ? data.ui.minimized : false; if (!application?.reactive?.minimized && minimized) { application.minimize({ animate: false, duration: 0 }); } } if (isObject(data?.beforeMinimized)) { application.position.state.set({ name: "#beforeMinimized", ...data.beforeMinimized }); } return application; }); if (async) { return promise2; } } else { if (rendered) { if (isObject(data?.options)) { application?.reactive.mergeOptions(data.options); } if (isObject(data?.ui)) { const minimized = typeof data.ui?.minimized === "boolean" ? data.ui.minimized : false; if (application?.reactive?.minimized && !minimized) { application.maximize({ animate: false, duration: 0 }); } else if (!application?.reactive?.minimized && minimized) { application.minimize({ animate: false, duration }); } } if (isObject(data?.beforeMinimized)) { application.position.state.set({ name: "#beforeMinimized", ...data.beforeMinimized }); } application.position.set(data.position); } else { let positionData = data.position; if (isObject(data.beforeMinimized)) { positionData = data.beforeMinimized; positionData.left = data.position.left; positionData.top = data.position.top; } application.position.set(positionData); } } return application; } } class GetSvelteData { /** * @type {MountedAppShell[]|null[]} */ #applicationShellHolder; /** * @type {SvelteData[]} */ #svelteData; /** * Keep a direct reference to the SvelteData array in an associated {@link SvelteApplication}. * * @param {MountedAppShell[]|null[]} applicationShellHolder - A reference to the MountedAppShell array. * * @param {SvelteData[]} svelteData - A reference to the SvelteData array of mounted components. */ constructor(applicationShellHolder, svelteData) { this.#applicationShellHolder = applicationShellHolder; this.#svelteData = svelteData; } /** * Returns any mounted {@link MountedAppShell}. * * @returns {MountedAppShell|null} Any mounted application shell. */ get applicationShell() { return this.#applicationShellHolder[0]; } /** * Returns the indexed Svelte component. * * @param {number} index - * * @returns {object} The loaded Svelte component. */ component(index) { const data = this.#svelteData[index]; return isObject(data) ? data?.component : void 0; } /** * Returns the Svelte component entries iterator. * * @returns {Generator>} Svelte component entries iterator. * @yields */ *componentEntries() { for (let cntr = 0; cntr < this.#svelteData.length; cntr++) { yield [cntr, this.#svelteData[cntr].component]; } } /** * Returns the Svelte component values iterator. * * @returns {Generator} Svelte component values iterator. * @yields */ *componentValues() { for (let cntr = 0; cntr < this.#svelteData.length; cntr++) { yield this.#svelteData[cntr].component; } } /** * Returns the indexed SvelteData entry. * * @param {number} index - * * @returns {SvelteData} The loaded Svelte config + component. */ data(index) { return this.#svelteData[index]; } /** * Returns the {@link SvelteData} instance for a given component. * * @param {object} component - Svelte component. * * @returns {SvelteData} - The loaded Svelte config + component. */ dataByComponent(component) { for (const data of this.#svelteData) { if (data.component === component) { return data; } } return void 0; } /** * Returns the SvelteData entries iterator. * * @returns {IterableIterator<[number, SvelteData]>} SvelteData entries iterator. */ dataEntries() { return this.#svelteData.entries(); } /** * Returns the SvelteData values iterator. * * @returns {IterableIterator} SvelteData values iterator. */ dataValues() { return this.#svelteData.values(); } /** * Returns the length of the mounted Svelte component list. * * @returns {number} Length of mounted Svelte component list. */ get length() { return this.#svelteData.length; } } function loadSvelteConfig({ app, template, config, elementRootUpdate } = {}) { const svelteOptions = isObject(config.options) ? config.options : {}; let target; if (config.target instanceof HTMLElement) { target = config.target; } else if (template instanceof HTMLElement && typeof config.target === "string") { target = template.querySelector(config.target); } else { target = document.createDocumentFragment(); } if (target === void 0) { console.log( `%c[TRL] loadSvelteConfig error - could not find target selector, '${config.target}', for config: `, "background: rgb(57,34,34)", config ); throw new Error(); } const NewSvelteComponent = config.class; const svelteConfig = parseSvelteConfig({ ...config, target }, app); const externalContext = svelteConfig.context.get("#external"); externalContext.application = app; externalContext.elementRootUpdate = elementRootUpdate; externalContext.sessionStorage = app.reactive.sessionStorage; let eventbus; if (isObject(app._eventbus) && typeof app._eventbus.createProxy === "function") { eventbus = app._eventbus.createProxy(); externalContext.eventbus = eventbus; } Object.seal(externalContext); svelteConfig.context.set("external", new Proxy({}, { get(targetUnused, prop) { console.warn(`[TRL] Deprecation warning: Please change getContext('external') to getContext('#external').`); return externalContext[prop]; } })); const component = new NewSvelteComponent(svelteConfig); svelteConfig.eventbus = eventbus; let element2; if (isApplicationShell(component)) { element2 = component.elementRoot; } if (target instanceof DocumentFragment && target.firstElementChild) { if (element2 === void 0) { element2 = target.firstElementChild; } template.append(target); } else if (config.target instanceof HTMLElement && element2 === void 0) { if (config.target instanceof HTMLElement && typeof svelteOptions.selectorElement !== "string") { console.log( `%c[TRL] loadSvelteConfig error - HTMLElement target with no 'selectorElement' defined. Note: If configuring an application shell and directly targeting a HTMLElement did you bind an'elementRoot' and include ''? Offending config: `, "background: rgb(57,34,34)", config ); throw new Error(); } element2 = target.querySelector(svelteOptions.selectorElement); if (element2 === null || element2 === void 0) { console.log( `%c[TRL] loadSvelteConfig error - HTMLElement target with 'selectorElement', '${svelteOptions.selectorElement}', not found for config: `, "background: rgb(57,34,34)", config ); throw new Error(); } } const injectHTML = !(config.target instanceof HTMLElement); return { config: svelteConfig, component, element: element2, injectHTML }; } class SvelteReactive { /** * @type {SvelteApplication} */ #application; /** * @type {boolean} */ #initialized = false; /** @type {TJSSessionStorage} */ #sessionStorage; /** * The Application option store which is injected into mounted Svelte component context under the `external` key. * * @type {StoreAppOptions} */ #storeAppOptions; /** * Stores the update function for `#storeAppOptions`. * * @type {import('svelte/store').Writable.update} */ #storeAppOptionsUpdate; /** * Stores the UI state data to make it accessible via getters. * * @type {object} */ #dataUIState; /** * The UI option store which is injected into mounted Svelte component context under the `external` key. * * @type {StoreUIOptions} */ #storeUIState; /** * Stores the update function for `#storeUIState`. * * @type {import('svelte/store').Writable.update} */ #storeUIStateUpdate; /** * Stores the unsubscribe functions from local store subscriptions. * * @type {import('svelte/store').Unsubscriber[]} */ #storeUnsubscribe = []; /** * @param {SvelteApplication} application - The host Foundry application. */ constructor(application) { this.#application = application; const optionsSessionStorage = application?.options?.sessionStorage; if (optionsSessionStorage !== void 0 && !(optionsSessionStorage instanceof TJSSessionStorage)) { throw new TypeError(`'options.sessionStorage' is not an instance of TJSSessionStorage.`); } this.#sessionStorage = optionsSessionStorage !== void 0 ? optionsSessionStorage : new TJSSessionStorage(); } /** * Initializes reactive support. Package private for internal use. * * @returns {SvelteStores|void} Internal methods to interact with Svelte stores. * @package */ initialize() { if (this.#initialized) { return; } this.#initialized = true; this.#storesInitialize(); return { appOptionsUpdate: this.#storeAppOptionsUpdate, uiOptionsUpdate: this.#storeUIStateUpdate, subscribe: this.#storesSubscribe.bind(this), unsubscribe: this.#storesUnsubscribe.bind(this) }; } // Store getters ----------------------------------------------------------------------------------------------------- /** * @returns {TJSSessionStorage} Returns TJSSessionStorage instance. */ get sessionStorage() { return this.#sessionStorage; } /** * Returns the store for app options. * * @returns {StoreAppOptions} App options store. */ get storeAppOptions() { return this.#storeAppOptions; } /** * Returns the store for UI options. * * @returns {StoreUIOptions} UI options store. */ get storeUIState() { return this.#storeUIState; } // Only reactive getters --------------------------------------------------------------------------------------------- /** * Returns the current dragging UI state. * * @returns {boolean} Dragging UI state. */ get dragging() { return this.#dataUIState.dragging; } /** * Returns the current minimized UI state. * * @returns {boolean} Minimized UI state. */ get minimized() { return this.#dataUIState.minimized; } /** * Returns the current resizing UI state. * * @returns {boolean} Resizing UI state. */ get resizing() { return this.#dataUIState.resizing; } // Reactive getter / setters ----------------------------------------------------------------------------------------- /** * Returns the draggable app option. * * @returns {boolean} Draggable app option. */ get draggable() { return this.#application?.options?.draggable; } /** * Returns the focusAuto app option. * * @returns {boolean} When true auto-management of app focus is enabled. */ get focusAuto() { return this.#application?.options?.focusAuto; } /** * Returns the focusKeep app option. * * @returns {boolean} When `focusAuto` and `focusKeep` is true; keeps internal focus. */ get focusKeep() { return this.#application?.options?.focusKeep; } /** * Returns the focusTrap app option. * * @returns {boolean} When true focus trapping / wrapping is enabled keeping focus inside app. */ get focusTrap() { return this.#application?.options?.focusTrap; } /** * Returns the headerButtonNoClose app option. * * @returns {boolean} Remove the close the button in header app option. */ get headerButtonNoClose() { return this.#application?.options?.headerButtonNoClose; } /** * Returns the headerButtonNoLabel app option. * * @returns {boolean} Remove the labels from buttons in header app option. */ get headerButtonNoLabel() { return this.#application?.options?.headerButtonNoLabel; } /** * Returns the headerIcon app option. * * @returns {string|void} URL for header app icon. */ get headerIcon() { return this.#application?.options?.headerIcon; } /** * Returns the headerNoTitleMinimized app option. * * @returns {boolean} When true removes the header title when minimized. */ get headerNoTitleMinimized() { return this.#application?.options?.headerNoTitleMinimized; } /** * Returns the minimizable app option. * * @returns {boolean} Minimizable app option. */ get minimizable() { return this.#application?.options?.minimizable; } /** * Returns the Foundry popOut state; {@link Application.popOut} * * @returns {boolean} Positionable app option. */ get popOut() { return this.#application.popOut; } /** * Returns the positionable app option; {@link SvelteApplicationOptions.positionable} * * @returns {boolean} Positionable app option. */ get positionable() { return this.#application?.options?.positionable; } /** * Returns the resizable option. * * @returns {boolean} Resizable app option. */ get resizable() { return this.#application?.options?.resizable; } /** * Returns the title accessor from the parent Application class; {@link Application.title} * TODO: Application v2; note that super.title localizes `this.options.title`; IMHO it shouldn't. * * @returns {string} Title. */ get title() { return this.#application.title; } /** * Sets `this.options.draggable` which is reactive for application shells. * * @param {boolean} draggable - Sets the draggable option. */ set draggable(draggable2) { if (typeof draggable2 === "boolean") { this.setOptions("draggable", draggable2); } } /** * Sets `this.options.focusAuto` which is reactive for application shells. * * @param {boolean} focusAuto - Sets the focusAuto option. */ set focusAuto(focusAuto) { if (typeof focusAuto === "boolean") { this.setOptions("focusAuto", focusAuto); } } /** * Sets `this.options.focusKeep` which is reactive for application shells. * * @param {boolean} focusKeep - Sets the focusKeep option. */ set focusKeep(focusKeep) { if (typeof focusKeep === "boolean") { this.setOptions("focusKeep", focusKeep); } } /** * Sets `this.options.focusTrap` which is reactive for application shells. * * @param {boolean} focusTrap - Sets the focusTrap option. */ set focusTrap(focusTrap) { if (typeof focusTrap === "boolean") { this.setOptions("focusTrap", focusTrap); } } /** * Sets `this.options.headerButtonNoClose` which is reactive for application shells. * * @param {boolean} headerButtonNoClose - Sets the headerButtonNoClose option. */ set headerButtonNoClose(headerButtonNoClose) { if (typeof headerButtonNoClose === "boolean") { this.setOptions("headerButtonNoClose", headerButtonNoClose); } } /** * Sets `this.options.headerButtonNoLabel` which is reactive for application shells. * * @param {boolean} headerButtonNoLabel - Sets the headerButtonNoLabel option. */ set headerButtonNoLabel(headerButtonNoLabel) { if (typeof headerButtonNoLabel === "boolean") { this.setOptions("headerButtonNoLabel", headerButtonNoLabel); } } /** * Sets `this.options.headerIcon` which is reactive for application shells. * * @param {string|void} headerIcon - Sets the headerButtonNoLabel option. */ set headerIcon(headerIcon) { if (headerIcon === void 0 || typeof headerIcon === "string") { this.setOptions("headerIcon", headerIcon); } } /** * Sets `this.options.headerNoTitleMinimized` which is reactive for application shells. * * @param {boolean} headerNoTitleMinimized - Sets the headerNoTitleMinimized option. */ set headerNoTitleMinimized(headerNoTitleMinimized) { if (typeof headerNoTitleMinimized === "boolean") { this.setOptions("headerNoTitleMinimized", headerNoTitleMinimized); } } /** * Sets `this.options.minimizable` which is reactive for application shells that are also pop out. * * @param {boolean} minimizable - Sets the minimizable option. */ set minimizable(minimizable) { if (typeof minimizable === "boolean") { this.setOptions("minimizable", minimizable); } } /** * Sets `this.options.popOut` which is reactive for application shells. This will add / remove this application * from `ui.windows`. * * @param {boolean} popOut - Sets the popOut option. */ set popOut(popOut) { if (typeof popOut === "boolean") { this.setOptions("popOut", popOut); } } /** * Sets `this.options.positionable` enabling / disabling {@link SvelteApplication.position.set}. * * @param {boolean} positionable - Sets the positionable option. */ set positionable(positionable) { if (typeof positionable === "boolean") { this.setOptions("positionable", positionable); } } /** * Sets `this.options.resizable` which is reactive for application shells. * * @param {boolean} resizable - Sets the resizable option. */ set resizable(resizable) { if (typeof resizable === "boolean") { this.setOptions("resizable", resizable); } } /** * Sets `this.options.title` which is reactive for application shells. * * Note: Will set empty string if title is undefined or null. * * @param {string|undefined|null} title - Application title; will be localized, so a translation key is fine. */ set title(title) { if (typeof title === "string") { this.setOptions("title", title); } else if (title === void 0 || title === null) { this.setOptions("title", ""); } } // Reactive Options API ------------------------------------------------------------------------------------------- /** * Provides a way to safely get this applications options given an accessor string which describes the * entries to walk. To access deeper entries into the object format the accessor string with `.` between entries * to walk. * * // TODO DOCUMENT the accessor in more detail. * * @param {string} accessor - The path / key to set. You can set multiple levels. * * @param {*} [defaultValue] - A default value returned if the accessor is not found. * * @returns {*} Value at the accessor. */ getOptions(accessor, defaultValue) { return safeAccess(this.#application.options, accessor, defaultValue); } /** * Provides a way to merge `options` into this applications options and update the appOptions store. * * @param {object} options - The options object to merge with `this.options`. */ mergeOptions(options) { this.#storeAppOptionsUpdate((instanceOptions) => deepMerge(instanceOptions, options)); } /** * Provides a way to safely set this applications options given an accessor string which describes the * entries to walk. To access deeper entries into the object format the accessor string with `.` between entries * to walk. * * Additionally if an application shell Svelte component is mounted and exports the `appOptions` property then * the application options is set to `appOptions` potentially updating the application shell / Svelte component. * * // TODO DOCUMENT the accessor in more detail. * * @param {string} accessor - The path / key to set. You can set multiple levels. * * @param {*} value - Value to set. */ setOptions(accessor, value) { const success = safeSet(this.#application.options, accessor, value); if (success) { this.#storeAppOptionsUpdate(() => this.#application.options); } } /** * Initializes the Svelte stores and derived stores for the application options and UI state. * * While writable stores are created the update method is stored in private variables locally and derived Readable * stores are provided for essential options which are commonly used. * * These stores are injected into all Svelte components mounted under the `external` context: `storeAppOptions` and * ` storeUIState`. */ #storesInitialize() { const writableAppOptions = writable$1(this.#application.options); this.#storeAppOptionsUpdate = writableAppOptions.update; const storeAppOptions = { subscribe: writableAppOptions.subscribe, draggable: propertyStore(writableAppOptions, "draggable"), focusAuto: propertyStore(writableAppOptions, "focusAuto"), focusKeep: propertyStore(writableAppOptions, "focusKeep"), focusTrap: propertyStore(writableAppOptions, "focusTrap"), headerButtonNoClose: propertyStore(writableAppOptions, "headerButtonNoClose"), headerButtonNoLabel: propertyStore(writableAppOptions, "headerButtonNoLabel"), headerIcon: propertyStore(writableAppOptions, "headerIcon"), headerNoTitleMinimized: propertyStore(writableAppOptions, "headerNoTitleMinimized"), minimizable: propertyStore(writableAppOptions, "minimizable"), popOut: propertyStore(writableAppOptions, "popOut"), positionable: propertyStore(writableAppOptions, "positionable"), resizable: propertyStore(writableAppOptions, "resizable"), title: propertyStore(writableAppOptions, "title") }; Object.freeze(storeAppOptions); this.#storeAppOptions = storeAppOptions; this.#dataUIState = { dragging: false, headerButtons: [], minimized: this.#application._minimized, resizing: false }; const writableUIOptions = writable$1(this.#dataUIState); this.#storeUIStateUpdate = writableUIOptions.update; const storeUIState = { subscribe: writableUIOptions.subscribe, dragging: propertyStore(writableUIOptions, "dragging"), headerButtons: derived(writableUIOptions, ($options, set) => set($options.headerButtons)), minimized: derived(writableUIOptions, ($options, set) => set($options.minimized)), resizing: propertyStore(writableUIOptions, "resizing") }; Object.freeze(storeUIState); this.#storeUIState = storeUIState; } /** * Registers local store subscriptions for app options. `popOut` controls registering this app with `ui.windows`. * * @see SvelteApplication._injectHTML */ #storesSubscribe() { this.#storeUnsubscribe.push(subscribeIgnoreFirst(this.#storeAppOptions.headerButtonNoClose, (value) => { this.updateHeaderButtons({ headerButtonNoClose: value }); })); this.#storeUnsubscribe.push(subscribeIgnoreFirst(this.#storeAppOptions.headerButtonNoLabel, (value) => { this.updateHeaderButtons({ headerButtonNoLabel: value }); })); this.#storeUnsubscribe.push(subscribeIgnoreFirst(this.#storeAppOptions.popOut, (value) => { if (value && this.#application.rendered) { globalThis.ui.windows[this.#application.appId] = this.#application; } else { delete globalThis.ui.windows[this.#application.appId]; } })); } /** * Unsubscribes from any locally monitored stores. * * @see SvelteApplication.close */ #storesUnsubscribe() { this.#storeUnsubscribe.forEach((unsubscribe) => unsubscribe()); this.#storeUnsubscribe = []; } /** * Updates the UI Options store with the current header buttons. You may dynamically add / remove header buttons * if using an application shell Svelte component. In either overriding `_getHeaderButtons` or responding to the * Hooks fired return a new button array and the uiOptions store is updated and the application shell will render * the new buttons. * * Optionally you can set in the SvelteApplication app options {@link SvelteApplicationOptions.headerButtonNoClose} * to remove the close button and {@link SvelteApplicationOptions.headerButtonNoLabel} to true and labels will be * removed from the header buttons. * * @param {object} opts - Optional parameters (for internal use) * * @param {boolean} opts.headerButtonNoClose - The value for `headerButtonNoClose`. * * @param {boolean} opts.headerButtonNoLabel - The value for `headerButtonNoLabel`. */ updateHeaderButtons({ headerButtonNoClose = this.#application.options.headerButtonNoClose, headerButtonNoLabel = this.#application.options.headerButtonNoLabel } = {}) { let buttons = this.#application._getHeaderButtons(); if (typeof headerButtonNoClose === "boolean" && headerButtonNoClose) { buttons = buttons.filter((button) => button.class !== "close"); } if (typeof headerButtonNoLabel === "boolean" && headerButtonNoLabel) { for (const button of buttons) { button.label = void 0; } } this.#storeUIStateUpdate((options) => { options.headerButtons = buttons; return options; }); } } class SvelteApplication extends Application { /** * Stores the first mounted component which follows the application shell contract. * * @type {MountedAppShell[]|null[]} Application shell. */ #applicationShellHolder = [null]; /** * Stores and manages application state for saving / restoring / serializing. * * @type {ApplicationState} */ #applicationState; /** * Stores the target element which may not necessarily be the main element. * * @type {HTMLElement} */ #elementTarget = null; /** * Stores the content element which is set for application shells. * * @type {HTMLElement} */ #elementContent = null; /** * Stores initial z-index from `_renderOuter` to set to target element / Svelte component. * * @type {number} */ #initialZIndex = 95; /** * Stores on mount state which is checked in _render to trigger onSvelteMount callback. * * @type {boolean} */ #onMount = false; /** * The position store. * * @type {Position} */ #position; /** * Contains the Svelte stores and reactive accessors. * * @type {SvelteReactive} */ #reactive; /** * Stores SvelteData entries with instantiated Svelte components. * * @type {SvelteData[]} */ #svelteData = []; /** * Provides a helper class that combines multiple methods for interacting with the mounted components tracked in * {@link SvelteData}. * * @type {GetSvelteData} */ #getSvelteData = new GetSvelteData(this.#applicationShellHolder, this.#svelteData); /** * Contains methods to interact with the Svelte stores. * * @type {SvelteStores} */ #stores; /** * @param {SvelteApplicationOptions} options - The options for the application. * * @inheritDoc */ constructor(options = {}) { super(options); this.#applicationState = new ApplicationState(this); this.#position = new Position(this, { ...this.position, ...this.options, initial: this.options.positionInitial, ortho: this.options.positionOrtho, validator: this.options.positionValidator }); delete this.position; Object.defineProperty(this, "position", { get: () => this.#position, set: (position) => { if (isObject(position)) { this.#position.set(position); } } }); this.#reactive = new SvelteReactive(this); this.#stores = this.#reactive.initialize(); } /** * Specifies the default options that SvelteApplication supports. * * @returns {SvelteApplicationOptions} options - Application options. * @see https://foundryvtt.com/api/interfaces/client.ApplicationOptions.html */ static get defaultOptions() { return deepMerge(super.defaultOptions, { defaultCloseAnimation: true, // If false the default slide close animation is not run. draggable: true, // If true then application shells are draggable. focusAuto: true, // When true auto-management of app focus is enabled. focusKeep: false, // When `focusAuto` and `focusKeep` is true; keeps internal focus. focusSource: void 0, // Stores any A11yFocusSource data that is applied when app is closed. focusTrap: true, // When true focus trapping / wrapping is enabled keeping focus inside app. headerButtonNoClose: false, // If true then the close header button is removed. headerButtonNoLabel: false, // If true then header button labels are removed for application shells. headerIcon: void 0, // Sets a header icon given an image URL. headerNoTitleMinimized: false, // If true then header title is hidden when application is minimized. minHeight: MIN_WINDOW_HEIGHT, // Assigned to position. Number specifying minimum window height. minWidth: MIN_WINDOW_WIDTH, // Assigned to position. Number specifying minimum window width. positionable: true, // If false then `position.set` does not take effect. positionInitial: Position.Initial.browserCentered, // A helper for initial position placement. positionOrtho: true, // When true Position is optimized for orthographic use. positionValidator: Position.Validators.transformWindow, // A function providing the default validator. sessionStorage: void 0, // An instance of SessionStorage to share across SvelteApplications. svelte: void 0, // A Svelte configuration object. transformOrigin: "top left" // By default, 'top / left' respects rotation when minimizing. }); } /** * Returns the content element if an application shell is mounted. * * @returns {HTMLElement} Content element. */ get elementContent() { return this.#elementContent; } /** * Returns the target element or main element if no target defined. * * @returns {HTMLElement} Target element. */ get elementTarget() { return this.#elementTarget; } /** * Returns the reactive accessors & Svelte stores for SvelteApplication. * * @returns {SvelteReactive} The reactive accessors & Svelte stores. */ get reactive() { return this.#reactive; } /** * Returns the application state manager. * * @returns {ApplicationState} The application state manager. */ get state() { return this.#applicationState; } /** * Returns the Svelte helper class w/ various methods to access mounted Svelte components. * * @returns {GetSvelteData} GetSvelteData */ get svelte() { return this.#getSvelteData; } /** * In this case of when a template is defined in app options `html` references the inner HTML / template. However, * to activate classic v1 tabs for a Svelte component the element target is passed as an array simulating JQuery as * the element is retrieved immediately and the core listeners use standard DOM queries. * * @inheritDoc * @protected * @ignore */ _activateCoreListeners(html) { super._activateCoreListeners(typeof this.options.template === "string" ? html : [this.#elementTarget]); } /** * Provide an override to set this application as the active window regardless of z-index. Changes behaviour from * Foundry core. This is important / used for instance in dialog key handling for left / right button selection. * * @param {object} [opts] - Optional parameters. * * @param {boolean} [opts.force=false] - Force bring to top; will increment z-index by popOut order. * */ bringToTop({ force = false } = {}) { if (force || this.popOut) { super.bringToTop(); } if (document.activeElement !== document.body && !this.elementTarget.contains(document.activeElement)) { if (document.activeElement instanceof HTMLElement) { document.activeElement.blur(); } document.body.focus(); } globalThis.ui.activeWindow = this; } /** * Note: This method is fully overridden and duplicated as Svelte components need to be destroyed manually and the * best visual result is to destroy them after the default slide up animation occurs, but before the element * is removed from the DOM. * * If you destroy the Svelte components before the slide up animation the Svelte elements are removed immediately * from the DOM. The purpose of overriding ensures the slide up animation is always completed before * the Svelte components are destroyed and then the element is removed from the DOM. * * Close the application and un-register references to it within UI mappings. * This function returns a Promise which resolves once the window closing animation concludes * * @param {object} [options] - Optional parameters. * * @param {boolean} [options.force] - Force close regardless of render state. * * @returns {Promise} A Promise which resolves once the application is closed. * @ignore */ async close(options = {}) { const states = Application.RENDER_STATES; if (!options.force && ![states.RENDERED, states.ERROR].includes(this._state)) { return; } this.#stores.unsubscribe(); this._state = states.CLOSING; const el = this.#elementTarget; if (!el) { return this._state = states.CLOSED; } const content = el.querySelector(".window-content"); if (content) { content.style.overflow = "hidden"; for (let cntr = content.children.length; --cntr >= 0; ) { content.children[cntr].style.overflow = "hidden"; } } for (const cls of this.constructor._getInheritanceChain()) { Hooks.call(`close${cls.name}`, this, el); } const animate = typeof this.options.defaultCloseAnimation === "boolean" ? this.options.defaultCloseAnimation : true; if (animate) { el.style.minHeight = "0"; const { paddingBottom, paddingTop } = globalThis.getComputedStyle(el); await el.animate([ { maxHeight: `${el.clientHeight}px`, paddingTop, paddingBottom }, { maxHeight: 0, paddingTop: 0, paddingBottom: 0 } ], { duration: 250, easing: "ease-in", fill: "forwards" }).finished; } const svelteDestroyPromises = []; for (const entry of this.#svelteData) { svelteDestroyPromises.push(outroAndDestroy(entry.component)); const eventbus = entry.config.eventbus; if (isObject(eventbus) && typeof eventbus.off === "function") { eventbus.off(); entry.config.eventbus = void 0; } } await Promise.all(svelteDestroyPromises); this.#svelteData.length = 0; el.remove(); this.position.state.restore({ name: "#beforeMinimized", properties: ["width", "height"], silent: true, remove: true }); this.#applicationShellHolder[0] = null; this._element = null; this.#elementContent = null; this.#elementTarget = null; delete globalThis.ui.windows[this.appId]; this._minimized = false; this._scrollPositions = null; this._state = states.CLOSED; this.#onMount = false; this.#stores.uiOptionsUpdate((storeOptions) => deepMerge(storeOptions, { minimized: this._minimized })); A11yHelper.applyFocusSource(this.options.focusSource); delete this.options.focusSource; } /** * Inject the Svelte components defined in `this.options.svelte`. The Svelte component can attach to the existing * pop-out of Application or provide no template and render into a document fragment which is then attached to the * DOM. * * @param {JQuery} html - * * @inheritDoc * @ignore */ _injectHTML(html) { if (this.popOut && html.length === 0 && Array.isArray(this.options.svelte)) { throw new Error( "SvelteApplication - _injectHTML - A popout app with no template can only support one Svelte component." ); } this.reactive.updateHeaderButtons(); const elementRootUpdate = () => { let cntr = 0; return (elementRoot) => { if (elementRoot !== null && elementRoot !== void 0 && cntr++ > 0) { this.#updateApplicationShell(); return true; } return false; }; }; if (Array.isArray(this.options.svelte)) { for (const svelteConfig of this.options.svelte) { const svelteData = loadSvelteConfig({ app: this, template: html[0], config: svelteConfig, elementRootUpdate }); if (isApplicationShell(svelteData.component)) { if (this.svelte.applicationShell !== null) { throw new Error( `SvelteApplication - _injectHTML - An application shell is already mounted; offending config: ${JSON.stringify(svelteConfig)}` ); } this.#applicationShellHolder[0] = svelteData.component; if (isHMRProxy(svelteData.component) && Array.isArray(svelteData.component?.$$?.on_hmr)) { svelteData.component.$$.on_hmr.push(() => () => this.#updateApplicationShell()); } } this.#svelteData.push(svelteData); } } else if (isObject(this.options.svelte)) { const svelteData = loadSvelteConfig({ app: this, template: html[0], config: this.options.svelte, elementRootUpdate }); if (isApplicationShell(svelteData.component)) { if (this.svelte.applicationShell !== null) { throw new Error( `SvelteApplication - _injectHTML - An application shell is already mounted; offending config: ${JSON.stringify(this.options.svelte)}` ); } this.#applicationShellHolder[0] = svelteData.component; if (isHMRProxy(svelteData.component) && Array.isArray(svelteData.component?.$$?.on_hmr)) { svelteData.component.$$.on_hmr.push(() => () => this.#updateApplicationShell()); } } this.#svelteData.push(svelteData); } const isDocumentFragment = html.length && html[0] instanceof DocumentFragment; let injectHTML = true; for (const svelteData of this.#svelteData) { if (!svelteData.injectHTML) { injectHTML = false; break; } } if (injectHTML) { super._injectHTML(html); } if (this.svelte.applicationShell !== null) { this._element = $(this.svelte.applicationShell.elementRoot); this.#elementContent = hasGetter(this.svelte.applicationShell, "elementContent") ? this.svelte.applicationShell.elementContent : null; this.#elementTarget = hasGetter(this.svelte.applicationShell, "elementTarget") ? this.svelte.applicationShell.elementTarget : null; } else if (isDocumentFragment) { for (const svelteData of this.#svelteData) { if (svelteData.element instanceof HTMLElement) { this._element = $(svelteData.element); break; } } } if (this.#elementTarget === null) { this.#elementTarget = typeof this.options.selectorTarget === "string" ? this._element[0].querySelector(this.options.selectorTarget) : this._element[0]; } if (this.#elementTarget === null || this.#elementTarget === void 0) { throw new Error(`SvelteApplication - _injectHTML: Target element '${this.options.selectorTarget}' not found.`); } if (typeof this.options.positionable === "boolean" && this.options.positionable) { this.#elementTarget.style.zIndex = typeof this.options.zIndex === "number" ? this.options.zIndex : this.#initialZIndex ?? 95; } this.#stores.subscribe(); } /** * Provides a mechanism to update the UI options store for maximized. * * Note: the sanity check is duplicated from {@link Application.maximize} the store is updated _before_ * performing the rest of animations. This allows application shells to remove / show any resize handlers * correctly. Extra constraint data is stored in a saved position state in {@link SvelteApplication.minimize} * to animate the content area. * * @param {object} [opts] - Optional parameters. * * @param {boolean} [opts.animate=true] - When true perform default maximizing animation. * * @param {number} [opts.duration=0.1] - Controls content area animation duration in seconds. */ async maximize({ animate = true, duration = 0.1 } = {}) { if (!this.popOut || [false, null].includes(this._minimized)) { return; } this._minimized = null; const durationMS = duration * 1e3; const element2 = this.elementTarget; const header = element2.querySelector(".window-header"); const content = element2.querySelector(".window-content"); const positionBefore = this.position.state.get({ name: "#beforeMinimized" }); if (animate) { await this.position.state.restore({ name: "#beforeMinimized", async: true, animateTo: true, properties: ["width"], duration: 0.1 }); } element2.classList.remove("minimized"); for (let cntr = header.children.length; --cntr >= 0; ) { header.children[cntr].style.display = null; } content.style.display = null; let constraints; if (animate) { ({ constraints } = this.position.state.restore({ name: "#beforeMinimized", animateTo: true, properties: ["height"], remove: true, duration })); } else { ({ constraints } = this.position.state.remove({ name: "#beforeMinimized" })); } await content.animate([ { maxHeight: 0, paddingTop: 0, paddingBottom: 0, offset: 0 }, { ...constraints, offset: 1 }, { maxHeight: "100%", offset: 1 } ], { duration: durationMS, fill: "forwards" }).finished; this.position.set({ minHeight: positionBefore.minHeight ?? this.options?.minHeight ?? MIN_WINDOW_HEIGHT, minWidth: positionBefore.minWidth ?? this.options?.minWidth ?? MIN_WINDOW_WIDTH }); element2.style.minWidth = null; element2.style.minHeight = null; this._minimized = false; setTimeout(() => { content.style.overflow = null; for (let cntr = content.children.length; --cntr >= 0; ) { content.children[cntr].style.overflow = null; } }, 50); this.#stores.uiOptionsUpdate((options) => deepMerge(options, { minimized: false })); } /** * Provides a mechanism to update the UI options store for minimized. * * Note: the sanity check is duplicated from {@link Application.minimize} the store is updated _before_ * performing the rest of animations. This allows application shells to remove / show any resize handlers * correctly. Extra constraint data is stored in a saved position state in {@link SvelteApplication.minimize} * to animate the content area. * * @param {object} [opts] - Optional parameters * * @param {boolean} [opts.animate=true] - When true perform default minimizing animation. * * @param {number} [opts.duration=0.1] - Controls content area animation duration in seconds. */ async minimize({ animate = true, duration = 0.1 } = {}) { if (!this.rendered || !this.popOut || [true, null].includes(this._minimized)) { return; } this.#stores.uiOptionsUpdate((options) => deepMerge(options, { minimized: true })); this._minimized = null; const durationMS = duration * 1e3; const element2 = this.elementTarget; const header = element2.querySelector(".window-header"); const content = element2.querySelector(".window-content"); const beforeMinWidth = this.position.minWidth; const beforeMinHeight = this.position.minHeight; this.position.set({ minWidth: 100, minHeight: 30 }); element2.style.minWidth = "100px"; element2.style.minHeight = "30px"; if (content) { content.style.overflow = "hidden"; for (let cntr = content.children.length; --cntr >= 0; ) { content.children[cntr].style.overflow = "hidden"; } } const { paddingBottom, paddingTop } = globalThis.getComputedStyle(content); const constraints = { maxHeight: `${content.clientHeight}px`, paddingTop, paddingBottom }; if (animate) { const animation2 = content.animate([ constraints, { maxHeight: 0, paddingTop: 0, paddingBottom: 0 } ], { duration: durationMS, fill: "forwards" }); animation2.finished.then(() => content.style.display = "none"); } else { setTimeout(() => content.style.display = "none", durationMS); } const saved = this.position.state.save({ name: "#beforeMinimized", constraints }); saved.minWidth = beforeMinWidth; saved.minHeight = beforeMinHeight; const headerOffsetHeight = header.offsetHeight; this.position.minHeight = headerOffsetHeight; if (animate) { await this.position.animate.to({ height: headerOffsetHeight }, { duration }).finished; } for (let cntr = header.children.length; --cntr >= 0; ) { const className = header.children[cntr].className; if (className.includes("window-title") || className.includes("close")) { continue; } if (className.includes("keep-minimized")) { header.children[cntr].style.display = "block"; continue; } header.children[cntr].style.display = "none"; } if (animate) { await this.position.animate.to({ width: MIN_WINDOW_WIDTH }, { duration: 0.1 }).finished; } element2.classList.add("minimized"); this._minimized = true; } /** * Provides a callback after all Svelte components are initialized. * * @param {object} [opts] - Optional parameters. * * @param {HTMLElement} [opts.element] - HTMLElement container for main application element. * * @param {HTMLElement} [opts.elementContent] - HTMLElement container for content area of application shells. * * @param {HTMLElement} [opts.elementTarget] - HTMLElement container for main application target element. */ onSvelteMount({ element: element2, elementContent, elementTarget } = {}) { } // eslint-disable-line no-unused-vars /** * Provides a callback after the main application shell is remounted. This may occur during HMR / hot module * replacement or directly invoked from the `elementRootUpdate` callback passed to the application shell component * context. * * @param {object} [opts] - Optional parameters. * * @param {HTMLElement} [opts.element] - HTMLElement container for main application element. * * @param {HTMLElement} [opts.elementContent] - HTMLElement container for content area of application shells. * * @param {HTMLElement} [opts.elementTarget] - HTMLElement container for main application target element. */ onSvelteRemount({ element: element2, elementContent, elementTarget } = {}) { } // eslint-disable-line no-unused-vars /** * Override replacing HTML as Svelte components control the rendering process. Only potentially change the outer * application frame / title for pop-out applications. * * @inheritDoc * @ignore */ _replaceHTML(element2, html) { if (!element2.length) { return; } this.reactive.updateHeaderButtons(); } /** * Provides an override verifying that a new Application being rendered for the first time doesn't have a * corresponding DOM element already loaded. This is a check that only occurs when `this._state` is * `Application.RENDER_STATES.NONE`. It is useful in particular when SvelteApplication has a static ID * explicitly set in `this.options.id` and long intro / outro transitions are assigned. If a new application * sharing this static ID attempts to open / render for the first time while an existing DOM element sharing * this static ID exists then the initial render is cancelled below rather than crashing later in the render * cycle {@link Position.set}. * * @inheritDoc * @protected * @ignore */ async _render(force = false, options = {}) { if (isObject(options?.focusSource)) { this.options.focusSource = options.focusSource; } if (this._state === Application.RENDER_STATES.NONE && document.querySelector(`#${this.id}`) instanceof HTMLElement) { console.warn(`SvelteApplication - _render: A DOM element already exists for CSS ID '${this.id}'. Cancelling initial render for new application with appId '${this.appId}'.`); return; } await super._render(force, options); if (!this.#onMount) { this.onSvelteMount({ element: this._element[0], elementContent: this.#elementContent, elementTarget: this.#elementTarget }); this.#onMount = true; } } /** * Render the inner application content. Only render a template if one is defined otherwise provide an empty * JQuery element per the core Foundry API. * * @param {object} data The data used to render the inner template * * @returns {Promise.} A promise resolving to the constructed jQuery object * * @protected * @ignore */ async _renderInner(data) { const html = typeof this.template === "string" ? await renderTemplate(this.template, data) : document.createDocumentFragment(); return $(html); } /** * Stores the initial z-index set in `_renderOuter` which is used in `_injectHTML` to set the target element * z-index after the Svelte component is mounted. * * @returns {Promise} Outer frame / unused. * @protected * @ignore */ async _renderOuter() { const html = await super._renderOuter(); this.#initialZIndex = html[0].style.zIndex; return html; } /** * All calculation and updates of position are implemented in {@link Position.set}. This allows position to be fully * reactive and in control of updating inline styles for the application. * * This method remains for backward compatibility with Foundry. If you have a custom override quite likely you need * to update to using the {@link Position.validators} functionality. * * @param {PositionDataExtended} [position] - Position data. * * @returns {Position} The updated position object for the application containing the new values */ setPosition(position) { return this.position.set(position); } /** * This method is invoked by the `elementRootUpdate` callback that is added to the external context passed to * Svelte components. When invoked it updates the local element roots tracked by SvelteApplication. * * This method may also be invoked by HMR / hot module replacement via `svelte-hmr`. */ #updateApplicationShell() { const applicationShell = this.svelte.applicationShell; if (applicationShell !== null) { this._element = $(applicationShell.elementRoot); this.#elementContent = hasGetter(applicationShell, "elementContent") ? applicationShell.elementContent : null; this.#elementTarget = hasGetter(applicationShell, "elementTarget") ? applicationShell.elementTarget : null; if (this.#elementTarget === null) { this.#elementTarget = typeof this.options.selectorTarget === "string" ? this._element[0].querySelector(this.options.selectorTarget) : this._element[0]; } if (typeof this.options.positionable === "boolean" && this.options.positionable) { this.#elementTarget.style.zIndex = typeof this.options.zIndex === "number" ? this.options.zIndex : this.#initialZIndex ?? 95; super.bringToTop(); this.position.set(this.position.get()); } super._activateCoreListeners([this.#elementTarget]); this.onSvelteRemount({ element: this._element[0], elementContent: this.#elementContent, elementTarget: this.#elementTarget }); } } } const s_STYLE_KEY = "#__trl-root-styles"; const cssVariables = new StyleManager({ docKey: s_STYLE_KEY, version: 1 }); const TJSContainer_svelte_svelte_type_style_lang = ""; function resizeObserver(node, target) { ResizeObserverManager.add(node, target); return { update: (newTarget) => { ResizeObserverManager.remove(node, target); target = newTarget; ResizeObserverManager.add(node, target); }, destroy: () => { ResizeObserverManager.remove(node, target); } }; } resizeObserver.updateCache = function(el) { if (!(el instanceof HTMLElement)) { throw new TypeError(`resizeObserverUpdate error: 'el' is not an HTMLElement.`); } const subscribers = s_MAP.get(el); if (Array.isArray(subscribers)) { const computed = globalThis.getComputedStyle(el); const borderBottom = styleParsePixels(el.style.borderBottom) ?? styleParsePixels(computed.borderBottom) ?? 0; const borderLeft = styleParsePixels(el.style.borderLeft) ?? styleParsePixels(computed.borderLeft) ?? 0; const borderRight = styleParsePixels(el.style.borderRight) ?? styleParsePixels(computed.borderRight) ?? 0; const borderTop = styleParsePixels(el.style.borderTop) ?? styleParsePixels(computed.borderTop) ?? 0; const paddingBottom = styleParsePixels(el.style.paddingBottom) ?? styleParsePixels(computed.paddingBottom) ?? 0; const paddingLeft = styleParsePixels(el.style.paddingLeft) ?? styleParsePixels(computed.paddingLeft) ?? 0; const paddingRight = styleParsePixels(el.style.paddingRight) ?? styleParsePixels(computed.paddingRight) ?? 0; const paddingTop = styleParsePixels(el.style.paddingTop) ?? styleParsePixels(computed.paddingTop) ?? 0; const additionalWidth = borderLeft + borderRight + paddingLeft + paddingRight; const additionalHeight = borderTop + borderBottom + paddingTop + paddingBottom; for (const subscriber of subscribers) { subscriber.styles.additionalWidth = additionalWidth; subscriber.styles.additionalHeight = additionalHeight; s_UPDATE_SUBSCRIBER(subscriber, subscriber.contentWidth, subscriber.contentHeight); } } }; const s_MAP = /* @__PURE__ */ new Map(); class ResizeObserverManager { /** * Add an HTMLElement and ResizeObserverTarget instance for monitoring. Create cached style attributes for the * given element include border & padding dimensions for offset width / height calculations. * * @param {HTMLElement} el - The element to observe. * * @param {ResizeObserverTarget} target - A target that contains one of several mechanisms for updating resize data. */ static add(el, target) { const updateType = s_GET_UPDATE_TYPE(target); if (updateType === 0) { throw new Error(`'target' does not match supported ResizeObserverManager update mechanisms.`); } const computed = globalThis.getComputedStyle(el); const borderBottom = styleParsePixels(el.style.borderBottom) ?? styleParsePixels(computed.borderBottom) ?? 0; const borderLeft = styleParsePixels(el.style.borderLeft) ?? styleParsePixels(computed.borderLeft) ?? 0; const borderRight = styleParsePixels(el.style.borderRight) ?? styleParsePixels(computed.borderRight) ?? 0; const borderTop = styleParsePixels(el.style.borderTop) ?? styleParsePixels(computed.borderTop) ?? 0; const paddingBottom = styleParsePixels(el.style.paddingBottom) ?? styleParsePixels(computed.paddingBottom) ?? 0; const paddingLeft = styleParsePixels(el.style.paddingLeft) ?? styleParsePixels(computed.paddingLeft) ?? 0; const paddingRight = styleParsePixels(el.style.paddingRight) ?? styleParsePixels(computed.paddingRight) ?? 0; const paddingTop = styleParsePixels(el.style.paddingTop) ?? styleParsePixels(computed.paddingTop) ?? 0; const data = { updateType, target, // Stores most recent contentRect.width and contentRect.height values from ResizeObserver. contentWidth: 0, contentHeight: 0, // Convenience data for total border & padding for offset width & height calculations. styles: { additionalWidth: borderLeft + borderRight + paddingLeft + paddingRight, additionalHeight: borderTop + borderBottom + paddingTop + paddingBottom } }; if (s_MAP.has(el)) { const subscribers = s_MAP.get(el); subscribers.push(data); } else { s_MAP.set(el, [data]); } s_RESIZE_OBSERVER.observe(el); } /** * Removes all targets from monitoring when just an element is provided otherwise removes a specific target * from the monitoring map. If no more targets remain then the element is removed from monitoring. * * @param {HTMLElement} el - Element to remove from monitoring. * * @param {ResizeObserverTarget} [target] - A specific target to remove from monitoring. */ static remove(el, target = void 0) { const subscribers = s_MAP.get(el); if (Array.isArray(subscribers)) { const index = subscribers.findIndex((entry) => entry.target === target); if (index >= 0) { s_UPDATE_SUBSCRIBER(subscribers[index], void 0, void 0); subscribers.splice(index, 1); } if (subscribers.length === 0) { s_MAP.delete(el); s_RESIZE_OBSERVER.unobserve(el); } } } } const s_UPDATE_TYPES = { none: 0, attribute: 1, function: 2, resizeObserved: 3, setContentBounds: 4, setDimension: 5, storeObject: 6, storesObject: 7 }; const s_RESIZE_OBSERVER = new ResizeObserver((entries) => { for (const entry of entries) { const subscribers = s_MAP.get(entry?.target); if (Array.isArray(subscribers)) { const contentWidth = entry.contentRect.width; const contentHeight = entry.contentRect.height; for (const subscriber of subscribers) { s_UPDATE_SUBSCRIBER(subscriber, contentWidth, contentHeight); } } } }); function s_GET_UPDATE_TYPE(target) { if (target?.resizeObserved instanceof Function) { return s_UPDATE_TYPES.resizeObserved; } if (target?.setDimension instanceof Function) { return s_UPDATE_TYPES.setDimension; } if (target?.setContentBounds instanceof Function) { return s_UPDATE_TYPES.setContentBounds; } const targetType = typeof target; if (targetType !== null && (targetType === "object" || targetType === "function")) { if (isUpdatableStore(target.resizeObserved)) { return s_UPDATE_TYPES.storeObject; } const stores = target?.stores; if (isObject(stores) || typeof stores === "function") { if (isUpdatableStore(stores.resizeObserved)) { return s_UPDATE_TYPES.storesObject; } } } if (targetType !== null && targetType === "object") { return s_UPDATE_TYPES.attribute; } if (targetType === "function") { return s_UPDATE_TYPES.function; } return s_UPDATE_TYPES.none; } function s_UPDATE_SUBSCRIBER(subscriber, contentWidth, contentHeight) { const styles = subscriber.styles; subscriber.contentWidth = contentWidth; subscriber.contentHeight = contentHeight; const offsetWidth = Number.isFinite(contentWidth) ? contentWidth + styles.additionalWidth : void 0; const offsetHeight = Number.isFinite(contentHeight) ? contentHeight + styles.additionalHeight : void 0; const target = subscriber.target; switch (subscriber.updateType) { case s_UPDATE_TYPES.attribute: target.contentWidth = contentWidth; target.contentHeight = contentHeight; target.offsetWidth = offsetWidth; target.offsetHeight = offsetHeight; break; case s_UPDATE_TYPES.function: target?.(offsetWidth, offsetHeight, contentWidth, contentHeight); break; case s_UPDATE_TYPES.resizeObserved: target.resizeObserved?.(offsetWidth, offsetHeight, contentWidth, contentHeight); break; case s_UPDATE_TYPES.setContentBounds: target.setContentBounds?.(contentWidth, contentHeight); break; case s_UPDATE_TYPES.setDimension: target.setDimension?.(offsetWidth, offsetHeight); break; case s_UPDATE_TYPES.storeObject: target.resizeObserved.update((object) => { object.contentHeight = contentHeight; object.contentWidth = contentWidth; object.offsetHeight = offsetHeight; object.offsetWidth = offsetWidth; return object; }); break; case s_UPDATE_TYPES.storesObject: target.stores.resizeObserved.update((object) => { object.contentHeight = contentHeight; object.contentWidth = contentWidth; object.offsetHeight = offsetHeight; object.offsetWidth = offsetWidth; return object; }); break; } } function applyStyles(node, properties) { function setProperties() { if (typeof properties !== "object") { return; } for (const prop of Object.keys(properties)) { node.style.setProperty(`${prop}`, properties[prop]); } } setProperties(); return { update(newProperties) { properties = newProperties; setProperties(); } }; } function draggable(node, { position, active: active2 = true, button = 0, storeDragging = void 0, ease = false, easeOptions = { duration: 0.1, ease: cubicOut }, hasTargetClassList, ignoreTargetClassList }) { if (hasTargetClassList !== void 0 && !isIterable(hasTargetClassList)) { throw new TypeError(`'hasTargetClassList' is not iterable.`); } if (ignoreTargetClassList !== void 0 && !isIterable(ignoreTargetClassList)) { throw new TypeError(`'ignoreTargetClassList' is not iterable.`); } let initialPosition = null; let initialDragPoint = {}; let dragging = false; let quickTo = position.animate.quickTo(["top", "left"], easeOptions); const handlers = { dragDown: ["pointerdown", (e) => onDragPointerDown(e), false], dragMove: ["pointermove", (e) => onDragPointerChange(e), false], dragUp: ["pointerup", (e) => onDragPointerUp(e), false] }; function activateListeners() { node.addEventListener(...handlers.dragDown); node.classList.add("draggable"); } function removeListeners() { if (typeof storeDragging?.set === "function") { storeDragging.set(false); } node.removeEventListener(...handlers.dragDown); node.removeEventListener(...handlers.dragMove); node.removeEventListener(...handlers.dragUp); node.classList.remove("draggable"); } if (active2) { activateListeners(); } function onDragPointerDown(event) { if (event.button !== button || !event.isPrimary) { return; } if (!position.enabled) { return; } if (ignoreTargetClassList !== void 0 && event.target instanceof HTMLElement) { for (const targetClass of ignoreTargetClassList) { if (event.target.classList.contains(targetClass)) { return; } } } if (hasTargetClassList !== void 0 && event.target instanceof HTMLElement) { let foundTarget = false; for (const targetClass of hasTargetClassList) { if (event.target.classList.contains(targetClass)) { foundTarget = true; break; } } if (!foundTarget) { return; } } event.preventDefault(); dragging = false; initialPosition = position.get(); initialDragPoint = { x: event.clientX, y: event.clientY }; node.addEventListener(...handlers.dragMove); node.addEventListener(...handlers.dragUp); node.setPointerCapture(event.pointerId); } function onDragPointerChange(event) { if ((event.buttons & 1) === 0) { onDragPointerUp(event); return; } if (event.button !== -1 || !event.isPrimary) { return; } event.preventDefault(); if (!dragging && typeof storeDragging?.set === "function") { dragging = true; storeDragging.set(true); } const newLeft = initialPosition.left + (event.clientX - initialDragPoint.x); const newTop = initialPosition.top + (event.clientY - initialDragPoint.y); if (ease) { quickTo(newTop, newLeft); } else { s_POSITION_DATA.left = newLeft; s_POSITION_DATA.top = newTop; position.set(s_POSITION_DATA); } } function onDragPointerUp(event) { event.preventDefault(); dragging = false; if (typeof storeDragging?.set === "function") { storeDragging.set(false); } node.removeEventListener(...handlers.dragMove); node.removeEventListener(...handlers.dragUp); } return { // The default of active being true won't automatically add listeners twice. update: (options) => { if (typeof options.active === "boolean") { active2 = options.active; if (active2) { activateListeners(); } else { removeListeners(); } } if (typeof options.button === "number") { button = options.button; } if (options.position !== void 0 && options.position !== position) { position = options.position; quickTo = position.animate.quickTo(["top", "left"], easeOptions); } if (typeof options.ease === "boolean") { ease = options.ease; } if (isObject(options.easeOptions)) { easeOptions = options.easeOptions; quickTo.options(easeOptions); } if (options.hasTargetClassList !== void 0) { if (!isIterable(options.hasTargetClassList)) { throw new TypeError(`'hasTargetClassList' is not iterable.`); } else { hasTargetClassList = options.hasTargetClassList; } } if (options.ignoreTargetClassList !== void 0) { if (!isIterable(options.ignoreTargetClassList)) { throw new TypeError(`'ignoreTargetClassList' is not iterable.`); } else { ignoreTargetClassList = options.ignoreTargetClassList; } } }, destroy: () => removeListeners() }; } class DraggableOptions { #ease = false; #easeOptions = { duration: 0.1, ease: cubicOut }; /** * Stores the subscribers. * * @type {(function(DraggableOptions): void)[]} */ #subscriptions = []; constructor({ ease, easeOptions } = {}) { Object.defineProperty(this, "ease", { get: () => { return this.#ease; }, set: (newEase) => { if (typeof newEase !== "boolean") { throw new TypeError(`'ease' is not a boolean.`); } this.#ease = newEase; this.#updateSubscribers(); }, enumerable: true }); Object.defineProperty(this, "easeOptions", { get: () => { return this.#easeOptions; }, set: (newEaseOptions) => { if (newEaseOptions === null || typeof newEaseOptions !== "object") { throw new TypeError(`'easeOptions' is not an object.`); } if (newEaseOptions.duration !== void 0) { if (!Number.isFinite(newEaseOptions.duration)) { throw new TypeError(`'easeOptions.duration' is not a finite number.`); } if (newEaseOptions.duration < 0) { throw new Error(`'easeOptions.duration' is less than 0.`); } this.#easeOptions.duration = newEaseOptions.duration; } if (newEaseOptions.ease !== void 0) { if (typeof newEaseOptions.ease !== "function" && typeof newEaseOptions.ease !== "string") { throw new TypeError(`'easeOptions.ease' is not a function or string.`); } this.#easeOptions.ease = newEaseOptions.ease; } this.#updateSubscribers(); }, enumerable: true }); if (ease !== void 0) { this.ease = ease; } if (easeOptions !== void 0) { this.easeOptions = easeOptions; } } /** * @returns {number} Get ease duration */ get easeDuration() { return this.#easeOptions.duration; } /** * @returns {string|Function} Get easing function value. */ get easeValue() { return this.#easeOptions.ease; } /** * @param {number} duration - Set ease duration. */ set easeDuration(duration) { if (!Number.isFinite(duration)) { throw new TypeError(`'duration' is not a finite number.`); } if (duration < 0) { throw new Error(`'duration' is less than 0.`); } this.#easeOptions.duration = duration; this.#updateSubscribers(); } /** * @param {string|Function} value - Get easing function value. */ set easeValue(value) { if (typeof value !== "function" && typeof value !== "string") { throw new TypeError(`'value' is not a function or string.`); } this.#easeOptions.ease = value; this.#updateSubscribers(); } /** * Resets all options data to default values. */ reset() { this.#ease = false; this.#easeOptions = { duration: 0.1, ease: cubicOut }; this.#updateSubscribers(); } /** * Resets easing options to default values. */ resetEase() { this.#easeOptions = { duration: 0.1, ease: cubicOut }; this.#updateSubscribers(); } /** * * @param {function(DraggableOptions): void} handler - Callback function that is invoked on update / changes. * Receives the DraggableOptions object / instance. * * @returns {(function(): void)} Unsubscribe function. */ subscribe(handler) { this.#subscriptions.push(handler); handler(this); return () => { const index = this.#subscriptions.findIndex((sub) => sub === handler); if (index >= 0) { this.#subscriptions.splice(index, 1); } }; } #updateSubscribers() { const subscriptions = this.#subscriptions; if (subscriptions.length > 0) { for (let cntr = 0; cntr < subscriptions.length; cntr++) { subscriptions[cntr](this); } } } } draggable.options = (options) => new DraggableOptions(options); const s_POSITION_DATA = { left: 0, top: 0 }; const s_DEFAULT_TRANSITION_OPTIONS = {}; const TJSGlassPane_svelte_svelte_type_style_lang = ""; class AppShellContextInternal { /** @type {InternalAppStores} */ #stores; constructor() { this.#stores = { elementContent: writable$1(void 0), elementRoot: writable$1(void 0) }; Object.freeze(this.#stores); Object.seal(this); } /** * @returns {InternalAppStores} The internal context stores for elementContent / elementRoot */ get stores() { return this.#stores; } } function localize(stringId, data) { const result = typeof data !== "object" ? globalThis.game.i18n.localize(stringId) : globalThis.game.i18n.format(stringId, data); return result !== void 0 ? result : ""; } const TJSHeaderButton_svelte_svelte_type_style_lang = ""; function create_if_block$a(ctx) { let span; let t; return { c() { span = element("span"); t = text$1( /*label*/ ctx[3] ); attr(span, "class", "svelte-ese-166l8wd"); toggle_class( span, "has-icon", /*icon*/ ctx[4] !== void 0 ); }, m(target, anchor) { insert(target, span, anchor); append(span, t); }, p(ctx2, dirty) { if (dirty & /*label*/ 8) set_data( t, /*label*/ ctx2[3] ); if (dirty & /*icon*/ 16) { toggle_class( span, "has-icon", /*icon*/ ctx2[4] !== void 0 ); } }, d(detaching) { if (detaching) detach(span); } }; } function create_fragment$p(ctx) { let a; let html_tag; let html_anchor; let a_class_value; let applyStyles_action; let mounted; let dispose; let if_block = ( /*label*/ ctx[3] && create_if_block$a(ctx) ); return { c() { a = element("a"); html_tag = new HtmlTag(false); html_anchor = empty(); if (if_block) if_block.c(); html_tag.a = html_anchor; attr(a, "class", a_class_value = "header-button " + /*button*/ ctx[0].class + " svelte-ese-166l8wd"); attr( a, "aria-label", /*label*/ ctx[3] ); attr(a, "tabindex", "0"); attr(a, "role", "button"); toggle_class( a, "keep-minimized", /*keepMinimized*/ ctx[2] ); }, m(target, anchor) { insert(target, a, anchor); html_tag.m( /*icon*/ ctx[4], a ); append(a, html_anchor); if (if_block) if_block.m(a, null); if (!mounted) { dispose = [ listen(a, "click", stop_propagation(prevent_default( /*onClick*/ ctx[5] ))), listen(a, "contextmenu", stop_propagation(prevent_default( /*onContextMenu*/ ctx[6] ))), listen( a, "keydown", /*onKeydown*/ ctx[7] ), listen( a, "keyup", /*onKeyup*/ ctx[8] ), action_destroyer(applyStyles_action = applyStyles.call( null, a, /*styles*/ ctx[1] )) ]; mounted = true; } }, p(ctx2, [dirty]) { if (dirty & /*icon*/ 16) html_tag.p( /*icon*/ ctx2[4] ); if ( /*label*/ ctx2[3] ) { if (if_block) { if_block.p(ctx2, dirty); } else { if_block = create_if_block$a(ctx2); if_block.c(); if_block.m(a, null); } } else if (if_block) { if_block.d(1); if_block = null; } if (dirty & /*button*/ 1 && a_class_value !== (a_class_value = "header-button " + /*button*/ ctx2[0].class + " svelte-ese-166l8wd")) { attr(a, "class", a_class_value); } if (dirty & /*label*/ 8) { attr( a, "aria-label", /*label*/ ctx2[3] ); } if (applyStyles_action && is_function(applyStyles_action.update) && dirty & /*styles*/ 2) applyStyles_action.update.call( null, /*styles*/ ctx2[1] ); if (dirty & /*button, keepMinimized*/ 5) { toggle_class( a, "keep-minimized", /*keepMinimized*/ ctx2[2] ); } }, i: noop, o: noop, d(detaching) { if (detaching) detach(a); if (if_block) if_block.d(); mounted = false; run_all(dispose); } }; } const s_REGEX_HTML = /^\s*<.*>$/; function instance$p($$self, $$props, $$invalidate) { let title; let icon; let label; let keepMinimized; let keyCode; let styles; let { button = void 0 } = $$props; function onClick(event) { const invoke = button?.onPress ?? button?.onclick; if (typeof invoke === "function") { invoke.call(button, event); $$invalidate(0, button); } } function onContextMenu(event) { const invoke = button?.onContextMenu; if (typeof invoke === "function") { invoke.call(button, event); $$invalidate(0, button); } } function onKeydown(event) { if (event.code === keyCode) { event.preventDefault(); event.stopPropagation(); } } function onKeyup(event) { if (event.code === keyCode) { const invoke = button.onPress ?? button.onclick; if (typeof invoke === "function") { invoke.call(button, event); $$invalidate(0, button); } event.preventDefault(); event.stopPropagation(); } } $$self.$$set = ($$props2) => { if ("button" in $$props2) $$invalidate(0, button = $$props2.button); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*button*/ 1) { $$invalidate(9, title = isObject(button) && typeof button.title === "string" ? localize(button.title) : ""); } if ($$self.$$.dirty & /*button, title*/ 513) { $$invalidate(4, icon = isObject(button) && typeof button.icon !== "string" ? void 0 : s_REGEX_HTML.test(button.icon) ? button.icon : ``); } if ($$self.$$.dirty & /*button*/ 1) { $$invalidate(3, label = isObject(button) && typeof button.label === "string" ? localize(button.label) : void 0); } if ($$self.$$.dirty & /*button*/ 1) { $$invalidate(2, keepMinimized = isObject(button) && typeof button.keepMinimized === "boolean" ? button.keepMinimized : false); } if ($$self.$$.dirty & /*button*/ 1) { keyCode = isObject(button) && typeof button.keyCode === "string" ? button.keyCode : "Enter"; } if ($$self.$$.dirty & /*button*/ 1) { $$invalidate(1, styles = isObject(button) && isObject(button.styles) ? button.styles : void 0); } }; return [ button, styles, keepMinimized, label, icon, onClick, onContextMenu, onKeydown, onKeyup, title ]; } class TJSHeaderButton extends SvelteComponent { constructor(options) { super(); init(this, options, instance$p, create_fragment$p, safe_not_equal, { button: 0 }); } get button() { return this.$$.ctx[0]; } set button(button) { this.$$set({ button }); flush(); } } const TJSApplicationHeader_svelte_svelte_type_style_lang = ""; function get_each_context$7(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[31] = list[i]; return child_ctx; } function get_each_context_1$2(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[31] = list[i]; return child_ctx; } function create_if_block$9(ctx) { let img; let img_src_value; return { c() { img = element("img"); attr(img, "class", "tjs-app-icon keep-minimized svelte-ese-1wviwl9"); if (!src_url_equal(img.src, img_src_value = /*$storeHeaderIcon*/ ctx[6])) attr(img, "src", img_src_value); attr(img, "alt", "icon"); }, m(target, anchor) { insert(target, img, anchor); }, p(ctx2, dirty) { if (dirty[0] & /*$storeHeaderIcon*/ 64 && !src_url_equal(img.src, img_src_value = /*$storeHeaderIcon*/ ctx2[6])) { attr(img, "src", img_src_value); } }, d(detaching) { if (detaching) detach(img); } }; } function create_each_block_1$2(ctx) { let switch_instance; let switch_instance_anchor; let current; const switch_instance_spread_levels = [ /*button*/ ctx[31].props ]; var switch_value = ( /*button*/ ctx[31].class ); function switch_props(ctx2) { let switch_instance_props = {}; for (let i = 0; i < switch_instance_spread_levels.length; i += 1) { switch_instance_props = assign(switch_instance_props, switch_instance_spread_levels[i]); } return { props: switch_instance_props }; } if (switch_value) { switch_instance = construct_svelte_component(switch_value, switch_props()); } return { c() { if (switch_instance) create_component(switch_instance.$$.fragment); switch_instance_anchor = empty(); }, m(target, anchor) { if (switch_instance) mount_component(switch_instance, target, anchor); insert(target, switch_instance_anchor, anchor); current = true; }, p(ctx2, dirty) { const switch_instance_changes = dirty[0] & /*buttonsLeft*/ 2 ? get_spread_update(switch_instance_spread_levels, [get_spread_object( /*button*/ ctx2[31].props )]) : {}; if (switch_value !== (switch_value = /*button*/ ctx2[31].class)) { if (switch_instance) { group_outros(); const old_component = switch_instance; transition_out(old_component.$$.fragment, 1, 0, () => { destroy_component(old_component, 1); }); check_outros(); } if (switch_value) { switch_instance = construct_svelte_component(switch_value, switch_props()); create_component(switch_instance.$$.fragment); transition_in(switch_instance.$$.fragment, 1); mount_component(switch_instance, switch_instance_anchor.parentNode, switch_instance_anchor); } else { switch_instance = null; } } else if (switch_value) { switch_instance.$set(switch_instance_changes); } }, i(local) { if (current) return; if (switch_instance) transition_in(switch_instance.$$.fragment, local); current = true; }, o(local) { if (switch_instance) transition_out(switch_instance.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(switch_instance_anchor); if (switch_instance) destroy_component(switch_instance, detaching); } }; } function create_each_block$7(ctx) { let switch_instance; let switch_instance_anchor; let current; const switch_instance_spread_levels = [ /*button*/ ctx[31].props ]; var switch_value = ( /*button*/ ctx[31].class ); function switch_props(ctx2) { let switch_instance_props = {}; for (let i = 0; i < switch_instance_spread_levels.length; i += 1) { switch_instance_props = assign(switch_instance_props, switch_instance_spread_levels[i]); } return { props: switch_instance_props }; } if (switch_value) { switch_instance = construct_svelte_component(switch_value, switch_props()); } return { c() { if (switch_instance) create_component(switch_instance.$$.fragment); switch_instance_anchor = empty(); }, m(target, anchor) { if (switch_instance) mount_component(switch_instance, target, anchor); insert(target, switch_instance_anchor, anchor); current = true; }, p(ctx2, dirty) { const switch_instance_changes = dirty[0] & /*buttonsRight*/ 4 ? get_spread_update(switch_instance_spread_levels, [get_spread_object( /*button*/ ctx2[31].props )]) : {}; if (switch_value !== (switch_value = /*button*/ ctx2[31].class)) { if (switch_instance) { group_outros(); const old_component = switch_instance; transition_out(old_component.$$.fragment, 1, 0, () => { destroy_component(old_component, 1); }); check_outros(); } if (switch_value) { switch_instance = construct_svelte_component(switch_value, switch_props()); create_component(switch_instance.$$.fragment); transition_in(switch_instance.$$.fragment, 1); mount_component(switch_instance, switch_instance_anchor.parentNode, switch_instance_anchor); } else { switch_instance = null; } } else if (switch_value) { switch_instance.$set(switch_instance_changes); } }, i(local) { if (current) return; if (switch_instance) transition_in(switch_instance.$$.fragment, local); current = true; }, o(local) { if (switch_instance) transition_out(switch_instance.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(switch_instance_anchor); if (switch_instance) destroy_component(switch_instance, detaching); } }; } function create_key_block(ctx) { let header; let t0; let h4; let t1_value = localize( /*$storeTitle*/ ctx[7] ) + ""; let t1; let t2; let t3; let span; let t4; let draggable_action; let minimizable_action; let current; let mounted; let dispose; let if_block = typeof /*$storeHeaderIcon*/ ctx[6] === "string" && create_if_block$9(ctx); let each_value_1 = ( /*buttonsLeft*/ ctx[1] ); let each_blocks_1 = []; for (let i = 0; i < each_value_1.length; i += 1) { each_blocks_1[i] = create_each_block_1$2(get_each_context_1$2(ctx, each_value_1, i)); } const out = (i) => transition_out(each_blocks_1[i], 1, 1, () => { each_blocks_1[i] = null; }); let each_value = ( /*buttonsRight*/ ctx[2] ); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { each_blocks[i] = create_each_block$7(get_each_context$7(ctx, each_value, i)); } const out_1 = (i) => transition_out(each_blocks[i], 1, 1, () => { each_blocks[i] = null; }); return { c() { header = element("header"); if (if_block) if_block.c(); t0 = space(); h4 = element("h4"); t1 = text$1(t1_value); t2 = space(); for (let i = 0; i < each_blocks_1.length; i += 1) { each_blocks_1[i].c(); } t3 = space(); span = element("span"); t4 = space(); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } attr(h4, "class", "window-title svelte-ese-1wviwl9"); set_style( h4, "display", /*displayHeaderTitle*/ ctx[4] ); attr(span, "class", "tjs-window-header-spacer keep-minimized svelte-ese-1wviwl9"); attr(header, "class", "window-header flexrow svelte-ese-1wviwl9"); }, m(target, anchor) { insert(target, header, anchor); if (if_block) if_block.m(header, null); append(header, t0); append(header, h4); append(h4, t1); append(header, t2); for (let i = 0; i < each_blocks_1.length; i += 1) { each_blocks_1[i].m(header, null); } append(header, t3); append(header, span); append(header, t4); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(header, null); } current = true; if (!mounted) { dispose = [ action_destroyer(draggable_action = /*draggable*/ ctx[0].call( null, header, /*dragOptions*/ ctx[3] )), action_destroyer(minimizable_action = /*minimizable*/ ctx[18].call( null, header, /*$storeMinimizable*/ ctx[5] )), listen( header, "pointerdown", /*onPointerdown*/ ctx[19] ) ]; mounted = true; } }, p(ctx2, dirty) { if (typeof /*$storeHeaderIcon*/ ctx2[6] === "string") { if (if_block) { if_block.p(ctx2, dirty); } else { if_block = create_if_block$9(ctx2); if_block.c(); if_block.m(header, t0); } } else if (if_block) { if_block.d(1); if_block = null; } if ((!current || dirty[0] & /*$storeTitle*/ 128) && t1_value !== (t1_value = localize( /*$storeTitle*/ ctx2[7] ) + "")) set_data(t1, t1_value); if (dirty[0] & /*displayHeaderTitle*/ 16) { set_style( h4, "display", /*displayHeaderTitle*/ ctx2[4] ); } if (dirty[0] & /*buttonsLeft*/ 2) { each_value_1 = /*buttonsLeft*/ ctx2[1]; let i; for (i = 0; i < each_value_1.length; i += 1) { const child_ctx = get_each_context_1$2(ctx2, each_value_1, i); if (each_blocks_1[i]) { each_blocks_1[i].p(child_ctx, dirty); transition_in(each_blocks_1[i], 1); } else { each_blocks_1[i] = create_each_block_1$2(child_ctx); each_blocks_1[i].c(); transition_in(each_blocks_1[i], 1); each_blocks_1[i].m(header, t3); } } group_outros(); for (i = each_value_1.length; i < each_blocks_1.length; i += 1) { out(i); } check_outros(); } if (dirty[0] & /*buttonsRight*/ 4) { each_value = /*buttonsRight*/ ctx2[2]; let i; for (i = 0; i < each_value.length; i += 1) { const child_ctx = get_each_context$7(ctx2, each_value, i); if (each_blocks[i]) { each_blocks[i].p(child_ctx, dirty); transition_in(each_blocks[i], 1); } else { each_blocks[i] = create_each_block$7(child_ctx); each_blocks[i].c(); transition_in(each_blocks[i], 1); each_blocks[i].m(header, null); } } group_outros(); for (i = each_value.length; i < each_blocks.length; i += 1) { out_1(i); } check_outros(); } if (draggable_action && is_function(draggable_action.update) && dirty[0] & /*dragOptions*/ 8) draggable_action.update.call( null, /*dragOptions*/ ctx2[3] ); if (minimizable_action && is_function(minimizable_action.update) && dirty[0] & /*$storeMinimizable*/ 32) minimizable_action.update.call( null, /*$storeMinimizable*/ ctx2[5] ); }, i(local) { if (current) return; for (let i = 0; i < each_value_1.length; i += 1) { transition_in(each_blocks_1[i]); } for (let i = 0; i < each_value.length; i += 1) { transition_in(each_blocks[i]); } current = true; }, o(local) { each_blocks_1 = each_blocks_1.filter(Boolean); for (let i = 0; i < each_blocks_1.length; i += 1) { transition_out(each_blocks_1[i]); } each_blocks = each_blocks.filter(Boolean); for (let i = 0; i < each_blocks.length; i += 1) { transition_out(each_blocks[i]); } current = false; }, d(detaching) { if (detaching) detach(header); if (if_block) if_block.d(); destroy_each(each_blocks_1, detaching); destroy_each(each_blocks, detaching); mounted = false; run_all(dispose); } }; } function create_fragment$o(ctx) { let previous_key = ( /*draggable*/ ctx[0] ); let key_block_anchor; let current; let key_block = create_key_block(ctx); return { c() { key_block.c(); key_block_anchor = empty(); }, m(target, anchor) { key_block.m(target, anchor); insert(target, key_block_anchor, anchor); current = true; }, p(ctx2, dirty) { if (dirty[0] & /*draggable*/ 1 && safe_not_equal(previous_key, previous_key = /*draggable*/ ctx2[0])) { group_outros(); transition_out(key_block, 1, 1, noop); check_outros(); key_block = create_key_block(ctx2); key_block.c(); transition_in(key_block, 1); key_block.m(key_block_anchor.parentNode, key_block_anchor); } else { key_block.p(ctx2, dirty); } }, i(local) { if (current) return; transition_in(key_block); current = true; }, o(local) { transition_out(key_block); current = false; }, d(detaching) { if (detaching) detach(key_block_anchor); key_block.d(detaching); } }; } function instance$o($$self, $$props, $$invalidate) { let $focusKeep; let $focusAuto; let $elementRoot; let $storeHeaderButtons; let $storeMinimized; let $storeHeaderNoTitleMinimized; let $storeDraggable; let $storeMinimizable; let $storeHeaderIcon; let $storeTitle; let { draggable: draggable$1 = void 0 } = $$props; let { draggableOptions = void 0 } = $$props; const { application } = getContext("#external"); const { focusAuto, focusKeep } = application.reactive.storeAppOptions; component_subscribe($$self, focusAuto, (value) => $$invalidate(26, $focusAuto = value)); component_subscribe($$self, focusKeep, (value) => $$invalidate(25, $focusKeep = value)); const { elementRoot } = getContext("#internal").stores; component_subscribe($$self, elementRoot, (value) => $$invalidate(27, $elementRoot = value)); const storeTitle = application.reactive.storeAppOptions.title; component_subscribe($$self, storeTitle, (value) => $$invalidate(7, $storeTitle = value)); const storeDraggable = application.reactive.storeAppOptions.draggable; component_subscribe($$self, storeDraggable, (value) => $$invalidate(24, $storeDraggable = value)); const storeDragging = application.reactive.storeUIState.dragging; const storeHeaderButtons = application.reactive.storeUIState.headerButtons; component_subscribe($$self, storeHeaderButtons, (value) => $$invalidate(21, $storeHeaderButtons = value)); const storeHeaderIcon = application.reactive.storeAppOptions.headerIcon; component_subscribe($$self, storeHeaderIcon, (value) => $$invalidate(6, $storeHeaderIcon = value)); const storeHeaderNoTitleMinimized = application.reactive.storeAppOptions.headerNoTitleMinimized; component_subscribe($$self, storeHeaderNoTitleMinimized, (value) => $$invalidate(23, $storeHeaderNoTitleMinimized = value)); const storeMinimizable = application.reactive.storeAppOptions.minimizable; component_subscribe($$self, storeMinimizable, (value) => $$invalidate(5, $storeMinimizable = value)); const storeMinimized = application.reactive.storeUIState.minimized; component_subscribe($$self, storeMinimized, (value) => $$invalidate(22, $storeMinimized = value)); const s_DRAG_TARGET_CLASSLIST = Object.freeze(["tjs-app-icon", "tjs-window-header-spacer", "window-header", "window-title"]); let dragOptions; let displayHeaderTitle; let buttonsLeft; let buttonsRight; function minimizable(node, booleanStore) { const callback = (event) => { if (event.target.classList.contains("window-title") || event.target.classList.contains("window-header") || event.target.classList.contains("keep-minimized")) { application._onToggleMinimize(event); } }; function activateListeners() { node.addEventListener("dblclick", callback); } function removeListeners() { node.removeEventListener("dblclick", callback); } if (booleanStore) { activateListeners(); } return { update: (booleanStore2) => { if (booleanStore2) { activateListeners(); } else { removeListeners(); } }, destroy: () => removeListeners() }; } function onPointerdown(event) { const rootEl = $elementRoot; if ($focusAuto && rootEl instanceof HTMLElement && rootEl?.isConnected) { if ($focusKeep) { const focusOutside = document.activeElement instanceof HTMLElement && !rootEl.contains(document.activeElement); if (focusOutside) { rootEl.focus(); } else { event.preventDefault(); } } else { rootEl.focus(); } } } $$self.$$set = ($$props2) => { if ("draggable" in $$props2) $$invalidate(0, draggable$1 = $$props2.draggable); if ("draggableOptions" in $$props2) $$invalidate(20, draggableOptions = $$props2.draggableOptions); }; $$self.$$.update = () => { if ($$self.$$.dirty[0] & /*draggable*/ 1) { $$invalidate(0, draggable$1 = typeof draggable$1 === "function" ? draggable$1 : draggable); } if ($$self.$$.dirty[0] & /*draggableOptions, $storeDraggable*/ 17825792) { $$invalidate(3, dragOptions = Object.assign( {}, { ease: true, easeOptions: { duration: 0.08, ease: cubicOut } }, isObject(draggableOptions) ? draggableOptions : {}, { position: application.position, active: $storeDraggable, storeDragging, hasTargetClassList: s_DRAG_TARGET_CLASSLIST } )); } if ($$self.$$.dirty[0] & /*$storeHeaderNoTitleMinimized, $storeMinimized*/ 12582912) { $$invalidate(4, displayHeaderTitle = $storeHeaderNoTitleMinimized && $storeMinimized ? "none" : null); } if ($$self.$$.dirty[0] & /*$storeHeaderButtons, buttonsLeft, buttonsRight*/ 2097158) { { $$invalidate(1, buttonsLeft = []); $$invalidate(2, buttonsRight = []); for (const button of $storeHeaderButtons) { const buttonsList = typeof button?.alignLeft === "boolean" && button?.alignLeft ? buttonsLeft : buttonsRight; buttonsList.push(isSvelteComponent(button) ? { class: button, props: {} } : { class: TJSHeaderButton, props: { button } }); } } } }; return [ draggable$1, buttonsLeft, buttonsRight, dragOptions, displayHeaderTitle, $storeMinimizable, $storeHeaderIcon, $storeTitle, focusAuto, focusKeep, elementRoot, storeTitle, storeDraggable, storeHeaderButtons, storeHeaderIcon, storeHeaderNoTitleMinimized, storeMinimizable, storeMinimized, minimizable, onPointerdown, draggableOptions, $storeHeaderButtons, $storeMinimized, $storeHeaderNoTitleMinimized, $storeDraggable ]; } class TJSApplicationHeader extends SvelteComponent { constructor(options) { super(); init(this, options, instance$o, create_fragment$o, safe_not_equal, { draggable: 0, draggableOptions: 20 }, null, [-1, -1]); } } const TJSFocusWrap_svelte_svelte_type_style_lang = ""; function create_fragment$n(ctx) { let div; let mounted; let dispose; return { c() { div = element("div"); attr(div, "class", "tjs-focus-wrap svelte-ese-kjcljd"); attr(div, "tabindex", "0"); }, m(target, anchor) { insert(target, div, anchor); ctx[4](div); if (!mounted) { dispose = listen( div, "focus", /*onFocus*/ ctx[1] ); mounted = true; } }, p: noop, i: noop, o: noop, d(detaching) { if (detaching) detach(div); ctx[4](null); mounted = false; dispose(); } }; } function instance$n($$self, $$props, $$invalidate) { let { elementRoot = void 0 } = $$props; let { enabled = true } = $$props; let ignoreElements, wrapEl; function onFocus() { if (!enabled) { return; } if (elementRoot instanceof HTMLElement) { const firstFocusEl = A11yHelper.getFirstFocusableElement(elementRoot, ignoreElements); if (firstFocusEl instanceof HTMLElement && firstFocusEl !== wrapEl) { firstFocusEl.focus(); } else { elementRoot.focus(); } } } function div_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { wrapEl = $$value; $$invalidate(0, wrapEl); }); } $$self.$$set = ($$props2) => { if ("elementRoot" in $$props2) $$invalidate(2, elementRoot = $$props2.elementRoot); if ("enabled" in $$props2) $$invalidate(3, enabled = $$props2.enabled); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*wrapEl*/ 1) { if (wrapEl) { ignoreElements = /* @__PURE__ */ new Set([wrapEl]); } } }; return [wrapEl, onFocus, elementRoot, enabled, div_binding]; } class TJSFocusWrap extends SvelteComponent { constructor(options) { super(); init(this, options, instance$n, create_fragment$n, safe_not_equal, { elementRoot: 2, enabled: 3 }); } } function create_fragment$m(ctx) { let div; let resizable_action; let mounted; let dispose; return { c() { div = element("div"); div.innerHTML = ``; attr(div, "class", "window-resizable-handle"); }, m(target, anchor) { insert(target, div, anchor); ctx[10](div); if (!mounted) { dispose = action_destroyer(resizable_action = /*resizable*/ ctx[6].call(null, div, { active: ( /*$storeResizable*/ ctx[1] ), storeResizing: ( /*storeResizing*/ ctx[5] ) })); mounted = true; } }, p(ctx2, [dirty]) { if (resizable_action && is_function(resizable_action.update) && dirty & /*$storeResizable*/ 2) resizable_action.update.call(null, { active: ( /*$storeResizable*/ ctx2[1] ), storeResizing: ( /*storeResizing*/ ctx2[5] ) }); }, i: noop, o: noop, d(detaching) { if (detaching) detach(div); ctx[10](null); mounted = false; dispose(); } }; } function instance$m($$self, $$props, $$invalidate) { let $storeElementRoot; let $storeMinimized; let $storeResizable; let { isResizable = false } = $$props; const application = getContext("#external").application; const storeElementRoot = getContext("storeElementRoot"); component_subscribe($$self, storeElementRoot, (value) => $$invalidate(8, $storeElementRoot = value)); const storeResizable = application.reactive.storeAppOptions.resizable; component_subscribe($$self, storeResizable, (value) => $$invalidate(1, $storeResizable = value)); const storeMinimized = application.reactive.storeUIState.minimized; component_subscribe($$self, storeMinimized, (value) => $$invalidate(9, $storeMinimized = value)); const storeResizing = application.reactive.storeUIState.resizing; let elementResize; function resizable(node, { active: active2 = true, storeResizing: storeResizing2 = void 0 } = {}) { let position = null; let initialPosition = {}; let resizing = false; const handlers = { resizeDown: ["pointerdown", (e) => onResizePointerDown(e), false], resizeMove: ["pointermove", (e) => onResizePointerMove(e), false], resizeUp: ["pointerup", (e) => onResizePointerUp(e), false] }; function activateListeners() { node.addEventListener(...handlers.resizeDown); $$invalidate(7, isResizable = true); node.style.display = "block"; } function removeListeners() { if (typeof storeResizing2?.set === "function") { storeResizing2.set(false); } node.removeEventListener(...handlers.resizeDown); node.removeEventListener(...handlers.resizeMove); node.removeEventListener(...handlers.resizeUp); node.style.display = "none"; $$invalidate(7, isResizable = false); } if (active2) { activateListeners(); } else { node.style.display = "none"; } function onResizePointerDown(event) { event.preventDefault(); resizing = false; position = application.position.get(); if (position.height === "auto") { position.height = $storeElementRoot.clientHeight; } if (position.width === "auto") { position.width = $storeElementRoot.clientWidth; } initialPosition = { x: event.clientX, y: event.clientY }; node.addEventListener(...handlers.resizeMove); node.addEventListener(...handlers.resizeUp); node.setPointerCapture(event.pointerId); } function onResizePointerMove(event) { event.preventDefault(); if (!resizing && typeof storeResizing2?.set === "function") { resizing = true; storeResizing2.set(true); } application.position.set({ width: position.width + (event.clientX - initialPosition.x), height: position.height + (event.clientY - initialPosition.y) }); } function onResizePointerUp(event) { resizing = false; if (typeof storeResizing2?.set === "function") { storeResizing2.set(false); } event.preventDefault(); node.removeEventListener(...handlers.resizeMove); node.removeEventListener(...handlers.resizeUp); application._onResize(event); } return { update: ({ active: active3 }) => { if (active3) { activateListeners(); } else { removeListeners(); } }, destroy: () => removeListeners() }; } function div_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { elementResize = $$value; $$invalidate(0, elementResize), $$invalidate(7, isResizable), $$invalidate(9, $storeMinimized), $$invalidate(8, $storeElementRoot); }); } $$self.$$set = ($$props2) => { if ("isResizable" in $$props2) $$invalidate(7, isResizable = $$props2.isResizable); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*elementResize, isResizable, $storeMinimized, $storeElementRoot*/ 897) { if (elementResize) { $$invalidate(0, elementResize.style.display = isResizable && !$storeMinimized ? "block" : "none", elementResize); const elementRoot = $storeElementRoot; if (elementRoot) { elementRoot.classList[isResizable ? "add" : "remove"]("resizable"); } } } }; return [ elementResize, $storeResizable, storeElementRoot, storeResizable, storeMinimized, storeResizing, resizable, isResizable, $storeElementRoot, $storeMinimized, div_binding ]; } class ResizableHandle extends SvelteComponent { constructor(options) { super(); init(this, options, instance$m, create_fragment$m, safe_not_equal, { isResizable: 7 }); } } const ApplicationShell_svelte_svelte_type_style_lang = ""; function create_else_block$2(ctx) { let div; let tjsapplicationheader; let t0; let section; let applyStyles_action; let t1; let resizablehandle; let t2; let tjsfocuswrap; let div_id_value; let div_class_value; let div_data_appid_value; let applyStyles_action_1; let current; let mounted; let dispose; tjsapplicationheader = new TJSApplicationHeader({ props: { draggable: ( /*draggable*/ ctx[6] ), draggableOptions: ( /*draggableOptions*/ ctx[7] ) } }); const default_slot_template = ( /*#slots*/ ctx[36].default ); const default_slot = create_slot( default_slot_template, ctx, /*$$scope*/ ctx[35], null ); resizablehandle = new ResizableHandle({}); tjsfocuswrap = new TJSFocusWrap({ props: { elementRoot: ( /*elementRoot*/ ctx[1] ), enabled: ( /*focusWrapEnabled*/ ctx[11] ) } }); return { c() { div = element("div"); create_component(tjsapplicationheader.$$.fragment); t0 = space(); section = element("section"); if (default_slot) default_slot.c(); t1 = space(); create_component(resizablehandle.$$.fragment); t2 = space(); create_component(tjsfocuswrap.$$.fragment); attr(section, "class", "window-content svelte-ese-oz81f7"); attr(section, "tabindex", "-1"); attr(div, "id", div_id_value = /*application*/ ctx[10].id); attr(div, "class", div_class_value = "app window-app " + /*application*/ ctx[10].options.classes.join(" ") + " svelte-ese-oz81f7"); attr(div, "data-appid", div_data_appid_value = /*application*/ ctx[10].appId); attr(div, "tabindex", "-1"); }, m(target, anchor) { insert(target, div, anchor); mount_component(tjsapplicationheader, div, null); append(div, t0); append(div, section); if (default_slot) { default_slot.m(section, null); } ctx[39](section); append(div, t1); mount_component(resizablehandle, div, null); append(div, t2); mount_component(tjsfocuswrap, div, null); ctx[40](div); current = true; if (!mounted) { dispose = [ listen( section, "pointerdown", /*onPointerdownContent*/ ctx[21] ), action_destroyer(applyStyles_action = applyStyles.call( null, section, /*stylesContent*/ ctx[9] )), action_destroyer( /*contentResizeObserver*/ ctx[13].call( null, section, /*resizeObservedContent*/ ctx[22] ) ), listen(div, "close:popup", stop_propagation(prevent_default( /*onClosePopup*/ ctx[18] ))), listen( div, "keydown", /*onKeydown*/ ctx[19], true ), listen( div, "pointerdown", /*onPointerdownApp*/ ctx[20] ), action_destroyer(applyStyles_action_1 = applyStyles.call( null, div, /*stylesApp*/ ctx[8] )), action_destroyer( /*appResizeObserver*/ ctx[12].call( null, div, /*resizeObservedApp*/ ctx[23] ) ) ]; mounted = true; } }, p(ctx2, dirty) { const tjsapplicationheader_changes = {}; if (dirty[0] & /*draggable*/ 64) tjsapplicationheader_changes.draggable = /*draggable*/ ctx2[6]; if (dirty[0] & /*draggableOptions*/ 128) tjsapplicationheader_changes.draggableOptions = /*draggableOptions*/ ctx2[7]; tjsapplicationheader.$set(tjsapplicationheader_changes); if (default_slot) { if (default_slot.p && (!current || dirty[1] & /*$$scope*/ 16)) { update_slot_base( default_slot, default_slot_template, ctx2, /*$$scope*/ ctx2[35], !current ? get_all_dirty_from_scope( /*$$scope*/ ctx2[35] ) : get_slot_changes( default_slot_template, /*$$scope*/ ctx2[35], dirty, null ), null ); } } if (applyStyles_action && is_function(applyStyles_action.update) && dirty[0] & /*stylesContent*/ 512) applyStyles_action.update.call( null, /*stylesContent*/ ctx2[9] ); const tjsfocuswrap_changes = {}; if (dirty[0] & /*elementRoot*/ 2) tjsfocuswrap_changes.elementRoot = /*elementRoot*/ ctx2[1]; if (dirty[0] & /*focusWrapEnabled*/ 2048) tjsfocuswrap_changes.enabled = /*focusWrapEnabled*/ ctx2[11]; tjsfocuswrap.$set(tjsfocuswrap_changes); if (!current || dirty[0] & /*application*/ 1024 && div_id_value !== (div_id_value = /*application*/ ctx2[10].id)) { attr(div, "id", div_id_value); } if (!current || dirty[0] & /*application*/ 1024 && div_class_value !== (div_class_value = "app window-app " + /*application*/ ctx2[10].options.classes.join(" ") + " svelte-ese-oz81f7")) { attr(div, "class", div_class_value); } if (!current || dirty[0] & /*application*/ 1024 && div_data_appid_value !== (div_data_appid_value = /*application*/ ctx2[10].appId)) { attr(div, "data-appid", div_data_appid_value); } if (applyStyles_action_1 && is_function(applyStyles_action_1.update) && dirty[0] & /*stylesApp*/ 256) applyStyles_action_1.update.call( null, /*stylesApp*/ ctx2[8] ); }, i(local) { if (current) return; transition_in(tjsapplicationheader.$$.fragment, local); transition_in(default_slot, local); transition_in(resizablehandle.$$.fragment, local); transition_in(tjsfocuswrap.$$.fragment, local); current = true; }, o(local) { transition_out(tjsapplicationheader.$$.fragment, local); transition_out(default_slot, local); transition_out(resizablehandle.$$.fragment, local); transition_out(tjsfocuswrap.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(div); destroy_component(tjsapplicationheader); if (default_slot) default_slot.d(detaching); ctx[39](null); destroy_component(resizablehandle); destroy_component(tjsfocuswrap); ctx[40](null); mounted = false; run_all(dispose); } }; } function create_if_block$8(ctx) { let div; let tjsapplicationheader; let t0; let section; let applyStyles_action; let t1; let resizablehandle; let t2; let tjsfocuswrap; let div_id_value; let div_class_value; let div_data_appid_value; let applyStyles_action_1; let div_intro; let div_outro; let current; let mounted; let dispose; tjsapplicationheader = new TJSApplicationHeader({ props: { draggable: ( /*draggable*/ ctx[6] ), draggableOptions: ( /*draggableOptions*/ ctx[7] ) } }); const default_slot_template = ( /*#slots*/ ctx[36].default ); const default_slot = create_slot( default_slot_template, ctx, /*$$scope*/ ctx[35], null ); resizablehandle = new ResizableHandle({}); tjsfocuswrap = new TJSFocusWrap({ props: { elementRoot: ( /*elementRoot*/ ctx[1] ) } }); return { c() { div = element("div"); create_component(tjsapplicationheader.$$.fragment); t0 = space(); section = element("section"); if (default_slot) default_slot.c(); t1 = space(); create_component(resizablehandle.$$.fragment); t2 = space(); create_component(tjsfocuswrap.$$.fragment); attr(section, "class", "window-content svelte-ese-oz81f7"); attr(section, "tabindex", "-1"); attr(div, "id", div_id_value = /*application*/ ctx[10].id); attr(div, "class", div_class_value = "app window-app " + /*application*/ ctx[10].options.classes.join(" ") + " svelte-ese-oz81f7"); attr(div, "data-appid", div_data_appid_value = /*application*/ ctx[10].appId); attr(div, "tabindex", "-1"); }, m(target, anchor) { insert(target, div, anchor); mount_component(tjsapplicationheader, div, null); append(div, t0); append(div, section); if (default_slot) { default_slot.m(section, null); } ctx[37](section); append(div, t1); mount_component(resizablehandle, div, null); append(div, t2); mount_component(tjsfocuswrap, div, null); ctx[38](div); current = true; if (!mounted) { dispose = [ listen( section, "pointerdown", /*onPointerdownContent*/ ctx[21] ), action_destroyer(applyStyles_action = applyStyles.call( null, section, /*stylesContent*/ ctx[9] )), action_destroyer( /*contentResizeObserver*/ ctx[13].call( null, section, /*resizeObservedContent*/ ctx[22] ) ), listen(div, "close:popup", stop_propagation(prevent_default( /*onClosePopup*/ ctx[18] ))), listen( div, "keydown", /*onKeydown*/ ctx[19], true ), listen( div, "pointerdown", /*onPointerdownApp*/ ctx[20] ), action_destroyer(applyStyles_action_1 = applyStyles.call( null, div, /*stylesApp*/ ctx[8] )), action_destroyer( /*appResizeObserver*/ ctx[12].call( null, div, /*resizeObservedApp*/ ctx[23] ) ) ]; mounted = true; } }, p(new_ctx, dirty) { ctx = new_ctx; const tjsapplicationheader_changes = {}; if (dirty[0] & /*draggable*/ 64) tjsapplicationheader_changes.draggable = /*draggable*/ ctx[6]; if (dirty[0] & /*draggableOptions*/ 128) tjsapplicationheader_changes.draggableOptions = /*draggableOptions*/ ctx[7]; tjsapplicationheader.$set(tjsapplicationheader_changes); if (default_slot) { if (default_slot.p && (!current || dirty[1] & /*$$scope*/ 16)) { update_slot_base( default_slot, default_slot_template, ctx, /*$$scope*/ ctx[35], !current ? get_all_dirty_from_scope( /*$$scope*/ ctx[35] ) : get_slot_changes( default_slot_template, /*$$scope*/ ctx[35], dirty, null ), null ); } } if (applyStyles_action && is_function(applyStyles_action.update) && dirty[0] & /*stylesContent*/ 512) applyStyles_action.update.call( null, /*stylesContent*/ ctx[9] ); const tjsfocuswrap_changes = {}; if (dirty[0] & /*elementRoot*/ 2) tjsfocuswrap_changes.elementRoot = /*elementRoot*/ ctx[1]; tjsfocuswrap.$set(tjsfocuswrap_changes); if (!current || dirty[0] & /*application*/ 1024 && div_id_value !== (div_id_value = /*application*/ ctx[10].id)) { attr(div, "id", div_id_value); } if (!current || dirty[0] & /*application*/ 1024 && div_class_value !== (div_class_value = "app window-app " + /*application*/ ctx[10].options.classes.join(" ") + " svelte-ese-oz81f7")) { attr(div, "class", div_class_value); } if (!current || dirty[0] & /*application*/ 1024 && div_data_appid_value !== (div_data_appid_value = /*application*/ ctx[10].appId)) { attr(div, "data-appid", div_data_appid_value); } if (applyStyles_action_1 && is_function(applyStyles_action_1.update) && dirty[0] & /*stylesApp*/ 256) applyStyles_action_1.update.call( null, /*stylesApp*/ ctx[8] ); }, i(local) { if (current) return; transition_in(tjsapplicationheader.$$.fragment, local); transition_in(default_slot, local); transition_in(resizablehandle.$$.fragment, local); transition_in(tjsfocuswrap.$$.fragment, local); add_render_callback(() => { if (div_outro) div_outro.end(1); div_intro = create_in_transition( div, /*inTransition*/ ctx[2], /*inTransitionOptions*/ ctx[4] ); div_intro.start(); }); current = true; }, o(local) { transition_out(tjsapplicationheader.$$.fragment, local); transition_out(default_slot, local); transition_out(resizablehandle.$$.fragment, local); transition_out(tjsfocuswrap.$$.fragment, local); if (div_intro) div_intro.invalidate(); div_outro = create_out_transition( div, /*outTransition*/ ctx[3], /*outTransitionOptions*/ ctx[5] ); current = false; }, d(detaching) { if (detaching) detach(div); destroy_component(tjsapplicationheader); if (default_slot) default_slot.d(detaching); ctx[37](null); destroy_component(resizablehandle); destroy_component(tjsfocuswrap); ctx[38](null); if (detaching && div_outro) div_outro.end(); mounted = false; run_all(dispose); } }; } function create_fragment$l(ctx) { let current_block_type_index; let if_block; let if_block_anchor; let current; const if_block_creators = [create_if_block$8, create_else_block$2]; const if_blocks = []; function select_block_type(ctx2, dirty) { if ( /*inTransition*/ ctx2[2] || /*outTransition*/ ctx2[3] ) return 0; return 1; } current_block_type_index = select_block_type(ctx); if_block = if_blocks[current_block_type_index] = if_block_creators[current_block_type_index](ctx); return { c() { if_block.c(); if_block_anchor = empty(); }, m(target, anchor) { if_blocks[current_block_type_index].m(target, anchor); insert(target, if_block_anchor, anchor); current = true; }, p(ctx2, dirty) { let previous_block_index = current_block_type_index; current_block_type_index = select_block_type(ctx2); if (current_block_type_index === previous_block_index) { if_blocks[current_block_type_index].p(ctx2, dirty); } else { group_outros(); transition_out(if_blocks[previous_block_index], 1, 1, () => { if_blocks[previous_block_index] = null; }); check_outros(); if_block = if_blocks[current_block_type_index]; if (!if_block) { if_block = if_blocks[current_block_type_index] = if_block_creators[current_block_type_index](ctx2); if_block.c(); } else { if_block.p(ctx2, dirty); } transition_in(if_block, 1); if_block.m(if_block_anchor.parentNode, if_block_anchor); } }, i(local) { if (current) return; transition_in(if_block); current = true; }, o(local) { transition_out(if_block); current = false; }, d(detaching) { if_blocks[current_block_type_index].d(detaching); if (detaching) detach(if_block_anchor); } }; } function instance$l($$self, $$props, $$invalidate) { let $focusKeep; let $focusAuto; let $minimized; let $focusTrap; let { $$slots: slots = {}, $$scope } = $$props; let { elementContent = void 0 } = $$props; let { elementRoot = void 0 } = $$props; let { draggable: draggable2 = void 0 } = $$props; let { draggableOptions = void 0 } = $$props; let { stylesApp = void 0 } = $$props; let { stylesContent = void 0 } = $$props; let { appOffsetHeight = false } = $$props; let { appOffsetWidth = false } = $$props; const appResizeObserver = !!appOffsetHeight || !!appOffsetWidth ? resizeObserver : () => null; let { contentOffsetHeight = false } = $$props; let { contentOffsetWidth = false } = $$props; const contentResizeObserver = !!contentOffsetHeight || !!contentOffsetWidth ? resizeObserver : () => null; const internal = new AppShellContextInternal(); const s_IGNORE_CLASSES = { ignoreClasses: ["tjs-focus-wrap"] }; setContext("#internal", internal); const { application } = getContext("#external"); const { focusAuto, focusKeep, focusTrap } = application.reactive.storeAppOptions; component_subscribe($$self, focusAuto, (value) => $$invalidate(32, $focusAuto = value)); component_subscribe($$self, focusKeep, (value) => $$invalidate(41, $focusKeep = value)); component_subscribe($$self, focusTrap, (value) => $$invalidate(34, $focusTrap = value)); const { minimized } = application.reactive.storeUIState; component_subscribe($$self, minimized, (value) => $$invalidate(33, $minimized = value)); let focusWrapEnabled; let { transition = void 0 } = $$props; let { inTransition = void 0 } = $$props; let { outTransition = void 0 } = $$props; let { transitionOptions = void 0 } = $$props; let { inTransitionOptions = s_DEFAULT_TRANSITION_OPTIONS } = $$props; let { outTransitionOptions = s_DEFAULT_TRANSITION_OPTIONS } = $$props; let oldTransition = void 0; let oldTransitionOptions = void 0; onMount(() => elementRoot.focus()); function onClosePopup(event) { if (!$focusAuto) { return; } const targetEl = event?.detail?.target; if (!(targetEl instanceof HTMLElement)) { return; } if (A11yHelper.isFocusable(targetEl)) { return; } const elementRootContains = elementRoot.contains(targetEl); if (targetEl === elementRoot) { elementRoot.focus(); } else if (targetEl === elementContent) { elementContent.focus(); } else if (elementRootContains) { if (elementContent.contains(targetEl)) { elementContent.focus(); } else { elementRoot.focus(); } } } function onKeydown(event) { if (focusWrapEnabled && event.shiftKey && event.code === "Tab") { const allFocusable = A11yHelper.getFocusableElements(elementRoot, s_IGNORE_CLASSES); const firstFocusEl = allFocusable.length > 0 ? allFocusable[0] : void 0; const lastFocusEl = allFocusable.length > 0 ? allFocusable[allFocusable.length - 1] : void 0; if (elementRoot === document.activeElement || firstFocusEl === document.activeElement) { if (lastFocusEl instanceof HTMLElement && firstFocusEl !== lastFocusEl) { lastFocusEl.focus(); } event.preventDefault(); event.stopPropagation(); } } if (typeof application?.options?.popOut === "boolean" && application.options.popOut && application !== globalThis.ui?.activeWindow) { application.bringToTop.call(application); } } function onPointerdownApp() { if (typeof application?.options?.popOut === "boolean" && application.options.popOut && application !== globalThis.ui?.activeWindow) { application.bringToTop.call(application); } } function onPointerdownContent(event) { const focusable = A11yHelper.isFocusable(event.target); if (!focusable && $focusAuto) { if ($focusKeep) { const focusOutside = document.activeElement instanceof HTMLElement && !elementRoot.contains(document.activeElement); if (focusOutside) { elementContent.focus(); } else { event.preventDefault(); } } else { elementContent.focus(); } } } function resizeObservedContent(offsetWidth, offsetHeight) { $$invalidate(27, contentOffsetWidth = offsetWidth); $$invalidate(26, contentOffsetHeight = offsetHeight); } function resizeObservedApp(offsetWidth, offsetHeight, contentWidth, contentHeight) { application.position.stores.resizeObserved.update((object) => { object.contentWidth = contentWidth; object.contentHeight = contentHeight; object.offsetWidth = offsetWidth; object.offsetHeight = offsetHeight; return object; }); $$invalidate(24, appOffsetHeight = offsetHeight); $$invalidate(25, appOffsetWidth = offsetWidth); } function section_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { elementContent = $$value; $$invalidate(0, elementContent); }); } function div_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { elementRoot = $$value; $$invalidate(1, elementRoot); }); } function section_binding_1($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { elementContent = $$value; $$invalidate(0, elementContent); }); } function div_binding_1($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { elementRoot = $$value; $$invalidate(1, elementRoot); }); } $$self.$$set = ($$props2) => { if ("elementContent" in $$props2) $$invalidate(0, elementContent = $$props2.elementContent); if ("elementRoot" in $$props2) $$invalidate(1, elementRoot = $$props2.elementRoot); if ("draggable" in $$props2) $$invalidate(6, draggable2 = $$props2.draggable); if ("draggableOptions" in $$props2) $$invalidate(7, draggableOptions = $$props2.draggableOptions); if ("stylesApp" in $$props2) $$invalidate(8, stylesApp = $$props2.stylesApp); if ("stylesContent" in $$props2) $$invalidate(9, stylesContent = $$props2.stylesContent); if ("appOffsetHeight" in $$props2) $$invalidate(24, appOffsetHeight = $$props2.appOffsetHeight); if ("appOffsetWidth" in $$props2) $$invalidate(25, appOffsetWidth = $$props2.appOffsetWidth); if ("contentOffsetHeight" in $$props2) $$invalidate(26, contentOffsetHeight = $$props2.contentOffsetHeight); if ("contentOffsetWidth" in $$props2) $$invalidate(27, contentOffsetWidth = $$props2.contentOffsetWidth); if ("transition" in $$props2) $$invalidate(28, transition = $$props2.transition); if ("inTransition" in $$props2) $$invalidate(2, inTransition = $$props2.inTransition); if ("outTransition" in $$props2) $$invalidate(3, outTransition = $$props2.outTransition); if ("transitionOptions" in $$props2) $$invalidate(29, transitionOptions = $$props2.transitionOptions); if ("inTransitionOptions" in $$props2) $$invalidate(4, inTransitionOptions = $$props2.inTransitionOptions); if ("outTransitionOptions" in $$props2) $$invalidate(5, outTransitionOptions = $$props2.outTransitionOptions); if ("$$scope" in $$props2) $$invalidate(35, $$scope = $$props2.$$scope); }; $$self.$$.update = () => { if ($$self.$$.dirty[0] & /*elementContent*/ 1) { if (elementContent !== void 0 && elementContent !== null) { getContext("#internal").stores.elementContent.set(elementContent); } } if ($$self.$$.dirty[0] & /*elementRoot*/ 2) { if (elementRoot !== void 0 && elementRoot !== null) { getContext("#internal").stores.elementRoot.set(elementRoot); } } if ($$self.$$.dirty[1] & /*$focusAuto, $focusTrap, $minimized*/ 14) { $$invalidate(11, focusWrapEnabled = $focusAuto && $focusTrap && !$minimized); } if ($$self.$$.dirty[0] & /*oldTransition, transition*/ 1342177280) { if (oldTransition !== transition) { const newTransition = typeof transition === "function" ? transition : void 0; $$invalidate(2, inTransition = newTransition); $$invalidate(3, outTransition = newTransition); $$invalidate(30, oldTransition = newTransition); } } if ($$self.$$.dirty[0] & /*transitionOptions*/ 536870912 | $$self.$$.dirty[1] & /*oldTransitionOptions*/ 1) { if (oldTransitionOptions !== transitionOptions) { const newOptions = transitionOptions !== s_DEFAULT_TRANSITION_OPTIONS && isObject(transitionOptions) ? transitionOptions : s_DEFAULT_TRANSITION_OPTIONS; $$invalidate(4, inTransitionOptions = newOptions); $$invalidate(5, outTransitionOptions = newOptions); $$invalidate(31, oldTransitionOptions = newOptions); } } if ($$self.$$.dirty[0] & /*inTransition*/ 4) { if (typeof inTransition !== "function") { $$invalidate(2, inTransition = void 0); } } if ($$self.$$.dirty[0] & /*outTransition, application*/ 1032) { { if (typeof outTransition !== "function") { $$invalidate(3, outTransition = void 0); } if (application && typeof application?.options?.defaultCloseAnimation === "boolean") { $$invalidate(10, application.options.defaultCloseAnimation = outTransition === void 0, application); } } } if ($$self.$$.dirty[0] & /*inTransitionOptions*/ 16) { if (typeof inTransitionOptions !== "object") { $$invalidate(4, inTransitionOptions = s_DEFAULT_TRANSITION_OPTIONS); } } if ($$self.$$.dirty[0] & /*outTransitionOptions*/ 32) { if (typeof outTransitionOptions !== "object") { $$invalidate(5, outTransitionOptions = s_DEFAULT_TRANSITION_OPTIONS); } } }; return [ elementContent, elementRoot, inTransition, outTransition, inTransitionOptions, outTransitionOptions, draggable2, draggableOptions, stylesApp, stylesContent, application, focusWrapEnabled, appResizeObserver, contentResizeObserver, focusAuto, focusKeep, focusTrap, minimized, onClosePopup, onKeydown, onPointerdownApp, onPointerdownContent, resizeObservedContent, resizeObservedApp, appOffsetHeight, appOffsetWidth, contentOffsetHeight, contentOffsetWidth, transition, transitionOptions, oldTransition, oldTransitionOptions, $focusAuto, $minimized, $focusTrap, $$scope, slots, section_binding, div_binding, section_binding_1, div_binding_1 ]; } class ApplicationShell extends SvelteComponent { constructor(options) { super(); init( this, options, instance$l, create_fragment$l, safe_not_equal, { elementContent: 0, elementRoot: 1, draggable: 6, draggableOptions: 7, stylesApp: 8, stylesContent: 9, appOffsetHeight: 24, appOffsetWidth: 25, contentOffsetHeight: 26, contentOffsetWidth: 27, transition: 28, inTransition: 2, outTransition: 3, transitionOptions: 29, inTransitionOptions: 4, outTransitionOptions: 5 }, null, [-1, -1] ); } get elementContent() { return this.$$.ctx[0]; } set elementContent(elementContent) { this.$$set({ elementContent }); flush(); } get elementRoot() { return this.$$.ctx[1]; } set elementRoot(elementRoot) { this.$$set({ elementRoot }); flush(); } get draggable() { return this.$$.ctx[6]; } set draggable(draggable2) { this.$$set({ draggable: draggable2 }); flush(); } get draggableOptions() { return this.$$.ctx[7]; } set draggableOptions(draggableOptions) { this.$$set({ draggableOptions }); flush(); } get stylesApp() { return this.$$.ctx[8]; } set stylesApp(stylesApp) { this.$$set({ stylesApp }); flush(); } get stylesContent() { return this.$$.ctx[9]; } set stylesContent(stylesContent) { this.$$set({ stylesContent }); flush(); } get appOffsetHeight() { return this.$$.ctx[24]; } set appOffsetHeight(appOffsetHeight) { this.$$set({ appOffsetHeight }); flush(); } get appOffsetWidth() { return this.$$.ctx[25]; } set appOffsetWidth(appOffsetWidth) { this.$$set({ appOffsetWidth }); flush(); } get contentOffsetHeight() { return this.$$.ctx[26]; } set contentOffsetHeight(contentOffsetHeight) { this.$$set({ contentOffsetHeight }); flush(); } get contentOffsetWidth() { return this.$$.ctx[27]; } set contentOffsetWidth(contentOffsetWidth) { this.$$set({ contentOffsetWidth }); flush(); } get transition() { return this.$$.ctx[28]; } set transition(transition) { this.$$set({ transition }); flush(); } get inTransition() { return this.$$.ctx[2]; } set inTransition(inTransition) { this.$$set({ inTransition }); flush(); } get outTransition() { return this.$$.ctx[3]; } set outTransition(outTransition) { this.$$set({ outTransition }); flush(); } get transitionOptions() { return this.$$.ctx[29]; } set transitionOptions(transitionOptions) { this.$$set({ transitionOptions }); flush(); } get inTransitionOptions() { return this.$$.ctx[4]; } set inTransitionOptions(inTransitionOptions) { this.$$set({ inTransitionOptions }); flush(); } get outTransitionOptions() { return this.$$.ctx[5]; } set outTransitionOptions(outTransitionOptions) { this.$$set({ outTransitionOptions }); flush(); } } const EmptyApplicationShell_svelte_svelte_type_style_lang = ""; const TJSApplicationShell_svelte_svelte_type_style_lang = ""; const DialogContent_svelte_svelte_type_style_lang = ""; cssVariables.setProperties({ // Anchor text shadow / header buttons "--tjs-default-text-shadow-focus-hover": "0 0 8px var(--color-shadow-primary)", // TJSApplicationShell app background. "--tjs-app-background": `url("${globalThis.foundry.utils.getRoute("/ui/denim075.png")}")` }, false); Hooks.on("PopOut:loading", (app) => { if (app instanceof SvelteApplication) { app.position.enabled = false; } }); Hooks.on("PopOut:popin", (app) => { if (app instanceof SvelteApplication) { app.position.enabled = true; } }); Hooks.on("PopOut:close", (app) => { if (app instanceof SvelteApplication) { app.position.enabled = true; } }); class loading_bar { constructor() { this.total = 0; this.current = 0; this.lastPct = 0; } init(context, total) { this.context = context; this.total = total; this.current = 0; this.lastPct = 0; SceneNavigation.displayProgressBar({ label: this.context, pct: 1 }); } incrementProgress() { this.current += 1; const pct = Math.round(this.current / this.total * 100); if (pct !== this.lastPct) { debug(`${pct}% loaded...`); SceneNavigation.displayProgressBar({ label: this.context, pct }); } this.lastPct = pct; } } const LoadingBar = new loading_bar(); const SequencerFileCache = { _videos: {}, _preloadedFiles: /* @__PURE__ */ new Set(), _totalCacheSize: 0, _validTypes: ["video/webm", "video/x-webm", "application/octet-stream"], async loadVideo(inSrc) { if (!this._videos[inSrc]) { const blob = await fetch(inSrc, { mode: "cors", credentials: "same-origin" }).then((r) => r.blob()).catch((err) => { console.error(err); }); if (this._validTypes.indexOf(blob?.type) === -1) return false; while (this._totalCacheSize + blob.size > 524288e3) { const entries = Object.entries(this._videos); entries.sort((a, b) => { return b[1].lastUsed - a[1].lastUsed; }); const [oldSrc] = entries[0]; this._preloadedFiles.delete(oldSrc); this._totalCacheSize -= this._videos[oldSrc].blob.size; delete this._videos[oldSrc]; } this._totalCacheSize += blob.size; this._preloadedFiles.add(inSrc); this._videos[inSrc] = { blob, lastUsed: +new Date() }; } this._videos[inSrc].lastUsed = +new Date(); return this._videos[inSrc].blob; }, srcExists(inSrc) { if (this._preloadedFiles.has(inSrc)) { return true; } return srcExists(inSrc); }, async loadFile(inSrc, preload = false) { if (inSrc.toLowerCase().endsWith(".webm")) { let blob = await this.loadVideo(inSrc); if (!blob) return false; this._preloadedFiles.add(inSrc); if (preload) return true; return get_video_texture(blob); } else if (AudioHelper.hasAudioExtension(inSrc)) { try { const audio2 = await AudioHelper.preloadSound(inSrc); if (audio2) { this._preloadedFiles.add(inSrc); } return audio2; } catch (err) { console.error(`Failed to load audio: ${inSrc}`); return false; } } const texture = await loadTexture(inSrc); if (texture) { this._preloadedFiles.add(inSrc); } return texture; } }; async function get_video_texture(inBlob) { return new Promise(async (resolve) => { const video = document.createElement("video"); video.preload = "auto"; video.crossOrigin = "anonymous"; video.controls = true; video.autoplay = false; video.autoload = true; video.muted = true; video.src = URL.createObjectURL(inBlob); let canplay = true; video.oncanplay = async () => { if (!canplay) return; canplay = false; video.height = video.videoHeight; video.width = video.videoWidth; const baseTexture = PIXI.BaseTexture.from(video, { resourceOptions: { autoPlay: false } }); if (game.settings.get(CONSTANTS.MODULE_NAME, "enable-fix-pixi")) { baseTexture.alphaMode = PIXI.ALPHA_MODES.PREMULTIPLIED_ALPHA; } const texture = new PIXI.Texture(baseTexture); resolve(texture); }; video.onerror = () => { URL.revokeObjectURL(video.src); reject(); }; }); } const flipBookTextureCache = {}; class SequencerFileBase { static make(inData, inDBPath, inMetadata) { const originalFile = inData?.file ?? inData; const file = foundry.utils.duplicate(originalFile); const isRangeFind = typeof file !== "string" && !Array.isArray(originalFile) ? Object.keys(originalFile).filter((key) => key.endsWith("ft")).length > 0 : false; return isRangeFind ? new SequencerFileRangeFind(inData, inDBPath, inMetadata) : new SequencerFile(inData, inDBPath, inMetadata); } } class SequencerFile extends SequencerFileBase { rangeFind = false; constructor(inData, inDBPath, inMetadata) { super(); inData = foundry.utils.duplicate(inData); inMetadata = foundry.utils.duplicate(inMetadata); this.originalData = inData; this.originalMetadata = inMetadata; for (let [key, value] of Object.entries(inMetadata)) { this[key] = value; } this.dbPath = inDBPath; this.moduleName = inDBPath.split(".")[0]; this.originalFile = inData?.file ?? inData; this.file = foundry.utils.duplicate(this.originalFile); this.fileIndex = null; this.fileTextureMap = Object.fromEntries( this.getAllFiles().map((file) => { return [file, false]; }) ); this.twister = false; } clone() { return SequencerFile.make( this.originalData, this.dbPath, this.originalMetadata ); } async validate() { let isValid = true; const directories = {}; const allFiles = this.getAllFiles(); for (const file of allFiles) { let directory = file.split("/"); directory.pop(); directory = directory.join("/"); if (directories[directory] === void 0) { directories[directory] = await getFiles(directory); } } for (const file of allFiles) { let directory = file.split("/"); directory.pop(); directory = directory.join("/"); if (directories[directory].indexOf(file) === -1) { console.warn( `"${this.dbPath}" has an incorrect file path, could not find file. Points to: ${file}` ); isValid = false; } } return isValid; } getAllFiles() { return [this.file].deepFlatten(); } getFile() { if (Array.isArray(this.file)) { this.fileIndex = is_real_number(this.fileIndex) ? this.fileIndex : random_array_element(this.file, { twister: this.twister, index: true }); return this.file[this.fileIndex]; } return this.file; } getTimestamps() { if (Array.isArray(this.originalMetadata?.timestamps)) { return this.originalMetadata?.timestamps?.[this.fileIndex] ?? this.originalMetadata?.timestamps[0]; } return this.originalMetadata?.timestamps; } getPreviewFile(entry) { let parts = entry.split("."); let files2 = this.getAllFiles(); if (Array.isArray(files2)) { if (is_real_number(parts[parts.length - 1])) { files2 = files2[parts[parts.length - 1]]; } else { const index = Math.floor(interpolate(0, files2.length - 1, 0.5)); files2 = files2?.[index - 1] ?? files2[index]; } } return files2; } destroy() { if (this.originalMetadata?.flipbook) return; for (let texture of Object.values(this.fileTextureMap)) { if (!texture) continue; try { texture?.baseTexture?.resource?.source?.removeAttribute("src"); } catch (err) { } try { texture?.baseTexture?.resource?.source?.pause(); } catch (err) { } try { texture?.baseTexture?.resource?.source?.remove(); } catch (err) { } try { texture?.baseTexture?.resource?.source?.load(); } catch (err) { } texture.destroy(); } } async _getTexture(file) { if (this.fileTextureMap[file]) return this.fileTextureMap[file]; this.fileTextureMap[file] = await SequencerFileCache.loadFile(file); return this.fileTextureMap[file]; } _adjustScaleForPadding(distance, width2) { return distance / (width2 - (this.template ? this.template[1] + this.template[2] : 0)); } _adjustAnchorForPadding(width2) { return this.template ? this.template[1] / width2 : void 0; } async _getFlipBookSheet(filePath) { if (!this.originalMetadata?.flipbook) return false; if (flipBookTextureCache[filePath]) { return flipBookTextureCache[filePath]; } flipBookTextureCache[filePath] = this.file.map((file) => { return PIXI.Texture.from(file); }); return flipBookTextureCache[filePath]; } async getTexture(distance) { const filePath = this.getFile(); const texture = await this._getTexture(this.getFile()); const sheet = await this._getFlipBookSheet(filePath); return { filePath, texture, sheet, spriteScale: this._adjustScaleForPadding(distance, texture.width), spriteAnchor: this._adjustAnchorForPadding(texture.width) }; } } class SequencerFileRangeFind extends SequencerFile { rangeFind = true; constructor(...args) { super(...args); this._fileDistanceMap = false; } static get ftToDistanceMap() { return { "90ft": canvas.grid.size * 15, "60ft": canvas.grid.size * 9, "30ft": canvas.grid.size * 5, "15ft": canvas.grid.size * 2, "05ft": 0 }; } get _gridSizeDiff() { return canvas.grid.size / this.template[0]; } getAllFiles() { return Object.values(this.file).deepFlatten(); } getFile(inFt) { if (inFt && this.file[inFt]) { if (Array.isArray(this.file[inFt])) { const fileIndex = is_real_number(this.fileIndex) ? Math.min(this.file[inFt].length - 1, this.fileIndex) : random_array_element(this.file[inFt], { twister: this.twister, index: true }); return this.file[inFt][fileIndex]; } return this.file[inFt]; } return this.file; } getPreviewFile(entry) { let parts = entry.split("."); const ft = parts.find( (part) => Object.keys(SequencerFileRangeFind.ftToDistanceMap).indexOf(part) > -1 ); if (!ft) { return super.getPreviewFile(entry); } const fileIndex = parts.slice(parts.indexOf(ft) + 1)?.[0]; if (is_real_number(Number(fileIndex))) { this.fileIndex = Number(fileIndex); } return this.getFile(ft); } async getTexture(distance = 400) { const { filePath, texture } = await this._getTextureForDistance(distance); return { filePath, texture, spriteScale: this._adjustScaleForPadding(distance, texture.width), spriteAnchor: this._adjustAnchorForPadding(texture.width) }; } _getMatchingDistance(inEntry) { return SequencerFileRangeFind.ftToDistanceMap[inEntry] / this._gridSizeDiff; } _rangeFind(inDistance) { if (!this._fileDistanceMap) { let distances = Object.keys(this.file).filter( (entry) => Object.keys(SequencerFileRangeFind.ftToDistanceMap).indexOf(entry) > -1 ).map((ft) => { return { file: this.getFile(ft), minDistance: this._getMatchingDistance(ft) }; }); let uniqueDistances = [ ...new Set(distances.map((item) => item.minDistance)) ]; uniqueDistances.sort((a, b) => a - b); let max = Math.max(...uniqueDistances); let min = Math.min(...uniqueDistances); this._fileDistanceMap = distances.map((entry) => { entry.distances = { min: entry.minDistance === min ? 0 : entry.minDistance, max: entry.minDistance === max ? Infinity : uniqueDistances[uniqueDistances.indexOf(entry.minDistance) + 1] }; return entry; }); } const possibleFiles = this._fileDistanceMap.filter((entry) => { const relativeDistance = inDistance / this._gridSizeDiff; return relativeDistance >= entry.distances.min && relativeDistance < entry.distances.max; }).map((entry) => entry.file).flat(); return possibleFiles.length > 1 ? random_array_element(possibleFiles, { twister: this.twister }) : possibleFiles[0]; } async _getTextureForDistance(distance) { const filePath = this._rangeFind(distance); const texture = await this._getTexture(filePath); return { filePath, texture }; } } class Database { #entriesStore = writable$1({}); privateModules = []; flattenedEntries = []; inverseFlattenedEntries = /* @__PURE__ */ new Map(); get entries() { return get_store_value(this.#entriesStore); } set entries(entries) { this.#entriesStore.set(entries); } get entriesStore() { return this.#entriesStore; } get publicModules() { return Object.keys(this.entries).filter( (module2) => !this.privateModules.includes(module2) ); } get publicFlattenedEntries() { return this.flattenedEntries.filter((entry) => { return this.privateModules.indexOf(entry.split(".")[0]) === -1; }); } get publicFlattenedSimpleEntries() { return make_array_unique( this.publicFlattenedEntries.map((entry) => { return entry.split(CONSTANTS.FEET_REGEX)[0]; }) ); } /** * Retrieves an object of every public entry * * @return {object} */ get filePathDatabasePaths() { const fileDatabaseObject = {}; Object.entries(this.entries).map((entry) => entry[1]).deepFlatten().forEach((sequencerFile) => { if (sequencerFile?.rangeFind) { Object.entries(sequencerFile.file).forEach((entry) => { fileDatabaseObject[entry[1]] = sequencerFile.dbPath + "." + entry[0]; }); } else { fileDatabaseObject[sequencerFile.file] = sequencerFile.dbPath; } }); return fileDatabaseObject; } /** * Registers a set of entries to the database on the given module name * * @param {string} inModuleName The namespace to assign to the inserted entries * @param {object} inEntries The entries to merge into the database * @param {boolean} isPrivate Whether to mark these entries as private and not show in Effect Player or Database Viewer * @return {boolean} */ registerEntries(inModuleName, inEntries, isPrivate = false) { if (inModuleName.includes(".")) return this._throwError( "registerEntries", "module name must not contain periods" ); if (this.entries[inModuleName]) custom_warning( "Sequencer", `registerEntries | module "${inModuleName}" has already been registered to the database! Do you have two similar modules active?`, true ); this._flatten(inEntries, inModuleName); const processedEntries = this._processEntries(inModuleName, inEntries); if (isPrivate) this.privateModules.push(inModuleName); this.entries = foundry.utils.mergeObject(this.entries, { [inModuleName]: processedEntries }); debug( `Sequencer | Database | Entries for "${inModuleName}" registered` ); Hooks.callAll("registerSequencerDatabaseEntries", inModuleName); return true; } /** * Validates the entries under a certain module name, checking whether paths to assets are correct or not * * @param {string} inModuleName The namespace to assign to the inserted entries * @return {boolean} */ async validateEntries(inModuleName) { let entries = this.getEntry(inModuleName); if (!Array.isArray(entries)) { entries = [entries]; } ui.notifications.info( `Validating paths registered to "${inModuleName}"...` ); let isValid = true; LoadingBar.init( `Validating paths registered to "${inModuleName}"...`, entries.length ); for (let entry of entries) { const result = await entry.validate(); LoadingBar.incrementProgress(); isValid = !(!result || !isValid); } if (!isValid) { ui.notifications.error( `Validation of paths registered to "${inModuleName}" failed! Errors logged in console.` ); } else { ui.notifications.info( `Validation of paths registered to "${inModuleName}" complete! No errors found!` ); } } /** * Quickly checks if the entry exists in the database * * @param {string} inString The entry to find in the database * @return {boolean} If the entry exists in the database */ entryExists(inString) { if (typeof inString !== "string") return this._throwError("entryExists", "inString must be of type string"); inString = inString.trim(); if (inString === "") return this._throwError("entryExists", "inString cannot be empty"); inString = inString.replace(/\[[0-9]+]$/, ""); return this.flattenedEntries.find((entry) => entry.startsWith(inString)); } /** * Gets the entry in the database by a dot-notated string * * @param {string} inString The entry to find in the database * @param {boolean} softFail Whether it should soft fail (no error) when no entry was found * @return {array|SequencerFile|boolean} The found entry in the database, or false if not found (with warning) */ getEntry(inString, { softFail = false } = {}) { if (typeof inString !== "string") { if (softFail) return false; return this._throwError("getEntry", "inString must be of type string"); } inString = inString.trim(); if (inString === "") { if (softFail) return false; return this._throwError("getEntry", "inString cannot be empty"); } inString = inString.replace(/\[[0-9]+]$/, ""); if (!this.entryExists(inString)) { if (softFail) return false; return this._throwError( "getEntry", `Could not find ${inString} in database` ); } let ft = false; let index = false; if (CONSTANTS.FEET_REGEX.test(inString)) { ft = inString.match(CONSTANTS.FEET_REGEX)[0]; const split = inString.split(ft).filter((str) => str !== ""); if (split.length > 1) { index = split[1].split(".")[0]; } inString = split[0]; } const module2 = inString.split(".")[0]; const exactEntries = this.entries[module2].filter((entry) => { return entry.dbPath === inString; }); let filteredEntries = (exactEntries.length ? exactEntries : this.entries[module2].filter((entry) => { return entry.dbPath.startsWith(inString); })).map((entry) => { let foundFile = entry; if (ft) foundFile = entry.file?.[ft] ?? foundFile; if (index) foundFile = foundFile?.[index] ?? foundFile; return foundFile; }); if (!filteredEntries.length) return this._throwError( "getEntry", `Could not find ${inString} in database` ); const foundEntry = filteredEntries.length === 1 ? filteredEntries[0] : filteredEntries; if (index && filteredEntries.length === 1) { foundEntry.fileIndex = Number(index); } return foundEntry; } /** * Gets all files under a database path * * @param {string} inDBPath The module to get all files from * @return {array|boolean} The found entries in the database under the module's name, or false if not found (with warning) */ getAllFileEntries(inDBPath) { if (typeof inDBPath !== "string") return this._throwError( "getAllFileEntries", "inDBPath must be of type string" ); inDBPath = inDBPath.trim(); if (inDBPath === "") return this._throwError("getAllFileEntries", "inDBPath cannot be empty"); if (!this.entryExists(inDBPath)) return this._throwError( "getAllFileEntries", `Could not find ${inDBPath} in database` ); const entries = this._recurseGetFilePaths(inDBPath); return make_array_unique(entries.flat()); } /** * Get all valid entries under a certain path * * @param {string} inPath The database path to get entries under * @return {array|boolean} An array containing the next layer of valid paths */ getPathsUnder(inPath, { ranges = false, arrays = false, match = false, fullyQualified = false } = {}) { if (typeof inPath !== "string") return this._throwError("getPathsUnder", "inPath must be of type string"); inPath = inPath.trim(); if (inPath === "") return this._throwError("getPathsUnder", "inPath cannot be empty"); inPath = inPath.replace(/\[[0-9]+]$/, ""); if (!this.entryExists(inPath)) return this._throwError( "getPathsUnder", `Could not find ${inPath} in database` ); const entries = this.flattenedEntries.filter((e) => { return (e.startsWith(inPath + ".") || e === inPath) && (!match || match && e.match(match)); }); if (entries.length === 0) return []; return make_array_unique( entries.map((e) => !arrays ? e.split(CONSTANTS.ARRAY_REGEX)[0] : e).map((e) => !ranges ? e.split(CONSTANTS.FEET_REGEX)[0] : e).map((e) => !fullyQualified ? e.split(inPath)[1] : e).map((e) => !fullyQualified ? e ? e.split(".")[1] : "" : e).filter(Boolean) ); } /** * Get all valid entries under a certain path * * @param {string} inPath The database path to search for * @param {boolean} publicOnly Whether to only search for public modules * @return {array|boolean} An array containing potential database paths */ searchFor(inPath, publicOnly = true) { const modules = publicOnly ? this.publicModules : Object.keys(this.entries); const originalEntries = publicOnly ? this.publicFlattenedEntries : this.flattenedEntries; if ((!inPath || inPath === "") && !modules.includes(inPath)) return modules; if (typeof inPath !== "string") { return this._throwError("searchFor", "inString must be of type string"); } inPath = inPath.trim(); if (inPath === "") return this._throwError("searchFor", "inString cannot be empty"); inPath = inPath.replace(/\[[0-9]+]$/, ""); inPath = inPath.trim(); let entries = originalEntries.filter( (e) => e.startsWith(inPath) && e !== inPath ); if (inPath.endsWith(".")) inPath = inPath.substring(0, inPath.length - 1); let length = inPath.split(".").length + 1; let foundEntries = entries.map((e) => { let path = e.split(CONSTANTS.FEET_REGEX)[0]; return path.split(".").slice(0, length).join("."); }); if (foundEntries.length === 0) { const regexString = str_to_search_regex_str(inPath).replace(/\s+/g, "|"); const searchParts = regexString.split("|").length; const regexSearch = new RegExp(regexString, "gu"); foundEntries = originalEntries.filter((e) => { return e.match(regexSearch)?.length >= searchParts; }).map((e) => { return e.split(CONSTANTS.FEET_REGEX)[0]; }); } return make_array_unique(foundEntries); } /** * Throws an error without THROWING one. Duh. * * @param inFunctionName * @param inError * @returns {boolean} * @private */ _throwError(inFunctionName, inError) { let error = `Sequencer | Database | ${inFunctionName} - ${inError}`; ui.notifications.error(error); return false; } /** * Gets all file paths from the entirety of * * @param inDBPath * @returns {Array} * @private */ _recurseGetFilePaths(inDBPath) { const module2 = inDBPath.split(".")[0]; return this.entries[module2].filter((entry) => entry.dbPath.startsWith(inDBPath)).map((entry) => { return entry.getAllFiles(); }).flat(); } /** * Flattens a given object to just their db path and file path * * @param entries * @param inModule * @private */ _flatten(entries, inModule) { let flattened = flatten_object( foundry.utils.duplicate({ [inModule]: entries }) ); this.flattenedEntries = make_array_unique( this.flattenedEntries.concat( Object.keys(flattened).map((file) => file.split(".file")[0]) ) ); this.inverseFlattenedEntries = Object.keys(flattened).reduce( (acc, entry) => { return acc.set(flattened[entry], entry.split(".file")[0]); }, this.inverseFlattenedEntries ); } /** * Processes and recurse into a large object containing file paths at any given depth * * @param moduleName * @param entries * @returns {object} * @private */ _processEntries(moduleName, entries) { const allPaths = new Set( this.flattenedEntries.filter((e) => e.split(".")[0] === moduleName).map((e) => e.split(CONSTANTS.FEET_REGEX)[0]) ); const allTemplates = foundry.utils.mergeObject(entries?._templates ?? {}, { default: [100, 0, 0] }); if (entries?._templates) { delete entries?._templates; } const moduleEntries = []; const mediaFileExtensions = Object.keys(CONST.FILE_CATEGORIES.IMAGE).concat(Object.keys(CONST.FILE_CATEGORIES.VIDEO)).concat(Object.keys(CONST.FILE_CATEGORIES.AUDIO)); for (let wholeDBPath of allPaths) { let metadata = this._getCleanData(entries); let dbPath = wholeDBPath.split("."); dbPath.shift(); let combinedPath = ""; for (let part of dbPath) { combinedPath = combinedPath ? combinedPath + "." + part : part; const entry = getProperty(entries, combinedPath); if (Array.isArray(entry) || typeof entry === "string" || entry?.file) { metadata = this._getCleanData(entry, { existingData: metadata }); break; } metadata = this._getCleanData(entry, { existingData: metadata }); } if (!metadata.template) metadata.template = "default"; if (typeof metadata.template === "string") { metadata.template = allTemplates?.[metadata.template] ?? allTemplates?.["default"]; } let data = getProperty(entries, dbPath.join(".")); if (!Array.isArray(data) && !(typeof data === "string")) { data = this._getCleanData(data, { metadata: false }); } if (typeof data === "string") { const existingEntry = this.entryExists(data); const extension = data.split(".")[data.split(".").length - 1].toLowerCase(); if (!existingEntry && extension && !mediaFileExtensions.includes(extension)) { console.warn( `Sequencer | Database | registerEntries - failed to register ${wholeDBPath} to ${data}!` ); this.flattenedEntries.splice( this.flattenedEntries.indexOf(wholeDBPath), 1 ); continue; } else if (existingEntry) { const sequencerFile = this.getEntry(data); const clone = sequencerFile.clone(); clone.dbPath = wholeDBPath; clone.metadata = foundry.utils.mergeObject( clone.metadata ?? {}, metadata ?? {} ); moduleEntries.push(clone); continue; } } moduleEntries.push(SequencerFileBase.make(data, wholeDBPath, metadata)); } return moduleEntries; } _getCleanData(data, { existingData = {}, metadata = true } = {}) { data = Object.entries(data).filter((entry) => { return metadata === entry[0].startsWith("_"); }); if (metadata) { data = data.map((entry) => [entry[0].slice(1), entry[1]]); } return foundry.utils.mergeObject(existingData, Object.fromEntries(data)); } } const SequencerDatabase = new Database(); const TreeViewEntry_svelte_svelte_type_style_lang = ""; function create_if_block_2$1(ctx) { let a; let i; let mounted; let dispose; return { c() { a = element("a"); i = element("i"); attr(i, "class", "fas svelte-ese-uyryhb"); toggle_class( i, "fa-angle-down", /*data*/ ctx[0].open ); toggle_class(i, "fa-angle-right", !/*data*/ ctx[0].open); attr(a, "class", "svelte-ese-uyryhb"); }, m(target, anchor) { insert(target, a, anchor); append(a, i); if (!mounted) { dispose = listen( a, "click", /*click_handler*/ ctx[6] ); mounted = true; } }, p(ctx2, dirty) { if (dirty & /*data*/ 1) { toggle_class( i, "fa-angle-down", /*data*/ ctx2[0].open ); } if (dirty & /*data*/ 1) { toggle_class(i, "fa-angle-right", !/*data*/ ctx2[0].open); } }, d(detaching) { if (detaching) detach(a); mounted = false; dispose(); } }; } function create_if_block$7(ctx) { let t0; let a0; let i0; let t1; let a1; let mounted; let dispose; let if_block = !/*data*/ ctx[0].hasChildren && create_if_block_1$4(ctx); return { c() { if (if_block) if_block.c(); t0 = space(); a0 = element("a"); i0 = element("i"); t1 = space(); a1 = element("a"); a1.innerHTML = ``; attr(i0, "class", "fas fa-database svelte-ese-uyryhb"); toggle_class( i0, "flash-it", /*flashDBPath*/ ctx[2] ); attr(a0, "class", "database-entry-button svelte-ese-uyryhb"); attr(a1, "class", "database-entry-button svelte-ese-uyryhb"); }, m(target, anchor) { if (if_block) if_block.m(target, anchor); insert(target, t0, anchor); insert(target, a0, anchor); append(a0, i0); insert(target, t1, anchor); insert(target, a1, anchor); if (!mounted) { dispose = [ listen( a0, "click", /*click_handler_2*/ ctx[8] ), listen( a1, "click", /*click_handler_3*/ ctx[9] ) ]; mounted = true; } }, p(ctx2, dirty) { if (!/*data*/ ctx2[0].hasChildren) { if (if_block) { if_block.p(ctx2, dirty); } else { if_block = create_if_block_1$4(ctx2); if_block.c(); if_block.m(t0.parentNode, t0); } } else if (if_block) { if_block.d(1); if_block = null; } if (dirty & /*flashDBPath*/ 4) { toggle_class( i0, "flash-it", /*flashDBPath*/ ctx2[2] ); } }, d(detaching) { if (if_block) if_block.d(detaching); if (detaching) detach(t0); if (detaching) detach(a0); if (detaching) detach(t1); if (detaching) detach(a1); mounted = false; run_all(dispose); } }; } function create_if_block_1$4(ctx) { let a; let i; let mounted; let dispose; return { c() { a = element("a"); i = element("i"); attr(i, "class", "fas fa-file svelte-ese-uyryhb"); toggle_class( i, "flash-it", /*flashFilePath*/ ctx[1] ); attr(a, "class", "database-entry-button svelte-ese-uyryhb"); }, m(target, anchor) { insert(target, a, anchor); append(a, i); if (!mounted) { dispose = listen( a, "click", /*click_handler_1*/ ctx[7] ); mounted = true; } }, p(ctx2, dirty) { if (dirty & /*flashFilePath*/ 2) { toggle_class( i, "flash-it", /*flashFilePath*/ ctx2[1] ); } }, d(detaching) { if (detaching) detach(a); mounted = false; dispose(); } }; } function create_fragment$k(ctx) { let div3; let div2; let t0; let show_if = ( /*data*/ ctx[0].fullPath.includes(".") ); let t1; let div1; let div0; let t2; let t3_value = ( /*data*/ ctx[0].path + "" ); let t3; let div1_title_value; let if_block0 = ( /*data*/ ctx[0].hasChildren && create_if_block_2$1(ctx) ); let if_block1 = show_if && create_if_block$7(ctx); return { c() { div3 = element("div"); div2 = element("div"); if (if_block0) if_block0.c(); t0 = space(); if (if_block1) if_block1.c(); t1 = space(); div1 = element("div"); div0 = element("div"); t2 = space(); t3 = text$1(t3_value); attr(div0, "class", "database-entry-text-highlight svelte-ese-uyryhb"); attr(div1, "class", "database-entry-text svelte-ese-uyryhb"); attr(div1, "title", div1_title_value = /*data*/ ctx[0].path); attr(div2, "class", "database-entry-text-container svelte-ese-uyryhb"); attr(div3, "class", "database-entry svelte-ese-uyryhb"); set_style( div3, "margin-left", /*data*/ ctx[0].depth * 15 + "px" ); }, m(target, anchor) { insert(target, div3, anchor); append(div3, div2); if (if_block0) if_block0.m(div2, null); append(div2, t0); if (if_block1) if_block1.m(div2, null); append(div2, t1); append(div2, div1); append(div1, div0); div0.innerHTML = /*highlight*/ ctx[3]; append(div1, t2); append(div1, t3); }, p(ctx2, [dirty]) { if ( /*data*/ ctx2[0].hasChildren ) { if (if_block0) { if_block0.p(ctx2, dirty); } else { if_block0 = create_if_block_2$1(ctx2); if_block0.c(); if_block0.m(div2, t0); } } else if (if_block0) { if_block0.d(1); if_block0 = null; } if (dirty & /*data*/ 1) show_if = /*data*/ ctx2[0].fullPath.includes("."); if (show_if) { if (if_block1) { if_block1.p(ctx2, dirty); } else { if_block1 = create_if_block$7(ctx2); if_block1.c(); if_block1.m(div2, t1); } } else if (if_block1) { if_block1.d(1); if_block1 = null; } if (dirty & /*highlight*/ 8) div0.innerHTML = /*highlight*/ ctx2[3]; if (dirty & /*data*/ 1 && t3_value !== (t3_value = /*data*/ ctx2[0].path + "")) set_data(t3, t3_value); if (dirty & /*data*/ 1 && div1_title_value !== (div1_title_value = /*data*/ ctx2[0].path)) { attr(div1, "title", div1_title_value); } if (dirty & /*data*/ 1) { set_style( div3, "margin-left", /*data*/ ctx2[0].depth * 15 + "px" ); } }, i: noop, o: noop, d(detaching) { if (detaching) detach(div3); if (if_block0) if_block0.d(); if (if_block1) if_block1.d(); } }; } function instance$k($$self, $$props, $$invalidate) { let highlight; let $searchRegex; let { data } = $$props; const searchRegex = databaseStore.searchRegex; component_subscribe($$self, searchRegex, (value) => $$invalidate(5, $searchRegex = value)); let flashFilePath = false; let flashDBPath = false; const click_handler = (e) => { databaseStore.openTreePath(data.fullPath, !data.open, e.ctrlKey); }; const click_handler_1 = (e) => { databaseStore.copyPath(data.fullPath, true, e.ctrlKey); $$invalidate(1, flashFilePath = true); setTimeout( () => { $$invalidate(1, flashFilePath = false); }, 400 ); }; const click_handler_2 = (e) => { databaseStore.copyPath(data.fullPath, false, e.ctrlKey); $$invalidate(2, flashDBPath = true); setTimeout( () => { $$invalidate(2, flashDBPath = false); }, 400 ); }; const click_handler_3 = () => { databaseStore.playFile(data.fullPath); }; $$self.$$set = ($$props2) => { if ("data" in $$props2) $$invalidate(0, data = $$props2.data); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*data, $searchRegex*/ 33) { $$invalidate(3, highlight = data.path.replace($searchRegex, "$&")); } }; return [ data, flashFilePath, flashDBPath, highlight, searchRegex, $searchRegex, click_handler, click_handler_1, click_handler_2, click_handler_3 ]; } class TreeViewEntry extends SvelteComponent { constructor(options) { super(); init(this, options, instance$k, create_fragment$k, safe_not_equal, { data: 0 }); } } function create_fragment$j(ctx) { let div; return { c() { div = element("div"); set_style(div, "margin-bottom", "0.3rem"); }, m(target, anchor) { insert(target, div, anchor); }, p: noop, i: noop, o: noop, d(detaching) { if (detaching) detach(div); } }; } function instance$j($$self, $$props, $$invalidate) { let { data } = $$props; $$self.$$set = ($$props2) => { if ("data" in $$props2) $$invalidate(0, data = $$props2.data); }; return [data]; } class TreeViewSeparator extends SvelteComponent { constructor(options) { super(); init(this, options, instance$j, create_fragment$j, safe_not_equal, { data: 0 }); } } let lastFile = false; function getFileData(entryText) { let entry = SequencerDatabase.getEntry(entryText); if (Array.isArray(entry)) { if (entry.includes(lastFile) && entry.length > 1) { entry.splice(entry.indexOf(lastFile), 1); } entry = random_array_element(entry); lastFile = entry; } let previewFile = entry?.file ?? entry; if (entry instanceof SequencerFileBase) { previewFile = entry.clone().getPreviewFile(entryText); } let lowerCaseEntry = previewFile ? previewFile.toLowerCase() : "unknown.jpg"; const isAudio = lowerCaseEntry.endsWith("ogg") || lowerCaseEntry.endsWith("mp3") || lowerCaseEntry.endsWith("wav"); const isImage = !lowerCaseEntry.endsWith("webm") && !isAudio; const isVideo = !isAudio && !isImage; const icon = previewFile ? isVideo ? "fa-film" : isAudio ? "fa-volume-high" : "fa-image" : "fa-question-mark"; const title = previewFile ? isVideo ? "Animated WebM" : isAudio ? "Audio" : "Image" : "Unknown"; return { file: previewFile ?? "unknown.jpg", dbEntry: entry, icon, title, isAudio, isImage, isVideo }; } function copyPath(dbPath, getFilepath, quotes = false) { const tempInput = document.createElement("input"); tempInput.value = `${dbPath}`; let entry; if (getFilepath) { entry = Sequencer.Database.getEntry(dbPath); if (Array.isArray(entry)) { entry = random_array_element(entry); } if (entry instanceof SequencerFileBase) { const specificFt = dbPath.match(CONSTANTS.FEET_REGEX); if (specificFt) { const ft = specificFt[0].replaceAll(".", ""); entry = entry.getFile(ft); } else { const files2 = entry.getAllFiles(); if (Array.isArray(files2)) { const index = Math.floor(interpolate(0, files2.length - 1, 0.5)); entry = files2[index]; } } } tempInput.value = `${entry?.file ?? entry}`; } if (quotes) { tempInput.value = `"${tempInput.value}"`; } document.body.appendChild(tempInput); tempInput.select(); document.execCommand("copy"); document.body.removeChild(tempInput); document.execCommand("copy"); } function playFile(entry) { const { file, isAudio, isImage, isVideo } = getFileData(entry); databaseStore.elements.audio.classList.toggle("hidden", !isAudio); databaseStore.elements.image.classList.toggle("hidden", !isImage); databaseStore.elements.player.classList.toggle("hidden", !isVideo); if (isImage) { databaseStore.elements.image.src = file; databaseStore.metadata.set({ type: "Image", duration: "n/a" }); return; } const element2 = isAudio ? databaseStore.elements.audio : databaseStore.elements.player; element2.onerror = () => { const error = `Sequencer Database Viewer | Could not play file: ${file}`; ui.notifications.error(error); console.error(error); }; element2.oncanplay = () => { element2.play(); }; element2.onloadedmetadata = () => { databaseStore.metadata.set({ type: isVideo ? "Video" : isAudio ? "Audio" : "Image", duration: isImage ? "n/a" : element2.duration * 1e3 + "ms" }); }; element2.src = file; } const treeStore = writable$1({}); const visibleTreeStore = writable$1([]); let flattenedEntries = []; const entriesStore = SequencerDatabase.entriesStore; const packStore = writable$1(SequencerDatabase.publicModules); const selectedPackStore = writable$1("all"); const searchStore = writable$1(""); const cleanSearchStore = writable$1(""); const searchRegexStore = writable$1(new RegExp("", "gu")); SequencerDatabase.entriesStore.subscribe(() => { packStore.set(SequencerDatabase.publicModules); }); const databaseStore = { metadata: writable$1(false), allRanges: writable$1(false), subLists: writable$1(false), listView: writable$1(false), packStore, selectedPackStore, visibleTreeStore, search: searchStore, cleanSearchStore, searchRegex: searchRegexStore, elements: {}, copyPath, playFile, openTreePath }; entriesStore.subscribe(() => { filterFlattenedEntries(); }); databaseStore.allRanges.subscribe(() => { filterFlattenedEntries(); }); databaseStore.subLists.subscribe(() => { filterFlattenedEntries(); }); databaseStore.selectedPackStore.subscribe(() => { filterFlattenedEntries(); }); searchStore.subscribe((val) => { const cleanSearch = str_to_search_regex_str(val).replace(/\s+/g, "|"); cleanSearchStore.set(cleanSearch); searchRegexStore.set(new RegExp(cleanSearch, "gu")); updateVisualTree(); }); function filterFlattenedEntries() { const selectedPack = get_store_value(selectedPackStore); const search = get_store_value(searchStore); const searchRegex = get_store_value(searchRegexStore); const subLists = get_store_value(databaseStore.subLists); const allRanges = get_store_value(databaseStore.allRanges); flattenedEntries = make_array_unique( SequencerDatabase.publicFlattenedEntries.filter((e) => { return (selectedPack === "all" || e.startsWith(selectedPack + ".")) && (!search || e.match(searchRegex)); }).map((e) => !subLists ? e.split(CONSTANTS.ARRAY_REGEX)[0] : e).map((e) => !allRanges ? e.split(CONSTANTS.FEET_REGEX)[0] : e) ); treeStore.set( flattenedEntries.reduce((acc, entry) => { let path = ""; for (const part of entry.split(".")) { const fullPath = path ? path + "." + part : part; path = path ? path + ".children." + part : part; if (!getProperty(acc, path)) { setProperty( acc, path, foundry.utils.mergeObject( { path: part, fullPath, open: false, children: {} }, getProperty(acc, path) ) ); } } return acc; }, {}) ); } function openTreePath(fullPath, open, openAll = false) { treeStore.update((tree) => { const fullTreePath = fullPath.split(".").join(".children."); const node = getProperty(tree, fullTreePath); setProperty(tree, fullTreePath + ".open", open); if ((!open || openAll) && !foundry.utils.isEmpty(node.children)) { recurseOpenTree(node.children, open); } return tree; }); } function recurseOpenTree(children2, open) { for (const node of Object.values(children2)) { node.open = open; if (!foundry.utils.isEmpty(node.children)) { recurseOpenTree(node.children, open); } } } treeStore.subscribe(() => { updateVisualTree(); }); function updateVisualTree() { const tree = get_store_value(treeStore); const visibleTree = recurseTree(tree).deepFlatten().filter((e) => e.visible); visibleTreeStore.set(visibleTree); } function recurseTree(tree, path = "", depth = 0) { const search = get_store_value(searchStore); const searchRegex = get_store_value(searchRegexStore); const searchParts = get_store_value(cleanSearchStore).split("|"); return Object.entries(tree).map(([key, data]) => { const fullPath = path ? path + "." + key : key; const children2 = recurseTree( data.children, fullPath, depth + 1 ).deepFlatten(); const matchParts = make_array_unique(fullPath.match(searchRegex) || []); const open = data.open || search && (matchParts.length >= searchParts.length || children2.filter((e) => e.visible).length); let visible = !search || matchParts.length >= searchParts.length; if (visible) { children2.forEach((e) => e.visible = true); } else { visible = children2.filter((e) => e.visible).length; } const entry = { class: TreeViewEntry, path: key, fullPath, open, visible, hasChildren: !foundry.utils.isEmpty(data.children), depth }; const leaf = [entry]; if ((data.open || entry.open) && entry.hasChildren) { leaf.push(...children2, { fullPath: randomID(), class: TreeViewSeparator }); } return leaf; }); } const DatabaseEntry_svelte_svelte_type_style_lang = ""; function create_fragment$i(ctx) { let div2; let button0; let t0; let button1; let i1; let t1; let button2; let i2; let t2; let div1; let div0; let t3; let t4; let mounted; let dispose; return { c() { div2 = element("div"); button0 = element("button"); button0.innerHTML = ``; t0 = space(); button1 = element("button"); i1 = element("i"); t1 = space(); button2 = element("button"); i2 = element("i"); t2 = space(); div1 = element("div"); div0 = element("div"); t3 = space(); t4 = text$1( /*entry*/ ctx[0] ); attr(button0, "type", "button"); attr(button0, "class", "btn_play svelte-ese-flzvpb"); attr(i1, "class", "fas fa-file svelte-ese-flzvpb"); toggle_class( i1, "flash-it", /*flashFilePath*/ ctx[1] ); attr(button1, "type", "button"); attr(button1, "class", "btn_copy_filepath svelte-ese-flzvpb"); attr(i2, "class", "fas fa-database svelte-ese-flzvpb"); toggle_class( i2, "flash-it", /*flashDBPath*/ ctx[2] ); attr(button2, "type", "button"); attr(button2, "class", "btn_copy_databasepath svelte-ese-flzvpb"); attr(div0, "class", "database-entry-text-highlight svelte-ese-flzvpb"); attr(div1, "class", "database-entry-text svelte-ese-flzvpb"); attr( div1, "title", /*entry*/ ctx[0] ); attr(div2, "class", "database-entry svelte-ese-flzvpb"); attr( div2, "data-id", /*entry*/ ctx[0] ); }, m(target, anchor) { insert(target, div2, anchor); append(div2, button0); append(div2, t0); append(div2, button1); append(button1, i1); append(div2, t1); append(div2, button2); append(button2, i2); append(div2, t2); append(div2, div1); append(div1, div0); div0.innerHTML = /*highlight*/ ctx[3]; append(div1, t3); append(div1, t4); if (!mounted) { dispose = [ listen( button0, "click", /*click_handler*/ ctx[6] ), listen( button1, "click", /*click_handler_1*/ ctx[7] ), listen( button2, "click", /*click_handler_2*/ ctx[8] ) ]; mounted = true; } }, p(ctx2, [dirty]) { if (dirty & /*flashFilePath*/ 2) { toggle_class( i1, "flash-it", /*flashFilePath*/ ctx2[1] ); } if (dirty & /*flashDBPath*/ 4) { toggle_class( i2, "flash-it", /*flashDBPath*/ ctx2[2] ); } if (dirty & /*highlight*/ 8) div0.innerHTML = /*highlight*/ ctx2[3]; if (dirty & /*entry*/ 1) set_data( t4, /*entry*/ ctx2[0] ); if (dirty & /*entry*/ 1) { attr( div1, "title", /*entry*/ ctx2[0] ); } if (dirty & /*entry*/ 1) { attr( div2, "data-id", /*entry*/ ctx2[0] ); } }, i: noop, o: noop, d(detaching) { if (detaching) detach(div2); mounted = false; run_all(dispose); } }; } function instance$i($$self, $$props, $$invalidate) { let highlight; let $searchRegex; createEventDispatcher(); let { entry } = $$props; const searchRegex = databaseStore.searchRegex; component_subscribe($$self, searchRegex, (value) => $$invalidate(5, $searchRegex = value)); let flashFilePath = false; let flashDBPath = false; const click_handler = () => { databaseStore.playFile(entry); }; const click_handler_1 = (e) => { databaseStore.copyPath(entry, true, e.ctrlKey); $$invalidate(1, flashFilePath = true); setTimeout( () => { $$invalidate(1, flashFilePath = false); }, 400 ); }; const click_handler_2 = (e) => { databaseStore.copyPath(entry, false, e.ctrlKey); $$invalidate(2, flashDBPath = true); setTimeout( () => { $$invalidate(2, flashDBPath = false); }, 400 ); }; $$self.$$set = ($$props2) => { if ("entry" in $$props2) $$invalidate(0, entry = $$props2.entry); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*entry, $searchRegex*/ 33) { $$invalidate(3, highlight = entry.replace($searchRegex, "$&")); } }; return [ entry, flashFilePath, flashDBPath, highlight, searchRegex, $searchRegex, click_handler, click_handler_1, click_handler_2 ]; } class DatabaseEntry extends SvelteComponent { constructor(options) { super(); init(this, options, instance$i, create_fragment$i, safe_not_equal, { entry: 0 }); } } const DIRECTION_TYPE = { FRONT: "FRONT", // scroll up or left BEHIND: "BEHIND" // scroll down or right }; const CALC_TYPE = { INIT: "INIT", FIXED: "FIXED", DYNAMIC: "DYNAMIC" }; const LEADING_BUFFER = 2; class Virtual { param; callUpdate; firstRangeTotalSize = 0; firstRangeAverageSize = 0; lastCalcIndex = 0; fixedSizeValue = 0; calcType = CALC_TYPE.INIT; offset = 0; direction = ""; range; constructor(param, callUpdate) { this.init(param, callUpdate); } init(param, callUpdate) { this.param = param; this.callUpdate = callUpdate; this.sizes = /* @__PURE__ */ new Map(); this.firstRangeTotalSize = 0; this.firstRangeAverageSize = 0; this.lastCalcIndex = 0; this.fixedSizeValue = 0; this.calcType = CALC_TYPE.INIT; this.offset = 0; this.direction = ""; this.range = /* @__PURE__ */ Object.create(null); if (param) { this.checkRange(0, param.keeps - 1); } } destroy() { this.init(null, null); } // return current render range getRange() { const range = /* @__PURE__ */ Object.create(null); range.start = this.range.start; range.end = this.range.end; range.padFront = this.range.padFront; range.padBehind = this.range.padBehind; return range; } isBehind() { return this.direction === DIRECTION_TYPE.BEHIND; } isFront() { return this.direction === DIRECTION_TYPE.FRONT; } // return start index offset getOffset(start) { return (start < 1 ? 0 : this.getIndexOffset(start)) + this.param.slotHeaderSize; } updateParam(key, value) { if (this.param && key in this.param) { if (key === "uniqueIds") { this.sizes.forEach((v, key2) => { if (!value.includes(key2)) { this.sizes.delete(key2); } }); } this.param[key] = value; } } // save each size map by id saveSize(id, size) { this.sizes.set(id, size); if (this.calcType === CALC_TYPE.INIT) { this.fixedSizeValue = size; this.calcType = CALC_TYPE.FIXED; } else if (this.calcType === CALC_TYPE.FIXED && this.fixedSizeValue !== size) { this.calcType = CALC_TYPE.DYNAMIC; delete this.fixedSizeValue; } if (this.calcType !== CALC_TYPE.FIXED && typeof this.firstRangeTotalSize !== "undefined") { if (this.sizes.size < Math.min(this.param.keeps, this.param.uniqueIds.length)) { this.firstRangeTotalSize = [...this.sizes.values()].reduce((acc, val) => acc + val, 0); this.firstRangeAverageSize = Math.round(this.firstRangeTotalSize / this.sizes.size); } else { delete this.firstRangeTotalSize; } } } // in some special situation (e.g. length change) we need to update in a row // try going to render next range by a leading buffer according to current direction handleDataSourcesChange() { let start = this.range.start; if (this.isFront()) { start = start - LEADING_BUFFER; } else if (this.isBehind()) { start = start + LEADING_BUFFER; } start = Math.max(start, 0); this.updateRange(this.range.start, this.getEndByStart(start)); } // when slot size change, we also need force update handleSlotSizeChange() { this.handleDataSourcesChange(); } // calculating range on scroll handleScroll(offset2) { this.direction = offset2 < this.offset ? DIRECTION_TYPE.FRONT : DIRECTION_TYPE.BEHIND; this.offset = offset2; if (!this.param) { return; } if (this.direction === DIRECTION_TYPE.FRONT) { this.handleFront(); } else if (this.direction === DIRECTION_TYPE.BEHIND) { this.handleBehind(); } } // ----------- public method end ----------- handleFront() { const overs = this.getScrollOvers(); if (overs > this.range.start) { return; } const start = Math.max(overs - this.param.buffer, 0); this.checkRange(start, this.getEndByStart(start)); } handleBehind() { const overs = this.getScrollOvers(); if (overs < this.range.start + this.param.buffer) { return; } this.checkRange(overs, this.getEndByStart(overs)); } // return the pass overs according to current scroll offset getScrollOvers() { const offset2 = this.offset - this.param.slotHeaderSize; if (offset2 <= 0) { return 0; } if (this.isFixedType()) { return Math.floor(offset2 / this.fixedSizeValue); } let low = 0; let middle = 0; let middleOffset = 0; let high = this.param.uniqueIds.length; while (low <= high) { middle = low + Math.floor((high - low) / 2); middleOffset = this.getIndexOffset(middle); if (middleOffset === offset2) { return middle; } else if (middleOffset < offset2) { low = middle + 1; } else if (middleOffset > offset2) { high = middle - 1; } } return low > 0 ? --low : 0; } // return a scroll offset from given index, can efficiency be improved more here? // although the call frequency is very high, its only a superposition of numbers getIndexOffset(givenIndex) { if (!givenIndex) { return 0; } let offset2 = 0; let indexSize = 0; for (let index = 0; index < givenIndex; index++) { indexSize = this.sizes.get(this.param.uniqueIds[index]); offset2 = offset2 + (typeof indexSize === "number" ? indexSize : this.getEstimateSize()); } this.lastCalcIndex = Math.max(this.lastCalcIndex, givenIndex - 1); this.lastCalcIndex = Math.min(this.lastCalcIndex, this.getLastIndex()); return offset2; } // is fixed size type isFixedType() { return this.calcType === CALC_TYPE.FIXED; } // return the real last index getLastIndex() { return this.param.uniqueIds.length - 1; } // in some conditions range is broke, we need correct it // and then decide whether need update to next range checkRange(start, end) { const keeps = this.param.keeps; const total = this.param.uniqueIds.length; if (total <= keeps) { start = 0; end = this.getLastIndex(); } else if (end - start < keeps - 1) { start = end - keeps + 1; } if (this.range.start !== start) { this.updateRange(start, end); } } // setting to a new range and rerender updateRange(start, end) { this.range.start = start; this.range.end = end; this.range.padFront = this.getPadFront(); this.range.padBehind = this.getPadBehind(); this.callUpdate(this.getRange()); } // return end base on start getEndByStart(start) { const theoryEnd = start + this.param.keeps - 1; const truelyEnd = Math.min(theoryEnd, this.getLastIndex()); return truelyEnd; } // return total front offset getPadFront() { if (this.isFixedType()) { return this.fixedSizeValue * this.range.start; } else { return this.getIndexOffset(this.range.start); } } // return total behind offset getPadBehind() { const end = this.range.end; const lastIndex = this.getLastIndex(); if (this.isFixedType()) { return (lastIndex - end) * this.fixedSizeValue; } if (this.lastCalcIndex === lastIndex) { return this.getIndexOffset(lastIndex) - this.getIndexOffset(end); } else { return (lastIndex - end) * this.getEstimateSize(); } } // get the item estimate size getEstimateSize() { return this.isFixedType() ? this.fixedSizeValue : this.firstRangeAverageSize || this.param.estimateSize; } } function create_fragment$h(ctx) { let div; let current; const default_slot_template = ( /*#slots*/ ctx[5].default ); const default_slot = create_slot( default_slot_template, ctx, /*$$scope*/ ctx[4], null ); return { c() { div = element("div"); if (default_slot) default_slot.c(); attr(div, "class", "virtual-scroll-item"); }, m(target, anchor) { insert(target, div, anchor); if (default_slot) { default_slot.m(div, null); } ctx[6](div); current = true; }, p(ctx2, [dirty]) { if (default_slot) { if (default_slot.p && (!current || dirty & /*$$scope*/ 16)) { update_slot_base( default_slot, default_slot_template, ctx2, /*$$scope*/ ctx2[4], !current ? get_all_dirty_from_scope( /*$$scope*/ ctx2[4] ) : get_slot_changes( default_slot_template, /*$$scope*/ ctx2[4], dirty, null ), null ); } } }, i(local) { if (current) return; transition_in(default_slot, local); current = true; }, o(local) { transition_out(default_slot, local); current = false; }, d(detaching) { if (detaching) detach(div); if (default_slot) default_slot.d(detaching); ctx[6](null); } }; } function instance$h($$self, $$props, $$invalidate) { let { $$slots: slots = {}, $$scope } = $$props; let { horizontal = false } = $$props; let { uniqueKey } = $$props; let { type = "item" } = $$props; let resizeObserver2; let itemDiv; let previousSize; const dispatch2 = createEventDispatcher(); const shapeKey = horizontal ? "offsetWidth" : "offsetHeight"; onMount(() => { if (typeof ResizeObserver !== "undefined") { resizeObserver2 = new ResizeObserver(dispatchSizeChange); resizeObserver2.observe(itemDiv); } }); afterUpdate(dispatchSizeChange); onDestroy(() => { if (resizeObserver2) { resizeObserver2.disconnect(); resizeObserver2 = null; } }); function dispatchSizeChange() { const size = itemDiv ? itemDiv[shapeKey] : 0; if (size === previousSize) return; previousSize = size; dispatch2("resize", { id: uniqueKey, size, type }); } function div_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { itemDiv = $$value; $$invalidate(0, itemDiv); }); } $$self.$$set = ($$props2) => { if ("horizontal" in $$props2) $$invalidate(1, horizontal = $$props2.horizontal); if ("uniqueKey" in $$props2) $$invalidate(2, uniqueKey = $$props2.uniqueKey); if ("type" in $$props2) $$invalidate(3, type = $$props2.type); if ("$$scope" in $$props2) $$invalidate(4, $$scope = $$props2.$$scope); }; return [itemDiv, horizontal, uniqueKey, type, $$scope, slots, div_binding]; } class Item extends SvelteComponent { constructor(options) { super(); init(this, options, instance$h, create_fragment$h, safe_not_equal, { horizontal: 1, uniqueKey: 2, type: 3 }); } } const get_footer_slot_changes = (dirty) => ({ data: dirty[0] & /*displayItems*/ 4 }); const get_footer_slot_context = (ctx) => ({ data: ( /*dataItem*/ ctx[39] ) }); function get_each_context$6(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[39] = list[i]; return child_ctx; } const get_default_slot_changes = (dirty) => ({ data: dirty[0] & /*displayItems*/ 4 }); const get_default_slot_context = (ctx) => ({ data: ( /*dataItem*/ ctx[39] ) }); const get_header_slot_changes = (dirty) => ({ data: dirty[0] & /*displayItems*/ 4 }); const get_header_slot_context = (ctx) => ({ data: ( /*dataItem*/ ctx[39] ) }); function create_if_block_1$3(ctx) { let item; let current; item = new Item({ props: { type: "slot", uniqueKey: "header", $$slots: { default: [create_default_slot_2$1] }, $$scope: { ctx } } }); item.$on( "resize", /*onItemResized*/ ctx[6] ); return { c() { create_component(item.$$.fragment); }, m(target, anchor) { mount_component(item, target, anchor); current = true; }, p(ctx2, dirty) { const item_changes = {}; if (dirty[0] & /*$$scope, displayItems*/ 536870916) { item_changes.$$scope = { dirty, ctx: ctx2 }; } item.$set(item_changes); }, i(local) { if (current) return; transition_in(item.$$.fragment, local); current = true; }, o(local) { transition_out(item.$$.fragment, local); current = false; }, d(detaching) { destroy_component(item, detaching); } }; } function create_default_slot_2$1(ctx) { let current; const header_slot_template = ( /*#slots*/ ctx[26].header ); const header_slot = create_slot( header_slot_template, ctx, /*$$scope*/ ctx[29], get_header_slot_context ); return { c() { if (header_slot) header_slot.c(); }, m(target, anchor) { if (header_slot) { header_slot.m(target, anchor); } current = true; }, p(ctx2, dirty) { if (header_slot) { if (header_slot.p && (!current || dirty[0] & /*$$scope, displayItems*/ 536870916)) { update_slot_base( header_slot, header_slot_template, ctx2, /*$$scope*/ ctx2[29], !current ? get_all_dirty_from_scope( /*$$scope*/ ctx2[29] ) : get_slot_changes( header_slot_template, /*$$scope*/ ctx2[29], dirty, get_header_slot_changes ), get_header_slot_context ); } } }, i(local) { if (current) return; transition_in(header_slot, local); current = true; }, o(local) { transition_out(header_slot, local); current = false; }, d(detaching) { if (header_slot) header_slot.d(detaching); } }; } function create_default_slot_1$1(ctx) { let t; let current; const default_slot_template = ( /*#slots*/ ctx[26].default ); const default_slot = create_slot( default_slot_template, ctx, /*$$scope*/ ctx[29], get_default_slot_context ); return { c() { if (default_slot) default_slot.c(); t = space(); }, m(target, anchor) { if (default_slot) { default_slot.m(target, anchor); } insert(target, t, anchor); current = true; }, p(ctx2, dirty) { if (default_slot) { if (default_slot.p && (!current || dirty[0] & /*$$scope, displayItems*/ 536870916)) { update_slot_base( default_slot, default_slot_template, ctx2, /*$$scope*/ ctx2[29], !current ? get_all_dirty_from_scope( /*$$scope*/ ctx2[29] ) : get_slot_changes( default_slot_template, /*$$scope*/ ctx2[29], dirty, get_default_slot_changes ), get_default_slot_context ); } } }, i(local) { if (current) return; transition_in(default_slot, local); current = true; }, o(local) { transition_out(default_slot, local); current = false; }, d(detaching) { if (default_slot) default_slot.d(detaching); if (detaching) detach(t); } }; } function create_each_block$6(key_2, ctx) { let first; let item; let current; item = new Item({ props: { uniqueKey: ( /*dataItem*/ ctx[39][ /*key*/ ctx[0] ] ), horizontal: ( /*isHorizontal*/ ctx[1] ), type: "item", $$slots: { default: [create_default_slot_1$1] }, $$scope: { ctx } } }); item.$on( "resize", /*onItemResized*/ ctx[6] ); return { key: key_2, first: null, c() { first = empty(); create_component(item.$$.fragment); this.first = first; }, m(target, anchor) { insert(target, first, anchor); mount_component(item, target, anchor); current = true; }, p(new_ctx, dirty) { ctx = new_ctx; const item_changes = {}; if (dirty[0] & /*displayItems, key*/ 5) item_changes.uniqueKey = /*dataItem*/ ctx[39][ /*key*/ ctx[0] ]; if (dirty[0] & /*isHorizontal*/ 2) item_changes.horizontal = /*isHorizontal*/ ctx[1]; if (dirty[0] & /*$$scope, displayItems*/ 536870916) { item_changes.$$scope = { dirty, ctx }; } item.$set(item_changes); }, i(local) { if (current) return; transition_in(item.$$.fragment, local); current = true; }, o(local) { transition_out(item.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(first); destroy_component(item, detaching); } }; } function create_if_block$6(ctx) { let item; let current; item = new Item({ props: { type: "slot", uniqueKey: "footer", $$slots: { default: [create_default_slot$2] }, $$scope: { ctx } } }); item.$on( "resize", /*onItemResized*/ ctx[6] ); return { c() { create_component(item.$$.fragment); }, m(target, anchor) { mount_component(item, target, anchor); current = true; }, p(ctx2, dirty) { const item_changes = {}; if (dirty[0] & /*$$scope, displayItems*/ 536870916) { item_changes.$$scope = { dirty, ctx: ctx2 }; } item.$set(item_changes); }, i(local) { if (current) return; transition_in(item.$$.fragment, local); current = true; }, o(local) { transition_out(item.$$.fragment, local); current = false; }, d(detaching) { destroy_component(item, detaching); } }; } function create_default_slot$2(ctx) { let current; const footer_slot_template = ( /*#slots*/ ctx[26].footer ); const footer_slot = create_slot( footer_slot_template, ctx, /*$$scope*/ ctx[29], get_footer_slot_context ); return { c() { if (footer_slot) footer_slot.c(); }, m(target, anchor) { if (footer_slot) { footer_slot.m(target, anchor); } current = true; }, p(ctx2, dirty) { if (footer_slot) { if (footer_slot.p && (!current || dirty[0] & /*$$scope, displayItems*/ 536870916)) { update_slot_base( footer_slot, footer_slot_template, ctx2, /*$$scope*/ ctx2[29], !current ? get_all_dirty_from_scope( /*$$scope*/ ctx2[29] ) : get_slot_changes( footer_slot_template, /*$$scope*/ ctx2[29], dirty, get_footer_slot_changes ), get_footer_slot_context ); } } }, i(local) { if (current) return; transition_in(footer_slot, local); current = true; }, o(local) { transition_out(footer_slot, local); current = false; }, d(detaching) { if (footer_slot) footer_slot.d(detaching); } }; } function create_fragment$g(ctx) { let div2; let t0; let div0; let each_blocks = []; let each_1_lookup = /* @__PURE__ */ new Map(); let t1; let t2; let div1; let current; let mounted; let dispose; let if_block0 = ( /*$$slots*/ ctx[8].header && create_if_block_1$3(ctx) ); let each_value = ( /*displayItems*/ ctx[2] ); const get_key = (ctx2) => ( /*dataItem*/ ctx2[39][ /*key*/ ctx2[0] ] ); for (let i = 0; i < each_value.length; i += 1) { let child_ctx = get_each_context$6(ctx, each_value, i); let key = get_key(child_ctx); each_1_lookup.set(key, each_blocks[i] = create_each_block$6(key, child_ctx)); } let if_block1 = ( /*$$slots*/ ctx[8].footer && create_if_block$6(ctx) ); return { c() { div2 = element("div"); if (if_block0) if_block0.c(); t0 = space(); div0 = element("div"); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } t1 = space(); if (if_block1) if_block1.c(); t2 = space(); div1 = element("div"); set_style( div0, "padding", /*paddingStyle*/ ctx[3] ); attr(div1, "class", "shepherd"); set_style( div1, "width", /*isHorizontal*/ ctx[1] ? "0px" : "100%" ); set_style( div1, "height", /*isHorizontal*/ ctx[1] ? "100%" : "0px" ); set_style(div2, "overflow-y", "auto"); set_style(div2, "height", "inherit"); }, m(target, anchor) { insert(target, div2, anchor); if (if_block0) if_block0.m(div2, null); append(div2, t0); append(div2, div0); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(div0, null); } append(div2, t1); if (if_block1) if_block1.m(div2, null); append(div2, t2); append(div2, div1); ctx[27](div1); ctx[28](div2); current = true; if (!mounted) { dispose = listen( div2, "scroll", /*onScroll*/ ctx[7] ); mounted = true; } }, p(ctx2, dirty) { if ( /*$$slots*/ ctx2[8].header ) { if (if_block0) { if_block0.p(ctx2, dirty); if (dirty[0] & /*$$slots*/ 256) { transition_in(if_block0, 1); } } else { if_block0 = create_if_block_1$3(ctx2); if_block0.c(); transition_in(if_block0, 1); if_block0.m(div2, t0); } } else if (if_block0) { group_outros(); transition_out(if_block0, 1, 1, () => { if_block0 = null; }); check_outros(); } if (dirty[0] & /*displayItems, key, isHorizontal, onItemResized, $$scope*/ 536870983) { each_value = /*displayItems*/ ctx2[2]; group_outros(); each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx2, each_value, each_1_lookup, div0, outro_and_destroy_block, create_each_block$6, null, get_each_context$6); check_outros(); } if (!current || dirty[0] & /*paddingStyle*/ 8) { set_style( div0, "padding", /*paddingStyle*/ ctx2[3] ); } if ( /*$$slots*/ ctx2[8].footer ) { if (if_block1) { if_block1.p(ctx2, dirty); if (dirty[0] & /*$$slots*/ 256) { transition_in(if_block1, 1); } } else { if_block1 = create_if_block$6(ctx2); if_block1.c(); transition_in(if_block1, 1); if_block1.m(div2, t2); } } else if (if_block1) { group_outros(); transition_out(if_block1, 1, 1, () => { if_block1 = null; }); check_outros(); } if (!current || dirty[0] & /*isHorizontal*/ 2) { set_style( div1, "width", /*isHorizontal*/ ctx2[1] ? "0px" : "100%" ); } if (!current || dirty[0] & /*isHorizontal*/ 2) { set_style( div1, "height", /*isHorizontal*/ ctx2[1] ? "100%" : "0px" ); } }, i(local) { if (current) return; transition_in(if_block0); for (let i = 0; i < each_value.length; i += 1) { transition_in(each_blocks[i]); } transition_in(if_block1); current = true; }, o(local) { transition_out(if_block0); for (let i = 0; i < each_blocks.length; i += 1) { transition_out(each_blocks[i]); } transition_out(if_block1); current = false; }, d(detaching) { if (detaching) detach(div2); if (if_block0) if_block0.d(); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].d(); } if (if_block1) if_block1.d(); ctx[27](null); ctx[28](null); mounted = false; dispose(); } }; } function instance$g($$self, $$props, $$invalidate) { let { $$slots: slots = {}, $$scope } = $$props; const $$slots = compute_slots(slots); let { key = "id" } = $$props; let { data } = $$props; let { keeps = 30 } = $$props; let { estimateSize = 50 } = $$props; let { isHorizontal = false } = $$props; let { start = 0 } = $$props; let { offset: offset2 = 0 } = $$props; let { pageMode = false } = $$props; let { topThreshold = 0 } = $$props; let { bottomThreshold = 0 } = $$props; let displayItems = []; let paddingStyle; let directionKey = isHorizontal ? "scrollLeft" : "scrollTop"; let range = null; let virtual = new Virtual( { slotHeaderSize: 0, slotFooterSize: 0, keeps, estimateSize, buffer: Math.round(keeps / 3), // recommend for a third of keeps uniqueIds: getUniqueIdFromDataSources() }, onRangeChanged ); let root; let shepherd; const dispatch2 = createEventDispatcher(); function getSize(id) { return virtual.sizes.get(id); } function getSizes() { return virtual.sizes.size; } function getOffset() { if (pageMode) { return document.documentElement[directionKey] || document.body[directionKey]; } else { return root ? Math.ceil(root[directionKey]) : 0; } } function getClientSize() { const key2 = isHorizontal ? "clientWidth" : "clientHeight"; if (pageMode) { return document.documentElement[key2] || document.body[key2]; } else { return root ? Math.ceil(root[key2]) : 0; } } function getScrollSize() { const key2 = isHorizontal ? "scrollWidth" : "scrollHeight"; if (pageMode) { return document.documentElement[key2] || document.body[key2]; } else { return root ? Math.ceil(root[key2]) : 0; } } function updatePageModeFront() { if (root) { const rect = root.getBoundingClientRect(); const { defaultView } = root.ownerDocument; const offsetFront = isHorizontal ? rect.left + defaultView.pageXOffset : rect.top + defaultView.pageYOffset; virtual.updateParam("slotHeaderSize", offsetFront); } } function scrollToOffset(offset3) { if (pageMode) { document.body[directionKey] = offset3; document.documentElement[directionKey] = offset3; } else if (root) { $$invalidate(4, root[directionKey] = offset3, root); } } function scrollToIndex(index) { if (index >= data.length - 1) { scrollToBottom(); } else { const offset3 = virtual.getOffset(index); scrollToOffset(offset3); } } function scrollToBottom() { if (shepherd) { const offset3 = shepherd[isHorizontal ? "offsetLeft" : "offsetTop"]; scrollToOffset(offset3); setTimeout( () => { if (getOffset() + getClientSize() + 1 < getScrollSize()) { scrollToBottom(); } }, 3 ); } } onMount(() => { if (start) { scrollToIndex(start); } else if (offset2) { scrollToOffset(offset2); } if (pageMode) { updatePageModeFront(); document.addEventListener("scroll", onScroll, { passive: false }); } }); onDestroy(() => { virtual.destroy(); if (pageMode) { document.removeEventListener("scroll", onScroll); } }); function getUniqueIdFromDataSources() { return data.map((dataSource) => dataSource[key]); } function onItemResized(event) { const { id, size, type } = event.detail; if (type === "item") virtual.saveSize(id, size); else if (type === "slot") { if (id === "header") virtual.updateParam("slotHeaderSize", size); else if (id === "footer") virtual.updateParam("slotFooterSize", size); } } function onRangeChanged(range_) { range = range_; $$invalidate(3, paddingStyle = $$invalidate(3, paddingStyle = isHorizontal ? `0px ${range.padBehind}px 0px ${range.padFront}px` : `${range.padFront}px 0px ${range.padBehind}px`)); $$invalidate(2, displayItems = data.slice(range.start, range.end + 1)); } function onScroll(event) { const offset3 = getOffset(); const clientSize = getClientSize(); const scrollSize = getScrollSize(); if (offset3 < 0 || offset3 + clientSize > scrollSize || !scrollSize) { return; } virtual.handleScroll(offset3); emitEvent(offset3, clientSize, scrollSize, event); } function emitEvent(offset3, clientSize, scrollSize, event) { dispatch2("scroll", { event, range: virtual.getRange() }); if (virtual.isFront() && !!data.length && offset3 - topThreshold <= 0) { dispatch2("top"); } else if (virtual.isBehind() && offset3 + clientSize + bottomThreshold >= scrollSize) { dispatch2("bottom"); } } function handleKeepsChange(keeps2) { virtual.updateParam("keeps", keeps2); virtual.handleSlotSizeChange(); } async function handleDataSourcesChange(data2) { virtual.updateParam("uniqueIds", getUniqueIdFromDataSources()); virtual.handleDataSourcesChange(); } function div1_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { shepherd = $$value; $$invalidate(5, shepherd); }); } function div2_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { root = $$value; $$invalidate(4, root); }); } $$self.$$set = ($$props2) => { if ("key" in $$props2) $$invalidate(0, key = $$props2.key); if ("data" in $$props2) $$invalidate(9, data = $$props2.data); if ("keeps" in $$props2) $$invalidate(10, keeps = $$props2.keeps); if ("estimateSize" in $$props2) $$invalidate(11, estimateSize = $$props2.estimateSize); if ("isHorizontal" in $$props2) $$invalidate(1, isHorizontal = $$props2.isHorizontal); if ("start" in $$props2) $$invalidate(12, start = $$props2.start); if ("offset" in $$props2) $$invalidate(13, offset2 = $$props2.offset); if ("pageMode" in $$props2) $$invalidate(14, pageMode = $$props2.pageMode); if ("topThreshold" in $$props2) $$invalidate(15, topThreshold = $$props2.topThreshold); if ("bottomThreshold" in $$props2) $$invalidate(16, bottomThreshold = $$props2.bottomThreshold); if ("$$scope" in $$props2) $$invalidate(29, $$scope = $$props2.$$scope); }; $$self.$$.update = () => { if ($$self.$$.dirty[0] & /*offset*/ 8192) { scrollToOffset(offset2); } if ($$self.$$.dirty[0] & /*start*/ 4096) { scrollToIndex(start); } if ($$self.$$.dirty[0] & /*keeps*/ 1024) { handleKeepsChange(keeps); } if ($$self.$$.dirty[0] & /*data*/ 512) { handleDataSourcesChange(); } }; return [ key, isHorizontal, displayItems, paddingStyle, root, shepherd, onItemResized, onScroll, $$slots, data, keeps, estimateSize, start, offset2, pageMode, topThreshold, bottomThreshold, getSize, getSizes, getOffset, getClientSize, getScrollSize, updatePageModeFront, scrollToOffset, scrollToIndex, scrollToBottom, slots, div1_binding, div2_binding, $$scope ]; } class VirtualScroll extends SvelteComponent { constructor(options) { super(); init( this, options, instance$g, create_fragment$g, safe_not_equal, { key: 0, data: 9, keeps: 10, estimateSize: 11, isHorizontal: 1, start: 12, offset: 13, pageMode: 14, topThreshold: 15, bottomThreshold: 16, getSize: 17, getSizes: 18, getOffset: 19, getClientSize: 20, getScrollSize: 21, updatePageModeFront: 22, scrollToOffset: 23, scrollToIndex: 24, scrollToBottom: 25 }, null, [-1, -1] ); } get getSize() { return this.$$.ctx[17]; } get getSizes() { return this.$$.ctx[18]; } get getOffset() { return this.$$.ctx[19]; } get getClientSize() { return this.$$.ctx[20]; } get getScrollSize() { return this.$$.ctx[21]; } get updatePageModeFront() { return this.$$.ctx[22]; } get scrollToOffset() { return this.$$.ctx[23]; } get scrollToIndex() { return this.$$.ctx[24]; } get scrollToBottom() { return this.$$.ctx[25]; } } const databaseShell_svelte_svelte_type_style_lang = ""; function get_each_context$5(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[41] = list[i]; child_ctx[43] = i; return child_ctx; } function create_each_block$5(ctx) { let option; let t_value = ( /*pack*/ ctx[41] + "" ); let t; let option_value_value; return { c() { option = element("option"); t = text$1(t_value); option.__value = option_value_value = /*pack*/ ctx[41]; option.value = option.__value; }, m(target, anchor) { insert(target, option, anchor); append(option, t); }, p(ctx2, dirty) { if (dirty[0] & /*$packStore*/ 128 && t_value !== (t_value = /*pack*/ ctx2[41] + "")) set_data(t, t_value); if (dirty[0] & /*$packStore*/ 128 && option_value_value !== (option_value_value = /*pack*/ ctx2[41])) { option.__value = option_value_value; option.value = option.__value; } }, d(detaching) { if (detaching) detach(option); } }; } function create_else_block_1(ctx) { let div; let virtualscroll; let current; virtualscroll = new VirtualScroll({ props: { data: ( /*$visibleTreeStore*/ ctx[9] ), key: "fullPath", $$slots: { default: [ create_default_slot_2, ({ data }) => ({ 40: data }), ({ data }) => [0, data ? 512 : 0] ] }, $$scope: { ctx } } }); return { c() { div = element("div"); create_component(virtualscroll.$$.fragment); attr(div, "class", "sequencer-database-entries-tree svelte-ese-gdt8h0"); }, m(target, anchor) { insert(target, div, anchor); mount_component(virtualscroll, div, null); current = true; }, p(ctx2, dirty) { const virtualscroll_changes = {}; if (dirty[0] & /*$visibleTreeStore*/ 512) virtualscroll_changes.data = /*$visibleTreeStore*/ ctx2[9]; if (dirty[1] & /*$$scope, data*/ 8704) { virtualscroll_changes.$$scope = { dirty, ctx: ctx2 }; } virtualscroll.$set(virtualscroll_changes); }, i(local) { if (current) return; transition_in(virtualscroll.$$.fragment, local); current = true; }, o(local) { transition_out(virtualscroll.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(div); destroy_component(virtualscroll); } }; } function create_if_block_1$2(ctx) { let div; let virtualscroll; let current; virtualscroll = new VirtualScroll({ props: { data: ( /*filteredEntries*/ ctx[6] ), key: "entry", $$slots: { default: [ create_default_slot_1, ({ data }) => ({ 40: data }), ({ data }) => [0, data ? 512 : 0] ] }, $$scope: { ctx } } }); return { c() { div = element("div"); create_component(virtualscroll.$$.fragment); attr(div, "class", "sequencer-database-entries svelte-ese-gdt8h0"); }, m(target, anchor) { insert(target, div, anchor); mount_component(virtualscroll, div, null); current = true; }, p(ctx2, dirty) { const virtualscroll_changes = {}; if (dirty[0] & /*filteredEntries*/ 64) virtualscroll_changes.data = /*filteredEntries*/ ctx2[6]; if (dirty[1] & /*$$scope, data*/ 8704) { virtualscroll_changes.$$scope = { dirty, ctx: ctx2 }; } virtualscroll.$set(virtualscroll_changes); }, i(local) { if (current) return; transition_in(virtualscroll.$$.fragment, local); current = true; }, o(local) { transition_out(virtualscroll.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(div); destroy_component(virtualscroll); } }; } function create_default_slot_2(ctx) { let switch_instance; let switch_instance_anchor; let current; var switch_value = ( /*data*/ ctx[40].class ); function switch_props(ctx2) { return { props: { data: ( /*data*/ ctx2[40] ) } }; } if (switch_value) { switch_instance = construct_svelte_component(switch_value, switch_props(ctx)); } return { c() { if (switch_instance) create_component(switch_instance.$$.fragment); switch_instance_anchor = empty(); }, m(target, anchor) { if (switch_instance) mount_component(switch_instance, target, anchor); insert(target, switch_instance_anchor, anchor); current = true; }, p(ctx2, dirty) { const switch_instance_changes = {}; if (dirty[1] & /*data*/ 512) switch_instance_changes.data = /*data*/ ctx2[40]; if (switch_value !== (switch_value = /*data*/ ctx2[40].class)) { if (switch_instance) { group_outros(); const old_component = switch_instance; transition_out(old_component.$$.fragment, 1, 0, () => { destroy_component(old_component, 1); }); check_outros(); } if (switch_value) { switch_instance = construct_svelte_component(switch_value, switch_props(ctx2)); create_component(switch_instance.$$.fragment); transition_in(switch_instance.$$.fragment, 1); mount_component(switch_instance, switch_instance_anchor.parentNode, switch_instance_anchor); } else { switch_instance = null; } } else if (switch_value) { switch_instance.$set(switch_instance_changes); } }, i(local) { if (current) return; if (switch_instance) transition_in(switch_instance.$$.fragment, local); current = true; }, o(local) { if (switch_instance) transition_out(switch_instance.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(switch_instance_anchor); if (switch_instance) destroy_component(switch_instance, detaching); } }; } function create_default_slot_1(ctx) { let databaseentry; let current; databaseentry = new DatabaseEntry({ props: { entry: ( /*data*/ ctx[40].entry ) } }); return { c() { create_component(databaseentry.$$.fragment); }, m(target, anchor) { mount_component(databaseentry, target, anchor); current = true; }, p(ctx2, dirty) { const databaseentry_changes = {}; if (dirty[1] & /*data*/ 512) databaseentry_changes.entry = /*data*/ ctx2[40].entry; databaseentry.$set(databaseentry_changes); }, i(local) { if (current) return; transition_in(databaseentry.$$.fragment, local); current = true; }, o(local) { transition_out(databaseentry.$$.fragment, local); current = false; }, d(detaching) { destroy_component(databaseentry, detaching); } }; } function create_else_block$1(ctx) { let t; return { c() { t = text$1("No file loaded..."); }, m(target, anchor) { insert(target, t, anchor); }, p: noop, d(detaching) { if (detaching) detach(t); } }; } function create_if_block$5(ctx) { let t0; let t1_value = ( /*$metadata*/ ctx[10].type + "" ); let t1; let t2; let t3_value = ( /*$metadata*/ ctx[10].duration + "" ); let t3; return { c() { t0 = text$1("Type: "); t1 = text$1(t1_value); t2 = text$1(" | Duration: "); t3 = text$1(t3_value); }, m(target, anchor) { insert(target, t0, anchor); insert(target, t1, anchor); insert(target, t2, anchor); insert(target, t3, anchor); }, p(ctx2, dirty) { if (dirty[0] & /*$metadata*/ 1024 && t1_value !== (t1_value = /*$metadata*/ ctx2[10].type + "")) set_data(t1, t1_value); if (dirty[0] & /*$metadata*/ 1024 && t3_value !== (t3_value = /*$metadata*/ ctx2[10].duration + "")) set_data(t3, t3_value); }, d(detaching) { if (detaching) detach(t0); if (detaching) detach(t1); if (detaching) detach(t2); if (detaching) detach(t3); } }; } function create_default_slot$1(ctx) { let div4; let div0; let select; let option; let t1; let input0; let t2; let input1; let t3; let label0; let t5; let input2; let t6; let label1; let t8; let input3; let t9; let label2; let t11; let div3; let current_block_type_index; let if_block0; let t12; let div2; let video; let t13; let img; let t14; let audio2; let t15; let div1; let current; let mounted; let dispose; let each_value = ( /*$packStore*/ ctx[7] ); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { each_blocks[i] = create_each_block$5(get_each_context$5(ctx, each_value, i)); } const if_block_creators = [create_if_block_1$2, create_else_block_1]; const if_blocks = []; function select_block_type(ctx2, dirty) { if ( /*$listView*/ ctx2[4] ) return 0; return 1; } current_block_type_index = select_block_type(ctx); if_block0 = if_blocks[current_block_type_index] = if_block_creators[current_block_type_index](ctx); function select_block_type_1(ctx2, dirty) { if ( /*$metadata*/ ctx2[10] ) return create_if_block$5; return create_else_block$1; } let current_block_type = select_block_type_1(ctx); let if_block1 = current_block_type(ctx); return { c() { div4 = element("div"); div0 = element("div"); select = element("select"); option = element("option"); option.textContent = `${localize("SEQUENCER.Database.AllPacks")}`; for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } t1 = space(); input0 = element("input"); t2 = space(); input1 = element("input"); t3 = space(); label0 = element("label"); label0.textContent = `${localize("SEQUENCER.Database.ShowAllRanges")}`; t5 = space(); input2 = element("input"); t6 = space(); label1 = element("label"); label1.textContent = `${localize("SEQUENCER.Database.ShowSubLists")}`; t8 = space(); input3 = element("input"); t9 = space(); label2 = element("label"); label2.textContent = `${localize("SEQUENCER.Database.ListView")}`; t11 = space(); div3 = element("div"); if_block0.c(); t12 = space(); div2 = element("div"); video = element("video"); t13 = space(); img = element("img"); t14 = space(); audio2 = element("audio"); audio2.innerHTML = ``; t15 = space(); div1 = element("div"); if_block1.c(); option.__value = "all"; option.value = option.__value; attr(select, "name", "pack-select"); attr(select, "class", "svelte-ese-gdt8h0"); if ( /*$selectedPackStore*/ ctx[2] === void 0 ) add_render_callback(() => ( /*select_change_handler*/ ctx[26].call(select) )); attr(input0, "class", "ml-2 svelte-ese-gdt8h0"); attr(input0, "placeholder", localize("SEQUENCER.Database.Search")); attr(input0, "type", "text"); attr(input1, "class", "ml-2"); attr(input1, "id", "database-all-ranges"); attr(input1, "type", "checkbox"); attr(label0, "class", "all-ranges-label svelte-ese-gdt8h0"); attr(label0, "for", "database-all-ranges"); attr(input2, "class", "ml-2"); attr(input2, "id", "include-sub-lists"); attr(input2, "type", "checkbox"); attr(label1, "class", "all-ranges-label svelte-ese-gdt8h0"); attr(label1, "for", "include-sub-lists"); attr(input3, "class", "ml-2"); attr(input3, "id", "treeview"); attr(input3, "type", "checkbox"); attr(label2, "class", "all-ranges-label svelte-ese-gdt8h0"); attr(label2, "for", "treeview"); attr(div0, "class", "sequencer-database-header svelte-ese-gdt8h0"); video.autoplay = true; attr(video, "class", "database-player svelte-ese-gdt8h0"); attr(video, "height", "335"); video.loop = true; attr(video, "preload", ""); attr(video, "width", "335"); attr(img, "class", "database-image hidden svelte-ese-gdt8h0"); attr(audio2, "class", "database-audio hidden svelte-ese-gdt8h0"); attr(div1, "class", "sequencer-database-metadata-container svelte-ese-gdt8h0"); attr(div2, "class", "sequencer-database-player-container svelte-ese-gdt8h0"); attr(div3, "class", "sequencer-database-entries-container svelte-ese-gdt8h0"); attr(div4, "class", "sequencer-database-content svelte-ese-gdt8h0"); }, m(target, anchor) { insert(target, div4, anchor); append(div4, div0); append(div0, select); append(select, option); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(select, null); } select_option( select, /*$selectedPackStore*/ ctx[2] ); append(div0, t1); append(div0, input0); set_input_value( input0, /*$search*/ ctx[1] ); append(div0, t2); append(div0, input1); input1.checked = /*$allRanges*/ ctx[3]; append(div0, t3); append(div0, label0); append(div0, t5); append(div0, input2); input2.checked = /*$subLists*/ ctx[8]; append(div0, t6); append(div0, label1); append(div0, t8); append(div0, input3); input3.checked = /*$listView*/ ctx[4]; append(div0, t9); append(div0, label2); append(div4, t11); append(div4, div3); if_blocks[current_block_type_index].m(div3, null); append(div3, t12); append(div3, div2); append(div2, video); ctx[31](video); append(div2, t13); append(div2, img); ctx[34](img); append(div2, t14); append(div2, audio2); ctx[35](audio2); append(div2, t15); append(div2, div1); if_block1.m(div1, null); current = true; if (!mounted) { dispose = [ listen( select, "change", /*select_change_handler*/ ctx[26] ), listen( input0, "input", /*input0_input_handler*/ ctx[27] ), listen( input1, "change", /*input1_change_handler*/ ctx[28] ), listen( input2, "change", /*input2_change_handler*/ ctx[29] ), listen( input3, "change", /*input3_change_handler*/ ctx[30] ), listen( video, "mouseenter", /*mouseenter_handler*/ ctx[32] ), listen( video, "mouseleave", /*mouseleave_handler*/ ctx[33] ), listen( audio2, "mouseenter", /*mouseenter_handler_1*/ ctx[36] ), listen( audio2, "mouseleave", /*mouseleave_handler_1*/ ctx[37] ) ]; mounted = true; } }, p(ctx2, dirty) { if (dirty[0] & /*$packStore*/ 128) { each_value = /*$packStore*/ ctx2[7]; let i; for (i = 0; i < each_value.length; i += 1) { const child_ctx = get_each_context$5(ctx2, each_value, i); if (each_blocks[i]) { each_blocks[i].p(child_ctx, dirty); } else { each_blocks[i] = create_each_block$5(child_ctx); each_blocks[i].c(); each_blocks[i].m(select, null); } } for (; i < each_blocks.length; i += 1) { each_blocks[i].d(1); } each_blocks.length = each_value.length; } if (dirty[0] & /*$selectedPackStore, $packStore*/ 132) { select_option( select, /*$selectedPackStore*/ ctx2[2] ); } if (dirty[0] & /*$search*/ 2 && input0.value !== /*$search*/ ctx2[1]) { set_input_value( input0, /*$search*/ ctx2[1] ); } if (dirty[0] & /*$allRanges*/ 8) { input1.checked = /*$allRanges*/ ctx2[3]; } if (dirty[0] & /*$subLists*/ 256) { input2.checked = /*$subLists*/ ctx2[8]; } if (dirty[0] & /*$listView*/ 16) { input3.checked = /*$listView*/ ctx2[4]; } let previous_block_index = current_block_type_index; current_block_type_index = select_block_type(ctx2); if (current_block_type_index === previous_block_index) { if_blocks[current_block_type_index].p(ctx2, dirty); } else { group_outros(); transition_out(if_blocks[previous_block_index], 1, 1, () => { if_blocks[previous_block_index] = null; }); check_outros(); if_block0 = if_blocks[current_block_type_index]; if (!if_block0) { if_block0 = if_blocks[current_block_type_index] = if_block_creators[current_block_type_index](ctx2); if_block0.c(); } else { if_block0.p(ctx2, dirty); } transition_in(if_block0, 1); if_block0.m(div3, t12); } if (current_block_type === (current_block_type = select_block_type_1(ctx2)) && if_block1) { if_block1.p(ctx2, dirty); } else { if_block1.d(1); if_block1 = current_block_type(ctx2); if (if_block1) { if_block1.c(); if_block1.m(div1, null); } } }, i(local) { if (current) return; transition_in(if_block0); current = true; }, o(local) { transition_out(if_block0); current = false; }, d(detaching) { if (detaching) detach(div4); destroy_each(each_blocks, detaching); if_blocks[current_block_type_index].d(); ctx[31](null); ctx[34](null); ctx[35](null); if_block1.d(); mounted = false; run_all(dispose); } }; } function create_fragment$f(ctx) { let applicationshell; let updating_elementRoot; let current; function applicationshell_elementRoot_binding(value) { ctx[38](value); } let applicationshell_props = { $$slots: { default: [create_default_slot$1] }, $$scope: { ctx } }; if ( /*elementRoot*/ ctx[0] !== void 0 ) { applicationshell_props.elementRoot = /*elementRoot*/ ctx[0]; } applicationshell = new ApplicationShell({ props: applicationshell_props }); binding_callbacks.push(() => bind(applicationshell, "elementRoot", applicationshell_elementRoot_binding)); return { c() { create_component(applicationshell.$$.fragment); }, m(target, anchor) { mount_component(applicationshell, target, anchor); current = true; }, p(ctx2, dirty) { const applicationshell_changes = {}; if (dirty[0] & /*$metadata, databaseStore, filteredEntries, $listView, $visibleTreeStore, $subLists, $allRanges, $search, $selectedPackStore, $packStore*/ 2046 | dirty[1] & /*$$scope*/ 8192) { applicationshell_changes.$$scope = { dirty, ctx: ctx2 }; } if (!updating_elementRoot && dirty[0] & /*elementRoot*/ 1) { updating_elementRoot = true; applicationshell_changes.elementRoot = /*elementRoot*/ ctx2[0]; add_flush_callback(() => updating_elementRoot = false); } applicationshell.$set(applicationshell_changes); }, i(local) { if (current) return; transition_in(applicationshell.$$.fragment, local); current = true; }, o(local) { transition_out(applicationshell.$$.fragment, local); current = false; }, d(detaching) { destroy_component(applicationshell, detaching); } }; } function instance$f($$self, $$props, $$invalidate) { let $search; let $selectedPackStore; let $searchRegex; let $cleanSearchStore; let $allRanges; let $entriesStore; let $listView; let $packStore; let $subLists; let $visibleTreeStore; let $metadata; getContext("#external"); let { elementRoot } = $$props; let entries = []; let filteredEntries = []; const selectedPackStore2 = databaseStore.selectedPackStore; component_subscribe($$self, selectedPackStore2, (value) => $$invalidate(2, $selectedPackStore = value)); const packStore2 = databaseStore.packStore; component_subscribe($$self, packStore2, (value) => $$invalidate(7, $packStore = value)); const metadata = databaseStore.metadata; component_subscribe($$self, metadata, (value) => $$invalidate(10, $metadata = value)); const allRanges = databaseStore.allRanges; component_subscribe($$self, allRanges, (value) => $$invalidate(3, $allRanges = value)); const subLists = databaseStore.subLists; component_subscribe($$self, subLists, (value) => $$invalidate(8, $subLists = value)); const listView = databaseStore.listView; component_subscribe($$self, listView, (value) => $$invalidate(4, $listView = value)); const visibleTreeStore2 = databaseStore.visibleTreeStore; component_subscribe($$self, visibleTreeStore2, (value) => $$invalidate(9, $visibleTreeStore = value)); const search = databaseStore.search; component_subscribe($$self, search, (value) => $$invalidate(1, $search = value)); const cleanSearchStore2 = databaseStore.cleanSearchStore; component_subscribe($$self, cleanSearchStore2, (value) => $$invalidate(24, $cleanSearchStore = value)); const searchRegex = databaseStore.searchRegex; component_subscribe($$self, searchRegex, (value) => $$invalidate(23, $searchRegex = value)); const entriesStore2 = SequencerDatabase.entriesStore; component_subscribe($$self, entriesStore2, (value) => $$invalidate(25, $entriesStore = value)); listView.set(game.settings.get(CONSTANTS.MODULE_NAME, "db-list-view")); function select_change_handler() { $selectedPackStore = select_value(this); selectedPackStore2.set($selectedPackStore); } function input0_input_handler() { $search = this.value; search.set($search); } function input1_change_handler() { $allRanges = this.checked; allRanges.set($allRanges); } function input2_change_handler() { $subLists = this.checked; subLists.set($subLists); } function input3_change_handler() { $listView = this.checked; listView.set($listView); } function video_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { databaseStore.elements.player = $$value; $$invalidate(5, databaseStore); }); } const mouseenter_handler = () => { $$invalidate(5, databaseStore.elements.player.controls = true, databaseStore); }; const mouseleave_handler = () => { $$invalidate(5, databaseStore.elements.player.controls = false, databaseStore); }; function img_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { databaseStore.elements.image = $$value; $$invalidate(5, databaseStore); }); } function audio_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { databaseStore.elements.audio = $$value; $$invalidate(5, databaseStore); }); } const mouseenter_handler_1 = () => { $$invalidate(5, databaseStore.elements.audio.controls = true, databaseStore); }; const mouseleave_handler_1 = () => { $$invalidate(5, databaseStore.elements.audio.controls = false, databaseStore); }; function applicationshell_elementRoot_binding(value) { elementRoot = value; $$invalidate(0, elementRoot); } $$self.$$set = ($$props2) => { if ("elementRoot" in $$props2) $$invalidate(0, elementRoot = $$props2.elementRoot); }; $$self.$$.update = () => { if ($$self.$$.dirty[0] & /*$listView*/ 16) { game.settings.set(CONSTANTS.MODULE_NAME, "db-list-view", $listView); } if ($$self.$$.dirty[0] & /*$entriesStore, $allRanges*/ 33554440) { { const specificRanges = $allRanges ? Sequencer.Database.publicFlattenedEntries : Sequencer.Database.publicFlattenedSimpleEntries; $$invalidate(22, entries = specificRanges.map((entry) => { return { pack: entry.split(".")[0], entry }; })); } } if ($$self.$$.dirty[0] & /*$cleanSearchStore, entries, $searchRegex, $selectedPackStore, $search*/ 29360134) { { const searchParts = $cleanSearchStore.split("|"); $$invalidate(6, filteredEntries = entries.filter((part) => { const matchParts = make_array_unique(part.entry.match($searchRegex) || []); return ($selectedPackStore === "all" || $selectedPackStore === part.pack) && ($search === "" || matchParts.length >= searchParts.length); })); } } }; return [ elementRoot, $search, $selectedPackStore, $allRanges, $listView, databaseStore, filteredEntries, $packStore, $subLists, $visibleTreeStore, $metadata, selectedPackStore2, packStore2, metadata, allRanges, subLists, listView, visibleTreeStore2, search, cleanSearchStore2, searchRegex, entriesStore2, entries, $searchRegex, $cleanSearchStore, $entriesStore, select_change_handler, input0_input_handler, input1_change_handler, input2_change_handler, input3_change_handler, video_binding, mouseenter_handler, mouseleave_handler, img_binding, audio_binding, mouseenter_handler_1, mouseleave_handler_1, applicationshell_elementRoot_binding ]; } class Database_shell extends SvelteComponent { constructor(options) { super(); init(this, options, instance$f, create_fragment$f, safe_not_equal, { elementRoot: 0 }, null, [-1, -1]); } get elementRoot() { return this.$$.ctx[0]; } set elementRoot(elementRoot) { this.$$set({ elementRoot }); flush(); } } class DatabaseViewerApp extends SvelteApplication { static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { title: game.i18n.localize("SEQUENCER.Database.Title"), classes: ["dialog"], width: 900, height: 425, svelte: { class: Database_shell, target: document.body } }); } static getActiveApp() { return Object.values(ui.windows).find((app) => { return app instanceof this && app._state > Application.RENDER_STATES.CLOSED; }); } static async show(options = {}) { const existingApp = this.getActiveApp(); if (existingApp) return existingApp.render(false, { focus: true }); return new Promise((resolve) => { options.resolve = resolve; new this(options).render(true, { focus: true }); }); } } const HowTo_svelte_svelte_type_style_lang = ""; function create_if_block$4(ctx) { let p; let raw0_value = localize("SEQUENCER.HowTo.PermissionsExplanation") + ""; let t0; let button; let i; let t1; let html_tag; let raw1_value = localize("SEQUENCER.HowTo.OpenModuleSettings") + ""; let mounted; let dispose; return { c() { p = element("p"); t0 = space(); button = element("button"); i = element("i"); t1 = space(); html_tag = new HtmlTag(false); attr(i, "class", "fas fa-plug"); html_tag.a = null; attr(button, "type", "button"); attr(button, "class", "w-100 open-module-settings"); }, m(target, anchor) { insert(target, p, anchor); p.innerHTML = raw0_value; insert(target, t0, anchor); insert(target, button, anchor); append(button, i); append(button, t1); html_tag.m(raw1_value, button); if (!mounted) { dispose = listen( button, "click", /*openSettings*/ ctx[0] ); mounted = true; } }, p: noop, d(detaching) { if (detaching) detach(p); if (detaching) detach(t0); if (detaching) detach(button); mounted = false; dispose(); } }; } function create_fragment$e(ctx) { let div; let p0; let raw0_value = localize("SEQUENCER.HowTo.Welcome") + ""; let t0; let p1; let raw1_value = localize("SEQUENCER.HowTo.Explanation") + ""; let t1; let t2; let p2; let raw2_value = localize("SEQUENCER.HowTo.PlayerExplanation") + ""; let t3; let p3; let raw3_value = localize("SEQUENCER.HowTo.LayerExplanation") + ""; let t4; let ol; let li0; let strong0; let raw4_value = localize("SEQUENCER.HowTo.Click") + ""; let br0; let t5; let html_tag; let raw5_value = localize("SEQUENCER.HowTo.ClickLabel") + ""; let t6; let li1; let strong1; let raw6_value = localize("SEQUENCER.HowTo.ClickDrag") + ""; let br1; let t7; let html_tag_1; let raw7_value = localize("SEQUENCER.HowTo.ClickDragLabel") + ""; let t8; let li2; let strong2; let raw8_value = localize("SEQUENCER.HowTo.Shift") + ""; let br2; let t9; let html_tag_2; let raw9_value = localize("SEQUENCER.HowTo.ShiftLabel") + ""; let t10; let li3; let strong3; let raw10_value = localize("SEQUENCER.HowTo.ShiftControl") + ""; let br3; let t11; let html_tag_3; let raw11_value = localize("SEQUENCER.HowTo.ShiftControlLabel") + ""; let t12; let li4; let strong4; let raw12_value = localize("SEQUENCER.HowTo.MoreToCome") + ""; let if_block = game.user.isGM && create_if_block$4(ctx); return { c() { div = element("div"); p0 = element("p"); t0 = space(); p1 = element("p"); t1 = space(); if (if_block) if_block.c(); t2 = space(); p2 = element("p"); t3 = space(); p3 = element("p"); t4 = space(); ol = element("ol"); li0 = element("li"); strong0 = element("strong"); br0 = element("br"); t5 = space(); html_tag = new HtmlTag(false); t6 = space(); li1 = element("li"); strong1 = element("strong"); br1 = element("br"); t7 = space(); html_tag_1 = new HtmlTag(false); t8 = space(); li2 = element("li"); strong2 = element("strong"); br2 = element("br"); t9 = space(); html_tag_2 = new HtmlTag(false); t10 = space(); li3 = element("li"); strong3 = element("strong"); br3 = element("br"); t11 = space(); html_tag_3 = new HtmlTag(false); t12 = space(); li4 = element("li"); strong4 = element("strong"); html_tag.a = null; attr(li0, "class", "svelte-ese-1gfsmnk"); html_tag_1.a = null; attr(li1, "class", "svelte-ese-1gfsmnk"); html_tag_2.a = null; attr(li2, "class", "svelte-ese-1gfsmnk"); html_tag_3.a = null; attr(li3, "class", "svelte-ese-1gfsmnk"); attr(li4, "class", "svelte-ese-1gfsmnk"); attr(div, "class", "howto-container svelte-ese-1gfsmnk"); }, m(target, anchor) { insert(target, div, anchor); append(div, p0); p0.innerHTML = raw0_value; append(div, t0); append(div, p1); p1.innerHTML = raw1_value; append(div, t1); if (if_block) if_block.m(div, null); append(div, t2); append(div, p2); p2.innerHTML = raw2_value; append(div, t3); append(div, p3); p3.innerHTML = raw3_value; append(div, t4); append(div, ol); append(ol, li0); append(li0, strong0); strong0.innerHTML = raw4_value; append(li0, br0); append(li0, t5); html_tag.m(raw5_value, li0); append(ol, t6); append(ol, li1); append(li1, strong1); strong1.innerHTML = raw6_value; append(li1, br1); append(li1, t7); html_tag_1.m(raw7_value, li1); append(ol, t8); append(ol, li2); append(li2, strong2); strong2.innerHTML = raw8_value; append(li2, br2); append(li2, t9); html_tag_2.m(raw9_value, li2); append(ol, t10); append(ol, li3); append(li3, strong3); strong3.innerHTML = raw10_value; append(li3, br3); append(li3, t11); html_tag_3.m(raw11_value, li3); append(ol, t12); append(ol, li4); append(li4, strong4); strong4.innerHTML = raw12_value; }, p(ctx2, [dirty]) { if (game.user.isGM) if_block.p(ctx2, dirty); }, i: noop, o: noop, d(detaching) { if (detaching) detach(div); if (if_block) if_block.d(); } }; } function instance$e($$self) { async function openSettings() { const settings = new SettingsConfig(); await settings.render(true); await wait$1(75); const focusElement = settings.element.find('select[name="sequencer.permissions-effect-create"]'); settings.element.find("div.scrollable").scrollTop(focusElement.offset().top); await wait$1(250); focusElement.css("box-shadow", "rgba(200, 64, 67, 0.75) inset 0 0 10px"); await wait$1(5e3); focusElement.css("box-shadow", "none"); } return [openSettings]; } class HowTo extends SvelteComponent { constructor(options) { super(); init(this, options, instance$e, create_fragment$e, safe_not_equal, {}); } } const Tabs_svelte_svelte_type_style_lang = ""; function get_each_context$4(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[6] = list[i]; child_ctx[8] = i; return child_ctx; } function create_if_block_1$1(ctx) { let div; return { c() { div = element("div"); set_style(div, "border-right", "1px solid rgba(0,0,0,0.5)"); set_style(div, "margin", "0 10px"); }, m(target, anchor) { insert(target, div, anchor); }, d(detaching) { if (detaching) detach(div); } }; } function create_if_block$3(ctx) { let i; let i_class_value; return { c() { i = element("i"); attr(i, "class", i_class_value = "icon " + /*tab*/ ctx[6].icon + " svelte-ese-123fyp"); }, m(target, anchor) { insert(target, i, anchor); }, p(ctx2, dirty) { if (dirty & /*tabs*/ 2 && i_class_value !== (i_class_value = "icon " + /*tab*/ ctx2[6].icon + " svelte-ese-123fyp")) { attr(i, "class", i_class_value); } }, d(detaching) { if (detaching) detach(i); } }; } function create_each_block$4(key_1, ctx) { let first; let t0; let div; let t1; let t2_value = localize( /*tab*/ ctx[6].label ) + ""; let t2; let t3; let mounted; let dispose; let if_block0 = ( /*separateElements*/ ctx[3] && /*index*/ ctx[8] > 0 && create_if_block_1$1() ); let if_block1 = ( /*tab*/ ctx[6].icon && create_if_block$3(ctx) ); function click_handler() { return ( /*click_handler*/ ctx[5]( /*tab*/ ctx[6] ) ); } return { key: key_1, first: null, c() { first = empty(); if (if_block0) if_block0.c(); t0 = space(); div = element("div"); if (if_block1) if_block1.c(); t1 = space(); t2 = text$1(t2_value); t3 = space(); attr(div, "class", "item item-piles-flexrow item-piles-clickable-link svelte-ese-123fyp"); attr(div, "data-tab", "rest"); toggle_class( div, "underscore", /*underscore*/ ctx[2] ); toggle_class( div, "active", /*activeTab*/ ctx[0] === /*tab*/ ctx[6].value ); this.first = first; }, m(target, anchor) { insert(target, first, anchor); if (if_block0) if_block0.m(target, anchor); insert(target, t0, anchor); insert(target, div, anchor); if (if_block1) if_block1.m(div, null); append(div, t1); append(div, t2); append(div, t3); if (!mounted) { dispose = listen(div, "click", click_handler); mounted = true; } }, p(new_ctx, dirty) { ctx = new_ctx; if ( /*separateElements*/ ctx[3] && /*index*/ ctx[8] > 0 ) { if (if_block0) ; else { if_block0 = create_if_block_1$1(); if_block0.c(); if_block0.m(t0.parentNode, t0); } } else if (if_block0) { if_block0.d(1); if_block0 = null; } if ( /*tab*/ ctx[6].icon ) { if (if_block1) { if_block1.p(ctx, dirty); } else { if_block1 = create_if_block$3(ctx); if_block1.c(); if_block1.m(div, t1); } } else if (if_block1) { if_block1.d(1); if_block1 = null; } if (dirty & /*tabs*/ 2 && t2_value !== (t2_value = localize( /*tab*/ ctx[6].label ) + "")) set_data(t2, t2_value); if (dirty & /*underscore*/ 4) { toggle_class( div, "underscore", /*underscore*/ ctx[2] ); } if (dirty & /*activeTab, tabs*/ 3) { toggle_class( div, "active", /*activeTab*/ ctx[0] === /*tab*/ ctx[6].value ); } }, d(detaching) { if (detaching) detach(first); if (if_block0) if_block0.d(detaching); if (detaching) detach(t0); if (detaching) detach(div); if (if_block1) if_block1.d(); mounted = false; dispose(); } }; } function create_fragment$d(ctx) { let nav; let each_blocks = []; let each_1_lookup = /* @__PURE__ */ new Map(); let nav_style_value; let each_value = ( /*tabs*/ ctx[1].filter(func) ); const get_key = (ctx2) => ( /*tab*/ ctx2[6].value ); for (let i = 0; i < each_value.length; i += 1) { let child_ctx = get_each_context$4(ctx, each_value, i); let key = get_key(child_ctx); each_1_lookup.set(key, each_blocks[i] = create_each_block$4(key, child_ctx)); } return { c() { nav = element("nav"); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } attr(nav, "class", "tabs svelte-ese-123fyp"); attr(nav, "data-group", "primary"); attr(nav, "style", nav_style_value = /*$$props*/ ctx[4].style); }, m(target, anchor) { insert(target, nav, anchor); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(nav, null); } }, p(ctx2, [dirty]) { if (dirty & /*underscore, activeTab, tabs, localize, separateElements*/ 15) { each_value = /*tabs*/ ctx2[1].filter(func); each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx2, each_value, each_1_lookup, nav, destroy_block, create_each_block$4, null, get_each_context$4); } if (dirty & /*$$props*/ 16 && nav_style_value !== (nav_style_value = /*$$props*/ ctx2[4].style)) { attr(nav, "style", nav_style_value); } }, i: noop, o: noop, d(detaching) { if (detaching) detach(nav); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].d(); } } }; } const func = (tab) => !tab.hidden; function instance$d($$self, $$props, $$invalidate) { let { activeTab } = $$props; let { tabs } = $$props; let { underscore = false } = $$props; let { separateElements = false } = $$props; const click_handler = (tab) => { $$invalidate(0, activeTab = tab.value); }; $$self.$$set = ($$new_props) => { $$invalidate(4, $$props = assign(assign({}, $$props), exclude_internal_props($$new_props))); if ("activeTab" in $$new_props) $$invalidate(0, activeTab = $$new_props.activeTab); if ("tabs" in $$new_props) $$invalidate(1, tabs = $$new_props.tabs); if ("underscore" in $$new_props) $$invalidate(2, underscore = $$new_props.underscore); if ("separateElements" in $$new_props) $$invalidate(3, separateElements = $$new_props.separateElements); }; $$props = exclude_internal_props($$props); return [activeTab, tabs, underscore, separateElements, $$props, click_handler]; } class Tabs extends SvelteComponent { constructor(options) { super(); init(this, options, instance$d, create_fragment$d, safe_not_equal, { activeTab: 0, tabs: 1, underscore: 2, separateElements: 3 }); } } const SequenceManager = { VisibleEffects: writable$1({}), RunningSounds: writable$1({}), RunningSequences: writable$1({}) }; SequenceManager.VisibleEffects.get = (id) => { return get_store_value(SequenceManager.VisibleEffects)[id]; }; SequenceManager.VisibleEffects.add = (id, data) => { SequenceManager.VisibleEffects.update((effects) => { effects[id] = data; return effects; }); }; SequenceManager.VisibleEffects.delete = (id) => { SequenceManager.VisibleEffects.update((effects) => { delete effects[id]; return effects; }); }; SequenceManager.VisibleEffects.values = () => { return Object.values(get_store_value(SequenceManager.VisibleEffects)); }; SequenceManager.RunningSounds.get = (id) => { return get_store_value(SequenceManager.RunningSounds)[id]; }; SequenceManager.RunningSounds.add = (id, data) => { SequenceManager.RunningSounds.update((effects) => { effects[id] = data; return effects; }); }; SequenceManager.RunningSounds.delete = (id) => { SequenceManager.RunningSounds.update((effects) => { delete effects[id]; return effects; }); }; SequenceManager.RunningSounds.values = () => { return Object.values(get_store_value(SequenceManager.RunningSounds)); }; SequenceManager.RunningSounds.keys = () => { return Object.keys(get_store_value(SequenceManager.RunningSounds)); }; SequenceManager.RunningSequences.get = (id) => { return get_store_value(SequenceManager.RunningSequences)[id]; }; SequenceManager.RunningSequences.add = (id, sequence) => { SequenceManager.RunningSequences.update((sequences) => { sequences[id] = sequence; return sequences; }); }; SequenceManager.RunningSequences.delete = (id) => { SequenceManager.RunningSequences.update((sequences) => { delete sequences[id]; return sequences; }); }; SequenceManager.RunningSequences.clearFinishedSequences = () => { SequenceManager.RunningSequences.update((sequences) => { for (const sequence of Object.values(sequences)) { if (get_store_value(sequence.status) === CONSTANTS.STATUS.COMPLETE || get_store_value(sequence.status) === CONSTANTS.STATUS.ABORTED) { delete sequences[sequence.id]; } } return sequences; }); }; SequenceManager.RunningSequences.stopAll = () => { SequenceManager.RunningSequences.update((sequences) => { for (const sequence of Object.values(sequences)) { sequence._abort(); } return sequences; }); }; SequenceManager.RunningSequences.values = () => { return Object.values(get_store_value(SequenceManager.RunningSequences)); }; class ColorMatrixFilter extends globalThis.PIXI.filters.ColorMatrixFilter { /** * Properties & default values: * - hue [false] * - brightness [1] * - contrast [1] * - saturate [1] */ constructor(inData) { super(); this.isValid = true; this.values = {}; for (let [key, value] of Object.entries(inData)) { this.setValue(key, value); if (!this.isValid) break; } } setValue(key, value) { try { this.values[key] = value; this[key](value, true); } catch (err) { ui.notifications.warn( `Sequencer | ${this.constructor.name} | Could not set property ${key}` ); } } } class BlurFilter extends globalThis.PIXI.filters.BlurFilter { /** * Properties & default values: * - strength [8] * - blur [2] * - blurX [2] * - blurY [2] * - quality [4] * - resolution [PIXI.settings.FILTER_RESOLUTION] * - kernelSize [5] */ constructor(inData = {}) { inData = foundry.utils.mergeObject( { strength: 1, quality: 4, resolution: PIXI.settings.FILTER_RESOLUTION, kernelSize: 5 }, inData ); super(...Object.values(inData)); this.isValid = true; for (let [key, value] of Object.entries(inData)) { try { this[key] = value; } catch (err) { ui.notifications.warn( `Sequencer | ${this.constructor.name} | Could not set property ${key}` ); this.isValid = false; } } } } class NoiseFilter extends globalThis.PIXI.filters.NoiseFilter { /** * Properties & default values: * - noise [0.5] * - seed [Math.random()] */ constructor(inData = {}) { super(); inData = foundry.utils.mergeObject( { noise: 0.5, seed: Math.random() }, inData ); this.isValid = true; for (let [key, value] of Object.entries(inData)) { try { this[key] = value; } catch (err) { ui.notifications.warn( `Sequencer | ${this.constructor.name} | Could not set property ${key}` ); this.isValid = false; } } } } class GlowFilter extends globalThis.PIXI.filters.GlowFilter { /** * Properties & default values: * - distance [10] * - outerStrength [4] * - innerStrength [0] * - color [0xffffff] * - quality [0.1] * - knockout [false] */ constructor(inData = {}) { inData = foundry.utils.mergeObject( { distance: 10, outerStrength: 4, innerStrength: 0, color: 16777215, quality: 0.1, knockout: false }, inData ); super(inData); this.isValid = true; } } let shader = ` uniform sampler2D uSampler; varying vec2 vTextureCoord; float alpha; void main() { vec4 pixel = texture2D(uSampler, vTextureCoord); alpha = smoothstep(0.6,1.0,pixel.a); gl_FragColor = vec4(alpha, alpha, alpha, pixel.a); } `; class ClipFilter extends PIXI.Filter { constructor() { super(null, shader); } } const filters = { ColorMatrix: ColorMatrixFilter, Blur: BlurFilter, Noise: NoiseFilter, Glow: GlowFilter, Clip: ClipFilter }; const SequencerAnimationEngine = { _animations: [], _debug: void 0, _deltas: [], ticker: false, dt: 0, isRunning: false, addAnimation(origin, attributes = [], timeDifference = 0) { if (!Array.isArray(attributes)) attributes = [attributes]; return new Promise((resolve) => { this._animations.push({ origin, attributes: attributes.map((attribute) => { attribute.targetId = get_object_identifier(attribute.target) + "-" + attribute.propertyName; attribute.started = false; attribute.initialized = false; attribute.finishing = false; attribute.complete = false; attribute.progress = 0; attribute.value = 0; attribute.duration = attribute.duration ?? 0; attribute.durationDone = timeDifference ?? 0; if (attribute?.looping) { attribute.loopDuration = attribute.loopDuration ?? attribute.duration ?? 0; attribute.loopDurationDone = timeDifference % attribute.loopDuration; attribute.loops = attribute.loops ?? 0; attribute.loopsDone = Math.floor( attribute.durationDone / attribute.duration ); attribute.index = attribute.loopsDone % attribute.values.length; attribute.nextIndex = (attribute.loopsDone + 1) % attribute.values.length; if (!attribute.pingPong && attribute.nextIndex === 0) { attribute.index = 0; attribute.nextIndex = 1; } } return attribute; }), complete: false, totalDt: timeDifference, resolve }); if (!this.ticker || !this.ticker.started) { this.start(); } debug(`Added animations to Animation Engine`); }); }, endAnimations(target) { this._animations = this._animations.filter( (animation2) => animation2.origin !== target ); }, start() { debug(`Animation Engine Started`); if (!this.ticker) { this.ticker = new PIXI.Ticker(); this.ticker.add(this.nextFrame.bind(this)); } this.ticker.start(); }, nextFrame() { if (this._animations.length === 0) { debug(`Animation Engine Paused`); this.ticker.stop(); this._startingValues = {}; return; } this._animations.forEach((animation2) => this._animate(animation2)); this._animations = this._animations.filter( (animation2) => !animation2.complete ); this._applyDeltas(); for (const targetId of Object.keys(this._startingValues)) { if (!this._animations.some( (_a) => _a.attributes.some((_b) => _b.targetId === targetId) )) { delete this._startingValues[targetId]; } } }, _startingValues: {}, _applyDeltas() { const deltas = []; for (const animation2 of this._animations) { for (const attribute of animation2.attributes) { if (!attribute.started || attribute.complete) continue; if (attribute.finishing) { attribute.complete = true; } let delta = deltas.find( (delta2) => attribute.targetId === delta2.targetId && attribute.setPropertyName === delta2.setPropertyName ); if (!delta) { delta = { targetId: attribute.targetId, target: attribute.target, getPropertyName: attribute.getPropertyName ?? attribute.propertyName, setPropertyName: attribute.propertyName, value: 0 }; if (attribute.target instanceof PIXI.filters.ColorMatrixFilter) { delta.value = attribute.previousValue; } deltas.push(delta); } delta.value += attribute.delta; } } for (let delta of deltas) { const finalValue = deep_get(delta.target, delta.getPropertyName) + delta.value; try { deep_set(delta.target, delta.setPropertyName, finalValue); } catch (err) { } } }, _animate(animation2) { animation2.totalDt += this.ticker.elapsedMS; animation2.attributes.filter((attribute) => !attribute.complete).forEach( (attribute) => this._animateAttribute(animation2.totalDt, attribute) ); animation2.complete = animation2.attributes.filter((attribute) => !attribute.complete).length === 0; if (animation2.complete) { animation2.resolve(); } }, _animateAttribute(totalDt, attribute) { if (totalDt < attribute.delay) return; if (!attribute.started) { const funkyProperty = attribute.propertyName.includes("scale") || attribute.propertyName.includes("alpha"); if (this._startingValues[attribute.targetId] === void 0 || attribute.absolute) { const getProperty2 = funkyProperty || attribute.from === void 0; this._startingValues[attribute.targetId] = getProperty2 ? deep_get( attribute.target, attribute.getPropertyName ?? attribute.propertyName ) : attribute.from; if (!attribute.propertyName.includes("scale")) { deep_set( attribute.target, attribute.propertyName, this._startingValues[attribute.targetId] ); } } attribute.previousValue = this._startingValues[attribute.targetId]; if (attribute?.looping) { attribute.values = attribute.values.map((value) => { return value + attribute.previousValue - (funkyProperty ? 1 : 0); }); } else if (attribute.from === void 0) { attribute.from = attribute.previousValue; } } attribute.started = true; if (attribute?.looping && attribute?.indefinite) { this._handleIndefiniteLoop(attribute); } else if (attribute?.looping) { this._handleLoops(attribute); } else { this._handleDefault(attribute); } attribute.delta = attribute.value - attribute.previousValue; attribute.previousValue = attribute.value; }, _handleBaseLoop(attribute) { if (!attribute.initialized) { if (attribute.values.length === 1) { attribute.values.unshift( deep_get(attribute.target, attribute.propertyName) ); } attribute.initialized = true; } attribute.loopDurationDone += this.ticker.deltaMS; attribute.progress = attribute.loopDurationDone / attribute.loopDuration; attribute.value = interpolate( attribute.values[attribute.index], attribute.values[attribute.nextIndex], attribute.progress, attribute.ease ); if (attribute.progress >= 1) { attribute.loopDurationDone -= attribute.loopDuration; attribute.index = (attribute.index + 1) % attribute.values.length; attribute.nextIndex = (attribute.nextIndex + 1) % attribute.values.length; if (!attribute.pingPong && attribute.nextIndex === 0) { attribute.index = 0; attribute.nextIndex = 1; } attribute.loopsDone++; attribute.value = interpolate( attribute.values[attribute.index], attribute.values[attribute.nextIndex], attribute.progress % 1, attribute.ease ); } }, _handleIndefiniteLoop(attribute) { return this._handleBaseLoop(attribute); }, _handleLoops(attribute) { this._handleBaseLoop(attribute); attribute.durationDone += this.ticker.deltaMS; attribute.overallProgress = attribute.durationDone / attribute.duration; if (attribute.progress >= 1 && attribute.loopsDone === attribute.loops * 2) { attribute.finishing = true; attribute.value = attribute.values[attribute.index]; } if (attribute.overallProgress >= 1) { attribute.finishing = true; } }, _handleDefault(attribute) { attribute.durationDone += this.ticker.deltaMS; attribute.progress = attribute.durationDone / attribute.duration; attribute.value = interpolate( attribute.from, attribute.to, attribute.progress, attribute.ease ); if (attribute.progress >= 1) { attribute.value = attribute.to; attribute.finishing = true; } } }; class SequencerAudioHelper { /** * Play an audio file. * * @param {{src: string, loop?: boolean, volume?: number, _fadeIn?: {duration: number}, _fadeOut?: {duration: number}, duration?: number}} data The data that describes the audio to play. * @param {boolean} [push=false] A flag indicating whether or not to make other clients play the audio, too. * @returns {Number} A promise that resolves when the audio file has finished playing. */ static async play(data, push = true) { if (push) sequencerSocket.executeForOthers(SOCKET_HANDLERS.PLAY_SOUND, data); return this._play(data); } /** * @param {{src: string, loop?: boolean, volume: number, _fadeIn?: {duration: number}, _fadeOut?: {duration: number}, duration?: number}} data * @returns {Number} * @private */ static async _play(data) { if (!game.settings.get("sequencer", "soundsEnabled") || game.user.viewedScene !== data.sceneId || data?.users?.length && !data?.users?.includes(game.userId)) { return new Promise((resolve) => setTimeout(resolve, data.duration)); } Hooks.callAll("createSequencerSound", data); debug(`Playing sound:`, data); data.volume = (data.volume ?? 0.8) * game.settings.get("core", "globalInterfaceVolume"); const sound = await game.audio.play(data.src, { volume: data.fadeIn ? 0 : data.volume, loop: data.loop, offset: data.startTime }); SequenceManager.RunningSounds.add(data.id, sound); if (data.fadeIn) { SequencerAnimationEngine.addAnimation(data.id, { target: sound, propertyName: "volume", from: 0, to: data.volume, duration: Math.min(data.fadeIn.duration, data.duration), ease: data.fadeIn.ease, delay: Math.min(data.fadeIn.delay, data.duration) }); } if (data.fadeOut) { SequencerAnimationEngine.addAnimation(data.id, { target: sound, propertyName: "volume", from: data.volume, to: 0, duration: Math.min(data.fadeOut.duration, data.duration), ease: data.fadeOut.ease, delay: Math.max( data.duration - data.fadeOut.duration + data.fadeOut.delay, 0 ) }); } if (data.duration) { setTimeout(() => { sound.stop(); }, data.duration); } new Promise((resolve) => { sound.on("stop", resolve); sound.on("end", resolve); }).then(() => { SequenceManager.RunningSounds.delete(data.id); Hooks.callAll("endedSequencerSound", data); }); return data.duration; } static stop(ids, push = true) { if (push && game.user.isGM) sequencerSocket.executeForOthers(SOCKET_HANDLERS.STOP_SOUNDS, ids); return this._stop(ids); } static _stop(ids) { for (const id of ids) { const sound = SequenceManager.RunningSounds.get(id); if (sound) { sound.stop(); } } } } let lockedView = false; class SequencerFoundryReplicator { static registerHooks() { Hooks.on("canvasPan", () => { if (!lockedView) return; canvas.stage.pivot.set(lockedView.x, lockedView.y); canvas.stage.scale.set(lockedView.scale, lockedView.scale); canvas.updateBlur(lockedView.scale); canvas.controls._onCanvasPan(); canvas.hud.align(); }); } static _validateObject(inObject, sceneId) { if (is_UUID(inObject) || !is_object_canvas_data(inObject)) { inObject = get_object_from_scene(inObject, sceneId); } return inObject?._object ?? inObject; } static _getPositionFromData(data) { const source = this._validateObject(data.source, data.sceneId); const position = source instanceof PlaceableObject ? get_object_position(source) : source?.worldPosition || source?.center || source; const multiplier = data.randomOffset; const twister = new MersenneTwister(data.seed); if (source && multiplier) { let randomOffset = get_random_offset( source, multiplier, twister ); position.x -= randomOffset.x; position.y -= randomOffset.y; } let extraOffset = data.offset; if (extraOffset) { let newOffset = { x: extraOffset.x, y: extraOffset.y }; if (extraOffset.gridUnits) { newOffset.x *= canvas.grid.size; newOffset.y *= canvas.grid.size; } if (extraOffset.local) { newOffset = rotateAroundPoint( 0, 0, newOffset.x, newOffset.y, source?.rotation ?? 0 ); } position.x -= newOffset.x; position.y -= newOffset.y; } return position; } static playScrollingText(data, push = true) { if (push) { sequencerSocket.executeForOthers( SOCKET_HANDLERS.CREATE_SCROLLING_TEXT, data ); } return this._playScrollingText(data); } static _playScrollingText(data) { if (game.user.viewedScene !== data.sceneId) return; if (data.users.length && !data.users.includes(game.userId)) return; canvas.interface.createScrollingText( this._getPositionFromData(data), data.content, data.options ); return data.options?.duration ?? 2e3; } static panCanvas(data, push = true) { if (push) { sequencerSocket.executeForOthers(SOCKET_HANDLERS.PAN_CANVAS, data); } return this._panCanvas(data); } static _panCanvas(data) { if (game.user.viewedScene !== data.sceneId) return; if (data.users.length && !data.users.includes(game.userId)) return; if (data.source) { const position = this._getPositionFromData(data); canvas.animatePan({ x: position.x, y: position.y, scale: data.scale, duration: data.duration, speed: data.speed }); if (data.speed) { let ray = new Ray(canvas.stage.pivot, { x: position.x, y: position.y }); data.duration = Math.round(ray.distance * 1e3 / data.speed); } if (data.lockView > 0) { setTimeout(() => { lockedView = { x: position.x, y: position.y, scale: data.scale }; }, data.duration); setTimeout(() => { lockedView = false; }, data.lockView + data.duration); } } else { data.duration = 0; } if (data.shake) { setTimeout(() => { this._shake(data.shake); }, data.duration); } return data.duration + Math.max(data.lockView ?? 0, data.shake?.duration ?? 0); } static _shake(shakeData) { let x = random_float_between(-1, 1); let y = random_float_between(-1, 1); let rot = shakeData.rotation ? random_float_between(-1, 1) : 0; let positions = [{ x, y, rot }]; for (let index = 0; index < Math.floor(shakeData.duration / shakeData.frequency); index++) { x = flip_negate(x, Math.random()); y = flip_negate(y, Math.random()); rot = shakeData.rotation ? flip_negate(rot, Math.random()) : 0; positions.push({ x, y, rot }); } let currentDuration = 0; positions = positions.map((pos) => { let fadeStrength = 1; if (shakeData.fadeInDuration && currentDuration <= shakeData.fadeInDuration) { fadeStrength = Math.max( 0, Math.min(1, currentDuration / shakeData.fadeInDuration) ); } if (shakeData.fadeOutDuration && currentDuration >= shakeData.duration - shakeData.fadeOutDuration) { fadeStrength = Math.max( 0, Math.min( 1, (shakeData.duration - currentDuration) / shakeData.fadeOutDuration ) ); } pos.x *= shakeData.strength * fadeStrength; pos.y *= shakeData.strength * fadeStrength; if (shakeData.rotation) { pos.rot *= shakeData.strength / 7.5 * fadeStrength; } else { pos.rot = 0; } currentDuration += shakeData.frequency; return { transform: `translate(${pos.x}px, ${pos.y}px) rotate(${pos.rot}deg)` }; }); document.getElementById("board").animate(positions, { duration: shakeData.duration }); } } const SOCKET_HANDLERS = { PLAY_EFFECT: "playEffect", END_EFFECTS: "endEffects", UPDATE_EFFECT: "updateEffects", ADD_EFFECT_ANIMATIONS: "addEffectAnimations", PLAY_SOUND: "playSound", STOP_SOUNDS: "stopSounds", PRELOAD: "preload", PRELOAD_RESPONSE: "preloadResponse", PRELOAD_DONE: "preloadDone", UPDATE_DOCUMENT: "updateDocument", ADD_FLAGS: "addFlags", REMOVE_FLAGS: "removeFlags", UPDATE_POSITION: "updatePosition", CREATE_SCROLLING_TEXT: "createScrollingText", PAN_CANVAS: "panCanvas", RUN_SEQUENCE_LOCALLY: "runSequenceLocally" }; let sequencerSocket; function registerSocket() { if (sequencerSocket) return; sequencerSocket = socketlib.registerModule(CONSTANTS.MODULE_NAME); sequencerSocket.register( SOCKET_HANDLERS.PLAY_EFFECT, (...args) => Sequencer.EffectManager._playEffect(...args) ); sequencerSocket.register( SOCKET_HANDLERS.END_EFFECTS, (...args) => Sequencer.EffectManager._endManyEffects(...args) ); sequencerSocket.register( SOCKET_HANDLERS.UPDATE_EFFECT, (...args) => Sequencer.EffectManager._updateEffect(...args) ); sequencerSocket.register( SOCKET_HANDLERS.ADD_EFFECT_ANIMATIONS, (...args) => Sequencer.EffectManager._addEffectAnimations(...args) ); sequencerSocket.register( SOCKET_HANDLERS.PLAY_SOUND, (...args) => SequencerAudioHelper._play(...args) ); sequencerSocket.register( SOCKET_HANDLERS.STOP_SOUNDS, (...args) => SequencerAudioHelper._stop(...args) ); sequencerSocket.register( SOCKET_HANDLERS.PRELOAD, (...args) => Sequencer.Preloader.respond(...args) ); sequencerSocket.register( SOCKET_HANDLERS.PRELOAD_RESPONSE, (...args) => Sequencer.Preloader.handleResponse(...args) ); sequencerSocket.register( SOCKET_HANDLERS.UPDATE_DOCUMENT, (...args) => updateDocument(...args) ); sequencerSocket.register( SOCKET_HANDLERS.ADD_FLAGS, (...args) => flagManager._addFlags(...args) ); sequencerSocket.register( SOCKET_HANDLERS.REMOVE_FLAGS, (...args) => flagManager._removeFlags(...args) ); sequencerSocket.register( SOCKET_HANDLERS.UPDATE_POSITION, (...args) => Sequencer.EffectManager._updatePosition(...args) ); sequencerSocket.register( SOCKET_HANDLERS.CREATE_SCROLLING_TEXT, (data) => SequencerFoundryReplicator._playScrollingText(data) ); sequencerSocket.register( SOCKET_HANDLERS.PAN_CANVAS, (data) => SequencerFoundryReplicator._panCanvas(data) ); sequencerSocket.register(SOCKET_HANDLERS.RUN_SEQUENCE_LOCALLY, (data) => { debug("Playing remote Sequence"); new Sequence().fromJSON(data).play(); }); } async function updateDocument(documentUuid, updates, animate) { const document2 = await fromUuid(documentUuid); return document2.update(updates, animate); } const flagManager = { flagAddBuffer: /* @__PURE__ */ new Map(), flagRemoveBuffer: /* @__PURE__ */ new Map(), _latestFlagVersion: false, get latestFlagVersion() { if (!this._latestFlagVersion) { const versions = Object.keys(this.migrations); versions.sort((a, b) => { return isNewerVersion(a, b) ? -1 : 1; }); this._latestFlagVersion = versions[0]; } return this._latestFlagVersion; }, /** * Sanitizes the effect data, accounting for changes to the structure in previous versions * * @param inDocument * @returns {array} */ getFlags(inDocument) { let effects = getProperty(inDocument, CONSTANTS.EFFECTS_FLAG); if (!effects?.length) return []; effects = foundry.utils.deepClone(effects); const changes = []; for (let [effectId, effectData] of effects) { let effectVersion = effectData?.flagVersion ?? "1.0.0"; if (effectData.flagVersion === this.latestFlagVersion) continue; for (let [version, migration] of Object.entries(this.migrations)) { if (!isNewerVersion(version, effectVersion)) continue; effectData = migration(inDocument, effectData); } debug( `Migrated effect with ID ${effectId} from version ${effectVersion} to version ${this.latestFlagVersion}` ); effectData.flagVersion = this.latestFlagVersion; changes.push(effectData); } if (changes.length) { flagManager.addFlags(inDocument.uuid, changes); } return effects; }, migrations: { "2.0.0": (inDocument, effectData) => { effectData._id = effectData.id; effectData.creationTimestamp = effectData.timestamp; if (effectData.template) { effectData.template = { gridSize: effectData.template[0], startPoint: effectData.template[1], endPoint: effectData.template[2] }; } if (effectData.attachTo) { effectData.attachTo = { active: true, align: "center", rotation: true, bindVisibility: true, bindAlpha: true }; effectData.source = inDocument.uuid; const objectSize = get_object_dimensions(inDocument, true); effectData.offset = { x: effectData.position.x - objectSize.width, y: effectData.position.y - objectSize.height }; } else if (effectData.position) { effectData.source = effectData.position; } if (effectData.reachTowards) { effectData.stretchTo = { attachTo: false, onlyX: false }; } if (effectData.filters) { effectData.filters = Object.entries(effectData.filters).map((entry) => { return { className: entry[0], ...entry[1] }; }); } effectData.moveSpeed = effectData.speed; effectData.target = null; effectData.forcedIndex = null; effectData.flipX = false; effectData.flipY = false; effectData.nameOffsetMap = {}; effectData.sequenceId = 0; delete effectData.id; delete effectData.timestamp; delete effectData.position; delete effectData.reachTowards; delete effectData.speed; delete effectData.audioVolume; delete effectData.gridSizeDifference; delete effectData.template; if (effectData.animatedProperties) { delete effectData.animatedProperties.fadeInAudio; delete effectData.animatedProperties.fadeOutAudio; } effectData = foundry.utils.mergeObject( effectData, effectData.animatedProperties ); delete effectData.animatedProperties; return effectData; }, "2.0.6": (inDocument, effectData) => { effectData.private = null; return effectData; }, "2.0.8": (inDocument, effectData) => { if (effectData.stretchTo) { effectData.stretchTo.tiling = false; } return effectData; }, "2.0.9": (inDocument, effectData) => { effectData.tilingTexture = false; if (effectData.stretchTo?.tiling !== void 0) { if (effectData.stretchTo.tiling) { effectData.tilingTexture = { scale: { x: 1, y: 1 }, position: { x: 0, y: 0 } }; } delete effectData.stretchTo.tiling; } return effectData; }, "2.1.0": (inDocument, effectData) => { if (effectData.randomOffset) { effectData.randomOffset = { source: !effectData.target ? effectData.randomOffset : false, target: !!effectData.target ? effectData.randomOffset : false }; } if (effectData.nameOffsetMap) { Object.values(effectData.nameOffsetMap).forEach((offsetMap) => { if (offsetMap.randomOffset) { offsetMap.randomOffset = { source: !offsetMap.target ? offsetMap.randomOffset : false, target: !!offsetMap.target ? offsetMap.randomOffset : false }; } }); } return effectData; } }, /** * Adds effects to a given document * * @param inObjectUUID * @param inEffects */ addFlags: (inObjectUUID, inEffects) => { if (!Array.isArray(inEffects)) inEffects = [inEffects]; sequencerSocket.executeAsGM( SOCKET_HANDLERS.ADD_FLAGS, inObjectUUID, inEffects ); }, /** * Removes effects from a given document * * @param inObjectUUID * @param inEffects * @param removeAll */ removeFlags: (inObjectUUID, inEffects, removeAll = false) => { sequencerSocket.executeAsGM( SOCKET_HANDLERS.REMOVE_FLAGS, inObjectUUID, inEffects, removeAll ); }, _addFlags: (inObjectUUID, inEffects) => { if (!Array.isArray(inEffects)) inEffects = [inEffects]; let flagsToSet = flagManager.flagAddBuffer.get(inObjectUUID) ?? { effects: [] }; flagsToSet.effects.push(...inEffects); flagManager.flagAddBuffer.set(inObjectUUID, flagsToSet); flagManager.updateFlags(); }, _removeFlags: (inObjectUUID, inEffects, removeAll = false) => { if (inEffects && !Array.isArray(inEffects)) inEffects = [inEffects]; let flagsToSet = flagManager.flagRemoveBuffer.get(inObjectUUID) ?? { effects: [], removeAll }; if (inEffects) flagsToSet.effects.push(...inEffects); flagManager.flagRemoveBuffer.set(inObjectUUID, flagsToSet); flagManager.updateFlags(); }, updateFlags: debounce(async () => { let flagsToAdd = Array.from(flagManager.flagAddBuffer); let flagsToRemove = Array.from(flagManager.flagRemoveBuffer); flagManager.flagAddBuffer.clear(); flagManager.flagRemoveBuffer.clear(); flagsToAdd.forEach((entry) => entry[1].original = true); flagsToRemove.forEach((entry) => entry[1].original = true); const objects = /* @__PURE__ */ new Set([ ...flagsToAdd.map((effect) => effect[0]).filter(Boolean), ...flagsToRemove.map((effect) => effect[0]).filter(Boolean) ]); flagsToAdd = new Map(flagsToAdd); flagsToRemove = new Map(flagsToRemove); const actorUpdates = {}; const sceneObjectsToUpdate = {}; for (let objectUUID of objects) { let object = fromUuidSync(objectUUID); if (!object) { debug( `Failed to set flags on non-existent object with UUID: ${objectUUID}` ); continue; } let toAdd = flagsToAdd.get(objectUUID) ?? { effects: [] }; let toRemove = flagsToRemove.get(objectUUID) ?? { effects: [], removeAll: false }; const existingFlags = new Map( getProperty(object, CONSTANTS.EFFECTS_FLAG) ?? [] ); if (toRemove?.removeAll) { toRemove.effects = Array.from(existingFlags).map((entry) => entry[0]); } for (let effect of toAdd.effects) { if (typeof effect === "string") { effect = existingFlags.get(effect); if (!effect) continue; } existingFlags.set(effect._id, effect); } for (let effect of toRemove.effects) { if (typeof effect === "string") { effect = existingFlags.get(effect); if (!effect) continue; } existingFlags.delete(effect._id); } let flagsToSet = Array.from(existingFlags); const options = {}; const isLinkedToken = object instanceof TokenDocument && object.actorLink; const isLinkedActor = object instanceof Actor && object.prototypeToken.actorLink; if ((isLinkedToken || isLinkedActor) && (toAdd.original || toRemove.original)) { const actor = isLinkedActor ? object : object.actor; actorUpdates[actor.id] = flagsToSet.filter( (effect) => effect[1]?.persistOptions?.persistTokenPrototype ); flagsToSet = flagsToSet.filter( (effect) => !effect[1]?.persistOptions?.persistTokenPrototype ); if (isLinkedToken && game.modules.get("multilevel-tokens")?.active && getProperty(object, "flags.multilevel-tokens.stoken")) { options["mlt_bypass"] = true; } } if (object?.documentName === "Scene") { const sceneId = object.id; sceneObjectsToUpdate[sceneId] = sceneObjectsToUpdate[sceneId] ?? { updates: {}, documents: {} }; sceneObjectsToUpdate[sceneId].updates[CONSTANTS.EFFECTS_FLAG] = flagsToSet; } else if (!(object instanceof Actor)) { const sceneId = object.parent.id; const docName = object.documentName; sceneObjectsToUpdate[sceneId] = sceneObjectsToUpdate[sceneId] ?? { updates: {}, documents: {} }; sceneObjectsToUpdate[sceneId].documents[docName] = sceneObjectsToUpdate[sceneId].documents[docName] ?? { options: {}, updates: [] }; sceneObjectsToUpdate[sceneId].documents[docName].options = options; sceneObjectsToUpdate[sceneId].documents[docName].updates.push({ _id: object.id, [CONSTANTS.EFFECTS_FLAG]: flagsToSet }); } } for (const [sceneId, sceneData] of Object.entries(sceneObjectsToUpdate)) { const scene = game.scenes.get(sceneId); if (!foundry.utils.isEmpty(sceneData.updates)) { await scene.update(sceneData.updates); } for (const [documentType, documentData] of Object.entries( sceneData.documents )) { await scene.updateEmbeddedDocuments( documentType, documentData.updates, documentData.options ); debug( `Flags set for documents of type "${documentType}" in scene with ID "${sceneId}"` ); } } await Actor.updateDocuments( Object.entries(actorUpdates).map(([actorId, effects]) => ({ _id: actorId, [CONSTANTS.EFFECTS_FLAG]: effects })) ); }, 250) }; const PositionContainer = /* @__PURE__ */ new Map(); const TemporaryPositionsContainer = /* @__PURE__ */ new Map(); class SequencerEffectManager { /** * Returns all of the currently running effects on the canvas * * @returns {Array} */ static get effects() { return Array.from(SequenceManager.VisibleEffects.values()); } static _updatePosition(uuid, position) { TemporaryPositionsContainer.set(uuid, position); } static getPositionForUUID(uuid) { return TemporaryPositionsContainer.get(uuid); } /** * Opens the Sequencer Effects UI with the effects tab open */ static show() { return EffectsUIApp.show({ tab: "manager" }); } /** * Play an effect on the canvas. * * @param {object} data The data that describes the audio to play * @param {boolean} [push=true] A flag indicating whether or not to make other clients play the effect * @returns {CanvasEffect} A CanvasEffect object */ static async play(data, push = true) { if (!user_can_do("permissions-effect-create")) { custom_warning( "Sequencer", "EffectManager | play | Players do not have permissions to play effects. This can be configured in Sequencer's module settings." ); return; } if (push) sequencerSocket.executeForOthers(SOCKET_HANDLERS.PLAY_EFFECT, data); if (data?.persistOptions?.persistTokenPrototype) { this._playPrototypeTokenEffects(data, push); } return this._playEffect(data); } /** * Get effects that are playing on the canvas based on a set of filters * * @param {object} inFilter An object containing filters that determine which effects to return * - object: An ID or a PlaceableObject * - name: The name of the effect * - sceneId: the ID of the scene to search within * @returns {Array} An array containing effects that match the given filter */ static getEffects(inFilter = {}) { const filters2 = this._validateFilters(inFilter); if (!inFilter) throw custom_error( "Sequencer", "EffectManager | getEffects | Incorrect or incomplete parameters provided" ); return this._filterEffects(filters2); } /** * Updates effects based on a set of filters * * @param {object} inFilter An object containing filters that determine which effects to return * - object: An ID or a PlaceableObject * - name: The name of the effect * - sceneId: the ID of the scene to search within * - effects: a single CanvasEffect or its ID, or an array of such * @param {object} inUpdates * @returns {promise} */ static updateEffects(inFilter, inUpdates) { inFilter = this._validateFilters(inFilter); if (!inFilter) throw custom_error( "Sequencer", "EffectManager | updateEffects | Incorrect or incomplete parameters provided" ); CanvasEffect.validateUpdate(inUpdates); const effectsToUpdate = this._filterEffects(inFilter).filter( (effect) => effect.userCanUpdate ); return Promise.allSettled( effectsToUpdate.map((effect) => effect.update(inUpdates)) ); } /** * End effects that are playing on the canvas based on a set of filters * * @param {object} inFilter An object containing filters that determine which effects to end * - object: An ID or a PlaceableObject * - name: The name of the effect * - sceneId: the ID of the scene to search within * - effects: a single CanvasEffect or its ID, or an array of such * @param {boolean} [push=true] A flag indicating whether or not to make other clients end the effects * @returns {promise} A promise that resolves when the effects have ended */ static async endEffects(inFilter = {}, push = true) { inFilter = this._validateFilters(inFilter); if (!inFilter) throw custom_error( "Sequencer", "EffectManager | endEffects | Incorrect or incomplete parameters provided" ); const effectsToEnd = this._getEffectsByFilter(inFilter); if (!effectsToEnd.length) return; if (push) sequencerSocket.executeForOthers( SOCKET_HANDLERS.END_EFFECTS, effectsToEnd ); return this._endManyEffects(effectsToEnd); } /** * End all effects that are playing on the canvas * * @param {string} [inSceneId] A parameter which determines which scene to end all effects on, defaults to current viewed scene * @param {boolean} [push=true] A flag indicating whether or not to make other clients end all effects * @returns {promise} A promise that resolves when all of the effects have _ended */ static async endAllEffects(inSceneId = game.user.viewedScene, push = true) { const inFilter = this._validateFilters({ sceneId: inSceneId }); if (!inFilter) throw custom_error( "Sequencer", "EffectManager | endAllEffects | Incorrect or incomplete parameters provided" ); const effectsToEnd = this._getEffectsByFilter(inFilter); if (!effectsToEnd.length) return; if (push) sequencerSocket.executeForOthers( SOCKET_HANDLERS.END_EFFECTS, effectsToEnd ); return this._endManyEffects(effectsToEnd); } static _getEffectsByFilter(inFilter) { return make_array_unique( this._filterEffects(inFilter).filter((effect) => effect.userCanDelete).map((effect) => { return effect.data?.persistOptions?.persistTokenPrototype ? effect.data?.persistOptions?.id ?? effect.id : effect.id; }) ); } /** * If an effect has been named its position will be cached, which can be retrieved with this method * * @param {string} inName * @returns {object|boolean} * @private */ static getEffectPositionByName(inName) { if (!(typeof inName === "string")) throw custom_error( "Sequencer", "EffectManager | getEffectPositionByName | inName must be of type string" ); return PositionContainer.get(inName) ?? false; } /** * Filters the existing effects based on the given filter * * @param inFilter * @returns {array} * @private */ static _filterEffects(inFilter) { if (inFilter.name) { inFilter.name = new RegExp( str_to_search_regex_str(safe_str(inFilter.name)), "gu" ); } let effects = this.effects; if (inFilter.sceneId && inFilter.sceneId !== canvas.scene.id) { effects = get_all_documents_from_scene(inFilter.sceneId).map((doc) => { return getProperty(doc, CONSTANTS.EFFECTS_FLAG); }).filter((flags) => !!flags).map((flags) => { return flags.map((flag) => CanvasEffect.make(flag[1])); }).deepFlatten(); } return effects.filter((effect) => { return (!inFilter.effects || inFilter.effects.includes(effect.id)) && (!inFilter.name || effect.data.name && effect.data.name.match(inFilter.name)?.length) && (!inFilter.source || inFilter.source === effect.data.source) && (!inFilter.target || inFilter.target === effect.data.target) && (!inFilter.origin || inFilter.origin === effect.data.origin); }); } /** * Validates an object actually exists, and gets its UUID * * @param object * @param sceneId * @returns {string} * @private */ static _validateObject(object, sceneId) { if (!(object instanceof foundry.abstract.Document || object instanceof PlaceableObject || typeof object === "string")) { throw custom_error( "Sequencer", "EffectManager | object must be instance of PlaceableObject or of type string" ); } else if (object instanceof PlaceableObject || object instanceof foundry.abstract.Document) { object = get_object_identifier(object?.document ?? object); } else if (typeof object === "string") { const actualObject = get_object_from_scene(object, sceneId); if (!actualObject) { throw custom_error( "Sequencer", `EffectManager | could not find object with ID: ${object}` ); } const uuid = get_object_identifier(actualObject); if (!uuid) { throw custom_error( "Sequencer", `EffectManager | could could not establish identifier of object with ID: ${object}` ); } object = uuid; } return object; } /** * Validates the filter given to any of the above public methods * * @param inFilter * @returns {boolean} * @private */ static _validateFilters(inFilter) { if (inFilter?.sceneId) { if (typeof inFilter.sceneId !== "string") throw custom_error( "Sequencer", "EffectManager | inFilter.sceneId must be of type string" ); if (!game.scenes.get(inFilter.sceneId)) throw custom_error( "Sequencer", "EffectManager | inFilter.sceneId must be a valid scene id (could not find scene)" ); } else { inFilter.sceneId = game.user.viewedScene; } if (inFilter?.object) { inFilter.source = this._validateObject(inFilter.object, inFilter.sceneId); delete inFilter.object; } if (inFilter?.source) { inFilter.source = this._validateObject(inFilter.source, inFilter.sceneId); } if (inFilter?.target) { inFilter.target = this._validateObject(inFilter.target, inFilter.sceneId); } if (inFilter?.name && typeof inFilter?.name !== "string") throw custom_error( "Sequencer", "EffectManager | inFilter.name must be of type string" ); if (inFilter?.origin && typeof inFilter?.origin !== "string") throw custom_error( "Sequencer", "EffectManager | inFilter.origin must be of type string" ); if (inFilter?.effects) { if (!Array.isArray(inFilter.effects)) inFilter.effects = [inFilter.effects]; inFilter.effects = inFilter.effects.map((effect) => { if (!(typeof effect === "string" || effect instanceof CanvasEffect)) throw custom_error( "Sequencer", "EffectManager | collections in inFilter.effects must be of type string or CanvasEffect" ); if (effect instanceof CanvasEffect) return effect.id; return effect; }); } if (!inFilter.name && !inFilter.origin && !inFilter.target && !inFilter.sceneId && !inFilter.effects && !inFilter.origin) return false; return foundry.utils.mergeObject( { effects: false, name: false, source: false, target: false, sceneId: false, origin: false }, inFilter ); } /** * Actually plays the effect on the canvas * * @param data * @param setFlags * @returns {Promise<{duration: Promise, promise: Promise}>} * @private */ static async _playEffect(data, setFlags = true) { const effect = CanvasEffect.make(data); if (data.persist && setFlags && effect.context && effect.owner && !data.temporary && !data.remote) { flagManager.addFlags(effect.context.uuid, effect.data); } if (!effect.shouldPlay) return; const playData = effect.play(); SequenceManager.VisibleEffects.add(effect.id, effect); if (effect.data.name) { effect._ticker.add(() => { if (effect.isDestroyed) return; PositionContainer.set(effect.data.name, { start: effect.sourcePosition, end: effect.targetPosition }); }); } if (data.temporary && effect.owner) { let lastSourcePosition = {}; let lastTargetPosition = {}; effect._ticker.add(() => { if (effect.source && !effect.isSourceDestroyed) { const sourceData = effect.getSourceData(); if (JSON.stringify(sourceData) !== lastSourcePosition) { sequencerSocket.executeForOthers( SOCKET_HANDLERS.UPDATE_POSITION, data.source, sourceData ); lastSourcePosition = JSON.stringify(sourceData); } } if (effect.target && !effect.isTargetDestroyed) { const targetData = effect.getTargetData(); if (JSON.stringify(targetData) !== lastTargetPosition) { sequencerSocket.executeForOthers( SOCKET_HANDLERS.UPDATE_POSITION, data.target, targetData ); lastTargetPosition = JSON.stringify(targetData); } } }); } if (!data.persist) { playData.promise.then(() => this._removeEffect(effect)); } return playData; } /** * Updates a single effect with the given data * * @param inEffectId * @param inUpdates * @returns {promise|boolean} * @private */ static _updateEffect(inEffectId, inUpdates) { const effect = SequenceManager.VisibleEffects.get(inEffectId); if (!effect) return false; return effect._update(inUpdates); } /** * Updates a single effect with new animations * * @param inEffectId * @param inAnimations * @param inLoopingAnimations * @returns {promise|boolean} * @private */ static _addEffectAnimations(inEffectId, inAnimations, inLoopingAnimations) { const effect = SequenceManager.VisibleEffects.get(inEffectId); if (!effect) return false; return effect._addAnimations(inAnimations, inLoopingAnimations); } /** * Sets up persisting effects when the scene is first loaded * * @returns {promise} */ static async initializePersistentEffects() { await this.tearDownPersists(); const allObjects = get_all_documents_from_scene(); allObjects.push(canvas.scene); const docEffectsMap = allObjects.reduce((acc, doc) => { let effects = flagManager.getFlags(doc); effects.forEach((e) => { if (is_UUID(e[1].source) && e[1].source !== doc.uuid) { e[1].delete = true; } }); if (doc instanceof TokenDocument && doc?.actorLink) { const actorEffects = flagManager.getFlags(doc?.actor); actorEffects.forEach((e) => { e[1]._id = randomID(); e[1].source = doc.uuid; e[1].sceneId = doc.parent.id; }); effects = effects.concat(actorEffects); } if (effects.length) { acc[doc.uuid] = effects; } return acc; }, {}); const promises = Object.entries(docEffectsMap).map(([uuid, effects]) => { return this._playEffectMap(effects, fromUuidSync(uuid)); }).flat(); return Promise.all(promises).then(() => { Hooks.callAll("sequencerEffectManagerReady"); }); } /** * Tears down persisting effects when the scene is unloaded */ static tearDownPersists() { return Promise.allSettled( this.effects.map((effect) => { SequenceManager.VisibleEffects.delete(effect.id); return effect.destroy(); }) ); } static setup() { Hooks.on("preCreateToken", this._patchCreationData.bind(this)); Hooks.on("preCreateDrawing", this._patchCreationData.bind(this)); Hooks.on("preCreateTile", this._patchCreationData.bind(this)); Hooks.on("preCreateMeasuredTemplate", this._patchCreationData.bind(this)); Hooks.on("createToken", this._documentCreated.bind(this)); Hooks.on("createDrawing", this._documentCreated.bind(this)); Hooks.on("createTile", this._documentCreated.bind(this)); Hooks.on("createMeasuredTemplate", this._documentCreated.bind(this)); } /** * Patches an object's creation data before it's created so that the effect plays on it correctly * * @param inDocument * @param data * @param options * @returns {*} */ static async _patchCreationData(inDocument, data, options) { const effects = flagManager.getFlags(inDocument); if (!effects?.length) return; const updates = {}; let documentUuid; if (!inDocument._id) { const documentId = randomID(); documentUuid = inDocument.uuid + documentId; updates["_id"] = documentId; options.keepId = true; } else { documentUuid = inDocument.uuid; } updates[CONSTANTS.EFFECTS_FLAG] = this._patchEffectDataForDocument( documentUuid, effects ); return inDocument.updateSource(updates); } static _patchEffectDataForDocument(inDocumentUuid, effects) { return effects.map((effect) => { effect[0] = randomID(); const effectData = effect[1]; effectData._id = effect[0]; if (is_UUID(effectData.source)) { if (effectData.masks.includes(effectData.source)) { const index = effectData.masks.indexOf(effectData.source); effectData.masks[index] = inDocumentUuid; } effectData.source = inDocumentUuid; } effectData.sceneId = inDocumentUuid.split(".")[1]; return effect; }); } /** * Plays the effects of a given document on creation * * @param inDocument * @returns {*} */ static async _documentCreated(inDocument) { let effects = flagManager.getFlags(inDocument); if (inDocument instanceof TokenDocument && inDocument?.actorLink) { let actorEffects = flagManager.getFlags(inDocument.actor); if (actorEffects.length) { actorEffects = this._patchEffectDataForDocument( inDocument.uuid, actorEffects ); } effects = effects.concat(actorEffects); } if (!effects?.length) return; return this._playEffectMap(effects, inDocument); } /** * Plays multiple effects at the same time * * @param inEffects * @param inDocument * @returns {Promise<{duration: Promise, promise: Promise}[]>} * @private */ static _playEffectMap(inEffects, inDocument) { if (inEffects instanceof Map) inEffects = Array.from(inEffects); return Promise.all( inEffects.map((effect) => { if (!CanvasEffect.checkValid(effect[1])) { if (!game.user.isGM) return; custom_warning( `Sequencer`, `Removed effect from ${inDocument.uuid} as it no longer had a valid source or target` ); return flagManager.removeFlags(inDocument.uuid, effect); } return this._playEffect(effect[1], false).then((result) => { if (!result) { debug("Error playing effect"); } }).catch((err) => { debug("Error playing effect:", err); }); }) ); } /** * Ends one or many effects at the same time, returning a promise that resolves once every effect has fully ended * * @param inEffectIds * @returns {Promise} * @private */ static async _endManyEffects(inEffectIds = false) { const actorEffectsToEnd = this.effects.filter((effect) => { return effect.context?.actorLink && inEffectIds.includes(effect.data?.persistOptions?.id); }); const effectsByActorUuid = Object.values( group_by(actorEffectsToEnd, "context.actor.uuid") ); const regularEffectsToEnd = this.effects.filter((effect) => { return inEffectIds.includes(effect.id) || !effect.context?.actorLink && inEffectIds.includes(effect.data?.persistOptions?.id); }); const effectsByContextUuid = Object.values( group_by(regularEffectsToEnd, "context.uuid") ); effectsByContextUuid.forEach((effects) => { effects = effects.filter( (effect) => effect.data.persist && !effect.data.temporary ); if (!effects.length) return; const effectData = effects.map((effect) => effect.data); flagManager.removeFlags( effects[0].context.uuid, effectData, !inEffectIds ); }); effectsByActorUuid.forEach((effects) => { effects = effects.filter( (effect) => effect.data.persist && !effect.data.temporary ); if (!effects.length) return; const effectContext = effects[0].context; const effectData = effects.map((effect) => effect.data); if (!(effectContext instanceof TokenDocument && effectContext.actorLink && effectContext.actor.prototypeToken.actorLink)) { return; } const persistentEffectData = effectData.filter( (data) => data?.persistOptions?.persistTokenPrototype ); if (!persistentEffectData.length) return; const actorEffects = flagManager.getFlags(effectContext.actor); const applicableActorEffects = actorEffects.filter((effect) => { return effect[1]?.persistOptions?.persistTokenPrototype && persistentEffectData.some( (persistentEffect) => persistentEffect.persistOptions.id === effect[1]?.persistOptions?.id ); }).map((e) => e[0]); flagManager.removeFlags( effectContext.actor.uuid, applicableActorEffects, !inEffectIds ); }); const effectsToEnd = effectsByContextUuid.concat(effectsByActorUuid).deepFlatten(); return Promise.allSettled( effectsToEnd.map((effect) => this._removeEffect(effect)) ); } static _effectContextFilter(inUUID, effectData) { return effectData?.source === inUUID || effectData?.target === inUUID || (effectData?.tiedDocuments ?? []).indexOf(inUUID) > -1; } /** * Handles the deletion of objects that effects are attached to * * @param inUUID * @returns {Promise} */ static objectDeleted(inUUID) { const documentsToCheck = game.scenes.filter((scene) => scene.id !== game.user.viewedScene).map((scene) => [scene, ...get_all_documents_from_scene(scene.id)]).deepFlatten(); const documentEffectsToEnd = documentsToCheck.map((obj) => { const objEffects = flagManager.getFlags(obj); const effectsToEnd = objEffects.filter( ([effectId, effectData]) => this._effectContextFilter(inUUID, effectData) ); return { document: obj, effects: effectsToEnd.map((effect) => effect[0]) }; }).filter((obj) => obj.effects.length); const visibleEffectsToEnd = this.effects.filter((effect) => this._effectContextFilter(inUUID, effect.data)).map((e) => e.id); return Promise.allSettled([ this._endManyEffects(visibleEffectsToEnd), ...documentEffectsToEnd.map((obj) => { return flagManager.removeFlags(obj.document.uuid, obj.effects); }) ]); } /** * Removes the effect from the manager and ends it, returning a promise that resolves once the effect has fully _ended * * @param effect * @returns {Promise} * @private */ static _removeEffect(effect) { SequenceManager.VisibleEffects.delete(effect.id); TemporaryPositionsContainer.delete(effect.data.source); TemporaryPositionsContainer.delete(effect.data.target); return effect.endEffect(); } static async _playPrototypeTokenEffects(data, push) { if (!is_UUID(data.source)) return; const object = fromUuidSync(data.source); if (!(object instanceof TokenDocument)) return; const tokenEffectsToPlay = game.scenes.map( (scene) => scene.tokens.filter((token) => { return token.actorLink && token.actor === object.actor && token !== object; }) ).deepFlatten(); for (const tokenDocument of tokenEffectsToPlay) { const duplicatedData = foundry.utils.deepClone(data); duplicatedData._id = randomID(); duplicatedData.sceneId = tokenDocument.uuid.split(".")[1]; duplicatedData.masks = duplicatedData.masks.map((uuid) => uuid.replace(duplicatedData.source, tokenDocument.uuid)).filter((uuid) => uuid.includes(duplicatedData.sceneId)); duplicatedData.source = tokenDocument.uuid; if (CanvasEffect.checkValid(duplicatedData)) { if (push) sequencerSocket.executeForOthers( SOCKET_HANDLERS.PLAY_EFFECT, duplicatedData ); if (duplicatedData.sceneId === game.user.viewedScene) { await this._playEffect(duplicatedData, false); } } } } } class BaseEffectsLayer extends InteractionLayer { static get layerOptions() { return foundry.utils.mergeObject(super.layerOptions, { elevation: 1e8, name: "sequencerEffects" }); } } class SequencerInterfaceLayer extends InteractionLayer { constructor(...args) { super(...args); } static get layerOptions() { return foundry.utils.mergeObject(super.layerOptions, { elevation: 1e8, name: "sequencerInterfaceEffects" }); } deactivate() { super.deactivate(); if (!this.active) return; this._clearChildren(); this.active = false; InteractionManager.tearDown(); } _setup() { if (!this.UIContainer || this.UIContainer._destroyed) { this.UIContainer = new PIXI.Container(); this.UIContainer.sortableChildren = true; this.UIContainer.parentName = "sequencerUIContainer"; this.UIContainer.zIndex = 1e13; this.addChild(this.UIContainer); this.linePoint = this.UIContainer.addChild(new PIXI.Graphics()); this.line = this.UIContainer.addChild(new PIXI.Graphics()); this.lineHead = this.UIContainer.addChild(new PIXI.Graphics()); this.suggestionPoint = this.UIContainer.addChild(new PIXI.Graphics()); this.effectHoverBoxes = this.UIContainer.addChild(new PIXI.Graphics()); this.effectSelectionBorder = this.UIContainer.addChild( new PIXI.Graphics() ); this.effectSourcePosition = this.UIContainer.addChild( new PIXI.Graphics() ); this.effectTargetPosition = this.UIContainer.addChild( new PIXI.Graphics() ); this.suggestionPoint.filters = [new PIXI.filters.AlphaFilter(0.75)]; this.effectSourcePosition.filters = [new PIXI.filters.AlphaFilter(0.75)]; this.effectTargetPosition.filters = [new PIXI.filters.AlphaFilter(0.75)]; this.effectSelectionBorder.zIndex = 1; this.effectSourcePosition.interactive = true; this.effectSourcePosition.on("mousedown", () => { SelectionManager.sourcePointSelected(); }); this.effectTargetPosition.interactive = true; this.effectTargetPosition.on("mousedown", () => { SelectionManager.targetPointSelected(); }); } } async _draw(...args) { } render(...args) { super.render(...args); this._setup(); this._clearChildren(); this._drawHoveredEffectElements(); if (!this.active) return; this._drawLine(); this._drawPoints(); this._drawSelectedEffectElements(); this._drawSuggestionPoint(); } _clearChildren() { if (!this.UIContainer) return; this.UIContainer.children.forEach((child) => { child.clear(); }); } _drawLine() { if (!EffectPlayer.startPos || !EffectPlayer.endPos || game?.activeTool !== "play-effect") return; this.line.lineStyle(3, CONSTANTS.COLOR.PRIMARY, 1); this.line.moveTo(EffectPlayer.startPos.x, EffectPlayer.startPos.y); this.line.lineTo(EffectPlayer.endPos.x, EffectPlayer.endPos.y); } _drawPoints() { if (game?.activeTool !== "play-effect") return; const startPos = EffectPlayer.startPos || EffectPlayer.cursorPos; this.linePoint.beginFill(CONSTANTS.COLOR.PRIMARY); this.linePoint.drawCircle(startPos.x, startPos.y, 5); if (EffectPlayer.sourceAttachFound) { this._drawCrossAtLocation(this.linePoint, startPos); } if (!EffectPlayer.endPos) return; const angle = new Ray(startPos, EffectPlayer.endPos).angle; this.lineHead.beginFill(CONSTANTS.COLOR.PRIMARY); this.lineHead.moveTo(0, -5); this.lineHead.lineTo(-15, 30); this.lineHead.lineTo(15, 30); this.lineHead.endFill(); this.lineHead.rotation = angle + Math.PI / 2; this.lineHead.position.set(EffectPlayer.endPos.x, EffectPlayer.endPos.y); if (EffectPlayer.targetAttachFound) { this.linePoint.beginFill(CONSTANTS.COLOR.SECONDARY); this._drawCrossAtLocation(this.linePoint, EffectPlayer.endPos); } } _drawHoveredEffectElements() { const effects = new Set(SelectionManager.hoveredEffects); if (SelectionManager.hoveredEffectUI) effects.add(SelectionManager.hoveredEffectUI); for (const effect of effects) { if (!effect || effect === SelectionManager.selectedEffect || effect.data.screenSpace || effect._isEnding) continue; this._drawBoxAroundEffect(this.effectHoverBoxes, effect); } } _drawSelectedEffectElements() { if (!SelectionManager.selectedEffect) return; this._drawBoxAroundEffect( this.effectSelectionBorder, SelectionManager.selectedEffect, true ); this._drawEffectStartEndPoints(SelectionManager.selectedEffect); } _drawBoxAroundEffect(graphic, effect, selected = false) { if (!effect || effect._destroyed || !effect.spriteContainer || !effect.ready) return; graphic.lineStyle(3, selected ? CONSTANTS.COLOR.PRIMARY : 16777215, 0.9); let boundingBox = effect.sprite.getLocalBounds(); let dimensions = { x: effect.position.x + boundingBox.x * effect.sprite.scale.x, y: effect.position.y + boundingBox.y * effect.sprite.scale.y, width: boundingBox.width * effect.sprite.scale.x, height: boundingBox.height * effect.sprite.scale.y }; if (effect.data.shapes.length) { for (const shape of Object.values(effect.shapes)) { boundingBox = shape.getLocalBounds(); dimensions = { x: Math.min( dimensions.x, effect.position.x + boundingBox.x * shape.scale.x ), y: Math.min( dimensions.y, effect.position.y + boundingBox.y * shape.scale.y ), width: Math.max(dimensions.width, boundingBox.width * shape.scale.x), height: Math.max( dimensions.height, boundingBox.height * shape.scale.y ) }; } } const rotation2 = Math.normalizeRadians( effect.rotationContainer.rotation + effect.spriteContainer.rotation + effect.sprite.rotation ); this._drawRectangle(graphic, effect.position, rotation2, dimensions); } _drawRectangle(graphic, position, rotation2, dimensions) { graphic.moveTo( ...rotate_coordinate( position, { x: dimensions.x, y: dimensions.y }, -rotation2 ) ); graphic.lineTo( ...rotate_coordinate( position, { x: dimensions.x + dimensions.width, y: dimensions.y }, -rotation2 ) ); graphic.lineTo( ...rotate_coordinate( position, { x: dimensions.x + dimensions.width, y: dimensions.y + dimensions.height }, -rotation2 ) ); graphic.lineTo( ...rotate_coordinate( position, { x: dimensions.x, y: dimensions.y + dimensions.height }, -rotation2 ) ); graphic.lineTo( ...rotate_coordinate( position, { x: dimensions.x, y: dimensions.y }, -rotation2 ) ); graphic.lineTo( ...rotate_coordinate( position, { x: dimensions.x + dimensions.width, y: dimensions.y }, -rotation2 ) ); } /** * Draws the start/end point circles * @private */ _drawEffectStartEndPoints(effect) { if (!effect || effect._destroyed || !effect.spriteContainer) return; if (!effect.data.stretchTo || !effect.sourcePosition || !effect.targetPosition) return; this.effectSourcePosition.beginFill(CONSTANTS.COLOR.PRIMARY); this.effectSourcePosition.drawCircle( effect.sourcePosition.x, effect.sourcePosition.y, canvas.grid.size * 0.25 ); if (typeof effect.data.source === "string") { this._drawCrossAtLocation( this.effectSourcePosition, effect.sourcePosition ); } this.effectTargetPosition.beginFill(CONSTANTS.COLOR.SECONDARY); this.effectTargetPosition.drawCircle( effect.targetPosition.x, effect.targetPosition.y, canvas.grid.size * 0.25 ); this.effectTargetPosition.alpha = 0.75; if (typeof effect.data.target === "string") { this._drawCrossAtLocation( this.effectTargetPosition, effect.targetPosition ); } } _drawSuggestionPoint() { if (!SelectionManager.suggestedProperties || !SelectionManager.selectedEffect) return; const effect = SelectionManager.selectedEffect; const suggestion = SelectionManager.suggestedProperties; this.suggestionPoint.position.set(0, 0); this.suggestionPoint.rotation = 0; if (effect.data.stretchTo) { this.suggestionPoint.beginFill(suggestion.color); this.suggestionPoint.drawCircle( suggestion.position.x, suggestion.position.y, canvas.grid.size * 0.25 ); if (suggestion.showCursor) { this._drawCrossAtLocation(this.suggestionPoint, suggestion.position); } return; } const boundingBox = effect.spriteContainer.getLocalBounds(); const dimensions = { x: boundingBox.x * effect.scale.x, y: boundingBox.y * effect.scale.y, width: boundingBox.width * effect.scale.x, height: boundingBox.height * effect.scale.y }; this.suggestionPoint.lineStyle(3, CONSTANTS.COLOR.PRIMARY, 0.9); this.suggestionPoint.position.set( suggestion.position.x, suggestion.position.y ); this._drawRectangle( this.suggestionPoint, suggestion.position, effect.rotation, dimensions, true ); if (suggestion.showCursor) { this.suggestionPoint.beginFill(CONSTANTS.COLOR.SECONDARY); this._drawCrossAtLocation(this.suggestionPoint); } if (suggestion.showPoint) { this.suggestionPoint.drawCircle(0, 0, canvas.grid.size * 0.2); } } _drawCrossAtLocation(inElement, inPosition = { x: 0, y: 0 }) { inElement.drawRect( inPosition.x + canvas.grid.size * -0.05, inPosition.y + canvas.grid.size * -0.5, canvas.grid.size * 0.1, canvas.grid.size ); inElement.drawRect( inPosition.x + canvas.grid.size * -0.5, inPosition.y + canvas.grid.size * -0.05, canvas.grid.size, canvas.grid.size * 0.1 ); } } class UIEffectsLayer extends InteractionLayer { static get layerOptions() { return foundry.utils.mergeObject(super.layerOptions, { zIndex: 999999999999999, name: "sequencerEffectsAboveEverything" }); } updateTransform() { if (this.sortableChildren && this.sortDirty) { this.sortChildren(); } this._boundsID++; this.transform.updateTransform(PIXI.Transform.IDENTITY); this.worldAlpha = this.alpha; for (let child of this.children) { if (child.visible) { child.updateTransform(); } } } } let layer = false; class SequencerAboveUILayer { constructor(name, zIndex = 0.1) { this.canvas = document.createElement("canvas"); this.canvas.id = name; this.canvas.style.cssText = ` position:absolute; touch-action: none; pointer-events: none; width:100%; height:100%; z-index:${zIndex}; padding: 0; margin: 0; `; document.body.appendChild(this.canvas); this.app = new PIXI.Application({ width: window.innerWidth, height: window.innerHeight, view: this.canvas, antialias: true, backgroundAlpha: 0, sharedTicker: true }); this.app.resizeTo = window; this.app.stage.renderable = false; } static setup() { if (!game.settings.get("sequencer", "enable-above-ui-screenspace")) return; layer = new this("sequencerUILayerAbove", 1e4); } static getLayer() { return layer ? layer.app.stage : canvas.uiEffectsLayer; } static addChild(...args) { const result = this.getLayer().addChild(...args); layer.app.stage.renderable = layer.app.stage.children.length > 0; return result; } static sortChildren() { return this.getLayer().sortChildren(); } static removeContainerByEffect(inEffect) { const child = this.getLayer().children.find((child2) => child2 === inEffect); if (!child) return; this.getLayer().removeChild(child); layer.app.stage.renderable = layer.app.stage.children.length > 0; } updateTransform() { if (this.app.stage.sortableChildren && this.app.stage.sortDirty) { this.app.stage.sortChildren(); } this.app.stage._boundsID++; this.app.stage.transform.updateTransform(PIXI.Transform.IDENTITY); this.app.stage.worldAlpha = this.app.stage.alpha; for (let child of this.app.stage.children) { if (child.visible) { child.updateTransform(); } } } } class VisionSamplerShader extends BaseSamplerShader { /** @override */ static classPluginName = null; /** @inheritdoc */ static vertexShader = ` precision ${PIXI.settings.PRECISION_VERTEX} float; attribute vec2 aVertexPosition; attribute vec2 aTextureCoord; uniform mat3 projectionMatrix; uniform vec2 screenDimensions; varying vec2 vUvsMask; varying vec2 vUvs; void main() { vUvs = aTextureCoord; vUvsMask = aVertexPosition / screenDimensions; gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); } `; /** @inheritdoc */ static fragmentShader = ` precision ${PIXI.settings.PRECISION_FRAGMENT} float; varying vec2 vUvs; varying vec2 vUvsMask; uniform vec4 tintAlpha; uniform sampler2D sampler; uniform sampler2D maskSampler; uniform bool enableVisionMasking; void main() { float mask = enableVisionMasking ? texture2D(maskSampler, vUvsMask).r : 1.0; gl_FragColor = texture2D(sampler, vUvs) * tintAlpha * mask; } `; /** @inheritdoc */ static defaultUniforms = { tintAlpha: [1, 1, 1, 1], sampler: 0, maskSampler: 0, screenDimensions: [1, 1], enableVisionMasking: false }; /** @override */ _preRender(mesh) { super._preRender(mesh); this.uniforms.maskSampler = canvas.masks.vision.renderTexture; this.uniforms.screenDimensions = canvas.screenDimensions; this.uniforms.enableVisionMasking = canvas.effects.visibility.visible; } } class MaskFilter extends AbstractBaseFilter { /** @override */ static fragmentShader = ` varying vec2 vTextureCoord; uniform sampler2D uSampler; uniform sampler2D uMaskSampler; void main(void) { gl_FragColor = texture2D(uSampler, vTextureCoord) * texture2D(uMaskSampler, vTextureCoord).a; }`; /** @override */ static defaultUniforms = { uMaskSampler: null }; /** @type {DisplayObject[]|null} */ masks = []; /** @override */ apply(filterManager, input, output, clearMode, currentState) { const maskFilterTexture = filterManager.getFilterTexture(); const originalFilterTexture = this.#push( filterManager, currentState, maskFilterTexture ); const renderer = filterManager.renderer; for (const mask of this.masks) { if (mask?.obj?.destroyed) continue; const renderable = mask.renderable; mask.renderable = true; mask.render(renderer); mask.renderable = renderable; } renderer.batch.flush(); this.#pop(filterManager, currentState, originalFilterTexture); this.uniforms.uMaskSampler = maskFilterTexture; filterManager.applyFilter(this, input, output, clearMode); filterManager.returnFilterTexture(maskFilterTexture); } #push(filterManager, currentState, maskFilterTexture) { const originalFilterTexture = currentState.renderTexture; currentState.renderTexture = maskFilterTexture; filterManager.defaultFilterStack.push(currentState); filterManager.bindAndClear(maskFilterTexture); return originalFilterTexture; } #pop(filterManager, currentState, originalFilterTexture) { currentState.renderTexture = originalFilterTexture; filterManager.defaultFilterStack.pop(); if (filterManager.activeState === currentState) { return; } filterManager.activeState = currentState; const globalUniforms = filterManager.globalUniforms.uniforms; globalUniforms.outputFrame = currentState.sourceFrame; globalUniforms.resolution = currentState.resolution; const inputSize = globalUniforms.inputSize; const inputPixel = globalUniforms.inputPixel; const inputClamp = globalUniforms.inputClamp; inputSize[0] = currentState.destinationFrame.width; inputSize[1] = currentState.destinationFrame.height; inputSize[2] = 1 / inputSize[0]; inputSize[3] = 1 / inputSize[1]; inputPixel[0] = Math.round(inputSize[0] * currentState.resolution); inputPixel[1] = Math.round(inputSize[1] * currentState.resolution); inputPixel[2] = 1 / inputPixel[0]; inputPixel[3] = 1 / inputPixel[1]; inputClamp[0] = 0.5 * inputPixel[2]; inputClamp[1] = 0.5 * inputPixel[3]; inputClamp[2] = currentState.sourceFrame.width * inputSize[2] - 0.5 * inputPixel[2]; inputClamp[3] = currentState.sourceFrame.height * inputSize[3] - 0.5 * inputPixel[3]; if (currentState.legacy) { const filterArea = globalUniforms.filterArea; filterArea[0] = currentState.destinationFrame.width; filterArea[1] = currentState.destinationFrame.height; filterArea[2] = currentState.sourceFrame.x; filterArea[3] = currentState.sourceFrame.y; globalUniforms.filterClamp = globalUniforms.inputClamp; } filterManager.globalUniforms.update(); } } const hooksManager = { _hooks: /* @__PURE__ */ new Map(), _hooksRegistered: /* @__PURE__ */ new Set(), addHook(effectUuid, hookName, callable, callNow = false) { if (!this._hooksRegistered.has(hookName)) { debug("registering hook for: " + hookName); this._hooksRegistered.add(hookName); Hooks.on(hookName, (...args) => { this._hookCalled(hookName, ...args); }); } const key = hookName + "-" + effectUuid; if (!this._hooks.has(key)) { this._hooks.set(key, []); } this._hooks.get(key).push(callable); if (callNow) { setTimeout(() => { callable(); }, 20); } }, _hookCalled(hookName, ...args) { Array.from(this._hooks).filter((entry) => entry[0].startsWith(hookName + "-")).map((hooks) => hooks[1]).deepFlatten().forEach((callback) => callback(...args)); }, removeHooks(effectUuid) { Array.from(this._hooks).filter((entry) => entry[0].endsWith("-" + effectUuid)).forEach((entry) => this._hooks.delete(entry[0])); } }; class CanvasEffect extends PIXI.Container { #elevation = 0; #sort = 0; constructor(inData) { super(); this.sortableChildren = true; this.actualCreationTime = +new Date(); this.data = inData; this._resolve = null; this._durationResolve = null; this.ready = false; this._ended = false; this._isEnding = false; this._cachedSourceData = {}; this._cachedTargetData = {}; this.uuid = false; } static get protectedValues() { return [ "_id", "sequenceId", "creationTimestamp", "creatorUserId", "moduleName", "index", "repetition", "moves", "fadeIn", "fadeOut", "scaleIn", "scaleOut", "rotateIn", "rotateOut", "fadeInAudio", "fadeOutAudio", "animations", "nameOffsetMap", "persist" ]; } /** @type {number} */ get elevation() { return this.#elevation; } set elevation(value) { this.#elevation = value; } /** @type {number} */ get sort() { return this.#sort; } set sort(value) { this.#sort = value; } get context() { return this.data.attachTo?.active && this.sourceDocument ? this.sourceDocument : game.scenes.get(this.data.sceneId); } get isIsometricActive() { const sceneIsIsometric = getProperty( game.scenes.get(this.data.sceneId), CONSTANTS.INTEGRATIONS.ISOMETRIC.SCENE_ENABLED ); return CONSTANTS.INTEGRATIONS.ISOMETRIC.ACTIVE && sceneIsIsometric; } /** * The ID of the effect * * @returns {string} */ get id() { return this.data._id; } /** * Whether this effect is destroyed or is in the process of being destroyed */ get isDestroyed() { return this.source && this.isSourceDestroyed || this.target && this.isTargetDestroyed; } /** * Whether the source of this effect is temporary * * @returns {boolean} */ get isSourceTemporary() { return this.data.attachTo?.active && this.sourceDocument && !is_UUID(this.sourceDocument?.uuid); } /** * Whether the source of this effect has been destroyed * * @returns {boolean} */ get isSourceDestroyed() { return this.source && (this.source?.destroyed || !this.sourceDocument?.object); } /** * Whether the target of this effect is temporary * * @returns {boolean} */ get isTargetTemporary() { return (this.data.stretchTo?.attachTo || this.data.rotateTowards?.attachTo) && this.targetDocument && !is_UUID(this.targetDocument.uuid); } /** * Whether the target of this effect has been destroyed * * @returns {boolean} */ get isTargetDestroyed() { return this.target && (this.target?.destroyed || !this.targetDocument?.object); } /** * The source object (or source location) of the effect * * @returns {boolean|object} */ get source() { if (!this._source && this.data.source) { this._source = this._getObjectByID(this.data.source); this._source = this._source?._object ?? this._source; } return this._source; } /** * Retrieves the source document * * @returns {Document|PlaceableObject} */ get sourceDocument() { return this.source?.document ?? this.source; } /** * Retrieves the PIXI object for the source object * * @returns {*|PIXI.Sprite|TileHUD} */ get sourceMesh() { return this.source?.mesh ?? this.source?.template; } /** * The source position with the relevant offsets calculated * * @returns {{x: number, y: number}} */ get sourcePosition() { let position = this.getSourceData().position; let offset2 = this._getOffset(this.data.source, true); return { x: position.x - offset2.x, y: position.y - offset2.y }; } /** * The target object (or target location) of the effect * * @returns {boolean|object} */ get target() { if (!this._target && this.data.target) { this._target = this._getObjectByID(this.data.target); this._target = this._target?._object ?? this._target; } return this._target; } /** * Retrieves the document of the target * * @returns {Document|PlaceableObject} */ get targetDocument() { return this.target?.document ?? this.target; } /** * Retrieves the PIXI object for the target object * * @returns {*|PIXI.Sprite|TileHUD} */ get targetMesh() { return this.target?.mesh ?? this.target?.template; } /** * The target position with the relevant offsets calculated * * @returns {{x: number, y: number}} */ get targetPosition() { const position = this.getTargetData().position; const offset2 = this._getOffset(this.data.target); return { x: position.x - offset2.x, y: position.y - offset2.y }; } /** * Returns this effect's world position * * @returns {{x: number, y: number}} */ get worldPosition() { const t = canvas.stage.worldTransform; return { x: (this.sprite.worldTransform.tx - t.tx) / canvas.stage.scale.x, y: (this.sprite.worldTransform.ty - t.ty) / canvas.stage.scale.y }; } /** * Whether the current user is the owner of this effect * * @returns {boolean} */ get owner() { return this.data.creatorUserId === game.user.id; } /** * Whether the current user can update this effect * * @returns {boolean} */ get userCanUpdate() { return game.user.isGM || this.owner || this.data.attachTo?.active && this.sourceDocument.canUserModify(game.user, "update"); } /** * Whether the current user can delete this effect * * @returns {boolean} */ get userCanDelete() { return this.userCanUpdate || user_can_do("permissions-effect-delete"); } /** * Whether this effect is on the current scene * * @returns {boolean} */ get onCurrentScene() { return this.data.sceneId === game.user.viewedScene; } /** * Whether this effect should be shown as faded or not - effects created by users for other users should be shown * for all * * @returns {boolean} */ get shouldShowFadedVersion() { return this.data.users && this.data.users.length && !(this.data.users.length === 1 && this.data.users.includes(this.data.creatorUserId)) && !this.data.users.includes(game.userId); } /** * Getter for the current playing video of the effect * * @returns {null|*} */ get video() { return this._video; } /** * Setter for the current playing video of the effect */ set video(inVideo) { if (!inVideo) return; inVideo.playbackRate = this.data.playbackRate ? this.data.playbackRate : 1; inVideo.muted = !this.data.volume; inVideo.volume = (this.data.volume ?? 0) * game.settings.get("core", "globalInterfaceVolume"); if (!this._video) { this._video = inVideo; return; } const isLooping = this._video?.loop; const currentTime = this._video.currentTime; this._video = inVideo; this._video.currentTime = this.playNaturally ? 0 : Math.min(currentTime, this._video.duration); this._video.loop = isLooping; this.updateTexture(); } async playMedia() { if (this.animatedSprite) { await this.sprite.play(); } else if (this.video) { try { await this.video.play().then(() => { this.updateTexture(); }); } catch (err) { } } this._setupTimestampHook(this.mediaCurrentTime * 1e3); } updateTexture() { if (this._texture.valid) { this._texture.update(); } } async pauseMedia() { if (this.animatedSprite) { return this.sprite.stop(); } else if (this.video) { return this.video.pause(); } } get mediaLooping() { if (this.animatedSprite) { return this.sprite.loop; } return this.video?.loop ?? false; } set mediaLooping(looping) { if (this.animatedSprite) { this.sprite.loop = looping; return; } if (this.video) { this.video.loop = looping; } } get mediaIsPlaying() { if (this.animatedSprite) { return this.sprite.playing; } return this.video; } get mediaCurrentTime() { if (this.animatedSprite) { return this.sprite.currentFrame / this.sprite.totalFrames * (this.sprite.totalFrames / 24); } return this.video?.currentTime ?? null; } get mediaPlaybackRate() { if (this.animatedSprite) { return this.sprite.animationSpeed; } else if (this.video) { return this.video.playbackRate; } } set mediaPlaybackRate(inPlaybackRate) { if (this.animatedSprite) { this.sprite.animationSpeed = 0.4 * inPlaybackRate; } else if (this.video) { this.video.playbackRate = inPlaybackRate; } } set mediaCurrentTime(newTime) { if (this.animatedSprite) { const newFrame = Math.floor(newTime * this.sprite.totalFrames); const clampedFrame = Math.max( 0, Math.min(newFrame, this.sprite.totalFrames) ); if (this.mediaIsPlaying) { this.sprite.gotoAndPlay(clampedFrame); } else { this.sprite.gotoAndStop(clampedFrame); } } else if (this.video) { this.video.currentTime = newTime; } } get mediaDuration() { if (this.animatedSprite) { return this.sprite.totalFrames / this.sprite.animationSpeed / PIXI.Ticker.shared.FPS; } else if (this.video) { return this.video?.duration / this.mediaPlaybackRate; } return 1; } get hasAnimatedMedia() { return !!(this.video || this.animatedSprite); } /** * The template of the effect, determining the effect's internal grid size, and start/end padding * * @returns {object} */ get template() { return foundry.utils.mergeObject( { gridSize: 100, startPoint: 0, endPoint: 0 }, this._template ?? {} ); } /** * The grid size difference between the internal effect's grid vs the grid on the canvas. If the effect is in screen space, we ignore this. * * @returns {number} */ get gridSizeDifference() { return canvas.grid.size / this.template.gridSize; } /** * Whether the effect should be flipped on any given axis * * @returns {number} */ get flipX() { return this.data.flipX ? -1 : 1; } get flipY() { return this.data.flipY ? -1 : 1; } /** * Whether this effect should play at all, depending on a multitude of factors * * @returns {boolean} */ get shouldPlay() { return (game.user.viewedScene === this.data.sceneId || this.data.creatorUserId === game.userId) && (game.user.isGM || !this.data.users || this.data.users.length === 0 || this.data.users.includes(game.userId)); } get shouldPlayVisible() { let playVisible = this.shouldPlay && game.settings.get("sequencer", "effectsEnabled") && game.user.viewedScene === this.data.sceneId; if (isNewerVersion(game.version, "10.289") && game.settings.get("core", "photosensitiveMode")) { playVisible = false; throttled_custom_warning( this.data.moduleName, "Photosensitive Mode is turned on, so Sequencer's visual effects aren't being rendered" ); } return playVisible; } /** * Whether this effect should play naturally, or be constrained to a subsection of the video * * @returns {boolean} */ get playNaturally() { return (!this.data.time || this._startTime === 0 && this._endTime === this.mediaDuration) && this._animationTimes.loopStart === void 0 && this._animationTimes.loopEnd === void 0; } static make(inData) { return !inData.persist ? new CanvasEffect(inData) : new PersistentCanvasEffect(inData); } static checkValid(effectData) { if (effectData.delete) { return false; } let sourceExists = true; let targetExists = true; if (effectData.source && is_UUID(effectData.source)) { sourceExists = fromUuidSync(effectData.source); } if (effectData.target && is_UUID(effectData.target)) { targetExists = fromUuidSync(effectData.target); } for (let tiedDocumentUuid of effectData?.tiedDocuments ?? []) { if (tiedDocumentUuid && is_UUID(tiedDocumentUuid)) { let tiedDocumentExists = fromUuidSync(tiedDocumentUuid); if (!tiedDocumentExists) return false; } } if (effectData.source && is_UUID(effectData.source) && effectData.target && is_UUID(effectData.target)) { const sourceScene = effectData.source.split(".")[1]; const targetScene = effectData.target.split(".")[1]; if (sourceScene !== targetScene || sourceScene !== effectData.sceneId) return false; } return sourceExists && targetExists; } /** * Validates that the update contains the appropriate data * * @param inUpdates */ static validateUpdate(inUpdates) { const updateKeys = Object.keys(inUpdates); const protectedValues = updateKeys.filter( (key) => CanvasEffect.protectedValues.includes(key) ); if (protectedValues.length) { throw custom_error( "Sequencer", `CanvasEffect | update | You cannot update the following keys of an effect's data: ${protectedValues.join( "\n - " )}` ); } if (updateKeys.includes("source")) { if (!(is_UUID(inUpdates.source) || is_object_canvas_data(inUpdates.source))) { throw custom_error( "Sequencer", `CanvasEffect | update | source must be of type document UUID or object with X and Y coordinates` ); } } if (updateKeys.includes("target")) { if (!(is_UUID(inUpdates.target) || is_object_canvas_data(inUpdates.target))) { throw custom_error( "Sequencer", `CanvasEffect | update | target must be of type document UUID or object with X and Y coordinates` ); } } } getHook(type, uuid) { if (!is_UUID(uuid)) return false; const parts = uuid.split("."); return type + parts[parts.length - 2]; } /** * Gets the source hook name * * @param {string} type * @returns {string|boolean} */ getSourceHook(type = "") { return this.getHook(type, this.data.source); } /** * The source object's current position, or its current position * * @returns {boolean|object} */ getSourceData() { if (this.data.temporary && !this.owner) { return SequencerEffectManager.getPositionForUUID(this.data.source); } const position = this.source instanceof PlaceableObject && !this.isSourceTemporary ? get_object_position(this.source) : this.source?.worldPosition || this.source?.center || this.source; const { width: width2, height } = get_object_dimensions(this.source); if (this.isIsometricActive && this.source instanceof PlaceableObject) { position.x += (this.sourceDocument.elevation ?? 0) / canvas.scene.grid.distance * canvas.grid.size; position.y -= (this.sourceDocument.elevation ?? 0) / canvas.scene.grid.distance * canvas.grid.size; if (this.data.isometric?.overlay || this.target instanceof PlaceableObject) { position.x += (this.source?.height ?? height) / 2; position.y -= (this.source?.height ?? height) / 2; } } if (position !== void 0) { this._cachedSourceData.position = position; } if (width2 !== void 0 && height !== void 0) { this._cachedSourceData.width = width2; this._cachedSourceData.height = height; } let rotation2 = 0; if (this.source instanceof MeasuredTemplate && this.sourceDocument?.t !== "rect") { rotation2 = Math.normalizeRadians( Math.toRadians(this.sourceDocument?.direction) ); } else if (!(this.source instanceof MeasuredTemplate)) { rotation2 = this.sourceDocument?.rotation ? Math.normalizeRadians(Math.toRadians(this.sourceDocument?.rotation)) : 0; } if (rotation2 !== void 0) { this._cachedSourceData.rotation = rotation2; } const alpha = this.sourceDocument instanceof TokenDocument || this.sourceDocument instanceof TileDocument ? this.sourceDocument?.alpha ?? 1 : 1; if (alpha !== void 0) { this._cachedSourceData.alpha = alpha; } return { ...this._cachedSourceData }; } /** * Gets the target hook name * * @param {string} type * @returns {string|boolean} */ getTargetHook(type = "") { return this.getHook(type, this.data.target); } /** * The target object's current position, or its current position * * @returns {boolean|object} */ getTargetData() { if (this.data.temporary && !this.owner) { return SequencerEffectManager.getPositionForUUID(this.data.target) ?? this.getSourceData(); } const position = this.target instanceof PlaceableObject && !this.isTargetTemporary ? get_object_position(this.target, { measure: true }) : this.target?.worldPosition || this.target?.center || this.target; const { width: width2, height } = get_object_dimensions(this.target); if (this.isIsometricActive && this.target instanceof PlaceableObject) { const targetHeight = this.target?.height ?? height; position.x += (this.targetDocument.elevation ?? 0) / canvas.scene.grid.distance * canvas.grid.size + targetHeight; position.y -= (this.targetDocument.elevation ?? 0) / canvas.scene.grid.distance * canvas.grid.size + targetHeight; } if (width2 !== void 0 && height !== void 0) { this._cachedTargetData.width = width2; this._cachedTargetData.height = height; } if (position !== void 0) { this._cachedTargetData.position = position; } let rotation2 = 0; if (this.target instanceof MeasuredTemplate && this.targetDocument?.t !== "rect") { rotation2 = Math.normalizeRadians( Math.toRadians(this.targetDocument?.direction) ); } else if (!(this.target instanceof MeasuredTemplate)) { rotation2 = this.targetDocument?.rotation ? Math.normalizeRadians(Math.toRadians(this.targetDocument?.rotation)) : 0; } if (rotation2 !== void 0) { this._cachedTargetData.rotation = rotation2; } const alpha = this.targetDocument instanceof TokenDocument || this.targetDocument instanceof TileDocument ? this.targetDocument?.alpha ?? 1 : 1; if (alpha !== void 0) { this._cachedTargetData.alpha = alpha; } return { ...this._cachedTargetData }; } /** * Calculates the offset for a given offset property and name mapping * * @param {string} offsetMapName * @param {boolean} source * @returns {{x: number, y: number}|*} * @private */ _getOffset(offsetMapName, source = false) { const key = source ? "source" : "target"; const offset2 = { x: 0, y: 0 }; let twister = this._twister; let nameOffsetMap = this._nameOffsetMap?.[this.data.name]; if (nameOffsetMap) { twister = nameOffsetMap.twister; } if (this.data.missed && (!source || !this.data.target)) { let missedOffset = this._offsetCache[key]?.missedOffset || calculate_missed_position(this.source, this.target, twister); this._offsetCache[key].missedOffset = missedOffset; offset2.x -= missedOffset.x; offset2.y -= missedOffset.y; } const obj = source ? this.source : this.target; const multiplier = source ? this.data.randomOffset?.source : this.data.randomOffset?.target; if (obj && multiplier) { let randomOffset = this._offsetCache[key]?.randomOffset || get_random_offset(obj, multiplier, twister); this._offsetCache[key].randomOffset = randomOffset; offset2.x -= randomOffset.x; offset2.y -= randomOffset.y; } let extraOffset = this.data?.offset?.[key]; if (extraOffset) { let newOffset = { x: extraOffset.x, y: extraOffset.y }; if (extraOffset.gridUnits) { newOffset.x *= canvas.grid.size; newOffset.y *= canvas.grid.size; } if (extraOffset.local) { if (!this._cachedSourceData?.position || !this._cachedTargetData?.position) { this.getSourceData(); this.getTargetData(); } const startPos = this._cachedSourceData.position; const endPos = this._cachedTargetData.position; const angle = this.target ? new Ray(startPos, endPos).angle : Ray.fromAngle( startPos.x, startPos.y, this._cachedSourceData.rotation, 1 ).angle; newOffset = rotateAroundPoint( 0, 0, newOffset.x, newOffset.y, -angle ); } offset2.x -= newOffset.x; offset2.y -= newOffset.y; } let offsetMap = this._nameOffsetMap?.[offsetMapName]; if (!this._offsetCache[key]["nameCache"][offsetMapName]) { this._offsetCache[key]["nameCache"][offsetMapName] = {}; } if (offsetMap) { if (offsetMap.missed) { const missedOffset = this._offsetCache[key]["nameCache"][offsetMapName]?.missedOffset || calculate_missed_position( offsetMap.sourceObj, offsetMap.targetObj, offsetMap.twister ); this._offsetCache[key]["nameCache"][offsetMapName].missedOffset = missedOffset; offset2.x -= missedOffset.x; offset2.y -= missedOffset.y; } const obj2 = offsetMap.targetObj || offsetMap.sourceObj; const multiplier2 = offsetMap.randomOffset?.source || offsetMap.randomOffset?.target; if (obj2 && multiplier2) { let randomOffset = this._offsetCache[key]["nameCache"][offsetMapName]?.randomOffset || get_random_offset(obj2, multiplier2, offsetMap.twister); this._offsetCache[key]["nameCache"][offsetMapName].randomOffset = randomOffset; offset2.x -= randomOffset.x; offset2.y -= randomOffset.y; } if (offsetMap.offset) { offset2.x += offsetMap.offset.x; offset2.y += offsetMap.offset.y; } } return offset2; } /** * Initializes the name offset map by establishing targets * * @param inOffsetMap * @returns {{setup}|*} * @private */ _setupOffsetMap(inOffsetMap) { if (!inOffsetMap.setup) { inOffsetMap.setup = true; inOffsetMap.sourceObj = inOffsetMap.source ? this._validateObject(inOffsetMap.source) : false; inOffsetMap.targetObj = inOffsetMap.target ? this._validateObject(inOffsetMap.target) : false; const repetition = this.data.repetition % inOffsetMap.repetitions; const seed = get_hash(`${inOffsetMap.seed}-${repetition}`); inOffsetMap.twister = new MersenneTwister(seed); } return inOffsetMap; } /** * Plays the effect, returning two promises; one that resolves once the duration has been established, and another * when the effect has finished playing * * @returns {Object} */ play() { const durationPromise = new Promise((resolve, reject2) => { this._durationResolve = resolve; }); const finishPromise = new Promise(async (resolve, reject2) => { this._resolve = resolve; Hooks.callAll("createSequencerEffect", this); debug(`Playing effect:`, this.data); this._initialize(); }); return { duration: durationPromise, promise: finishPromise }; } /** * Ends the effect */ endEffect() { if (this._ended) return; Hooks.callAll("endedSequencerEffect", this); this.destroy(); } destroy(...args) { this._destroyDependencies(); return super.destroy(...args); } /** * Updates this effect with the given parameters * @param inUpdates * @returns {Promise} */ async update(inUpdates) { if (!this.userCanUpdate) throw custom_error( "Sequencer", "CanvasEffect | Update | You do not have permission to update this effect" ); CanvasEffect.validateUpdate(inUpdates); const newData = foundry.utils.deepClone(this.data); const updateKeys = Object.keys(inUpdates); updateKeys.forEach((key) => { setProperty(newData, key, inUpdates[key]); }); if (Object.keys(foundry.utils.diffObject(newData, this.data)).length === 0) { debug( `Skipped updating effect with ID ${this.id} - no changes needed` ); return; } if (this.data.persist) { const originalSourceUUID = is_UUID(this.data.source) && this.data.attachTo ? this.data.source : "Scene." + this.data.sceneId; const newSourceUUID = is_UUID(newData.source) && newData.attachTo ? newData.source : "Scene." + newData.sceneId; if (originalSourceUUID !== newSourceUUID) { flagManager.removeFlags(originalSourceUUID, newData); } flagManager.addFlags(newSourceUUID, newData); } debug(`Updated effect with ID ${this.id}`); return sequencerSocket.executeForEveryone( SOCKET_HANDLERS.UPDATE_EFFECT, this.id, newData ); } async addAnimatedProperties({ animations = [], loopingAnimation = [] } = {}) { const animationsToAdd = []; if (!Array.isArray(animations)) { throw custom_error( this.data.moduleName, `animations must be an array of arrays` ); } for (const animationData of animations) { if (!Array.isArray(animationData)) { throw custom_error( this.data.moduleName, `each entry in animations must be an array, each with target, property name, and animation options` ); } const result = validateAnimation(...animationData); if (typeof result === "string") { throw custom_error(this.data.moduleName, result); } result.creationTimestamp = +new Date(); animationsToAdd.push(result); } if (!Array.isArray(loopingAnimation)) { throw custom_error( this.data.moduleName, `loopingAnimation must be an array of arrays` ); } for (const animationData of loopingAnimation) { if (!Array.isArray(animationData)) { throw custom_error( this.data.moduleName, `each entry in loopingAnimation must be an array, each with target, property name, and animation options` ); } const result = validateLoopingAnimation(...animationData); if (typeof result === "string") { throw custom_error(this.data.moduleName, result); } result.creationTimestamp = +new Date(); animationsToAdd.push(result); } if (this.data.persist) { const originalSourceUUID = is_UUID(this.data.source) && this.data.attachTo ? this.data.source : "Scene." + this.data.sceneId; const newData = foundry.utils.deepClone(this.data); newData.animations = (newData.animations ?? []).concat( foundry.utils.deepClone(animationsToAdd) ); flagManager.addFlags(originalSourceUUID, newData); } return sequencerSocket.executeForEveryone( SOCKET_HANDLERS.ADD_EFFECT_ANIMATIONS, this.id, animationsToAdd ); } async _addAnimations(inAnimations) { this._playAnimations(inAnimations); this.data.animations = (this.data.animations ?? []).concat(inAnimations); } /** * Updates the effect * * @param inUpdates * @returns {Promise} * @private */ _update(inUpdates) { this.data = inUpdates; Hooks.callAll("updateSequencerEffect", this); this._destroyDependencies(); return this._reinitialize(); } /** * Determines whether a position is within the bounds of this effect * * @param inPosition * @returns {boolean} */ isPositionWithinBounds(inPosition) { if (!this.spriteContainer) return false; return is_position_within_bounds( inPosition, this.spriteContainer, this.parent ); } /** * Initializes the effect and places it on the canvas * * @param {boolean} play * @returns {Promise} * @private */ async _initialize(play = true) { this.ready = false; this._initializeVariables(); await this._contextLostCallback(); await this._loadTexture(); this._addToContainer(); this._createSprite(); this._calculateDuration(); this._createShapes(); await this._setupMasks(); await this._transformSprite(); this._playCustomAnimations(); this._playPresetAnimations(); this._setEndTimeout(); this._timeoutVisibility(); if (play) await this.playMedia(); this.ready = true; } /** * Reinitializes the effect after it has been updated * * @param play * @returns {Promise} * @private */ async _reinitialize(play = true) { this.renderable = false; if (!this.shouldPlay) { return Sequencer.EffectManager._removeEffect(this); } this.actualCreationTime = +new Date(); return this._initialize(play); } /** * Initializes variables core to the function of the effect * This is run as a part of the construction of the effect * * @private */ _initializeVariables() { this.rotationContainer = this.addChild(new PIXI.Container()); this.rotationContainer.id = this.id + "-rotationContainer"; this.isometricContainer = this.rotationContainer.addChild( new PIXI.Container() ); this.isometricContainer.id = this.id + "-isometricContainer"; this.spriteContainer = this.isometricContainer.addChild( new PIXI.Container() ); this.spriteContainer.id = this.id + "-spriteContainer"; this._template = this.data.template; this._ended = null; this._maskContainer = null; this._maskSprite = null; this._file = null; this._loopOffset = 0; this.effectFilters = {}; this._animationDuration = 0; this._animationTimes = {}; this._twister = new MersenneTwister(this.data.creationTimestamp); this._video = null; this._distanceCache = null; this._isRangeFind = false; this._customAngle = 0; this._currentFilePath = this.data.file; this._relatedSprites = {}; this._hooks = []; if (this._resetTimeout) { clearTimeout(this._resetTimeout); } this._resetTimeout = null; this._source = false; this._target = false; this._offsetCache = { source: { nameCache: {} }, target: { nameCache: {} } }; this._nameOffsetMap = Object.fromEntries( Object.entries( foundry.utils.deepClone(this.data.nameOffsetMap ?? {}) ).map((entry) => { return [entry[0], this._setupOffsetMap(entry[1])]; }) ); this.uuid = !is_UUID(this.context.uuid) ? this.id : this.context.uuid + ".data.flags.sequencer.effects." + this.id; const maxPerformance = game.settings.get("core", "performanceMode") === 3; const maxFPS = game.settings.get("core", "maxFPS"); this._ticker = new PIXI.Ticker(); this._ticker.maxFPS = maxPerformance && maxFPS === 60 ? 60 : maxFPS; this._ticker.start(); } /** * Destroys all dependencies to this element, such as tickers, animations, textures, and child elements * * @private */ _destroyDependencies() { if (this._ended) return; this._ended = true; this.mask = null; hooksManager.removeHooks(this.uuid); try { this._ticker.stop(); this._ticker.destroy(); } catch (err) { } this._ticker = null; Object.values(this._relatedSprites).forEach( (sprite) => sprite.destroy({ children: true }) ); SequencerAnimationEngine.endAnimations(this.id); if (this._maskContainer) this._maskContainer.destroy({ children: true }); if (this._maskSprite) { try { this._maskSprite.texture.destroy(true); this._maskSprite.destroy(); } catch (err) { } } if (this._file instanceof SequencerFileBase) { this._file.destroy(); } if (this.video) { try { this.video.removeAttribute("src"); this.video.pause(); this.video.load(); } catch (err) { } } try { if (this.data.screenSpace) { SequencerAboveUILayer.removeContainerByEffect(this); } } catch (err) { } this.removeChildren().forEach((child) => child.destroy({ children: true })); } /** * Plays preset animations * * @private */ _playPresetAnimations() { this._moveTowards(); this._fadeIn(); this._fadeInAudio(); this._scaleIn(); this._rotateIn(); this._fadeOut(); this._fadeOutAudio(); this._scaleOut(); this._rotateOut(); } /** * Gets an object based on an identifier, checking if it exists within the named offset map, whether it's a * coordinate object, or if it's an UUID that needs to be fetched from the scene * * @param inIdentifier * @returns {*} * @private */ _getObjectByID(inIdentifier) { let source = inIdentifier; let offsetMap = this._nameOffsetMap?.[inIdentifier]; if (offsetMap) { source = offsetMap?.targetObj || offsetMap?.sourceObj || source; } else { source = this._validateObject(source); } return source; } /** * Validates the given parameter, whether it's a UUID or a coordinate object, and returns the proper one * * @param inObject * @returns {*} * @private */ _validateObject(inObject) { if (is_UUID(inObject) || !is_object_canvas_data(inObject)) { inObject = get_object_from_scene(inObject, this.data.sceneId); inObject = inObject?._object ?? inObject; } return inObject; } /** * Adds this effect to the appropriate container on the right layer * * @private */ _addToContainer() { let layer2; if (this.data.screenSpaceAboveUI) { layer2 = SequencerAboveUILayer; } else if (this.data.screenSpace) { layer2 = canvas.sequencerEffectsUILayer; } else if (this.data.aboveInterface) { layer2 = canvas.controls; } else if (this.data.aboveLighting) { layer2 = canvas.interface; } else { layer2 = canvas.primary; } layer2.addChild(this); layer2.sortChildren(); } /** * Loads the texture for this effect, handling cases where it's a simple path or a database path * * @private */ async _loadTexture() { if (this.data.file === "") { return; } if (this.data.customRange) { this._file = SequencerFileBase.make( this.data.file, Object.values(this.template), "temporary.range.file" ); } else { if (!Sequencer.Database.entryExists(this.data.file)) { let texture = await SequencerFileCache.loadFile(this.data.file); this.video = this.data.file.toLowerCase().endsWith(".webm") ? texture?.baseTexture?.resource?.source ?? false : false; this._texture = texture; this._file = texture; this._currentFilePath = this.data.file; return; } this._file = Sequencer.Database.getEntry(this.data.file).clone(); } this._file.fileIndex = this.data.forcedIndex; this._file.twister = this._twister; this._isRangeFind = this._file?.rangeFind; this.spriteSheet = false; if (this.data.stretchTo) { let ray = new Ray(this.sourcePosition, this.targetPosition); this._rotateTowards(ray); ray = new Ray(this.sourcePosition, this.targetPosition); let { filePath, texture, sheet } = await this._getTextureForDistance( ray.distance ); this._currentFilePath = filePath; this._texture = texture; this.spriteSheet = sheet; } else if (!this._isRangeFind || this._isRangeFind && !this.data.stretchTo) { const { filePath, texture, sheet } = await this._file.getTexture(); this._currentFilePath = filePath; this._texture = texture; this.spriteSheet = sheet; } if (this._isRangeFind && (this.data.stretchTo?.attachTo?.active || this.data.attachTo?.active)) { let spriteType = this.data.tilingTexture ? PIXI.TilingSprite : SpriteMesh; this._relatedSprites[this._currentFilePath] = new spriteType( this._texture, this.data.xray ? null : VisionSamplerShader ); new Promise(async (resolve) => { for (let filePath of this._file.getAllFiles()) { if (filePath === this._currentFilePath) continue; let texture = await this._file._getTexture(filePath); let spriteType2 = this.data.tilingTexture ? PIXI.TilingSprite : SpriteMesh; let sprite = new spriteType2( texture, this.data.xray ? null : VisionSamplerShader ); sprite.renderable = false; this._relatedSprites[filePath] = sprite; } resolve(); }); } this._template = this._file.template ?? this._template; this.video = this._currentFilePath.toLowerCase().endsWith(".webm") ? this._texture?.baseTexture?.resource?.source : false; } /** * Calculates the duration of this effect, based on animation durations, the video source duration, end/start times, etc * * @private */ _calculateDuration() { let playbackRate = this.data.playbackRate ? this.data.playbackRate : 1; this.mediaPlaybackRate = playbackRate; this._animationDuration = this.data.duration || this.mediaDuration * 1e3; if (this.data.moveSpeed && this.data.moves) { let distance = distance_between( this.sourcePosition, this.targetPosition ); let durationFromSpeed = distance / this.data.moveSpeed * 1e3; this._animationDuration = Math.max(durationFromSpeed, this.data.duration); } else if (!this.data.duration && !this.hasAnimatedMedia) { let fadeDuration = (this.data.fadeIn?.duration ?? 0) + (this.data.fadeOut?.duration ?? 0); let scaleDuration = (this.data.scaleIn?.duration ?? 0) + (this.data.scaleOut?.duration ?? 0); let rotateDuration = (this.data.rotateIn?.duration ?? 0) + (this.data.rotateOut?.duration ?? 0); let moveDuration = 0; if (this.data.moves) { let distance = distance_between( this.sourcePosition, this.targetPosition ); moveDuration = (this.data.moveSpeed ? distance / this.data.moveSpeed * 1e3 : 1e3) + this.data.moves.delay; } let animationDurations = this.data.animations ? Math.max( ...this.data.animations.map((animation2) => { if (animation2.looping) { if (animation2.loops === 0) return 0; return (animation2?.duration ?? 0) * (animation2?.loops ?? 0) + (animation2?.delay ?? 0); } else { return (animation2?.duration ?? 0) + (animation2?.delay ?? 0); } }) ) : 0; this._animationDuration = Math.max( fadeDuration, scaleDuration, rotateDuration, moveDuration, animationDurations ); this._animationDuration = this._animationDuration || 1e3; } this._startTime = 0; if (this.data.time?.start && this.mediaCurrentTime !== null) { let currentTime = !this.data.time.start.isPerc ? this.data.time.start.value ?? 0 : this._animationDuration * this.data.time.start.value; this.mediaCurrentTime = currentTime / 1e3; this._startTime = this.mediaCurrentTime; } if (this.data.time?.end) { this._animationDuration = !this.data.time.end.isPerc ? this.data.time.isRange ? this.data.time.end.value - this.data.time.start.value : this._animationDuration - this.data.time.end.value : this._animationDuration * this.data.time.end.value; } this._endTime = this._animationDuration / 1e3; if (this._file?.markers && this._startTime === 0 && this._endTime === this.mediaDuration) { this._animationTimes.loopStart = this._file.markers.loop.start / playbackRate / 1e3; this._animationTimes.loopEnd = this._file.markers.loop.end / playbackRate / 1e3; this._animationTimes.forcedEnd = this._file.markers.forcedEnd / playbackRate / 1e3; } this._durationResolve(this._animationDuration); this.mediaLooping = this._animationDuration / 1e3 > this.mediaDuration && !this.data.noLoop; } /** * If this effect is animatable, hold off on rendering it for a bit so that the animations have time to initialize to * prevent it from spawning and then jumping to the right place * * @private */ _timeoutVisibility() { setTimeout( () => { this._setupHooks(); }, this.data.animations ? 50 : 0 ); } /** * If this effect is attached to an object, check whether the object has been destroyed, if so, end the effect * * @private */ _contextLostCallback() { if (this.isSourceTemporary) { this._ticker.add(() => { if (this.isSourceDestroyed) { this._ticker.stop(); this._source = this.sourcePosition; SequencerEffectManager.endEffects({ effects: this }); } }); } if (this.isTargetTemporary) { this._ticker.add(() => { if (this.isTargetDestroyed) { this._ticker.stop(); this._target = this.targetPosition; SequencerEffectManager.endEffects({ effects: this }); } }); } } /** * Creates the sprite, and the relevant containers that manage the position and offsets of the overall visual look of the sprite * * @private */ _createSprite() { this.renderable = false; const args = [this.spriteSheet ? this.spriteSheet : null]; if (!this.data.xray && !this.spriteSheet && !this.data.screenSpace && !this.data.screenSpaceAboveUI) { args.push(VisionSamplerShader); } const spriteType = this.spriteSheet ? PIXI.AnimatedSprite : SpriteMesh; const sprite = new spriteType(...args); this.sprite = this.spriteContainer.addChild(sprite); this.sprite.id = this.id + "-sprite"; Object.values(this._relatedSprites).forEach((sprite2) => { this.sprite.addChild(sprite2); }); this.animatedSprite = false; if (this.spriteSheet) { this.animatedSprite = true; this.sprite.animationSpeed = 0.4; this.sprite.loop = false; } let textSprite; if (this.data.text) { const text2 = this.data.text.text; const fontSettings = foundry.utils.deepClone(this.data.text); fontSettings.fontSize = (fontSettings?.fontSize ?? 26) * (150 / canvas.grid.size); textSprite = new PIXI.Text(text2, fontSettings); textSprite.resolution = 5; textSprite.zIndex = 1; textSprite.anchor.set( this.data.text?.anchor?.x ?? 0.5, this.data.text?.anchor?.y ?? 0.5 ); } this.sprite.filters = []; if (this.data.filters) { for (let index = 0; index < this.data.filters.length; index++) { const filterData = this.data.filters[index]; const filter2 = new filters[filterData.className](filterData.data); filter2.id = this.id + "-" + filterData.className + "-" + index.toString(); this.sprite.filters.push(filter2); const filterKeyName = filterData.name || filterData.className; this.effectFilters[filterKeyName] = filter2; } } this.alphaFilter = new PIXI.filters.AlphaFilter(this.data.opacity); this.alphaFilter.id = this.id + "-alphaFilter"; this.sprite.filters.push(this.alphaFilter); let spriteOffsetX = this.data.spriteOffset?.x ?? 0; let spriteOffsetY = this.data.spriteOffset?.y ?? 0; if (this.data.spriteOffset?.gridUnits) { spriteOffsetX *= canvas.grid.size; spriteOffsetY *= canvas.grid.size; } this.sprite.position.set(spriteOffsetX, spriteOffsetY); this.sprite.anchor.set( this.data.spriteAnchor?.x ?? 0.5, this.data.spriteAnchor?.y ?? 0.5 ); let spriteRotation = this.data.spriteRotation ?? 0; if (this.data.randomSpriteRotation) { spriteRotation += random_float_between(-360, 360, this._twister); } this.sprite.rotation = Math.normalizeRadians( Math.toRadians(spriteRotation) ); this._customAngle = this.data.angle ?? 0; if (this.data.randomRotation) { this._customAngle += random_float_between(-360, 360, this._twister); } this.spriteContainer.rotation = -Math.normalizeRadians( Math.toRadians(this._customAngle) ); if (CONSTANTS.INTEGRATIONS.ISOMETRIC.ACTIVE) { this.isometricContainer.rotation = Math.PI / 4; } if (this.data.tint) { this.sprite.tint = this.data.tint; } if (textSprite) { if (this.data.tint) { textSprite.tint = this.data.tint; } this.sprite.addChild(textSprite); } this.filters = [ new PIXI.filters.ColorMatrixFilter({ saturation: this.shouldShowFadedVersion ? -1 : 1 }), new PIXI.filters.AlphaFilter( this.shouldShowFadedVersion ? game.settings.get(CONSTANTS.MODULE_NAME, "user-effect-opacity") / 100 : 1 ) ]; this.updateElevation(); } _createShapes() { const nonMaskShapes = (this.data?.shapes ?? []).filter( (shape) => !shape.isMask ); this.shapes = {}; for (const shape of nonMaskShapes) { const graphic = createShape(shape); graphic.filters = this.sprite.filters; this.spriteContainer.addChild(graphic); this.shapes[shape?.name ?? "shape-" + randomID()] = graphic; } } updateElevation() { const targetElevation = Math.max( get_object_elevation(this.source ?? {}), get_object_elevation(this.target ?? {}) ) + 1; let effectElevation = this.data.elevation?.elevation ?? 0; if (!this.data.elevation?.absolute) { effectElevation += targetElevation; } const isIsometric = getProperty( game.scenes.get(this.data.sceneId), CONSTANTS.INTEGRATIONS.ISOMETRIC.SCENE_ENABLED ); if (CONSTANTS.INTEGRATIONS.ISOMETRIC.ACTIVE && isIsometric) { const sourceSort = this.source ? this.sourceMesh.sort + (this.data.isometric?.overlay ? 1 : -1) : 0; const targetSort = this.target ? this.targetMesh.sort + (this.data.isometric?.overlay ? 1 : -1) : 0; this.sort = Math.max(sourceSort, targetSort); } else { this.sort = !is_real_number(this.data.zIndex) ? this.data.index + (this?.parent?.children?.length ?? 0) : 1e5 + this.data.zIndex; } this.elevation = effectElevation; this.zIndex = this.sort; this.sort += 100; if (this.parent) { this.parent.sortChildren(); } } updateTransform() { super.updateTransform(); if (this.data.screenSpace || this.data.screenSpaceAboveUI) { const [screenWidth, screenHeight] = canvas.screenDimensions; this.position.set( (this.data.screenSpacePosition?.x ?? 0) + screenWidth * (this.data.screenSpaceAnchor?.x ?? this.data.anchor?.x ?? 0.5), (this.data.screenSpacePosition?.y ?? 0) + screenHeight * (this.data.screenSpaceAnchor?.y ?? this.data.anchor?.y ?? 0.5) ); if (this.data.screenSpaceScale) { const scaleData = this.data.screenSpaceScale ?? { x: 1, y: 1 }; let scaleX = scaleData.x; let scaleY = scaleData.y; if (scaleData.fitX) { scaleX = scaleX * (screenWidth / this.sprite.width); } if (scaleData.fitY) { scaleY = scaleY * (screenHeight / this.sprite.height); } scaleX = scaleData.ratioX ? scaleY : scaleX; scaleY = scaleData.ratioY ? scaleX : scaleY; this.scale.set(scaleX, scaleY); } } } async _setupMasks() { const maskShapes = this.data.shapes.filter((shape) => shape.isMask); if (!this.data?.masks?.length && !maskShapes.length) return; const maskFilter = MaskFilter.create(); for (const uuid of this.data.masks) { const documentObj = fromUuidSync(uuid); if (!documentObj || documentObj.parent.id !== this.data.sceneId) continue; const obj = documentObj.object; let shape = obj?.mesh; let shapeToAdd = shape; if (obj instanceof MeasuredTemplate || obj instanceof Drawing) { shape = obj?.shape?.geometry?.graphicsData?.[0]?.shape ?? obj?.shape; if (game.modules.get("walledtemplates")?.active && obj.walledtemplates?.walledTemplate) { let wt = obj.walledtemplates.walledTemplate; wt.options.padding = 3 * canvas.dimensions.distancePixels; shape = wt.computeShape(); wt.options.padding = 0; } shapeToAdd = new PIXI.LegacyGraphics().beginFill().drawShape(shape).endFill(); if (obj instanceof MeasuredTemplate) { shapeToAdd.position.set(documentObj.x, documentObj.y); } else { const { x, y, shape: { width: width2, height }, rotation: rotation2 } = documentObj; shapeToAdd.pivot.set(width2 / 2, height / 2); shapeToAdd.position.set(x + width2 / 2, y + height / 2); shapeToAdd.angle = rotation2; } shapeToAdd.cullable = true; shapeToAdd.custom = true; shapeToAdd.renderable = false; shapeToAdd.uuid = uuid; canvas.stage.addChild(shapeToAdd); } shapeToAdd.obj = obj; const updateMethod = (doc) => { if (doc !== documentObj) return; const mask = maskFilter.masks.find((shape2) => shape2.uuid === uuid); if (!mask) return; if (!mask.custom) return; mask.clear(); if (obj instanceof MeasuredTemplate) { mask.position.set(documentObj.x, documentObj.y); let maskObj = documentObj.object; if (game.modules.get("walledtemplates")?.active && maskObj.walledtemplates?.walledTemplate) { let wt = maskObj.walledtemplates.walledTemplate; wt.options.padding = 3 * canvas.dimensions.distancePixels; shape = wt.computeShape(); wt.options.padding = 0; } } else { const { x, y, shape: { width: width2, height }, rotation: rotation2 } = documentObj; mask.pivot.set(width2 / 2, height / 2); mask.position.set(x + width2 / 2, y + height / 2); mask.angle = rotation2; } mask.beginFill().drawShape(shape).endFill(); }; if (game.modules.get("walledtemplates")?.active) { hooksManager.addHook(this.uuid, "createWall", () => { setTimeout(() => { updateMethod(documentObj); }, 100); }); hooksManager.addHook(this.uuid, "updateWall", () => { setTimeout(() => { updateMethod(documentObj); }, 100); }); hooksManager.addHook(this.uuid, "deleteWall", () => { setTimeout(() => { updateMethod(documentObj); }, 100); }); } hooksManager.addHook(this.uuid, this.getHook("update", uuid), (doc) => { setTimeout(() => { updateMethod(doc); }, 100); }); maskFilter.masks.push(shapeToAdd); } for (const shapeData of maskShapes) { const shape = createShape(shapeData); shape.cullable = true; shape.custom = true; shape.renderable = false; this.spriteContainer.addChild(shape); this.shapes[shapeData?.name ?? "shape-" + randomID()] = shape; maskFilter.masks.push(shape); } this.sprite.filters.push(maskFilter); } /** * Sets up the hooks relating to this effect's source and target * * @private */ _setupHooks() { const attachedToSource = this.data.attachTo?.active && is_UUID(this.data.source); const attachedToTarget = (this.data.stretchTo?.attachTo || this.data.rotateTowards?.attachTo) && is_UUID(this.data.target); const baseRenderable = this.shouldPlayVisible; let renderable = baseRenderable; let alpha = null; if (attachedToSource) { hooksManager.addHook(this.uuid, this.getSourceHook("delete"), (doc) => { const uuid = doc.uuid; if (doc !== this.sourceDocument) return; this._source = this._cachedSourceData.position; SequencerEffectManager.objectDeleted(uuid); }); if (this.data.attachTo?.bindVisibility) { hooksManager.addHook( this.uuid, "sightRefresh", () => { const sourceVisible = this.source && (this.sourceMesh?.visible ?? true); const sourceHidden = this.sourceDocument && (this.sourceDocument?.hidden ?? false); const targetVisible = this.target && (!attachedToTarget || (this.targetMesh?.visible ?? true)); this.renderable = baseRenderable && (sourceVisible || targetVisible) && this._checkWallCollisions(); this.alpha = sourceVisible && sourceHidden ? 0.5 : 1; renderable = baseRenderable && this.renderable; }, true ); } if (this.data.attachTo?.bindAlpha || this.data.attachTo?.bindElevation) { hooksManager.addHook(this.uuid, this.getSourceHook("update"), (doc) => { if (doc !== this.sourceDocument) return; if (this.data.attachTo?.bindAlpha) { this.spriteContainer.alpha = this.getSourceData().alpha; } if (this.data.attachTo?.bindElevation) { this.updateElevation(); } }); } if (this.data.attachTo?.bindAlpha) { alpha = this.getSourceData().alpha; } } if (attachedToTarget) { hooksManager.addHook(this.uuid, this.getTargetHook("delete"), (doc) => { if (doc !== this.target) return; this._target = this._cachedTargetData.position; const uuid = doc.uuid; SequencerEffectManager.objectDeleted(uuid); }); hooksManager.addHook(this.uuid, this.getTargetHook("update"), (doc) => { if (doc !== this.target) return; this.updateElevation(); }); } for (let uuid of this.data?.tiedDocuments ?? []) { const tiedDocument = fromUuidSync(uuid); if (tiedDocument) { hooksManager.addHook( this.uuid, this.getHook("delete", tiedDocument.uuid), (doc) => { if (tiedDocument !== doc) return; SequencerEffectManager.objectDeleted(doc.uuid); } ); } } setTimeout(() => { this.renderable = renderable; this.spriteContainer.alpha = alpha ?? 1; }, 25); } /** * Calculates the padding and scale to stretch an effect across the given distance * * If the file is a SequencerFileBase instance, it will also pick the appropriate file for the right distance * * @param distance * @returns {Object} * @private */ async _getTextureForDistance(distance) { if (!this._distanceCache || this._distanceCache?.distance !== distance) { let scaleX = 1; let scaleY = 1; let texture; let filePath; let spriteAnchor = this.data.anchor?.x ?? 1; if (this._file instanceof SequencerFileBase) { const scaledDistance = distance / (this.data.scale.x ?? 1); const result = await this._file.getTexture(scaledDistance); filePath = result.filePath; texture = result.texture; spriteAnchor = result.spriteAnchor ?? this.data.anchor?.x ?? 0; scaleX = result.spriteScale; if (this.data.stretchTo?.onlyX) { const widthWithPadding = texture.width - (this.template.startPoint + this.template.endPoint); scaleY = widthWithPadding / texture.width; } else { scaleY = result.spriteScale; } } else if (this._file instanceof PIXI.Texture) { filePath = this.data.file; texture = this._file; spriteAnchor = this.template.startPoint / texture.width; const widthWithPadding = texture.width - (this.template.startPoint + this.template.endPoint); let spriteScale = distance / widthWithPadding; scaleX = spriteScale; if (this.data.stretchTo?.onlyX) { scaleY = widthWithPadding / texture.width; } else { scaleY = spriteScale; } } this._distanceCache = { filePath, texture, spriteAnchor, scaleX, scaleY, distance }; } return this._distanceCache; } /** * Applies the distance scaling to the sprite based on the previous method * * @returns {Promise} * @private */ async _applyDistanceScaling() { const ray = new Ray(this.sourcePosition, this.targetPosition); this._rotateTowards(ray); let { filePath, texture, spriteAnchor, scaleX, scaleY, distance } = await this._getTextureForDistance(ray.distance); if (this._currentFilePath !== filePath || this._relatedSprites[filePath] === void 0) { this._texture = texture; this.video = filePath.toLowerCase().endsWith(".webm") ? texture?.baseTexture?.resource?.source ?? false : false; Object.values(this._relatedSprites).forEach((subsprite) => { subsprite.renderable = false; }); this._currentFilePath = filePath; if (this._relatedSprites[filePath]) { this._relatedSprites[filePath].renderable = true; } else { let sprite; let spriteType = this.data.tilingTexture ? PIXI.TilingSprite : SpriteMesh; if (this.data.xray) { sprite = new spriteType(texture); } else { sprite = new spriteType(texture, VisionSamplerShader); } this._relatedSprites[filePath] = sprite; if (this.data.tint) { sprite.tint = this.data.tint; } this.sprite.addChild(sprite); } } this.playMedia(); if (this._relatedSprites[filePath]) { if (this.data.attachTo?.active) { this._applyAttachmentOffset(); } const sprite = this._relatedSprites[filePath]; if (!sprite.parent) { this.sprite.addChild(sprite); } if (this.data.tilingTexture) { const scaleX2 = (this.data.scale.x ?? 1) * this.gridSizeDifference; const scaleY2 = (this.data.scale.y ?? 1) * this.gridSizeDifference; sprite.width = distance / scaleX2; sprite.height = texture.height; sprite.scale.set(scaleX2 * this.flipX, scaleY2 * this.flipY); sprite.tileScale.x = this.data.tilingTexture.scale.x; sprite.tileScale.y = this.data.tilingTexture.scale.y; sprite.tilePosition = this.data.tilingTexture.position; } else { sprite.scale.set( scaleX * (this.data.scale.x ?? 1) * this.flipX, scaleY * (this.data.scale.y ?? 1) * this.flipY ); } sprite.anchor.set( this.flipX === 1 ? spriteAnchor : 1 - spriteAnchor, this.data.anchor?.y ?? 0.5 ); } } _checkWallCollisions() { if (!this.data.stretchTo?.attachTo || !this.data.stretchTo?.requiresLineOfSight) return true; const ray = new Ray(this.sourcePosition, this.targetPosition); const blockingObjects = canvas.walls.checkCollision(ray, { type: "sight" }); if (!blockingObjects.length && !this.data.stretchTo?.hideLineOfSight) { this._ticker.stop(); SequencerEffectManager.endEffects({ effects: this }); } return !blockingObjects.length; } /** * Rotates the effect towards the target * * @param ray * @private */ _rotateTowards(ray) { if (!ray) { const sourcePosition = this.flipX === 1 ? this.sourcePosition : this.targetPosition; const targetPosition = this.flipX === 1 ? this.targetPosition : this.sourcePosition; ray = new Ray(sourcePosition, targetPosition); } this.rotationContainer.rotation = Math.normalizeRadians( ray.angle + Math.toRadians(this.data.rotateTowards?.rotationOffset ?? 0) ); this._tweakRotationForIsometric(); } _tweakRotationForIsometric() { if (!CONSTANTS.INTEGRATIONS.ISOMETRIC.ACTIVE) return; if (this.data.stretchTo) { let skew = Math.normalizeRadians( this.rotationContainer.rotation - Math.PI / 4 ); if (Math.abs(skew) >= Math.PI / 2 - 0.5 && Math.abs(skew) <= Math.PI / 2 + 0.5) { skew -= Math.PI / 2; } this.isometricContainer.skew.set(Math.normalizeRadians(skew), 0); this.isometricContainer.rotation = 0; } else if (this.data?.isometric?.overlay) { this.rotationContainer.rotation = 0; let skew = Math.PI / 4 + this.rotationContainer.rotation; this.isometricContainer.skew.set( Math.normalizeRadians(skew - Math.PI / 4), 0 ); this.isometricContainer.scale.set( 1, window.scale ?? CONSTANTS.INTEGRATIONS.ISOMETRIC.ISOMETRIC_CONVERSION ); } else { this.isometricContainer.rotation = 0; } } /** * Transforms the sprite, rotating it, stretching it, scaling it, sizing it according its data * * @private */ async _transformSprite() { if (this.data.stretchTo) { if (this.data.stretchTo?.attachTo) { this._transformStretchToAttachedSprite(); } await this._applyDistanceScaling(); } else { if (!this.sprite?.texture?.valid && this._texture?.valid) { this.sprite.texture = this._texture; } } if (this.video && (this._startTime || this._loopOffset > 0) && this.video?.currentTime !== void 0) { await wait$1(20); this.updateTexture(); } if (!this.data.stretchTo) { this._transformNoStretchSprite(); } if (this.data.attachTo?.active && !this.data.stretchTo?.attachTo) { await this._transformAttachedNoStretchSprite(); } else { if (!this.data.screenSpace) { this.position.set(this.sourcePosition.x, this.sourcePosition.y); } } if (this.data.rotateTowards) { this._rotateTowards(); if (this.data.rotateTowards?.attachTo) { this._transformRotateTowardsAttachedSprite(); } } this._tweakRotationForIsometric(); if (!this.data.anchor && this.data.rotateTowards) { const textureWidth = (this._texture?.width ?? this.sprite.width) / 2; const startPointRatio = this.template.startPoint / textureWidth; this.spriteContainer.pivot.set( this.sprite.width * (-0.5 + startPointRatio), 0 ); } else { this.spriteContainer.pivot.set( interpolate( this.sprite.width * -0.5, this.sprite.width * 0.5, this.data.anchor?.x ?? 0.5 ), interpolate( this.sprite.height * -0.5, this.sprite.height * 0.5, this.data.anchor?.y ?? 0.5 ) ); } } _transformStretchToAttachedSprite() { this._ticker.add(async () => { try { await this._applyDistanceScaling(); } catch (err) { } }); } _transformNoStretchSprite() { if (this.data.tilingTexture) { this.sprite.tileScale = { x: this.data.tilingTexture.scale.x * this.gridSizeDifference, y: this.data.tilingTexture.scale.y * this.gridSizeDifference }; this.sprite.tilePosition = this.data.tilingTexture.position; } const baseScaleX = (this.data.scale?.x ?? 1) * (this.data.spriteScale?.x ?? 1) * this.flipX; const baseScaleY = (this.data.scale?.y ?? 1) * (this.data.spriteScale?.y ?? 1) * this.flipY; const heightWidthRatio = this.sprite.height / this.sprite.width; const widthHeightRatio = this.sprite.width / this.sprite.height; const ratioToUse = heightWidthRatio > widthHeightRatio; if (this.data.scaleToObject) { let { width: width2, height } = this.target ? this.getTargetData() : this.getSourceData(); const target = this.targetDocument || this.sourceDocument; if (target instanceof TokenDocument) { width2 *= this.data.scaleToObject?.considerTokenScale ? target.texture.scaleX : 1; height *= this.data.scaleToObject?.considerTokenScale ? target.texture.scaleY : 1; } if (this.data.scaleToObject?.uniform) { let newWidth = Math.max(width2, height); height = Math.max(width2, height); width2 = newWidth; } else { width2 = width2 * (ratioToUse ? widthHeightRatio : 1); height = height * (!ratioToUse ? heightWidthRatio : 1); } this.sprite.width = width2 * (this.data.scaleToObject?.scale ?? 1) * baseScaleX; this.sprite.height = height * (this.data.scaleToObject?.scale ?? 1) * baseScaleY; } else if (this.data.size) { let { height, width: width2 } = this.data.size; if (this.data.size.width === "auto" || this.data.size.height === "auto") { height = this.sprite.height; width2 = this.sprite.width; if (this.data.size.width === "auto") { height = this.data.size.height; if (this.data.size.gridUnits) { height *= canvas.grid.size; } width2 = height * widthHeightRatio; } else if (this.data.size.height === "auto") { width2 = this.data.size.width; if (this.data.size.gridUnits) { width2 *= canvas.grid.size; } height = width2 * heightWidthRatio; } } else if (this.data.size.gridUnits) { height *= canvas.grid.size; width2 *= canvas.grid.size; } this.sprite.width = width2 * baseScaleX; this.sprite.height = height * baseScaleY; } else { this.sprite.scale.set( baseScaleX * this.gridSizeDifference, baseScaleY * this.gridSizeDifference ); } } async _transformAttachedNoStretchSprite() { const applyRotation = this.data.attachTo?.followRotation && !(this.sourceDocument instanceof TokenDocument && this.sourceDocument.lockRotation) && (this.sourceDocument?.rotation !== void 0 || this.sourceDocument?.direction !== void 0) && !this.data.rotateTowards && !this.data.stretchTo; this._ticker.add(() => { if (this.isDestroyed) return; if (applyRotation) { this.rotationContainer.rotation = this.getSourceData().rotation; } this._tweakRotationForIsometric(); try { this._applyAttachmentOffset(); } catch (err) { debug_error(err); } }); } _applyAttachmentOffset() { let offset2 = { x: 0, y: 0 }; if (this.data.attachTo?.align && this.data.attachTo?.align !== "center") { offset2 = align({ context: this.source, spriteWidth: this.sprite.width, spriteHeight: this.sprite.height, align: this.data.attachTo?.align, edge: this.data.attachTo?.edge }); } this.position.set( this.sourcePosition.x - offset2.x, this.sourcePosition.y - offset2.y ); } _transformRotateTowardsAttachedSprite() { this._ticker.add(async () => { if (this.isDestroyed) return; try { this._rotateTowards(); } catch (err) { debug_error(err); } }); } /** * Provided an animation targeting the rotation of the sprite's primary container, this method will counter-rotate * the sprite in an equal fashion so that the sprite's rotation remains static relative to this animation * * @param animation * @returns {*[]} * @private */ _counterAnimateRotation(animation2) { if (animation2.target === this.spriteContainer && this.data.zeroSpriteRotation) { delete animation2.target; let counterAnimation = foundry.utils.deepClone(animation2); animation2.target = this.spriteContainer; counterAnimation.target = this.sprite; if (counterAnimation.values) { counterAnimation.values = counterAnimation.values.map( (value) => value * -1 ); } else { counterAnimation.from *= -1; counterAnimation.to *= -1; } if (!Array.isArray(animation2)) { animation2 = [animation2, counterAnimation]; } else { animation2.push(counterAnimation); } } return animation2; } /** * Plays the custom animations of this effect * * @returns {number} * @private */ _playCustomAnimations() { if (!this.data.animations) return 0; this._playAnimations( foundry.utils.deepClone(this.data.animations) ?? [], this.actualCreationTime - this.data.creationTimestamp ); } _playAnimations(animations, timeDifference = 0) { let animationsToSend = []; const oneShotAnimations = animations.filter( (animation2) => !animation2.looping && !animation2.fromEnd ); for (let animation2 of oneShotAnimations) { animation2.target = foundry.utils.getProperty(this, animation2.target); if (!animation2.target) continue; if (animation2.propertyName.indexOf("rotation") > -1) { animation2.from = animation2.from * (Math.PI / 180); animation2.to = animation2.to * (Math.PI / 180); } if (["position.x", "position.y", "height", "width"].includes( animation2.propertyName ) && animation2.gridUnits) { animation2.from *= canvas.grid.size; animation2.to *= canvas.grid.size; } if (["hue"].includes(animation2.propertyName)) { animation2.getPropertyName = "values." + animation2.propertyName; } animationsToSend = animationsToSend.concat( this._counterAnimateRotation(animation2) ); } const loopingAnimations = animations.filter( (animation2) => animation2.looping ); for (let animation2 of loopingAnimations) { animation2.target = foundry.utils.getProperty(this, animation2.target); if (!animation2.target) continue; if (animation2.propertyName.indexOf("rotation") > -1) { animation2.values = animation2.values.map((angle) => { return angle * (Math.PI / 180); }); } if (["position.x", "position.y", "height", "width"].includes( animation2.propertyName ) && animation2.gridUnits) { animation2.values = animation2.values.map((value) => { return value * canvas.grid.size; }); } if (["hue"].includes(animation2.propertyName)) { animation2.getPropertyName = "values." + animation2.propertyName; } animationsToSend = animationsToSend.concat( this._counterAnimateRotation(animation2) ); } if (!(this instanceof PersistentCanvasEffect)) { animationsToSend = animationsToSend.concat( this._getFromEndCustomAnimations() ); } setTimeout(() => { SequencerAnimationEngine.addAnimation( this.id, animationsToSend, timeDifference ); }, 20); } _getFromEndCustomAnimations(immediate = false) { let fromEndAnimations = []; const animations = foundry.utils.deepClone(this.data.animations) ?? []; const oneShotEndingAnimations = animations.filter( (animation2) => !animation2.looping && animation2.fromEnd ); for (let animation2 of oneShotEndingAnimations) { animation2.target = foundry.utils.getProperty(this, animation2.target); if (!animation2.target) continue; animation2.delay = is_real_number(immediate) ? Math.max(immediate - animation2.duration + animation2.delay, 0) : Math.max( this._animationDuration - animation2.duration + animation2.delay, 0 ); if (animation2.propertyName.indexOf("rotation") > -1) { animation2.from = animation2.from * (Math.PI / 180); animation2.to = animation2.to * (Math.PI / 180); } if (["position.x", "position.y", "height", "width"].includes( animation2.propertyName ) && animation2.gridUnits) { animation2.from *= canvas.grid.size; animation2.to *= canvas.grid.size; } fromEndAnimations = fromEndAnimations.concat( this._counterAnimateRotation(animation2) ); } return fromEndAnimations; } /** * Fades in the effect at the start of the effect * * @returns {number|*} * @private */ _fadeIn() { if (!this.data.fadeIn || !this.sprite) return 0; let fadeIn = this.data.fadeIn; if (this.actualCreationTime - (this.data.creationTimestamp + fadeIn.duration + fadeIn.delay) > 0) { return; } this.alphaFilter.alpha = 0; SequencerAnimationEngine.addAnimation(this.id, { target: this.alphaFilter, propertyName: "alpha", to: this.data.opacity, duration: fadeIn.duration, ease: fadeIn.ease, delay: fadeIn.delay, absolute: true }); return fadeIn.duration + fadeIn.delay; } /** * Fades in the effect's audio at the start of the effect * * @returns {number|*} * @private */ _fadeInAudio() { if (!this.data.fadeInAudio || !this.sprite || !this.video) return 0; let fadeInAudio = this.data.fadeInAudio; if (this.actualCreationTime - (this.data.creationTimestamp + fadeInAudio.duration + fadeInAudio.delay) > 0) return; this.video.volume = 0; SequencerAnimationEngine.addAnimation(this.id, { target: this, propertyName: "video.volume", to: (this.data.volume ?? 0) * game.settings.get("core", "globalInterfaceVolume"), duration: fadeInAudio.duration, ease: fadeInAudio.ease, delay: fadeInAudio.delay, absolute: true }); return fadeInAudio.duration + fadeInAudio.delay; } /** * Fades out the effect at the end of the effect's duration * * @returns {number|*} * @private */ _fadeOut(immediate = false) { if (!this.data.fadeOut || !this.sprite) return 0; let fadeOut = this.data.fadeOut; fadeOut.delay = is_real_number(immediate) ? Math.max(immediate - fadeOut.duration + fadeOut.delay, 0) : Math.max(this._animationDuration - fadeOut.duration + fadeOut.delay, 0); SequencerAnimationEngine.addAnimation(this.id, { target: this.alphaFilter, propertyName: "alpha", to: 0, duration: fadeOut.duration, ease: fadeOut.ease, delay: fadeOut.delay, absolute: true }); return fadeOut.duration + fadeOut.delay; } /** * Fades out the effect at the end of the effect's duration * * @returns {number|*} * @private */ _fadeOutAudio(immediate = false) { if (!this.data.fadeOutAudio || !this.sprite || !this.video) return 0; let fadeOutAudio = this.data.fadeOutAudio; fadeOutAudio.delay = is_real_number(immediate) ? Math.max(immediate - fadeOutAudio.duration + fadeOutAudio.delay, 0) : Math.max( this._animationDuration - fadeOutAudio.duration + fadeOutAudio.delay, 0 ); SequencerAnimationEngine.addAnimation(this.id, { target: this, propertyName: "video.volume", to: 0, duration: fadeOutAudio.duration, ease: fadeOutAudio.ease, delay: fadeOutAudio.delay, absolute: true }); return fadeOutAudio.duration + fadeOutAudio.delay; } /** * Determines the scale to animate from or to * @param property * @returns {{x: number, y: number}} * @private */ _determineScale(property) { let scale2 = { x: this.sprite.scale.x, y: this.sprite.scale.y }; if (is_real_number(property.value)) { scale2.x *= property.value * this.gridSizeDifference * this.flipX; scale2.y *= property.value * this.gridSizeDifference * this.flipY; } else { scale2.x *= property.value.x * this.gridSizeDifference * this.flipX; scale2.y *= property.value.y * this.gridSizeDifference * this.flipY; } return scale2; } /** * Scales the effect in at the start of the effect * * @returns {number|*} * @private */ _scaleIn() { if (!this.data.scaleIn || !this.sprite) return 0; let scaleIn = this.data.scaleIn; let fromScale = this._determineScale(scaleIn); if (this.actualCreationTime - (this.data.creationTimestamp + scaleIn.duration + scaleIn.delay) > 0) return; let toScale = { x: this.sprite.scale.x, y: this.sprite.scale.y }; this.sprite.scale.set(fromScale.x, fromScale.y); SequencerAnimationEngine.addAnimation(this.id, [ { target: this.sprite, propertyName: "scale.x", from: fromScale.x, to: toScale.x, duration: scaleIn.duration, ease: scaleIn.ease, delay: scaleIn.delay, absolute: true }, { target: this.sprite, propertyName: "scale.y", from: fromScale.y, to: toScale.y, duration: scaleIn.duration, ease: scaleIn.ease, delay: scaleIn.delay, absolute: true } ]); return scaleIn.duration + scaleIn.delay; } /** * Scales the effect out at the end of the effect's duration * * @returns {number|*} * @private */ _scaleOut(immediate = false) { if (!this.data.scaleOut || !this.sprite) return 0; let scaleOut = this.data.scaleOut; let scale2 = this._determineScale(scaleOut); scaleOut.delay = is_real_number(immediate) ? Math.max(immediate - scaleOut.duration + scaleOut.delay, 0) : Math.max( this._animationDuration - scaleOut.duration + scaleOut.delay, 0 ); SequencerAnimationEngine.addAnimation(this.id, [ { target: this.sprite, propertyName: "scale.x", to: scale2.x, duration: scaleOut.duration, ease: scaleOut.ease, delay: scaleOut.delay, absolute: true }, { target: this.sprite, propertyName: "scale.y", to: scale2.y, duration: scaleOut.duration, ease: scaleOut.ease, delay: scaleOut.delay, absolute: true } ]); return scaleOut.duration + scaleOut.delay; } /** * Rotates the effect in at the start of the effect * * @returns {number|*} * @private */ _rotateIn() { if (!this.data.rotateIn || !this.sprite) return 0; let rotateIn = this.data.rotateIn; if (this.actualCreationTime - (this.data.creationTimestamp + rotateIn.duration + rotateIn.delay) > 0) return; let original_radians = this.spriteContainer.rotation; this.spriteContainer.rotation = rotateIn.value * (Math.PI / 180); SequencerAnimationEngine.addAnimation( this.id, this._counterAnimateRotation({ target: this.spriteContainer, propertyName: "rotation", to: original_radians, duration: rotateIn.duration, ease: rotateIn.ease, delay: rotateIn.delay, absolute: true }) ); return rotateIn.duration + rotateIn.delay; } /** * Rotates the effect out at the end of the effect's duration * * @returns {number|*} * @private */ _rotateOut(immediate = false) { if (!this.data.rotateOut || !this.sprite) return 0; let rotateOut = this.data.rotateOut; rotateOut.delay = is_real_number(immediate) ? Math.max(immediate - rotateOut.duration + rotateOut.delay, 0) : Math.max( this._animationDuration - rotateOut.duration + rotateOut.delay, 0 ); SequencerAnimationEngine.addAnimation( this.id, this._counterAnimateRotation({ target: this.spriteContainer, propertyName: "rotation", to: rotateOut.value * (Math.PI / 180), duration: rotateOut.duration, ease: rotateOut.ease, delay: rotateOut.delay, absolute: true }) ); return rotateOut.duration + rotateOut.delay; } /** * Causes the effect to move towards the given location * * @returns {number|*} * @private */ _moveTowards() { if (!this.data.moves || !this.sprite) return 0; let moves2 = this.data.moves; let movementDuration = this._animationDuration; if (this.data.moveSpeed) { const distance = distance_between( this.sourcePosition, this.targetPosition ); movementDuration = distance / this.data.moveSpeed * 1e3; } if (this.data.moves.rotate) this._rotateTowards(); const duration = movementDuration - moves2.delay; if (this.actualCreationTime - (this.data.creationTimestamp + duration + moves2.delay) > 0) return; SequencerAnimationEngine.addAnimation(this.id, [ { target: this, propertyName: "position.x", to: this.targetPosition.x, duration, ease: moves2.ease, delay: moves2.delay }, { target: this, propertyName: "position.y", to: this.targetPosition.y, duration, ease: moves2.ease, delay: moves2.delay } ]); return duration + moves2.delay; } /** * If this effect is temporary, this sets the timeout for when the effect should resolve and get removed; * * @private */ _setEndTimeout() { setTimeout(() => { this._resolve(this.data); this.endEffect(); }, this._animationDuration); } _setupTimestampHook(offset2) { if (!this._file?.originalMetadata?.timestamps || this._ended) return; const timestamps = this._file.getTimestamps(); const timestampArray = Array.isArray(timestamps) ? timestamps : [timestamps]; for (const timestamp of timestampArray) { if (!is_real_number(timestamp)) continue; let realTimestamp = timestamp - offset2 / this.mediaPlaybackRate; if (realTimestamp < 0) { realTimestamp += this._endTime; } setTimeout(() => { if (this._ended) return; Hooks.callAll("sequencerEffectTimestamp", this, this._file); if (this.mediaLooping) { const offsets = (this._endTime - this.mediaCurrentTime) * -1e3; this._setupTimestampHook(offsets); } }, realTimestamp); } } } class PersistentCanvasEffect extends CanvasEffect { /** * @OVERRIDE * @returns {Promise} * @private */ async _initialize() { await super._initialize(false); await this._startEffect(); } /** * @OVERRIDE * @returns {Promise} * @private */ async _reinitialize() { await super._reinitialize(false); } /** @OVERRIDE */ _playPresetAnimations() { this._moveTowards(); this._fadeIn(); this._scaleIn(); this._rotateIn(); } /** * Starts the loop of this effect, calculating the difference between the effect's creation time, and the actual * creation time on the client * * @returns {Promise} * @private */ async _startEffect() { if (!this.hasAnimatedMedia) return; let creationTimeDifference = this.actualCreationTime - this.data.creationTimestamp; if (!this.data.noLoop) { return this._startLoop(creationTimeDifference); } if (creationTimeDifference < this._animationDuration) { this.mediaCurrentTime = creationTimeDifference / 1e3; if (this._endTime !== this.mediaDuration) { setTimeout(() => { this.mediaCurrentTime = this._endTime; this.updateTexture(); }, this._endTime * 1e3 - creationTimeDifference); } await this.playMedia(); return; } await this.pauseMedia(); this.mediaCurrentTime = this._endTime; if (this.sprite.texture && this.video) { const oldRenderable = this.renderable; this.renderable = false; setTimeout(() => { this.renderable = oldRenderable; this.updateTexture(); }, 350); } } /** * Kicks off the loop, or just sets the video to loop * * @param creationTimeDifference * @returns {Promise} * @private */ async _startLoop(creationTimeDifference) { this.mediaLooping = this.playNaturally; if (!this._animationTimes.loopStart) { this._loopOffset = creationTimeDifference % this._animationDuration / 1e3; } else if (creationTimeDifference / 1e3 > this._animationTimes.loopStart) { const loopDuration = this._animationTimes.loopEnd - this._animationTimes.loopStart; this._loopOffset = creationTimeDifference % (loopDuration * 1e3) / 1e3; } return this._resetLoop(); } /** * Continuously reset the video to the right time so that the start and end time can be preserved * * @returns {Promise} * @private */ async _resetLoop(firstLoop = true) { if (this._ended) return; let loopWaitTime = 0; if (this._animationTimes.loopStart) { if (this._isEnding) return; this.mediaCurrentTime = (firstLoop ? 0 : this._animationTimes.loopStart) + (this._loopOffset > 0 ? this._loopOffset : 0); loopWaitTime = (this._animationTimes.loopEnd - this.mediaCurrentTime) * 1e3; } else { this.mediaCurrentTime = this._startTime + this._loopOffset; loopWaitTime = this._animationDuration - this._loopOffset * 1e3; } await this.playMedia(); if (this.mediaLooping) { return; } this._resetTimeout = setTimeout(() => { if (this._ended) return; this._loopOffset = 0; this._resetLoop(false); }, loopWaitTime); } /** @OVERRIDE */ _timeoutVisibility() { let creationTimeDifference = this.actualCreationTime - this.data.creationTimestamp; let timeout = creationTimeDifference === 0 && !this.data.animations ? 0 : 50; setTimeout(() => { this._setupHooks(); }, timeout); } /** @OVERRIDE */ _setEndTimeout() { let creationTimeDifference = this.actualCreationTime - this.data.creationTimestamp; if (!this.data.noLoop || creationTimeDifference >= this._animationDuration || !(this.hasAnimatedMedia || this.data.text)) return; setTimeout(() => { this.pauseMedia(); }, this._animationDuration); } /** @OVERRIDE */ async endEffect() { if (this._isEnding) return; this._isEnding = true; let fullWaitDuration = 0; let extraEndDuration = this.data.extraEndDuration ?? 0; if (this._animationTimes?.forcedEnd) { this.mediaCurrentTime = this._animationTimes.forcedEnd; fullWaitDuration = (this.mediaDuration - (this._animationTimes?.forcedEnd ?? 0)) * 1e3; } else if (this._animationTimes?.loopEnd) { fullWaitDuration = (this.mediaDuration - this.mediaCurrentTime) * 1e3; this.mediaLooping = false; extraEndDuration = Math.max(extraEndDuration, fullWaitDuration); } const fromEndCustomAnimations = this._getFromEndCustomAnimations(extraEndDuration); const durations = [ this._fadeOut(extraEndDuration), this._fadeOutAudio(extraEndDuration), this._scaleOut(extraEndDuration), this._rotateOut(extraEndDuration), this.data.extraEndDuration, fullWaitDuration, ...fromEndCustomAnimations.map( (animation2) => animation2.duration + animation2.delay ) ].filter(Boolean); SequencerAnimationEngine.addAnimation(this.id, fromEndCustomAnimations); const waitDuration = Math.max(...durations, 0); this._resolve(waitDuration); return new Promise( (resolve) => setTimeout(() => { super.endEffect(); resolve(this.data); }, waitDuration) ); } } function createShape(shape) { const graphic = new PIXI.LegacyGraphics(); graphic.beginFill( shape?.fillColor ?? 16777215, shape?.fillAlpha ?? shape?.isMask ? 1 : 0 ); graphic.lineStyle( shape.lineSize ?? (shape?.isMask ? 1 : 0), shape?.lineColor ?? 16777215 ); const offsetX = (shape.offset?.x ?? 0) * (shape.offset?.gridUnits ? canvas.grid.size : 1); const offsetY = (shape.offset?.y ?? 0) * (shape.offset?.gridUnits ? canvas.grid.size : 1); const sizeMultiplier = shape.gridUnits ? canvas.grid.size : 1; graphic.offset = { x: offsetX, y: offsetY }; switch (shape.type) { case CONSTANTS.SHAPES.CIRC: graphic.drawCircle( graphic.offset.x, graphic.offset.y, shape.radius * sizeMultiplier ); break; case CONSTANTS.SHAPES.RECT: graphic.drawRect( graphic.offset.x, graphic.offset.y, shape.width * sizeMultiplier, shape.height * sizeMultiplier ); break; case CONSTANTS.SHAPES.ELIP: graphic.drawEllipse( graphic.offset.x, graphic.offset.y, shape.width * sizeMultiplier, shape.height * sizeMultiplier ); break; case CONSTANTS.SHAPES.RREC: graphic.drawRoundedRect( graphic.offset.x, graphic.offset.y, shape.width * sizeMultiplier, shape.height * sizeMultiplier, shape.radius * sizeMultiplier ); break; case CONSTANTS.SHAPES.POLY: graphic.drawPolygon( shape.points.map((point) => { return new PIXI.Point( point[0] * sizeMultiplier, point[1] * sizeMultiplier ); }) ); break; } graphic.alpha = shape.alpha ?? 1; graphic.endFill(); return graphic; } function calculate_missed_position(source, target, twister) { const sourcePosition = get_object_position(source); const sourceDimensions = get_object_dimensions(source, true); if (!target) { const angle2 = twister.random() * Math.PI * 2; let x2 = Math.cos(angle2) * sourceDimensions.width; let y2 = Math.sin(angle2) * sourceDimensions.height; return { x: random_float_between(x2 * 1.5, x2 * 2.5, twister), y: random_float_between(y2 * 1.5, y2 * 2.5, twister) }; } const targetDimensions = get_object_dimensions(target, true); const targetPosition = get_object_position(target); const ray = new Ray(targetPosition, sourcePosition); let startRadians = ray.angle + Math.PI / 2; let endRadians = ray.angle - Math.PI / 2; const sizeCompensation = Math.max( 1, Math.abs(sourceDimensions.width - targetDimensions.height) ); let distance = ray.distance / canvas.grid.size - sizeCompensation; if (distance <= 1) { const angle2 = twister.random() > 0.5 ? ray.angle + Math.PI / 4 : ray.angle - Math.PI / 4; const x2 = Math.cos(angle2) * targetDimensions.width; const y2 = Math.sin(angle2) * targetDimensions.height; return { x: x2, y: y2 }; } distance = Math.max(Math.abs(distance - 15), 6); endRadians -= Math.PI / distance; startRadians += Math.PI / distance; const angle = interpolate(startRadians, endRadians, twister.random()); const x = Math.cos(angle) * targetDimensions.width; const y = Math.sin(angle) * targetDimensions.height; return { x: random_float_between(x * 1.5, x * 2.5, twister), y: random_float_between(y * 1.5, y * 2.5, twister) }; } function get_object_position(obj, { measure = false, exact = false } = {}) { if (obj instanceof CanvasEffect) { return obj.worldPosition; } obj = obj?._object ?? obj.object ?? obj; let pos = {}; if (obj instanceof MeasuredTemplate) { if (measure) { if (obj.document.t === "cone" || obj.document.t === "ray") { pos.x = obj.ray.B.x; pos.y = obj.ray.B.y; } } if (obj.document.t === "rect") { pos.x = obj.x; pos.y = obj.y; if (!exact) { pos.x += Math.abs(obj.shape.width / 2) + obj.shape.x; pos.y += Math.abs(obj.shape.height / 2) + obj.shape.y; } } } else if (obj instanceof Tile) { pos = { x: obj.document.x, y: obj.document.y }; if (!exact) { pos.x += Math.abs(obj.document.width / 2); pos.y += Math.abs(obj.document.height / 2); } } else if (obj instanceof Token) { const halfSize = get_object_dimensions(obj, true); pos = { x: obj.x + halfSize.width, y: obj.y + halfSize.height }; if (exact) { pos.x -= halfSize.width; pos.y -= halfSize.height; } } else if (obj instanceof Drawing) { pos = { x: obj.document.x, y: obj.document.y }; if (!exact) { const halfSize = get_object_dimensions(obj, true); pos.x += halfSize.width; pos.y += halfSize.height; } } pos = { x: pos.x ?? obj?.x ?? obj?.position?.x ?? obj?.position?._x ?? obj?.document?.x ?? obj?.document?.position?.x ?? null, y: pos.y ?? obj?.y ?? obj?.position?.y ?? obj?.position?._y ?? obj?.document?.y ?? obj?.document?.position?.y ?? null, elevation: obj?.elevation ?? obj?.document?.elevation ?? null }; if (pos.x === null) delete pos["x"]; if (pos.y === null) delete pos["y"]; if (pos.elevation === null) delete pos["elevation"]; return pos; } function get_random_offset(target, randomOffset, twister = false) { let { width: width2, height } = get_object_dimensions(target, true); width2 *= randomOffset; height *= randomOffset; return { x: random_float_between(width2 * -1, width2, twister), y: random_float_between(height * -1, height, twister) }; } function get_object_dimensions(inObj, half = false) { inObj = inObj?.object ?? inObj?._object ?? inObj; let width2 = inObj?.hitArea?.width ?? inObj?.w ?? inObj?.shape?.width ?? (inObj?.shape?.radius ? inObj?.shape?.radius * 2 : void 0) ?? inObj?.width ?? canvas.grid.size; let height = inObj?.hitArea?.height ?? inObj?.h ?? inObj?.shape?.height ?? (inObj?.shape?.radius ? inObj?.shape?.radius * 2 : void 0) ?? inObj?.height ?? canvas.grid.size; return { width: width2 / (half ? 2 : 1), height: height / (half ? 2 : 1) }; } const alignments = { "top-left": { x: 0.5, y: 0.5 }, top: { x: 0, y: 0.5 }, "top-right": { x: -0.5, y: 0.5 }, left: { x: 0.5, y: 0 }, center: { x: 0, y: 0 }, right: { x: -0.5, y: 0 }, "bottom-left": { x: 0.5, y: -0.5 }, bottom: { x: 0, y: -0.5 }, "bottom-right": { x: -0.5, y: -0.5 } }; function align({ context, spriteWidth, spriteHeight, align: align2, edge } = {}) { let { width: width2, height } = get_object_dimensions(context); const alignRatio = alignments[align2]; const offset2 = { x: interpolate(width2 * -0.5, width2 * 0.5, alignRatio.x + 0.5), y: interpolate(height * -0.5, height * 0.5, alignRatio.y + 0.5) }; return { x: offset2.x + (edge && edge !== "on" ? spriteWidth * alignRatio.x * (edge === "outer" ? 1 : -1) : 0), y: offset2.y + (edge && edge !== "on" ? spriteHeight * alignRatio.y * (edge === "outer" ? 1 : -1) : 0) }; } function is_object_canvas_data(inObj) { if (typeof inObj !== "object") return false; const keys = Object.keys(inObj); keys.sort(); return keys.includes("x") && keys.includes("y") || keys.includes("height") && keys.includes("width") && keys.includes("x") && keys.includes("y"); } function get_object_canvas_data(inObject, measure = false) { inObject = inObject?.object ?? inObject; return { ...get_object_position(inObject, { measure }), ...get_object_dimensions(inObject?.mesh ?? inObject?.tile ?? inObject), elevation: get_object_elevation(inObject), uuid: inObject?.document?.uuid ?? inObject?.uuid, cachedLocation: true }; } function get_object_elevation(inObject) { return inObject?.document?.elevation ?? inObject?.elevation ?? 0; } function get_mouse_position(snapToGrid = false, gridSnap = 2) { const pos = getCanvasMouse().getLocalPosition(canvas.app.stage); return !snapToGrid ? new PIXI.Point(pos.x, pos.y) : canvas.grid.getSnappedPosition(pos.x, pos.y, gridSnap); } function distance_between(p1, p2) { return new Ray(p1, p2).distance; } function is_position_within_bounds(inPosition, inElement, relativeTo) { const localPosition = inElement.toLocal(inPosition, relativeTo); return inElement.getLocalBounds().contains(localPosition.x, localPosition.y); } function rotate_coordinate(p1, p2, radians) { let cos = Math.cos(radians); let sin = Math.sin(radians); let nx = cos * (p2.x - p1.x) + sin * (p2.y - p1.y) + p1.x; let ny = cos * (p2.y - p1.y) - sin * (p2.x - p1.x) + p1.y; return [nx, ny]; } function get_closest_token(inPosition, { minimumDistance = false } = {}) { let tokens = Array.from(canvas.scene.tokens); if (minimumDistance) { tokens = tokens.filter( (token) => distance_between(get_object_position(token), inPosition) <= minimumDistance ); } tokens.sort((a, b) => { return distance_between(get_object_position(a), inPosition) - distance_between(get_object_position(b), inPosition); }); return tokens?.[0] ?? false; } function rotateAroundPoint(cx, cy, x, y, radians) { const cos = Math.cos(radians); const sin = Math.sin(radians); const nx = cos * (x - cx) + sin * (y - cy) + cx; const ny = cos * (y - cy) - sin * (x - cx) + cy; return { x: nx, y: ny }; } function validateAnimation(inTarget, inPropertyName, inOptions) { if (typeof inPropertyName !== "string") { return `inPropertyName must be of type string`; } if (typeof inTarget !== "string") { return `inTarget must be of type string`; } if (!is_real_number(inOptions.from)) { return `inOptions.from must be of type number`; } if (!is_real_number(inOptions.to)) { return `inOptions.to must be of type number`; } if (!is_real_number(inOptions.duration)) { return `inOptions.duration must be of type number`; } if (inOptions?.delay !== void 0 && !is_real_number(inOptions.delay)) { return `inOptions.delay must be of type number`; } if (inOptions?.ease !== void 0 && typeof inOptions.ease !== "string") { return `inOptions.ease must be of type string`; } if (inOptions?.fromEnd !== void 0 && typeof inOptions.fromEnd !== "boolean") { return `inOptions.fromEnd must be of type boolean`; } if (inOptions?.gridUnits !== void 0) { if (typeof inOptions.gridUnits !== "boolean") { return `inOptions.gridUnits must be of type boolean`; } if (inOptions.gridUnits && ![ "position.x", "position.y", "scale.x", "scale.y", "height", "width" ].includes(inPropertyName)) { return `if inOptions.gridUnits is true, inPropertyName must be position.x, position.y, scale.x, scale.y, width, or height`; } } return { target: inTarget, propertyName: inPropertyName, from: inOptions?.from, to: inOptions?.to, duration: inOptions?.duration ?? 0, delay: inOptions?.delay ?? 0, ease: inOptions?.ease ?? "linear", looping: false, fromEnd: inOptions?.fromEnd ?? false, gridUnits: inOptions?.gridUnits ?? false }; } function validateLoopingAnimation(inTarget, inPropertyName, inOptions) { if (typeof inPropertyName !== "string") { return `inPropertyName must be of type string`; } if (typeof inTarget !== "string") { return `inTarget must be of type string`; } if (!inOptions?.values) { if (!inOptions?.from === void 0 || !inOptions?.to === void 0) { return `if inOptions.values is not set, you must provide inOptions.from and inOptions.to`; } if (!is_real_number(inOptions.from)) { return `inOptions.from must be of type number`; } if (!is_real_number(inOptions.to)) { return `inOptions.to must be of type number`; } inOptions.values = [inOptions?.from, inOptions?.to]; delete inOptions.from; delete inOptions.to; } else { if (!Array.isArray(inOptions.values)) { return `inOptions.values must be of type array`; } inOptions.values.forEach((value) => { if (!is_real_number(value)) { return `values in inOptions.keys must be of type number`; } }); } if (!is_real_number(inOptions.duration)) { return `inOptions.duration must be of type number`; } if (inOptions?.delay !== void 0 && !is_real_number(inOptions.delay)) { return `inOptions.delay must be of type number`; } if (inOptions?.ease !== void 0 && typeof inOptions.ease !== "string") { return `inOptions.ease must be of type string`; } if (inOptions?.loops !== void 0 && !is_real_number(inOptions.loops)) { return `inOptions.loops must be of type number`; } if (inOptions?.pingPong !== void 0 && typeof inOptions.pingPong !== "boolean") { return `inOptions.pingPong must be of type boolean`; } if (inOptions?.gridUnits !== void 0) { if (typeof inOptions.gridUnits !== "boolean") { return `inOptions.gridUnits must be of type boolean`; } if (inOptions.gridUnits && ![ "position.x", "position.y", "scale.x", "scale.y", "height", "width" ].includes(inPropertyName)) { return `if inOptions.gridUnits is true, inPropertyName must be position.x, position.y, scale.x, scale.y, width, or height`; } } return { target: inTarget, propertyName: inPropertyName, values: inOptions?.values, duration: inOptions?.duration ?? 0, delay: inOptions?.delay ?? 0, ease: inOptions?.ease ?? "linear", looping: true, loops: inOptions?.loops, indefinite: inOptions?.loops === void 0 || !is_real_number(inOptions?.loops), pingPong: inOptions?.pingPong ?? false, gridUnits: inOptions?.gridUnits ?? false }; } const PlayerSettings = { file: { label: "SEQUENCER.Player.Option.File", store: writable$1(""), default: "" }, scale: { label: "SEQUENCER.Player.Option.Scale", store: writable$1(1), default: 1 }, users: { label: "SEQUENCER.Player.Option.ForUsers", store: writable$1([]), default: [] }, belowTokens: { label: "SEQUENCER.Player.Option.BelowTokens", store: writable$1(false), default: false }, snapToGrid: { label: "SEQUENCER.Player.Option.SnapToGrid", store: writable$1(false), default: false }, rotation: { label: "SEQUENCER.Player.Option.Rotation", store: writable$1(0), default: 0 }, randomRotation: { label: "SEQUENCER.Player.Option.Randomize", store: writable$1(false), default: false }, fadeIn: { label: "SEQUENCER.Player.Option.FadeIn", store: writable$1(0), default: 0 }, fadeOut: { label: "SEQUENCER.Player.Option.FadeOut", store: writable$1(0), default: 0 }, scaleIn: { label: "SEQUENCER.Player.Option.ScaleIn", store: writable$1(0), default: 0 }, scaleOut: { label: "SEQUENCER.Player.Option.ScaleOut", store: writable$1(0), default: 0 }, mirrorX: { label: "SEQUENCER.Player.Option.MirrorX", store: writable$1(false), default: false }, mirrorY: { label: "SEQUENCER.Player.Option.MirrorY", store: writable$1(false), default: false }, randomMirrorX: { label: "SEQUENCER.Player.Option.Randomize", store: writable$1(false), default: false }, randomMirrorY: { label: "SEQUENCER.Player.Option.Randomize", store: writable$1(false), default: false }, offsetX: { label: "SEQUENCER.Player.Option.OffsetX", store: writable$1(0), default: 0 }, offsetY: { label: "SEQUENCER.Player.Option.OffsetY", store: writable$1(0), default: 0 }, offsetGridUnits: { label: "SEQUENCER.Player.Option.GridUnits", store: writable$1(false), default: false }, randomOffsetAmount: { label: "SEQUENCER.Player.Option.RandomOffset", store: writable$1(0), default: 0 }, randomOffset: { label: "SEQUENCER.Player.Option.Randomize", store: writable$1(false), default: false }, preload: { label: "SEQUENCER.Player.Option.Preload", store: writable$1(false), default: false }, moveTowards: { label: "SEQUENCER.Player.Option.DragBehavior", label_off: "SEQUENCER.Player.Option.DragStretch", label_on: "SEQUENCER.Player.Option.DragMove", store: writable$1(false), default: false }, moveSpeed: { label: "SEQUENCER.Player.Option.MoveSpeed", store: writable$1(0), default: 0 }, attachTo: { label: "SEQUENCER.Player.Option.AttachTo", store: writable$1(false), default: false, callback: (e) => { EffectPlayer.sourceAttach = e.target.checked; } }, stretchToAttach: { label: "SEQUENCER.Player.Option.StretchToAttach", store: writable$1(false), default: false, callback: (e) => { EffectPlayer.targetAttach = e.target.checked; } }, persist: { label: "SEQUENCER.Player.Option.Persist", store: writable$1(false), default: false }, repeat: { label: "SEQUENCER.Player.Option.Repeat", store: writable$1(false), default: false }, repetitions: { label: "SEQUENCER.Player.Option.Repetitions", store: writable$1(1), default: 1 }, repeatDelayMin: { label: "SEQUENCER.Player.Option.DelayMin", store: writable$1(200), default: 200 }, repeatDelayMax: { label: "SEQUENCER.Player.Option.DelayMax", store: writable$1(400), default: 400 } }; PlayerSettings.export = () => { return Object.fromEntries( Object.entries(PlayerSettings).map((entry) => { return [entry[0], get_store_value(entry[1].store)]; }) ); }; PlayerSettings.import = (settings) => { Object.entries(PlayerSettings).forEach((entry) => { if (settings?.[entry[0]] !== void 0) { entry[1].store.set(settings?.[entry[0]]); } else if (entry[1]?.default !== void 0) { entry[1].store.set(entry[1].default); } }); }; PlayerSettings.getPresets = () => { return Object.keys(game.settings.get(CONSTANTS.MODULE_NAME, "effectPresets")); }; PlayerSettings.loadPreset = (name) => { const effectPresets = game.settings.get( CONSTANTS.MODULE_NAME, "effectPresets" ); return PlayerSettings.import(effectPresets[name]); }; PlayerSettings.savePreset = async (name) => { const newName = await promptNewPresetName(name); if (!newName) return; const effectPresets = game.settings.get( CONSTANTS.MODULE_NAME, "effectPresets" ); effectPresets[newName] = PlayerSettings.export(); return game.settings.set( CONSTANTS.MODULE_NAME, "effectPresets", effectPresets ); }; PlayerSettings.copyPreset = async (name) => { const newName = await promptNewPresetName(name, true); if (!newName) return; const effectPresets = game.settings.get( CONSTANTS.MODULE_NAME, "effectPresets" ); effectPresets[newName] = PlayerSettings.export(); return game.settings.set( CONSTANTS.MODULE_NAME, "effectPresets", effectPresets ); }; PlayerSettings.deletePreset = (name) => { const effectPresets = game.settings.get( CONSTANTS.MODULE_NAME, "effectPresets" ); delete effectPresets[name]; return game.settings.set( CONSTANTS.MODULE_NAME, "effectPresets", effectPresets ); }; async function promptNewPresetName(inName, copy = false) { const effectPresets = game.settings.get( CONSTANTS.MODULE_NAME, "effectPresets" ); let title = copy ? game.i18n.localize("SEQUENCER.Player.CopyPresetTitle") : game.i18n.localize("SEQUENCER.Player.CreateNewPresetTitle"); let presetName = await new Promise((resolve) => { new Dialog({ title, content: `

`, buttons: { okay: { icon: '', label: game.i18n.localize("SEQUENCER.OK"), callback: async (html) => { let name = html.find("#newPresetName").val(); if (name === "" || !name) { name = false; } resolve(name); } }, cancel: { icon: '', label: game.i18n.localize("SEQUENCER.Cancel"), callback: () => { resolve(false); } } }, close: () => { }, render: (html) => { html.find("#newPresetName").val(inName).focus(); } }).render(true); }); if (presetName) { if (presetName.toLowerCase() === "default") { Dialog.prompt({ title: game.i18n.localize("SEQUENCER.Player.DefaultErrorTitle"), content: `

${game.i18n.localize( "SEQUENCER.Player.DefaultErrorContent" )}

`, label: game.i18n.localize("SEQUENCER.OK"), callback: () => { } }); return false; } if (effectPresets[presetName]) { const overwrite = await Dialog.confirm({ title: game.i18n.localize("SEQUENCER.Player.OverwritePresetTitle"), content: `

${game.i18n.format( "SEQUENCER.Player.OverwritePresetContent", { preset_name: presetName } )}

` }); if (!overwrite) { presetName = await promptPresetName(presetName); } } } return presetName; } PlayerSettings.migrateOldPresets = () => { if (!game.user.isGM) return; const effectPresets = game.settings.get( CONSTANTS.MODULE_NAME, "effectPresets" ); const presetsToUpdate = Object.values(effectPresets).filter((preset) => { return !preset?.version; }); if (!presetsToUpdate.length) return; const newEffectPresets = Object.fromEntries( Object.entries(effectPresets).map(([name, preset]) => { if (preset?.version) return [name, preset]; preset.version = game.modules.get(CONSTANTS.MODULE_NAME).version; if (preset.repetitions > 1) { preset.repeat = true; } if (preset.randomMirrorY) { preset.mirrorY = true; } if (preset.randomOffset) { preset.randomOffsetAmount = 1; preset.offsetGridUnits = true; } return [name, preset]; }) ); return game.settings.set( CONSTANTS.MODULE_NAME, "effectPresets", newEffectPresets ); }; const InteractionManager = { startDragPosition: false, state: { LeftMouseDown: false, RightMouseDown: false, Dragging: false }, get isLayerActive() { return canvas.sequencerInterfaceLayer.active; }, initialize() { window.addEventListener("mousedown", (event) => { if (!canvas.ready) return; if (!this.isLayerActive) return; const hover = document.elementFromPoint(event.clientX, event.clientY); if (!hover || hover.id !== "board") return; const button = event.button; if (!(button === 0 || button === 2)) return; if (button === 0) { this.state.LeftMouseDown = true; this._propagateEvent("mouseLeftDown"); } if (button === 2) { this.state.RightMouseDown = true; this._propagateEvent("mouseRightDown"); } }); window.addEventListener("mouseup", (event) => { if (!canvas.ready) return; if (!this.isLayerActive) return; const hover = document.elementFromPoint(event.clientX, event.clientY); if (!hover || hover.id !== "board") return; if (document.activeElement.tagName !== "BODY") return; const button = event.button; if (!(button === 0 || button === 2)) return; if (button === 0) { this.state.LeftMouseDown = false; this._propagateEvent("mouseLeftUp"); this.state.Dragging = false; this.startDragPosition = false; } if (button === 2) { this.state.RightMouseDown = false; this._propagateEvent("mouseRightUp"); this.state.Dragging = false; this.startDragPosition = false; } }); window.addEventListener("mousemove", (event) => { if (!canvas.ready) return; const hover = document.elementFromPoint(event.clientX, event.clientY); if (!hover || hover.id !== "board") return; if (!this.isLayerActive) return; this._propagateEvent("mouseMove"); if (this.state.LeftMouseDown && !this.startDragPosition) { this.startDragPosition = get_mouse_position(); } if (this.state.LeftMouseDown && !this.state.Dragging) { const distance = distance_between( this.startDragPosition, get_mouse_position() ); this.state.Dragging = distance > 20; } }); EffectPlayer.initialize(); SelectionManager.initialize(); }, tearDown() { EffectPlayer.tearDown(); SelectionManager.tearDown(); }, _propagateEvent(eventName) { if (EffectPlayer.isActive && EffectPlayer[eventName]) { EffectPlayer[eventName](); } if (SelectionManager.isActive && SelectionManager[eventName]) { SelectionManager[eventName](); } } }; const EffectPlayer = { sequenceBuffer: [], playMany: false, playManySequenced: false, cursorPos: false, startPos: false, endPos: false, get snapLocationToGrid() { return get_store_value(PlayerSettings.snapToGrid.store); }, get sourceAttach() { return get_store_value(PlayerSettings.attachTo.store); }, get targetAttach() { return get_store_value(PlayerSettings.stretchToAttach.store); }, sourceAttachFound: false, targetAttachFound: false, get isActive() { return InteractionManager.isLayerActive && game?.activeTool === "play-effect"; }, /** * Opens the Sequencer Effects UI with the player tab open */ show() { return EffectsUIApp.show({ tab: "player" }); }, initialize() { this.layer = canvas.sequencerInterfaceLayer; }, tearDown() { this._reset(); }, /** * Mouse events */ mouseLeftDown() { this._evaluateStartPosition(); }, mouseLeftUp() { if (!this.startPos) return; this._playEffect(); this.startPos = false; this.endPos = false; this.sourceAttachFound = false; this.targetAttachFound = false; }, mouseRightUp() { this._reset(); }, mouseMove() { this._evaluateCursorPosition(); if (InteractionManager.state.Dragging) { this._evaluateEndPosition(); } }, /** * Hotkeys */ playManyUp() { this._playEffects(); this._reset(); }, /** * Private methods */ _evaluatePosition(attach = false) { let position = get_mouse_position(this.snapLocationToGrid); const attachToObject = attach ? get_closest_token(position, { minimumDistance: canvas.grid.size }) : false; let attachFound = false; if (attachToObject) { attachFound = true; position = get_object_position(attachToObject); } return [position, attachFound]; }, _evaluateCursorPosition() { const attach = InteractionManager.state.Dragging ? this.targetAttach : this.sourceAttach; [this.cursorPos] = this._evaluatePosition(attach); }, _evaluateStartPosition() { if (this.startPos) return; [this.startPos, this.sourceAttachFound] = this._evaluatePosition( this.sourceAttach ); }, _evaluateEndPosition() { [this.endPos, this.targetAttachFound] = this._evaluatePosition( this.targetAttach ); }, _reset() { if (!this.layer) return; this.startPos = false; this.endPos = false; this.sourceAttachFound = false; this.targetAttachFound = false; this.sequenceBuffer = []; this._evaluateCursorPosition(); }, async _playEffect() { const settings = foundry.utils.mergeObject(PlayerSettings.export(), { ...InteractionManager.state, startPos: this.startPos, endPos: this.endPos }); if (!settings.users.length || settings.users?.[0] === "all") settings.users = []; if (settings.file === "") return; if (!(Sequencer.Database.entryExists(settings.file) || await SequencerFileCache.srcExists(settings.file))) { throw custom_error( "Sequencer", `Sequencer Player | Could not find file or database entry: ${settings.file}` ); } if (settings.preload) { await Sequencer.Preloader.preloadForClients(settings.file); } const sequence = this.sequenceBuffer.length > 0 && this.playManySequenced ? this.sequenceBuffer[this.sequenceBuffer.length - 1] : new Sequence(); const effect = sequence.effect().file(settings.file).forUsers(settings.users).mirrorX(settings.mirrorX && !settings.randomMirrorX).mirrorY(settings.mirrorY && !settings.randomMirrorY).randomizeMirrorX(settings.randomMirrorX).randomizeMirrorY(settings.randomMirrorY).persist(settings.persist).belowTokens(settings.belowTokens); if (settings.repeat) { effect.repeats( settings.repetitions, settings.repeatDelayMin, settings.repeatDelayMax ); } if (settings.fadeIn > 0) effect.fadeIn(settings.fadeIn); if (settings.fadeOut > 0) effect.fadeOut(settings.fadeOut); const offsetData = settings.randomOffset ? { offset: { x: settings.offsetX, y: settings.offsetY }, gridUnits: settings.offsetGridUnits } : {}; const offsetSource = !settings.Dragging ? offsetData : {}; const attachToObject = settings.attachTo ? get_closest_token(settings.startPos, { minimumDistance: canvas.grid.size }) : false; if (attachToObject) { effect.attachTo(attachToObject, { randomOffset: settings.randomOffset && !settings.Dragging ? settings.randomOffsetAmount : false, ...offsetSource }); } else { effect.atLocation(settings.startPos, { randomOffset: settings.randomOffset && !settings.Dragging ? settings.randomOffsetAmount : false, ...offsetSource }); } if (settings.persist && settings.name && settings.name !== "" && settings.name !== "default" && settings.name !== "new") { effect.name("Preset: " + settings.name); } if (settings.Dragging) { if (settings.moveTowards) { effect.moveTowards(settings.endPos, { randomOffset: settings.randomOffset ? settings.randomOffsetAmount : false, ...offsetData }); if (settings.moveSpeed) { effect.moveSpeed(settings.moveSpeed); } } else { let target = settings.stretchToAttach ? get_closest_token(settings.endPos, { minimumDistance: canvas.grid.size }) : settings.endPos; effect.stretchTo(target, { attachTo: settings.stretchToAttach, randomOffset: settings.randomOffset ? settings.randomOffsetAmount : false, ...offsetData }); } } if (!settings.Dragging || settings.Dragging && settings.moveTowards) { effect.scale(settings.scale); if (settings.scaleIn > 0) effect.scaleIn(0, settings.scaleIn, { ease: "easeInOutSine" }); if (settings.scaleOut > 0) effect.scaleOut(0, settings.scaleOut, { ease: "easeInOutSine" }); effect.randomRotation(settings.randomRotation); } if (this.playManySequenced) { effect.waitUntilFinished(); } if (!this.playManySequenced || this.sequenceBuffer.length === 0) { this.sequenceBuffer.push(sequence); } if (!this.playMany && !this.playManySequenced) this._playEffects(); }, _playEffects() { this.sequenceBuffer.forEach((sequence) => sequence.play()); this.sequenceBuffer = []; } }; const SelectionManager = { selectedEffect: false, hoveredEffects: /* @__PURE__ */ new Set(), suggestedProperties: false, sourceOrTarget: false, dragOffset: false, hoveredEffectUI: false, get snapToGrid() { return get_store_value(PlayerSettings.snapToGrid.store); }, set snapToGrid(bool) { PlayerSettings.snapToGrid.store.set(bool); }, set attachToTarget(bool) { PlayerSettings.stretchToAttach.store.set(bool); }, get attachToTarget() { return get_store_value(PlayerSettings.stretchToAttach.store); }, get isActive() { return InteractionManager.isLayerActive && game?.activeTool === "select-effect"; }, get effects() { return SequencerEffectManager.effects.filter( (effect) => effect.userCanDelete ); }, initialize() { this.layer = canvas.sequencerInterfaceLayer; }, tearDown() { this._reset(); this.hoveredEffects = /* @__PURE__ */ new Set(); }, sourcePointSelected() { this.sourceOrTarget = "source"; }, targetPointSelected() { this.sourceOrTarget = "target"; }, /** * Mouse Events */ mouseLeftDown() { if (!this.selectedEffect) { return this._selectEffects(); } if (!this.hoveredEffects.size) { this._reset(); } }, mouseRightDown() { }, mouseLeftUp() { if (!InteractionManager.state.Dragging) { return this._selectEffects(); } if (!InteractionManager.state.Dragging || !this.selectedEffect || !this.suggestedProperties) return; this._updateEffect(); }, mouseRightUp() { InteractionManager.state.LeftMouseDown = false; this.suggestedProperties = false; this.sourceOrTarget = false; this.dragOffset = false; }, mouseMove() { this._evaluateHoveredEffects(); if (InteractionManager.state.LeftMouseDown && !InteractionManager.state.RightMouseDown) { this._evaluateEffectPositionUpdate(); } }, /** * Hotkeys */ async delete() { if (!this.selectedEffect) return; await SequencerEffectManager.endEffects({ effects: this.selectedEffect }); this.selectedEffect = false; }, attachToTargetDown() { if (InteractionManager.state.LeftMouseDown && !InteractionManager.state.RightMouseDown) { this._evaluateEffectPositionUpdate(); } }, /** * Private methods */ _selectEffects() { this._reset(); if (!this.hoveredEffects.size) return; const firstElement = Array.from(this.hoveredEffects)[0]; this.selectedEffect = !firstElement.selected ? firstElement : false; }, _evaluateHoveredEffects() { const position = get_mouse_position(); this.hoveredEffects = this.effects.filter( (effect) => effect.isPositionWithinBounds(position) ); this.hoveredEffects.sort((a, b) => { return a.data.layer !== b.data.zIndex ? a.data.zIndex - b.data.zIndex : a.data.layer - b.data.zIndex; }); this.hoveredEffects = new Set(this.hoveredEffects); }, _evaluateEffectPositionUpdate() { if (!this.selectedEffect) return; if (this.selectedEffect.data.stretchTo && !this.sourceOrTarget) { return; } let showCursor = false; let showPoint = this.snapToGrid; let position = get_mouse_position(this.snapToGrid); if (!this.selectedEffect.data.stretchTo && !this.dragOffset) { this.dragOffset = { x: position.x - this.selectedEffect.position.x, y: position.y - this.selectedEffect.position.y }; } if (this.attachToTarget) { const obj = get_closest_token(position, { minimumDistance: canvas.grid.size }); if (obj) { position = get_object_position(obj); showCursor = true; showPoint = false; } } if (this.dragOffset && !showCursor && !this.snapToGrid) { position.x -= this.dragOffset.x; position.y -= this.dragOffset.y; } const color = (this.sourceOrTarget || "source") === "source" ? CONSTANTS.COLOR.PRIMARY : CONSTANTS.COLOR.SECONDARY; this.suggestedProperties = { position, showCursor, showPoint, color }; }, _updateEffect() { if (!this.selectedEffect) return; const updates = { attachTo: this.selectedEffect.data.attachTo, stretchTo: this.selectedEffect.data.stretchTo }; const obj = this.attachToTarget ? get_closest_token(this.suggestedProperties.position, { minimumDistance: canvas.grid.size, type: TokenDocument }) : false; let objUuid = obj ? get_object_identifier(obj) : false; if (this.sourceOrTarget === "source") { if (!updates.attachTo) { updates.attachTo = { active: false, align: "center", rotation: true, bindVisibility: true, bindAlpha: true }; } updates.attachTo = foundry.utils.mergeObject(updates.attachTo || {}, { active: this.attachToTarget && !!objUuid }); if (this.attachToTarget && objUuid) { updates.source = objUuid; } else { updates["source"] = this.suggestedProperties.position; } } else if (this.sourceOrTarget === "target") { updates.stretchTo.attachTo = this.attachToTarget && !!objUuid; if (this.attachToTarget && objUuid) { updates.target = objUuid; } else { updates["target"] = this.suggestedProperties.position; } } else { updates["source"] = this.suggestedProperties.position; } this.selectedEffect.update(updates); this.suggestedProperties = false; this.sourceOrTarget = false; this.dragOffset = false; }, _reset() { this.selectedEffect = false; this.suggestedProperties = false; this.sourceOrTarget = false; this.dragOffset = false; } }; const EffectEntry_svelte_svelte_type_style_lang = ""; function create_fragment$c(ctx) { let div1; let button; let t0; let div0; let t1_value = ( /*getEffectName*/ ctx[1]( /*effect*/ ctx[0] ) + "" ); let t1; let mounted; let dispose; return { c() { div1 = element("div"); button = element("button"); button.innerHTML = ``; t0 = space(); div0 = element("div"); t1 = text$1(t1_value); attr(button, "class", "btn_end svelte-ese-1fyjvdk"); attr(button, "type", "button"); attr(div0, "class", "effect-text hover-text svelte-ese-1fyjvdk"); attr(div1, "class", "effect hover-highlight svelte-ese-1fyjvdk"); }, m(target, anchor) { insert(target, div1, anchor); append(div1, button); append(div1, t0); append(div1, div0); append(div0, t1); if (!mounted) { dispose = [ listen( button, "click", /*endEffect*/ ctx[4] ), listen( div1, "mouseleave", /*mouseLeave*/ ctx[3] ), listen( div1, "mouseover", /*mouseOver*/ ctx[2] ) ]; mounted = true; } }, p(ctx2, [dirty]) { if (dirty & /*effect*/ 1 && t1_value !== (t1_value = /*getEffectName*/ ctx2[1]( /*effect*/ ctx2[0] ) + "")) set_data(t1, t1_value); }, i: noop, o: noop, d(detaching) { if (detaching) detach(div1); mounted = false; run_all(dispose); } }; } function instance$c($$self, $$props, $$invalidate) { let { effect } = $$props; function getEffectName(effect2) { let effectName = "Unknown effect"; if (effect2.data.file) { effectName = effect2.data.file.split("\\").pop().split("/").pop(); } else if (effect2.data.text) { effectName = "Text: " + effect2.data.text.text; } else { effectName = "Shape: " + effect2.data.shapes[0].type; } effectName = effect2.data.name ? `${effect2.data.name} (${effectName})` : effectName; if (effect2.data.creatorUserId !== game.userId) { let user_name = game.users.get(effect2.data.creatorUserId)?.name; let formattedUsername = user_name ? localize("SEQUENCER.ManagerPlayersEffect", { user_name }) : localize("SEQUENCER.ManagerUnknownEffect"); effectName += ` (${formattedUsername})`; } return effectName; } function mouseOver() { SelectionManager.hoveredEffectUI = effect; } function mouseLeave() { SelectionManager.hoveredEffectUI = false; } function endEffect() { SequencerEffectManager.endEffects({ effects: [effect] }); } $$self.$$set = ($$props2) => { if ("effect" in $$props2) $$invalidate(0, effect = $$props2.effect); }; return [effect, getEffectName, mouseOver, mouseLeave, endEffect]; } class EffectEntry extends SvelteComponent { constructor(options) { super(); init(this, options, instance$c, create_fragment$c, safe_not_equal, { effect: 0 }); } } const SoundEntry_svelte_svelte_type_style_lang = ""; function create_fragment$b(ctx) { let div1; let button; let t0; let div0; let t1_value = ( /*sound*/ ctx[0].src.split("/").slice(-1) + "" ); let t1; let mounted; let dispose; return { c() { div1 = element("div"); button = element("button"); button.innerHTML = ``; t0 = space(); div0 = element("div"); t1 = text$1(t1_value); attr(button, "class", "btn_end svelte-ese-1fyjvdk"); attr(button, "type", "button"); attr(div0, "class", "effect-text hover-text svelte-ese-1fyjvdk"); attr(div1, "class", "effect hover-highlight svelte-ese-1fyjvdk"); }, m(target, anchor) { insert(target, div1, anchor); append(div1, button); append(div1, t0); append(div1, div0); append(div0, t1); if (!mounted) { dispose = listen( button, "click", /*endSound*/ ctx[1] ); mounted = true; } }, p(ctx2, [dirty]) { if (dirty & /*sound*/ 1 && t1_value !== (t1_value = /*sound*/ ctx2[0].src.split("/").slice(-1) + "")) set_data(t1, t1_value); }, i: noop, o: noop, d(detaching) { if (detaching) detach(div1); mounted = false; dispose(); } }; } function instance$b($$self, $$props, $$invalidate) { let { id } = $$props; let { sound } = $$props; function endSound() { SequencerAudioHelper.stop([id], true); } $$self.$$set = ($$props2) => { if ("id" in $$props2) $$invalidate(2, id = $$props2.id); if ("sound" in $$props2) $$invalidate(0, sound = $$props2.sound); }; return [sound, endSound, id]; } class SoundEntry extends SvelteComponent { constructor(options) { super(); init(this, options, instance$b, create_fragment$b, safe_not_equal, { id: 2, sound: 0 }); } } const Manager_svelte_svelte_type_style_lang = ""; function get_each_context$3(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[10] = list[i][0]; child_ctx[11] = list[i][1]; return child_ctx; } function get_each_context_1$1(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[14] = list[i]; return child_ctx; } function get_each_context_2$1(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[14] = list[i]; return child_ctx; } function create_if_block_5(ctx) { let div; let h2; return { c() { div = element("div"); h2 = element("h2"); h2.textContent = `${localize("SEQUENCER.Manager.NothingPlaying")}`; attr(div, "class", "no-effects"); }, m(target, anchor) { insert(target, div, anchor); append(div, h2); }, p: noop, d(detaching) { if (detaching) detach(div); } }; } function create_if_block_1(ctx) { let button; let t1; let div; let t2; let t3; let current; let mounted; let dispose; let if_block0 = ( /*persistentEffects*/ ctx[2].length && create_if_block_4(ctx) ); let if_block1 = ( /*temporaryEffects*/ ctx[1].length && /*persistentEffects*/ ctx[2].length && create_if_block_3() ); let if_block2 = ( /*temporaryEffects*/ ctx[1].length && create_if_block_2(ctx) ); return { c() { button = element("button"); button.textContent = `${localize("SEQUENCER.Manager.EndAllEffects")}`; t1 = space(); div = element("div"); if (if_block0) if_block0.c(); t2 = space(); if (if_block1) if_block1.c(); t3 = space(); if (if_block2) if_block2.c(); attr(button, "class", "w-100 end-all-effects mb-2 svelte-ese-nc5j73"); attr(button, "type", "button"); attr(div, "class", "effects svelte-ese-nc5j73"); }, m(target, anchor) { insert(target, button, anchor); insert(target, t1, anchor); insert(target, div, anchor); if (if_block0) if_block0.m(div, null); append(div, t2); if (if_block1) if_block1.m(div, null); append(div, t3); if (if_block2) if_block2.m(div, null); current = true; if (!mounted) { dispose = listen( button, "click", /*endAllEffects*/ ctx[6] ); mounted = true; } }, p(ctx2, dirty) { if ( /*persistentEffects*/ ctx2[2].length ) { if (if_block0) { if_block0.p(ctx2, dirty); if (dirty & /*persistentEffects*/ 4) { transition_in(if_block0, 1); } } else { if_block0 = create_if_block_4(ctx2); if_block0.c(); transition_in(if_block0, 1); if_block0.m(div, t2); } } else if (if_block0) { group_outros(); transition_out(if_block0, 1, 1, () => { if_block0 = null; }); check_outros(); } if ( /*temporaryEffects*/ ctx2[1].length && /*persistentEffects*/ ctx2[2].length ) { if (if_block1) ; else { if_block1 = create_if_block_3(); if_block1.c(); if_block1.m(div, t3); } } else if (if_block1) { if_block1.d(1); if_block1 = null; } if ( /*temporaryEffects*/ ctx2[1].length ) { if (if_block2) { if_block2.p(ctx2, dirty); if (dirty & /*temporaryEffects*/ 2) { transition_in(if_block2, 1); } } else { if_block2 = create_if_block_2(ctx2); if_block2.c(); transition_in(if_block2, 1); if_block2.m(div, null); } } else if (if_block2) { group_outros(); transition_out(if_block2, 1, 1, () => { if_block2 = null; }); check_outros(); } }, i(local) { if (current) return; transition_in(if_block0); transition_in(if_block2); current = true; }, o(local) { transition_out(if_block0); transition_out(if_block2); current = false; }, d(detaching) { if (detaching) detach(button); if (detaching) detach(t1); if (detaching) detach(div); if (if_block0) if_block0.d(); if (if_block1) if_block1.d(); if (if_block2) if_block2.d(); mounted = false; dispose(); } }; } function create_if_block_4(ctx) { let h2; let t1; let div; let each_blocks = []; let each_1_lookup = /* @__PURE__ */ new Map(); let current; let each_value_2 = ( /*persistentEffects*/ ctx[2] ); const get_key = (ctx2) => ( /*effect*/ ctx2[14].id ); for (let i = 0; i < each_value_2.length; i += 1) { let child_ctx = get_each_context_2$1(ctx, each_value_2, i); let key = get_key(child_ctx); each_1_lookup.set(key, each_blocks[i] = create_each_block_2$1(key, child_ctx)); } return { c() { h2 = element("h2"); h2.textContent = `${localize("SEQUENCER.Manager.PersistentEffects")}`; t1 = space(); div = element("div"); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } }, m(target, anchor) { insert(target, h2, anchor); insert(target, t1, anchor); insert(target, div, anchor); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(div, null); } current = true; }, p(ctx2, dirty) { if (dirty & /*persistentEffects*/ 4) { each_value_2 = /*persistentEffects*/ ctx2[2]; group_outros(); each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx2, each_value_2, each_1_lookup, div, outro_and_destroy_block, create_each_block_2$1, null, get_each_context_2$1); check_outros(); } }, i(local) { if (current) return; for (let i = 0; i < each_value_2.length; i += 1) { transition_in(each_blocks[i]); } current = true; }, o(local) { for (let i = 0; i < each_blocks.length; i += 1) { transition_out(each_blocks[i]); } current = false; }, d(detaching) { if (detaching) detach(h2); if (detaching) detach(t1); if (detaching) detach(div); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].d(); } } }; } function create_each_block_2$1(key_1, ctx) { let first; let effectentry; let current; effectentry = new EffectEntry({ props: { effect: ( /*effect*/ ctx[14] ) } }); return { key: key_1, first: null, c() { first = empty(); create_component(effectentry.$$.fragment); this.first = first; }, m(target, anchor) { insert(target, first, anchor); mount_component(effectentry, target, anchor); current = true; }, p(new_ctx, dirty) { ctx = new_ctx; const effectentry_changes = {}; if (dirty & /*persistentEffects*/ 4) effectentry_changes.effect = /*effect*/ ctx[14]; effectentry.$set(effectentry_changes); }, i(local) { if (current) return; transition_in(effectentry.$$.fragment, local); current = true; }, o(local) { transition_out(effectentry.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(first); destroy_component(effectentry, detaching); } }; } function create_if_block_3(ctx) { let hr; return { c() { hr = element("hr"); }, m(target, anchor) { insert(target, hr, anchor); }, d(detaching) { if (detaching) detach(hr); } }; } function create_if_block_2(ctx) { let h2; let t1; let div; let each_blocks = []; let each_1_lookup = /* @__PURE__ */ new Map(); let current; let each_value_1 = ( /*temporaryEffects*/ ctx[1] ); const get_key = (ctx2) => ( /*effect*/ ctx2[14].id ); for (let i = 0; i < each_value_1.length; i += 1) { let child_ctx = get_each_context_1$1(ctx, each_value_1, i); let key = get_key(child_ctx); each_1_lookup.set(key, each_blocks[i] = create_each_block_1$1(key, child_ctx)); } return { c() { h2 = element("h2"); h2.textContent = `${localize("SEQUENCER.Manager.TemporaryEffects")}`; t1 = space(); div = element("div"); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } }, m(target, anchor) { insert(target, h2, anchor); insert(target, t1, anchor); insert(target, div, anchor); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(div, null); } current = true; }, p(ctx2, dirty) { if (dirty & /*temporaryEffects*/ 2) { each_value_1 = /*temporaryEffects*/ ctx2[1]; group_outros(); each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx2, each_value_1, each_1_lookup, div, outro_and_destroy_block, create_each_block_1$1, null, get_each_context_1$1); check_outros(); } }, i(local) { if (current) return; for (let i = 0; i < each_value_1.length; i += 1) { transition_in(each_blocks[i]); } current = true; }, o(local) { for (let i = 0; i < each_blocks.length; i += 1) { transition_out(each_blocks[i]); } current = false; }, d(detaching) { if (detaching) detach(h2); if (detaching) detach(t1); if (detaching) detach(div); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].d(); } } }; } function create_each_block_1$1(key_1, ctx) { let first; let effectentry; let current; effectentry = new EffectEntry({ props: { effect: ( /*effect*/ ctx[14] ) } }); return { key: key_1, first: null, c() { first = empty(); create_component(effectentry.$$.fragment); this.first = first; }, m(target, anchor) { insert(target, first, anchor); mount_component(effectentry, target, anchor); current = true; }, p(new_ctx, dirty) { ctx = new_ctx; const effectentry_changes = {}; if (dirty & /*temporaryEffects*/ 2) effectentry_changes.effect = /*effect*/ ctx[14]; effectentry.$set(effectentry_changes); }, i(local) { if (current) return; transition_in(effectentry.$$.fragment, local); current = true; }, o(local) { transition_out(effectentry.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(first); destroy_component(effectentry, detaching); } }; } function create_if_block$2(ctx) { let button; let t1; let div1; let h2; let t3; let div0; let each_blocks = []; let each_1_lookup = /* @__PURE__ */ new Map(); let current; let mounted; let dispose; let each_value = ( /*sounds*/ ctx[3] ); const get_key = (ctx2) => ( /*id*/ ctx2[10] ); for (let i = 0; i < each_value.length; i += 1) { let child_ctx = get_each_context$3(ctx, each_value, i); let key = get_key(child_ctx); each_1_lookup.set(key, each_blocks[i] = create_each_block$3(key, child_ctx)); } return { c() { button = element("button"); button.textContent = `${localize("SEQUENCER.Manager.EndAllSounds")}`; t1 = space(); div1 = element("div"); h2 = element("h2"); h2.textContent = `${localize("SEQUENCER.Manager.Sounds")}`; t3 = space(); div0 = element("div"); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } attr(button, "class", "w-100 end-all-effects mb-2 svelte-ese-nc5j73"); attr(button, "type", "button"); }, m(target, anchor) { insert(target, button, anchor); insert(target, t1, anchor); insert(target, div1, anchor); append(div1, h2); append(div1, t3); append(div1, div0); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(div0, null); } current = true; if (!mounted) { dispose = listen( button, "click", /*endAllSounds*/ ctx[7] ); mounted = true; } }, p(ctx2, dirty) { if (dirty & /*sounds*/ 8) { each_value = /*sounds*/ ctx2[3]; group_outros(); each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx2, each_value, each_1_lookup, div0, outro_and_destroy_block, create_each_block$3, null, get_each_context$3); check_outros(); } }, i(local) { if (current) return; for (let i = 0; i < each_value.length; i += 1) { transition_in(each_blocks[i]); } current = true; }, o(local) { for (let i = 0; i < each_blocks.length; i += 1) { transition_out(each_blocks[i]); } current = false; }, d(detaching) { if (detaching) detach(button); if (detaching) detach(t1); if (detaching) detach(div1); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].d(); } mounted = false; dispose(); } }; } function create_each_block$3(key_1, ctx) { let first; let soundentry; let current; soundentry = new SoundEntry({ props: { id: ( /*id*/ ctx[10] ), sound: ( /*sound*/ ctx[11] ) } }); return { key: key_1, first: null, c() { first = empty(); create_component(soundentry.$$.fragment); this.first = first; }, m(target, anchor) { insert(target, first, anchor); mount_component(soundentry, target, anchor); current = true; }, p(new_ctx, dirty) { ctx = new_ctx; const soundentry_changes = {}; if (dirty & /*sounds*/ 8) soundentry_changes.id = /*id*/ ctx[10]; if (dirty & /*sounds*/ 8) soundentry_changes.sound = /*sound*/ ctx[11]; soundentry.$set(soundentry_changes); }, i(local) { if (current) return; transition_in(soundentry.$$.fragment, local); current = true; }, o(local) { transition_out(soundentry.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(first); destroy_component(soundentry, detaching); } }; } function create_fragment$a(ctx) { let div; let t0; let t1; let current; let if_block0 = !/*effects*/ ctx[0].length && !/*sounds*/ ctx[3].length && create_if_block_5(); let if_block1 = ( /*effects*/ ctx[0].length && create_if_block_1(ctx) ); let if_block2 = ( /*sounds*/ ctx[3].length && create_if_block$2(ctx) ); return { c() { div = element("div"); if (if_block0) if_block0.c(); t0 = space(); if (if_block1) if_block1.c(); t1 = space(); if (if_block2) if_block2.c(); attr(div, "class", "effects-container svelte-ese-nc5j73"); }, m(target, anchor) { insert(target, div, anchor); if (if_block0) if_block0.m(div, null); append(div, t0); if (if_block1) if_block1.m(div, null); append(div, t1); if (if_block2) if_block2.m(div, null); current = true; }, p(ctx2, [dirty]) { if (!/*effects*/ ctx2[0].length && !/*sounds*/ ctx2[3].length) { if (if_block0) { if_block0.p(ctx2, dirty); } else { if_block0 = create_if_block_5(); if_block0.c(); if_block0.m(div, t0); } } else if (if_block0) { if_block0.d(1); if_block0 = null; } if ( /*effects*/ ctx2[0].length ) { if (if_block1) { if_block1.p(ctx2, dirty); if (dirty & /*effects*/ 1) { transition_in(if_block1, 1); } } else { if_block1 = create_if_block_1(ctx2); if_block1.c(); transition_in(if_block1, 1); if_block1.m(div, t1); } } else if (if_block1) { group_outros(); transition_out(if_block1, 1, 1, () => { if_block1 = null; }); check_outros(); } if ( /*sounds*/ ctx2[3].length ) { if (if_block2) { if_block2.p(ctx2, dirty); if (dirty & /*sounds*/ 8) { transition_in(if_block2, 1); } } else { if_block2 = create_if_block$2(ctx2); if_block2.c(); transition_in(if_block2, 1); if_block2.m(div, null); } } else if (if_block2) { group_outros(); transition_out(if_block2, 1, 1, () => { if_block2 = null; }); check_outros(); } }, i(local) { if (current) return; transition_in(if_block1); transition_in(if_block2); current = true; }, o(local) { transition_out(if_block1); transition_out(if_block2); current = false; }, d(detaching) { if (detaching) detach(div); if (if_block0) if_block0.d(); if (if_block1) if_block1.d(); if (if_block2) if_block2.d(); } }; } function instance$a($$self, $$props, $$invalidate) { let effects; let sounds; let persistentEffects; let temporaryEffects; let $RunningSounds; let $VisibleEffects; const VisibleEffects = SequenceManager.VisibleEffects; component_subscribe($$self, VisibleEffects, (value) => $$invalidate(9, $VisibleEffects = value)); const RunningSounds = SequenceManager.RunningSounds; component_subscribe($$self, RunningSounds, (value) => $$invalidate(8, $RunningSounds = value)); function endAllEffects() { Sequencer.EffectManager.endEffects({ effects: effects.filter((effect) => effect.userCanDelete && !effect.data.private) }); } function endAllSounds() { SequencerAudioHelper.stop(RunningSounds.keys()); } $$self.$$.update = () => { if ($$self.$$.dirty & /*$VisibleEffects*/ 512) { $$invalidate(0, effects = Object.values($VisibleEffects)); } if ($$self.$$.dirty & /*$RunningSounds*/ 256) { $$invalidate(3, sounds = Object.entries($RunningSounds)); } if ($$self.$$.dirty & /*effects*/ 1) { $$invalidate(2, persistentEffects = effects.filter((effect) => effect.data.persist)); } if ($$self.$$.dirty & /*effects*/ 1) { $$invalidate(1, temporaryEffects = effects.filter((effect) => !effect.data.persist)); } }; return [ effects, temporaryEffects, persistentEffects, sounds, VisibleEffects, RunningSounds, endAllEffects, endAllSounds, $RunningSounds, $VisibleEffects ]; } class Manager extends SvelteComponent { constructor(options) { super(); init(this, options, instance$a, create_fragment$a, safe_not_equal, {}); } } const SliderInput_svelte_svelte_type_style_lang = ""; function create_fragment$9(ctx) { let div; let label; let t0_value = localize( /*setting*/ ctx[0].label ) + ""; let t0; let t1; let input0; let t2; let input1; let applyStyles_action; let mounted; let dispose; return { c() { div = element("div"); label = element("label"); t0 = text$1(t0_value); t1 = space(); input0 = element("input"); t2 = space(); input1 = element("input"); attr(label, "class", "svelte-ese-x3192w"); input0.disabled = /*$isLocked*/ ctx[6]; attr( input0, "max", /*max*/ ctx[2] ); attr( input0, "min", /*min*/ ctx[1] ); attr( input0, "step", /*step*/ ctx[4] ); attr(input0, "type", "range"); attr(input0, "class", "svelte-ese-x3192w"); input1.disabled = /*$isLocked*/ ctx[6]; attr( input1, "max", /*maxInput*/ ctx[3] ); attr( input1, "min", /*min*/ ctx[1] ); input1.required = true; attr( input1, "step", /*step*/ ctx[4] ); attr(input1, "type", "number"); attr(input1, "class", "svelte-ese-x3192w"); set_style(div, "display", "flex"); set_style(div, "align-items", "center"); }, m(target, anchor) { insert(target, div, anchor); append(div, label); append(label, t0); append(div, t1); append(div, input0); set_input_value( input0, /*$store*/ ctx[7] ); append(div, t2); append(div, input1); set_input_value( input1, /*$store*/ ctx[7] ); if (!mounted) { dispose = [ listen( input0, "change", /*input0_change_input_handler*/ ctx[11] ), listen( input0, "input", /*input0_change_input_handler*/ ctx[11] ), listen( input1, "input", /*input1_input_handler*/ ctx[12] ), action_destroyer(applyStyles_action = applyStyles.call( null, div, /*styles*/ ctx[5] )) ]; mounted = true; } }, p(ctx2, [dirty]) { if (dirty & /*setting*/ 1 && t0_value !== (t0_value = localize( /*setting*/ ctx2[0].label ) + "")) set_data(t0, t0_value); if (dirty & /*$isLocked*/ 64) { input0.disabled = /*$isLocked*/ ctx2[6]; } if (dirty & /*max*/ 4) { attr( input0, "max", /*max*/ ctx2[2] ); } if (dirty & /*min*/ 2) { attr( input0, "min", /*min*/ ctx2[1] ); } if (dirty & /*step*/ 16) { attr( input0, "step", /*step*/ ctx2[4] ); } if (dirty & /*$store*/ 128) { set_input_value( input0, /*$store*/ ctx2[7] ); } if (dirty & /*$isLocked*/ 64) { input1.disabled = /*$isLocked*/ ctx2[6]; } if (dirty & /*maxInput*/ 8) { attr( input1, "max", /*maxInput*/ ctx2[3] ); } if (dirty & /*min*/ 2) { attr( input1, "min", /*min*/ ctx2[1] ); } if (dirty & /*step*/ 16) { attr( input1, "step", /*step*/ ctx2[4] ); } if (dirty & /*$store*/ 128 && to_number(input1.value) !== /*$store*/ ctx2[7]) { set_input_value( input1, /*$store*/ ctx2[7] ); } if (applyStyles_action && is_function(applyStyles_action.update) && dirty & /*styles*/ 32) applyStyles_action.update.call( null, /*styles*/ ctx2[5] ); }, i: noop, o: noop, d(detaching) { if (detaching) detach(div); mounted = false; run_all(dispose); } }; } function instance$9($$self, $$props, $$invalidate) { let $isLocked; let $store; let { setting } = $$props; let { lock = false } = $$props; let { min = 0.1 } = $$props; let { max = 2 } = $$props; let { maxInput = Infinity } = $$props; let { step = 0.01 } = $$props; let { styles = {} } = $$props; const store = setting.store; component_subscribe($$self, store, (value) => $$invalidate(7, $store = value)); const isLocked = lock ? lock.store : writable$1(false); component_subscribe($$self, isLocked, (value) => $$invalidate(6, $isLocked = value)); function input0_change_input_handler() { $store = to_number(this.value); store.set($store); } function input1_input_handler() { $store = to_number(this.value); store.set($store); } $$self.$$set = ($$props2) => { if ("setting" in $$props2) $$invalidate(0, setting = $$props2.setting); if ("lock" in $$props2) $$invalidate(10, lock = $$props2.lock); if ("min" in $$props2) $$invalidate(1, min = $$props2.min); if ("max" in $$props2) $$invalidate(2, max = $$props2.max); if ("maxInput" in $$props2) $$invalidate(3, maxInput = $$props2.maxInput); if ("step" in $$props2) $$invalidate(4, step = $$props2.step); if ("styles" in $$props2) $$invalidate(5, styles = $$props2.styles); }; return [ setting, min, max, maxInput, step, styles, $isLocked, $store, store, isLocked, lock, input0_change_input_handler, input1_input_handler ]; } class SliderInput extends SvelteComponent { constructor(options) { super(); init(this, options, instance$9, create_fragment$9, safe_not_equal, { setting: 0, lock: 10, min: 1, max: 2, maxInput: 3, step: 4, styles: 5 }); } } function create_fragment$8(ctx) { let div; let input; let input_disabled_value; let t0; let label_1; let t1_value = localize( /*setting*/ ctx[0].label ) + ""; let t1; let applyStyles_action; let mounted; let dispose; return { c() { div = element("div"); input = element("input"); t0 = space(); label_1 = element("label"); t1 = text$1(t1_value); attr( input, "id", /*id*/ ctx[5] ); input.disabled = input_disabled_value = /*$isLocked*/ ctx[3] !== /*inverse*/ ctx[1]; set_style(input, "height", "15px"); attr(input, "type", "checkbox"); attr( label_1, "for", /*id*/ ctx[5] ); set_style(div, "display", "flex"); set_style(div, "align-items", "center"); }, m(target, anchor) { insert(target, div, anchor); append(div, input); input.checked = /*$store*/ ctx[4]; append(div, t0); append(div, label_1); append(label_1, t1); if (!mounted) { dispose = [ listen( input, "change", /*input_change_handler*/ ctx[10] ), action_destroyer(applyStyles_action = applyStyles.call( null, div, /*styles*/ ctx[2] )) ]; mounted = true; } }, p(ctx2, [dirty]) { if (dirty & /*$isLocked, inverse*/ 10 && input_disabled_value !== (input_disabled_value = /*$isLocked*/ ctx2[3] !== /*inverse*/ ctx2[1])) { input.disabled = input_disabled_value; } if (dirty & /*$store*/ 16) { input.checked = /*$store*/ ctx2[4]; } if (dirty & /*setting*/ 1 && t1_value !== (t1_value = localize( /*setting*/ ctx2[0].label ) + "")) set_data(t1, t1_value); if (applyStyles_action && is_function(applyStyles_action.update) && dirty & /*styles*/ 4) applyStyles_action.update.call( null, /*styles*/ ctx2[2] ); }, i: noop, o: noop, d(detaching) { if (detaching) detach(div); mounted = false; run_all(dispose); } }; } function instance$8($$self, $$props, $$invalidate) { let $store; let $isLocked; let { setting } = $$props; let { label = false } = $$props; let { lock = false } = $$props; let { inverse = false } = $$props; let { styles = {} } = $$props; const id = "sequencer-input-" + randomID(); const store = setting.store; component_subscribe($$self, store, (value) => $$invalidate(4, $store = value)); const isLocked = lock ? lock.store : writable$1(inverse); component_subscribe($$self, isLocked, (value) => $$invalidate(3, $isLocked = value)); function input_change_handler() { $store = this.checked; store.set($store); } $$self.$$set = ($$props2) => { if ("setting" in $$props2) $$invalidate(0, setting = $$props2.setting); if ("label" in $$props2) $$invalidate(8, label = $$props2.label); if ("lock" in $$props2) $$invalidate(9, lock = $$props2.lock); if ("inverse" in $$props2) $$invalidate(1, inverse = $$props2.inverse); if ("styles" in $$props2) $$invalidate(2, styles = $$props2.styles); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*$isLocked, inverse*/ 10) { { if ($isLocked !== inverse) { set_store_value(store, $store = false, $store); } } } }; return [ setting, inverse, styles, $isLocked, $store, id, store, isLocked, label, lock, input_change_handler ]; } class Checkbox extends SvelteComponent { constructor(options) { super(); init(this, options, instance$8, create_fragment$8, safe_not_equal, { setting: 0, label: 8, lock: 9, inverse: 1, styles: 2 }); } } const NumberInput_svelte_svelte_type_style_lang = ""; function create_if_block$1(ctx) { let label; let t_value = localize( /*setting*/ ctx[0].label ) + ""; let t; return { c() { label = element("label"); t = text$1(t_value); attr( label, "for", /*id*/ ctx[5] ); attr(label, "class", "svelte-ese-ywsxq0"); }, m(target, anchor) { insert(target, label, anchor); append(label, t); }, p(ctx2, dirty) { if (dirty & /*setting*/ 1 && t_value !== (t_value = localize( /*setting*/ ctx2[0].label ) + "")) set_data(t, t_value); }, d(detaching) { if (detaching) detach(label); } }; } function create_fragment$7(ctx) { let div; let show_if = localize( /*setting*/ ctx[0].label ); let t; let input; let input_disabled_value; let applyStyles_action; let mounted; let dispose; let if_block = show_if && create_if_block$1(ctx); return { c() { div = element("div"); if (if_block) if_block.c(); t = space(); input = element("input"); attr( input, "id", /*id*/ ctx[5] ); attr(input, "type", "number"); input.disabled = input_disabled_value = /*$isLocked*/ ctx[3] !== /*inverse*/ ctx[1]; attr(input, "class", "svelte-ese-ywsxq0"); set_style(div, "display", "flex"); set_style(div, "align-items", "center"); }, m(target, anchor) { insert(target, div, anchor); if (if_block) if_block.m(div, null); append(div, t); append(div, input); set_input_value( input, /*$store*/ ctx[4] ); if (!mounted) { dispose = [ listen( input, "input", /*input_input_handler*/ ctx[9] ), listen( input, "change", /*change_handler*/ ctx[10] ), action_destroyer(applyStyles_action = applyStyles.call( null, div, /*styles*/ ctx[2] )) ]; mounted = true; } }, p(ctx2, [dirty]) { if (dirty & /*setting*/ 1) show_if = localize( /*setting*/ ctx2[0].label ); if (show_if) { if (if_block) { if_block.p(ctx2, dirty); } else { if_block = create_if_block$1(ctx2); if_block.c(); if_block.m(div, t); } } else if (if_block) { if_block.d(1); if_block = null; } if (dirty & /*$isLocked, inverse*/ 10 && input_disabled_value !== (input_disabled_value = /*$isLocked*/ ctx2[3] !== /*inverse*/ ctx2[1])) { input.disabled = input_disabled_value; } if (dirty & /*$store*/ 16 && to_number(input.value) !== /*$store*/ ctx2[4]) { set_input_value( input, /*$store*/ ctx2[4] ); } if (applyStyles_action && is_function(applyStyles_action.update) && dirty & /*styles*/ 4) applyStyles_action.update.call( null, /*styles*/ ctx2[2] ); }, i: noop, o: noop, d(detaching) { if (detaching) detach(div); if (if_block) if_block.d(); mounted = false; run_all(dispose); } }; } function instance$7($$self, $$props, $$invalidate) { let $isLocked; let $store; let { setting } = $$props; let { lock = false } = $$props; let { inverse = false } = $$props; let { styles = {} } = $$props; const id = "sequencer-input-" + randomID(); const store = setting.store; component_subscribe($$self, store, (value) => $$invalidate(4, $store = value)); const isLocked = lock ? lock.store : writable$1(inverse); component_subscribe($$self, isLocked, (value) => $$invalidate(3, $isLocked = value)); function input_input_handler() { $store = to_number(this.value); store.set($store); } const change_handler = () => { if ($store === null) set_store_value(store, $store = 0, $store); }; $$self.$$set = ($$props2) => { if ("setting" in $$props2) $$invalidate(0, setting = $$props2.setting); if ("lock" in $$props2) $$invalidate(8, lock = $$props2.lock); if ("inverse" in $$props2) $$invalidate(1, inverse = $$props2.inverse); if ("styles" in $$props2) $$invalidate(2, styles = $$props2.styles); }; return [ setting, inverse, styles, $isLocked, $store, id, store, isLocked, lock, input_input_handler, change_handler ]; } class NumberInput extends SvelteComponent { constructor(options) { super(); init(this, options, instance$7, create_fragment$7, safe_not_equal, { setting: 0, lock: 8, inverse: 1, styles: 2 }); } } const SwitchToggle_svelte_svelte_type_style_lang = ""; function create_fragment$6(ctx) { let div2; let div0; let span0; let t0_value = localize( /*setting*/ ctx[0].label_off ) + ""; let t0; let t1; let div1; let span1; let t2_value = localize( /*setting*/ ctx[0].label_on ) + ""; let t2; let applyStyles_action; let mounted; let dispose; return { c() { div2 = element("div"); div0 = element("div"); span0 = element("span"); t0 = text$1(t0_value); t1 = space(); div1 = element("div"); span1 = element("span"); t2 = text$1(t2_value); attr(div0, "class", "first svelte-ese-o0yoxs"); toggle_class(div0, "active", !/*$store*/ ctx[2]); attr(div1, "class", "second svelte-ese-o0yoxs"); toggle_class( div1, "active", /*$store*/ ctx[2] ); set_style(div2, "display", "flex"); set_style(div2, "align-items", "center"); attr(div2, "class", "svelte-ese-o0yoxs"); }, m(target, anchor) { insert(target, div2, anchor); append(div2, div0); append(div0, span0); append(span0, t0); append(div2, t1); append(div2, div1); append(div1, span1); append(span1, t2); if (!mounted) { dispose = [ listen( div0, "click", /*click_handler*/ ctx[5] ), listen( div1, "click", /*click_handler_1*/ ctx[6] ), action_destroyer(applyStyles_action = applyStyles.call( null, div2, /*finalStyles*/ ctx[1] )) ]; mounted = true; } }, p(ctx2, [dirty]) { if (dirty & /*setting*/ 1 && t0_value !== (t0_value = localize( /*setting*/ ctx2[0].label_off ) + "")) set_data(t0, t0_value); if (dirty & /*$store*/ 4) { toggle_class(div0, "active", !/*$store*/ ctx2[2]); } if (dirty & /*setting*/ 1 && t2_value !== (t2_value = localize( /*setting*/ ctx2[0].label_on ) + "")) set_data(t2, t2_value); if (dirty & /*$store*/ 4) { toggle_class( div1, "active", /*$store*/ ctx2[2] ); } if (applyStyles_action && is_function(applyStyles_action.update) && dirty & /*finalStyles*/ 2) applyStyles_action.update.call( null, /*finalStyles*/ ctx2[1] ); }, i: noop, o: noop, d(detaching) { if (detaching) detach(div2); mounted = false; run_all(dispose); } }; } const width = 5.75; function instance$6($$self, $$props, $$invalidate) { let finalStyles; let $store; let { setting } = $$props; let { styles = {} } = $$props; const store = setting.store; component_subscribe($$self, store, (value) => $$invalidate(2, $store = value)); const click_handler = () => { set_store_value(store, $store = false, $store); }; const click_handler_1 = () => { set_store_value(store, $store = true, $store); }; $$self.$$set = ($$props2) => { if ("setting" in $$props2) $$invalidate(0, setting = $$props2.setting); if ("styles" in $$props2) $$invalidate(4, styles = $$props2.styles); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*styles*/ 16) { $$invalidate(1, finalStyles = { ...styles, "--switch-width": `${width}em`, "--half-switch-width": `${width - 1.5}em`, "--half-switch-width-on": `${width - 1.7}em` }); } }; return [setting, finalStyles, $store, store, styles, click_handler, click_handler_1]; } class SwitchToggle extends SvelteComponent { constructor(options) { super(); init(this, options, instance$6, create_fragment$6, safe_not_equal, { setting: 0, styles: 4 }); } } const Player_svelte_svelte_type_style_lang = ""; function get_each_context$2(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[20] = list[i]; return child_ctx; } function get_each_context_1(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[23] = list[i]; return child_ctx; } function get_each_context_2(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[26] = list[i]; return child_ctx; } function create_each_block_2(ctx) { let option; let t_value = ( /*suggestion*/ ctx[26] + "" ); let t; let option_value_value; return { c() { option = element("option"); t = text$1(t_value); option.__value = option_value_value = /*suggestion*/ ctx[26]; option.value = option.__value; }, m(target, anchor) { insert(target, option, anchor); append(option, t); }, p(ctx2, dirty) { if (dirty & /*suggestions*/ 2 && t_value !== (t_value = /*suggestion*/ ctx2[26] + "")) set_data(t, t_value); if (dirty & /*suggestions*/ 2 && option_value_value !== (option_value_value = /*suggestion*/ ctx2[26])) { option.__value = option_value_value; option.value = option.__value; } }, d(detaching) { if (detaching) detach(option); } }; } function create_each_block_1(key_1, ctx) { let option; let t_value = ( /*user*/ ctx[23].name + "" ); let t; return { key: key_1, first: null, c() { option = element("option"); t = text$1(t_value); option.__value = /*user*/ ctx[23].id; option.value = option.__value; this.first = option; }, m(target, anchor) { insert(target, option, anchor); append(option, t); }, p(new_ctx, dirty) { ctx = new_ctx; }, d(detaching) { if (detaching) detach(option); } }; } function create_each_block$2(ctx) { let option; let t_value = ( /*preset*/ ctx[20] + "" ); let t; return { c() { option = element("option"); t = text$1(t_value); option.__value = /*preset*/ ctx[20]; option.value = option.__value; }, m(target, anchor) { insert(target, option, anchor); append(option, t); }, p: noop, d(detaching) { if (detaching) detach(option); } }; } function create_fragment$5(ctx) { let div16; let fieldset0; let legend0; let t1; let button0; let t3; let div0; let input; let t4; let button1; let t5; let datalist; let t6; let div1; let label; let t8; let select0; let option0; let each_blocks_1 = []; let each1_lookup = /* @__PURE__ */ new Map(); let t10; let div4; let div2; let t12; let div3; let select1; let option1; let t14; let button2; let t15; let button3; let i2; let button3_disabled_value; let t16; let button4; let i3; let button4_disabled_value; let t17; let fieldset1; let legend1; let t19; let div11; let sliderinput0; let t20; let numberinput0; let t21; let numberinput1; let t22; let div5; let t23; let sliderinput1; let t24; let div6; let t25; let checkbox0; let t26; let div7; let t27; let div8; let t28; let checkbox1; let t29; let checkbox2; let t30; let checkbox3; let t31; let checkbox4; let t32; let div9; let t33; let numberinput2; let t34; let numberinput3; let t35; let checkbox5; let t36; let numberinput4; let t37; let checkbox6; let t38; let div10; let t39; let fieldset2; let legend2; let t41; let div15; let numberinput5; let t42; let numberinput6; let t43; let div12; let t44; let checkbox7; let t45; let numberinput7; let t46; let numberinput8; let t47; let numberinput9; let t48; let div13; let t49; let switchtoggle; let t50; let numberinput10; let t51; let div14; let t52; let checkbox8; let t53; let checkbox9; let t54; let checkbox10; let t55; let checkbox11; let t56; let checkbox12; let current; let mounted; let dispose; let each_value_2 = ( /*suggestions*/ ctx[1] ); let each_blocks_2 = []; for (let i = 0; i < each_value_2.length; i += 1) { each_blocks_2[i] = create_each_block_2(get_each_context_2(ctx, each_value_2, i)); } let each_value_1 = ( /*users*/ ctx[5] ); const get_key = (ctx2) => ( /*user*/ ctx2[23].id ); for (let i = 0; i < each_value_1.length; i += 1) { let child_ctx = get_each_context_1(ctx, each_value_1, i); let key = get_key(child_ctx); each1_lookup.set(key, each_blocks_1[i] = create_each_block_1(key, child_ctx)); } let each_value = ( /*presets*/ ctx[8] ); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { each_blocks[i] = create_each_block$2(get_each_context$2(ctx, each_value, i)); } sliderinput0 = new SliderInput({ props: { setting: PlayerSettings.scale, styles: { "grid-column": `1 / 5` } } }); numberinput0 = new NumberInput({ props: { setting: PlayerSettings.scaleIn, styles: { "grid-column": `1 / 3` } } }); numberinput1 = new NumberInput({ props: { setting: PlayerSettings.scaleOut, styles: { "grid-column": `3 / 5` } } }); sliderinput1 = new SliderInput({ props: { setting: PlayerSettings.rotation, lock: PlayerSettings.randomRotation, min: "-180", max: "180", styles: { "grid-column": `1 / 5` } } }); checkbox0 = new Checkbox({ props: { setting: PlayerSettings.randomRotation, styles: { "grid-column": `2 / 4` } } }); checkbox1 = new Checkbox({ props: { setting: PlayerSettings.mirrorX, styles: { "grid-column": `1 / 3` } } }); checkbox2 = new Checkbox({ props: { setting: PlayerSettings.mirrorY, styles: { "grid-column": `3 / 5` } } }); checkbox3 = new Checkbox({ props: { setting: PlayerSettings.randomMirrorX, styles: { "grid-column": `1 / 3` }, lock: PlayerSettings.mirrorX, inverse: true } }); checkbox4 = new Checkbox({ props: { setting: PlayerSettings.randomMirrorY, styles: { "grid-column": `3 / 5` }, lock: PlayerSettings.mirrorY, inverse: true } }); numberinput2 = new NumberInput({ props: { setting: PlayerSettings.offsetX, lock: PlayerSettings.randomOffset, styles: { "grid-column": `1 / 3` } } }); numberinput3 = new NumberInput({ props: { setting: PlayerSettings.offsetY, lock: PlayerSettings.randomOffset, styles: { "grid-column": `3 / 5` } } }); checkbox5 = new Checkbox({ props: { setting: PlayerSettings.randomOffset, styles: { "grid-column": `1 / 3` } } }); numberinput4 = new NumberInput({ props: { setting: PlayerSettings.randomOffsetAmount, lock: PlayerSettings.randomOffset, inverse: true, styles: { "grid-column": `3 / 5` } } }); checkbox6 = new Checkbox({ props: { setting: PlayerSettings.offsetGridUnits, styles: { "grid-column": `2 / 5` } } }); numberinput5 = new NumberInput({ props: { setting: PlayerSettings.fadeIn, styles: { "grid-column": `1 / 3` } } }); numberinput6 = new NumberInput({ props: { setting: PlayerSettings.fadeOut, styles: { "grid-column": `3 / 5` } } }); checkbox7 = new Checkbox({ props: { setting: PlayerSettings.repeat, styles: { "grid-column": `1 / 3` } } }); numberinput7 = new NumberInput({ props: { setting: PlayerSettings.repetitions, lock: PlayerSettings.repeat, inverse: true, styles: { "grid-column": `3 / 5` } } }); numberinput8 = new NumberInput({ props: { setting: PlayerSettings.repeatDelayMin, lock: PlayerSettings.repeat, inverse: true, styles: { "grid-column": `3 / 4` } } }); numberinput9 = new NumberInput({ props: { setting: PlayerSettings.repeatDelayMax, lock: PlayerSettings.repeat, inverse: true, styles: { "grid-column": `4 / 5` } } }); switchtoggle = new SwitchToggle({ props: { setting: PlayerSettings.moveTowards, styles: { "grid-column": `1 / 3` } } }); numberinput10 = new NumberInput({ props: { setting: PlayerSettings.moveSpeed, lock: PlayerSettings.moveTowards, inverse: true, styles: { "grid-column": `3 / 5` } } }); checkbox8 = new Checkbox({ props: { setting: PlayerSettings.attachTo, styles: { "grid-column": `1 / 5` } } }); checkbox9 = new Checkbox({ props: { setting: PlayerSettings.stretchToAttach, styles: { "grid-column": `1 / 5` } } }); checkbox10 = new Checkbox({ props: { setting: PlayerSettings.snapToGrid, styles: { "grid-column": `1 / 5` } } }); checkbox11 = new Checkbox({ props: { setting: PlayerSettings.persist, styles: { "grid-column": `1 / 5` } } }); checkbox12 = new Checkbox({ props: { setting: PlayerSettings.belowTokens, styles: { "grid-column": `1 / 5` } } }); return { c() { div16 = element("div"); fieldset0 = element("fieldset"); legend0 = element("legend"); legend0.textContent = "Effect"; t1 = space(); button0 = element("button"); button0.textContent = `${localize("SEQUENCER.Player.SwitchToLayer")}`; t3 = space(); div0 = element("div"); input = element("input"); t4 = space(); button1 = element("button"); button1.innerHTML = ``; t5 = space(); datalist = element("datalist"); for (let i = 0; i < each_blocks_2.length; i += 1) { each_blocks_2[i].c(); } t6 = space(); div1 = element("div"); label = element("label"); label.textContent = "Play for users:"; t8 = space(); select0 = element("select"); option0 = element("option"); option0.textContent = `${localize("SEQUENCER.Player.AllUsers")}`; for (let i = 0; i < each_blocks_1.length; i += 1) { each_blocks_1[i].c(); } t10 = space(); div4 = element("div"); div2 = element("div"); div2.textContent = `${localize("SEQUENCER.Player.Presets")}`; t12 = space(); div3 = element("div"); select1 = element("select"); option1 = element("option"); option1.textContent = `${localize("SEQUENCER.Player.PresetsDefault")}`; for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } t14 = space(); button2 = element("button"); button2.innerHTML = ``; t15 = space(); button3 = element("button"); i2 = element("i"); t16 = space(); button4 = element("button"); i3 = element("i"); t17 = space(); fieldset1 = element("fieldset"); legend1 = element("legend"); legend1.textContent = "Transform"; t19 = space(); div11 = element("div"); create_component(sliderinput0.$$.fragment); t20 = space(); create_component(numberinput0.$$.fragment); t21 = space(); create_component(numberinput1.$$.fragment); t22 = space(); div5 = element("div"); t23 = space(); create_component(sliderinput1.$$.fragment); t24 = space(); div6 = element("div"); t25 = space(); create_component(checkbox0.$$.fragment); t26 = space(); div7 = element("div"); t27 = space(); div8 = element("div"); t28 = space(); create_component(checkbox1.$$.fragment); t29 = space(); create_component(checkbox2.$$.fragment); t30 = space(); create_component(checkbox3.$$.fragment); t31 = space(); create_component(checkbox4.$$.fragment); t32 = space(); div9 = element("div"); t33 = space(); create_component(numberinput2.$$.fragment); t34 = space(); create_component(numberinput3.$$.fragment); t35 = space(); create_component(checkbox5.$$.fragment); t36 = space(); create_component(numberinput4.$$.fragment); t37 = space(); create_component(checkbox6.$$.fragment); t38 = space(); div10 = element("div"); t39 = space(); fieldset2 = element("fieldset"); legend2 = element("legend"); legend2.textContent = "Behavior"; t41 = space(); div15 = element("div"); create_component(numberinput5.$$.fragment); t42 = space(); create_component(numberinput6.$$.fragment); t43 = space(); div12 = element("div"); t44 = space(); create_component(checkbox7.$$.fragment); t45 = space(); create_component(numberinput7.$$.fragment); t46 = space(); create_component(numberinput8.$$.fragment); t47 = space(); create_component(numberinput9.$$.fragment); t48 = space(); div13 = element("div"); t49 = space(); create_component(switchtoggle.$$.fragment); t50 = space(); create_component(numberinput10.$$.fragment); t51 = space(); div14 = element("div"); t52 = space(); create_component(checkbox8.$$.fragment); t53 = space(); create_component(checkbox9.$$.fragment); t54 = space(); create_component(checkbox10.$$.fragment); t55 = space(); create_component(checkbox11.$$.fragment); t56 = space(); create_component(checkbox12.$$.fragment); attr(button0, "class", "activate-layer svelte-ese-1ipnpu1"); attr(button0, "type", "button"); attr(input, "class", "file-input flex3"); attr(input, "list", "dblist"); attr(input, "placeholder", localize("SEQUENCER.Player.PathInput")); attr(input, "type", "text"); attr(button1, "class", "custom-file-picker small-button svelte-ese-1ipnpu1"); attr(button1, "type", "button"); attr(datalist, "id", "dblist"); attr(div0, "class", "file-settings svelte-ese-1ipnpu1"); attr(label, "for", "user-select"); option0.selected = true; option0.__value = "all"; option0.value = option0.__value; attr(select0, "class", "user-select"); attr(select0, "id", "user-select"); select0.multiple = true; if ( /*$userStore*/ ctx[3] === void 0 ) add_render_callback(() => ( /*select0_change_handler*/ ctx[11].call(select0) )); attr(div1, "class", "user-settings flexcol svelte-ese-1ipnpu1"); attr(div2, "class", "row w-100"); option1.__value = "default"; option1.value = option1.__value; attr(select1, "class", "preset-select svelte-ese-1ipnpu1"); if ( /*selectedPreset*/ ctx[2] === void 0 ) add_render_callback(() => ( /*select1_change_handler*/ ctx[12].call(select1) )); attr(button2, "class", "save-preset small-button svelte-ese-1ipnpu1"); attr(button2, "data-tooltip", "Save Preset"); attr(button2, "type", "button"); attr(i2, "class", "fas fa-copy svelte-ese-1ipnpu1"); attr(button3, "class", "copy-preset small-button svelte-ese-1ipnpu1"); attr(button3, "data-tooltip", "Copy Preset"); button3.disabled = button3_disabled_value = /*selectedPreset*/ ctx[2] === "default"; attr(button3, "type", "button"); attr(i3, "class", "fas fa-times svelte-ese-1ipnpu1"); attr(button4, "class", "delete-preset small-button svelte-ese-1ipnpu1"); attr(button4, "data-tooltip", "Delete Preset"); button4.disabled = button4_disabled_value = /*selectedPreset*/ ctx[2] === "default"; attr(button4, "type", "button"); attr(div3, "class", "preset-container svelte-ese-1ipnpu1"); attr(div4, "class", "flexcol"); attr(div5, "class", "divider"); attr(div8, "class", "divider"); attr(div9, "class", "divider"); attr(div11, "class", "effect-transform-container"); attr(div12, "class", "divider"); attr(div13, "class", "divider"); attr(div14, "class", "divider"); attr(div15, "class", "effect-transform-container"); attr(div16, "class", "effect-player-container"); }, m(target, anchor) { insert(target, div16, anchor); append(div16, fieldset0); append(fieldset0, legend0); append(fieldset0, t1); append(fieldset0, button0); append(fieldset0, t3); append(fieldset0, div0); append(div0, input); set_input_value( input, /*$fileStore*/ ctx[0] ); append(div0, t4); append(div0, button1); append(div0, t5); append(div0, datalist); for (let i = 0; i < each_blocks_2.length; i += 1) { each_blocks_2[i].m(datalist, null); } append(fieldset0, t6); append(fieldset0, div1); append(div1, label); append(div1, t8); append(div1, select0); append(select0, option0); for (let i = 0; i < each_blocks_1.length; i += 1) { each_blocks_1[i].m(select0, null); } select_options( select0, /*$userStore*/ ctx[3] ); append(fieldset0, t10); append(fieldset0, div4); append(div4, div2); append(div4, t12); append(div4, div3); append(div3, select1); append(select1, option1); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(select1, null); } select_option( select1, /*selectedPreset*/ ctx[2] ); append(div3, t14); append(div3, button2); append(div3, t15); append(div3, button3); append(button3, i2); append(div3, t16); append(div3, button4); append(button4, i3); append(div16, t17); append(div16, fieldset1); append(fieldset1, legend1); append(fieldset1, t19); append(fieldset1, div11); mount_component(sliderinput0, div11, null); append(div11, t20); mount_component(numberinput0, div11, null); append(div11, t21); mount_component(numberinput1, div11, null); append(div11, t22); append(div11, div5); append(div11, t23); mount_component(sliderinput1, div11, null); append(div11, t24); append(div11, div6); append(div11, t25); mount_component(checkbox0, div11, null); append(div11, t26); append(div11, div7); append(div11, t27); append(div11, div8); append(div11, t28); mount_component(checkbox1, div11, null); append(div11, t29); mount_component(checkbox2, div11, null); append(div11, t30); mount_component(checkbox3, div11, null); append(div11, t31); mount_component(checkbox4, div11, null); append(div11, t32); append(div11, div9); append(div11, t33); mount_component(numberinput2, div11, null); append(div11, t34); mount_component(numberinput3, div11, null); append(div11, t35); mount_component(checkbox5, div11, null); append(div11, t36); mount_component(numberinput4, div11, null); append(div11, t37); mount_component(checkbox6, div11, null); append(div11, t38); append(div11, div10); append(div16, t39); append(div16, fieldset2); append(fieldset2, legend2); append(fieldset2, t41); append(fieldset2, div15); mount_component(numberinput5, div15, null); append(div15, t42); mount_component(numberinput6, div15, null); append(div15, t43); append(div15, div12); append(div15, t44); mount_component(checkbox7, div15, null); append(div15, t45); mount_component(numberinput7, div15, null); append(div15, t46); mount_component(numberinput8, div15, null); append(div15, t47); mount_component(numberinput9, div15, null); append(div15, t48); append(div15, div13); append(div15, t49); mount_component(switchtoggle, div15, null); append(div15, t50); mount_component(numberinput10, div15, null); append(div15, t51); append(div15, div14); append(div15, t52); mount_component(checkbox8, div15, null); append(div15, t53); mount_component(checkbox9, div15, null); append(div15, t54); mount_component(checkbox10, div15, null); append(div15, t55); mount_component(checkbox11, div15, null); append(div15, t56); mount_component(checkbox12, div15, null); current = true; if (!mounted) { dispose = [ listen( button0, "click", /*click_handler*/ ctx[9] ), listen( input, "input", /*input_input_handler*/ ctx[10] ), listen( button1, "click", /*handleClick*/ ctx[7] ), listen( select0, "change", /*select0_change_handler*/ ctx[11] ), listen( select1, "change", /*select1_change_handler*/ ctx[12] ), listen( select1, "change", /*change_handler*/ ctx[13] ), listen( button2, "click", /*click_handler_1*/ ctx[14] ), listen( button3, "click", /*click_handler_2*/ ctx[15] ), listen( button4, "click", /*click_handler_3*/ ctx[16] ) ]; mounted = true; } }, p(ctx2, [dirty]) { if (dirty & /*$fileStore*/ 1 && input.value !== /*$fileStore*/ ctx2[0]) { set_input_value( input, /*$fileStore*/ ctx2[0] ); } if (dirty & /*suggestions*/ 2) { each_value_2 = /*suggestions*/ ctx2[1]; let i; for (i = 0; i < each_value_2.length; i += 1) { const child_ctx = get_each_context_2(ctx2, each_value_2, i); if (each_blocks_2[i]) { each_blocks_2[i].p(child_ctx, dirty); } else { each_blocks_2[i] = create_each_block_2(child_ctx); each_blocks_2[i].c(); each_blocks_2[i].m(datalist, null); } } for (; i < each_blocks_2.length; i += 1) { each_blocks_2[i].d(1); } each_blocks_2.length = each_value_2.length; } if (dirty & /*users*/ 32) { each_value_1 = /*users*/ ctx2[5]; each_blocks_1 = update_keyed_each(each_blocks_1, dirty, get_key, 1, ctx2, each_value_1, each1_lookup, select0, destroy_block, create_each_block_1, null, get_each_context_1); } if (dirty & /*$userStore, users*/ 40) { select_options( select0, /*$userStore*/ ctx2[3] ); } if (dirty & /*presets*/ 256) { each_value = /*presets*/ ctx2[8]; let i; for (i = 0; i < each_value.length; i += 1) { const child_ctx = get_each_context$2(ctx2, each_value, i); if (each_blocks[i]) { each_blocks[i].p(child_ctx, dirty); } else { each_blocks[i] = create_each_block$2(child_ctx); each_blocks[i].c(); each_blocks[i].m(select1, null); } } for (; i < each_blocks.length; i += 1) { each_blocks[i].d(1); } each_blocks.length = each_value.length; } if (dirty & /*selectedPreset, presets*/ 260) { select_option( select1, /*selectedPreset*/ ctx2[2] ); } if (!current || dirty & /*selectedPreset, presets*/ 260 && button3_disabled_value !== (button3_disabled_value = /*selectedPreset*/ ctx2[2] === "default")) { button3.disabled = button3_disabled_value; } if (!current || dirty & /*selectedPreset, presets*/ 260 && button4_disabled_value !== (button4_disabled_value = /*selectedPreset*/ ctx2[2] === "default")) { button4.disabled = button4_disabled_value; } }, i(local) { if (current) return; transition_in(sliderinput0.$$.fragment, local); transition_in(numberinput0.$$.fragment, local); transition_in(numberinput1.$$.fragment, local); transition_in(sliderinput1.$$.fragment, local); transition_in(checkbox0.$$.fragment, local); transition_in(checkbox1.$$.fragment, local); transition_in(checkbox2.$$.fragment, local); transition_in(checkbox3.$$.fragment, local); transition_in(checkbox4.$$.fragment, local); transition_in(numberinput2.$$.fragment, local); transition_in(numberinput3.$$.fragment, local); transition_in(checkbox5.$$.fragment, local); transition_in(numberinput4.$$.fragment, local); transition_in(checkbox6.$$.fragment, local); transition_in(numberinput5.$$.fragment, local); transition_in(numberinput6.$$.fragment, local); transition_in(checkbox7.$$.fragment, local); transition_in(numberinput7.$$.fragment, local); transition_in(numberinput8.$$.fragment, local); transition_in(numberinput9.$$.fragment, local); transition_in(switchtoggle.$$.fragment, local); transition_in(numberinput10.$$.fragment, local); transition_in(checkbox8.$$.fragment, local); transition_in(checkbox9.$$.fragment, local); transition_in(checkbox10.$$.fragment, local); transition_in(checkbox11.$$.fragment, local); transition_in(checkbox12.$$.fragment, local); current = true; }, o(local) { transition_out(sliderinput0.$$.fragment, local); transition_out(numberinput0.$$.fragment, local); transition_out(numberinput1.$$.fragment, local); transition_out(sliderinput1.$$.fragment, local); transition_out(checkbox0.$$.fragment, local); transition_out(checkbox1.$$.fragment, local); transition_out(checkbox2.$$.fragment, local); transition_out(checkbox3.$$.fragment, local); transition_out(checkbox4.$$.fragment, local); transition_out(numberinput2.$$.fragment, local); transition_out(numberinput3.$$.fragment, local); transition_out(checkbox5.$$.fragment, local); transition_out(numberinput4.$$.fragment, local); transition_out(checkbox6.$$.fragment, local); transition_out(numberinput5.$$.fragment, local); transition_out(numberinput6.$$.fragment, local); transition_out(checkbox7.$$.fragment, local); transition_out(numberinput7.$$.fragment, local); transition_out(numberinput8.$$.fragment, local); transition_out(numberinput9.$$.fragment, local); transition_out(switchtoggle.$$.fragment, local); transition_out(numberinput10.$$.fragment, local); transition_out(checkbox8.$$.fragment, local); transition_out(checkbox9.$$.fragment, local); transition_out(checkbox10.$$.fragment, local); transition_out(checkbox11.$$.fragment, local); transition_out(checkbox12.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(div16); destroy_each(each_blocks_2, detaching); for (let i = 0; i < each_blocks_1.length; i += 1) { each_blocks_1[i].d(); } destroy_each(each_blocks, detaching); destroy_component(sliderinput0); destroy_component(numberinput0); destroy_component(numberinput1); destroy_component(sliderinput1); destroy_component(checkbox0); destroy_component(checkbox1); destroy_component(checkbox2); destroy_component(checkbox3); destroy_component(checkbox4); destroy_component(numberinput2); destroy_component(numberinput3); destroy_component(checkbox5); destroy_component(numberinput4); destroy_component(checkbox6); destroy_component(numberinput5); destroy_component(numberinput6); destroy_component(checkbox7); destroy_component(numberinput7); destroy_component(numberinput8); destroy_component(numberinput9); destroy_component(switchtoggle); destroy_component(numberinput10); destroy_component(checkbox8); destroy_component(checkbox9); destroy_component(checkbox10); destroy_component(checkbox11); destroy_component(checkbox12); mounted = false; run_all(dispose); } }; } let lastInput = ""; async function activateLayer() { ui.controls.initialize({ control: "sequencer", tool: "play-effect" }); canvas.sequencerInterfaceLayer.activate({ tool: "play-effect" }); } function instance$5($$self, $$props, $$invalidate) { let $fileStore; let $userStore; const fileStore = PlayerSettings.file.store; component_subscribe($$self, fileStore, (value) => $$invalidate(0, $fileStore = value)); const users2 = game.users.filter((user) => user.active); const userStore = PlayerSettings.users.store; component_subscribe($$self, userStore, (value) => $$invalidate(3, $userStore = value)); let lastResults = []; let suggestions = []; const searchDebounce = foundry.utils.debounce( () => { const fileInput = get_store_value(fileStore).toLowerCase(); if (!fileInput) { $$invalidate(1, suggestions = SequencerDatabase.publicModules); return; } if (lastInput === fileInput) return; let results = SequencerDatabase.searchFor(fileInput); if (lastResults.equals(results)) return; lastResults = foundry.utils.duplicate(results); if (results.length === 1 && results[0].startsWith(fileInput)) return; if (results.length > 100) { results = results.slice(0, 100); } $$invalidate(1, suggestions = foundry.utils.duplicate(results)); }, 200 ); let filePicker = false; function handleClick() { if (!filePicker) { const file = get_store_value(fileStore); const current = SequencerDatabase.getEntry(file, { softFail: true }) ? file : ""; filePicker = new FilePicker({ type: "imageVideo", current, callback: (path) => { fileStore.set(path); filePicker = false; } }); } filePicker.render(true, { focus: true }); } const presets = PlayerSettings.getPresets(); let selectedPreset = "default"; const click_handler = () => activateLayer(); function input_input_handler() { $fileStore = this.value; fileStore.set($fileStore); } function select0_change_handler() { $userStore = select_multiple_value(this); userStore.set($userStore); $$invalidate(5, users2); } function select1_change_handler() { selectedPreset = select_value(this); $$invalidate(2, selectedPreset); $$invalidate(8, presets); } const change_handler = () => PlayerSettings.loadPreset(selectedPreset); const click_handler_1 = () => PlayerSettings.savePreset(selectedPreset); const click_handler_2 = () => PlayerSettings.copyPreset(selectedPreset); const click_handler_3 = () => PlayerSettings.deletePreset(selectedPreset); $$self.$$.update = () => { if ($$self.$$.dirty & /*$fileStore*/ 1) { searchDebounce($fileStore); } }; return [ $fileStore, suggestions, selectedPreset, $userStore, fileStore, users2, userStore, handleClick, presets, click_handler, input_input_handler, select0_change_handler, select1_change_handler, change_handler, click_handler_1, click_handler_2, click_handler_3 ]; } class Player extends SvelteComponent { constructor(options) { super(); init(this, options, instance$5, create_fragment$5, safe_not_equal, {}); } } const SequenceStatus_svelte_svelte_type_style_lang = ""; function create_fragment$4(ctx) { let i0; let t; let i1; return { c() { i0 = element("i"); t = space(); i1 = element("i"); attr(i0, "class", "fa-solid background-circle svelte-ese-1wkm0y3"); toggle_class( i0, "invisible", /*$status*/ ctx[1] === CONSTANTS.STATUS.READY ); toggle_class( i0, "fa-arrow-right", /*$status*/ ctx[1] === CONSTANTS.STATUS.RUNNING || /*$status*/ ctx[1] === CONSTANTS.STATUS.READY ); toggle_class( i0, "fa-check", /*$status*/ ctx[1] === CONSTANTS.STATUS.COMPLETE ); toggle_class( i0, "fa-arrow-down", /*$status*/ ctx[1] === CONSTANTS.STATUS.SKIPPED ); toggle_class( i0, "fa-times", /*$status*/ ctx[1] === CONSTANTS.STATUS.ABORTED ); attr(i1, "class", "fa-solid "); }, m(target, anchor) { insert(target, i0, anchor); insert(target, t, anchor); insert(target, i1, anchor); }, p(ctx2, [dirty]) { if (dirty & /*$status, CONSTANTS*/ 2) { toggle_class( i0, "invisible", /*$status*/ ctx2[1] === CONSTANTS.STATUS.READY ); } if (dirty & /*$status, CONSTANTS*/ 2) { toggle_class( i0, "fa-arrow-right", /*$status*/ ctx2[1] === CONSTANTS.STATUS.RUNNING || /*$status*/ ctx2[1] === CONSTANTS.STATUS.READY ); } if (dirty & /*$status, CONSTANTS*/ 2) { toggle_class( i0, "fa-check", /*$status*/ ctx2[1] === CONSTANTS.STATUS.COMPLETE ); } if (dirty & /*$status, CONSTANTS*/ 2) { toggle_class( i0, "fa-arrow-down", /*$status*/ ctx2[1] === CONSTANTS.STATUS.SKIPPED ); } if (dirty & /*$status, CONSTANTS*/ 2) { toggle_class( i0, "fa-times", /*$status*/ ctx2[1] === CONSTANTS.STATUS.ABORTED ); } }, i: noop, o: noop, d(detaching) { if (detaching) detach(i0); if (detaching) detach(t); if (detaching) detach(i1); } }; } function instance$4($$self, $$props, $$invalidate) { let $status, $$unsubscribe_status = noop, $$subscribe_status = () => ($$unsubscribe_status(), $$unsubscribe_status = subscribe(status, ($$value) => $$invalidate(1, $status = $$value)), status); $$self.$$.on_destroy.push(() => $$unsubscribe_status()); let { status } = $$props; $$subscribe_status(); $$self.$$set = ($$props2) => { if ("status" in $$props2) $$subscribe_status($$invalidate(0, status = $$props2.status)); }; return [status, $status]; } class SequenceStatus extends SvelteComponent { constructor(options) { super(); init(this, options, instance$4, create_fragment$4, safe_not_equal, { status: 0 }); } } const SequenceSection_svelte_svelte_type_style_lang = ""; function create_fragment$3(ctx) { let div; let span0; let sequencestatus; let t0; let span1; let t2; let span2; let a; let current; let mounted; let dispose; sequencestatus = new SequenceStatus({ props: { status: ( /*status*/ ctx[2] ) } }); return { c() { div = element("div"); span0 = element("span"); create_component(sequencestatus.$$.fragment); t0 = space(); span1 = element("span"); span1.textContent = `${/*sectionName*/ ctx[3]}`; t2 = space(); span2 = element("span"); a = element("a"); a.innerHTML = ``; attr(span1, "class", "section-name svelte-ese-1ee9h3"); attr(a, "class", "svelte-ese-1ee9h3"); toggle_class( a, "section-done", /*$status*/ ctx[1] > CONSTANTS.STATUS.READY ); attr(span2, "class", "sequence-actions svelte-ese-1ee9h3"); attr(span2, "data-tooltip", localize("SEQUENCER.Sequences.AbortSection")); attr(div, "class", "svelte-ese-1ee9h3"); }, m(target, anchor) { insert(target, div, anchor); append(div, span0); mount_component(sequencestatus, span0, null); append(div, t0); append(div, span1); append(div, t2); append(div, span2); append(span2, a); current = true; if (!mounted) { dispose = listen( a, "click", /*click_handler*/ ctx[4] ); mounted = true; } }, p(ctx2, [dirty]) { if (!current || dirty & /*$status, CONSTANTS*/ 2) { toggle_class( a, "section-done", /*$status*/ ctx2[1] > CONSTANTS.STATUS.READY ); } }, i(local) { if (current) return; transition_in(sequencestatus.$$.fragment, local); current = true; }, o(local) { transition_out(sequencestatus.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(div); destroy_component(sequencestatus); mounted = false; dispose(); } }; } function instance$3($$self, $$props, $$invalidate) { let $status; let { section } = $$props; const status = section.sectionStatus; component_subscribe($$self, status, (value) => $$invalidate(1, $status = value)); const sectionName = (section?.constructor?.niceName ? section?.constructor?.niceName : section.constructor.name) + (section?._name ? " - " + section._name : ""); const click_handler = () => { section._abortSection(); }; $$self.$$set = ($$props2) => { if ("section" in $$props2) $$invalidate(0, section = $$props2.section); }; return [section, $status, status, sectionName, click_handler]; } class SequenceSection extends SvelteComponent { constructor(options) { super(); init(this, options, instance$3, create_fragment$3, safe_not_equal, { section: 0 }); } } const Sequence_svelte_svelte_type_style_lang = ""; function get_each_context$1(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[6] = list[i]; return child_ctx; } function create_each_block$1(ctx) { let sequencesection; let current; sequencesection = new SequenceSection({ props: { section: ( /*section*/ ctx[6] ) } }); return { c() { create_component(sequencesection.$$.fragment); }, m(target, anchor) { mount_component(sequencesection, target, anchor); current = true; }, p(ctx2, dirty) { const sequencesection_changes = {}; if (dirty & /*sequence*/ 1) sequencesection_changes.section = /*section*/ ctx2[6]; sequencesection.$set(sequencesection_changes); }, i(local) { if (current) return; transition_in(sequencesection.$$.fragment, local); current = true; }, o(local) { transition_out(sequencesection.$$.fragment, local); current = false; }, d(detaching) { destroy_component(sequencesection, detaching); } }; } function create_fragment$2(ctx) { let div; let span0; let sequencestatus; let t0; let span1; let t1; let t2; let t3_value = ( /*sequence*/ ctx[0].moduleName ? ` (${/*sequence*/ ctx[0].moduleName})` : "" ); let t3; let t4; let span2; let a0; let i0; let t5; let a1; let i1; let t6; let each_1_anchor; let current; let mounted; let dispose; sequencestatus = new SequenceStatus({ props: { status: ( /*status*/ ctx[3] ) } }); let each_value = ( /*sequence*/ ctx[0].sections ); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { each_blocks[i] = create_each_block$1(get_each_context$1(ctx, each_value, i)); } const out = (i) => transition_out(each_blocks[i], 1, 1, () => { each_blocks[i] = null; }); return { c() { div = element("div"); span0 = element("span"); create_component(sequencestatus.$$.fragment); t0 = space(); span1 = element("span"); t1 = text$1("Sequence "); t2 = text$1( /*index*/ ctx[1] ); t3 = text$1(t3_value); t4 = space(); span2 = element("span"); a0 = element("a"); i0 = element("i"); t5 = space(); a1 = element("a"); i1 = element("i"); t6 = space(); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } each_1_anchor = empty(); attr(span1, "class", "sequence-name svelte-ese-1dcwqos"); attr(i0, "class", "fas fa-trash-can"); attr(a0, "class", "clear-sequence svelte-ese-1dcwqos"); attr(a0, "data-tooltip", localize("SEQUENCER.Sequences.Clear")); toggle_class( a0, "sequence-done-show", /*$status*/ ctx[2] > CONSTANTS.STATUS.RUNNING ); attr(i1, "class", "fas fa-stop"); attr(a1, "data-tooltip", localize("SEQUENCER.Sequences.AbortSequence")); attr(a1, "class", "svelte-ese-1dcwqos"); toggle_class( a1, "sequence-done-hide", /*$status*/ ctx[2] > CONSTANTS.STATUS.RUNNING ); attr(span2, "class", "sequence-actions svelte-ese-1dcwqos"); attr(div, "class", "sequence-name-container svelte-ese-1dcwqos"); }, m(target, anchor) { insert(target, div, anchor); append(div, span0); mount_component(sequencestatus, span0, null); append(div, t0); append(div, span1); append(span1, t1); append(span1, t2); append(span1, t3); append(div, t4); append(div, span2); append(span2, a0); append(a0, i0); append(span2, t5); append(span2, a1); append(a1, i1); insert(target, t6, anchor); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(target, anchor); } insert(target, each_1_anchor, anchor); current = true; if (!mounted) { dispose = [ listen( a0, "click", /*click_handler*/ ctx[4] ), listen( a1, "click", /*click_handler_1*/ ctx[5] ) ]; mounted = true; } }, p(ctx2, [dirty]) { if (!current || dirty & /*index*/ 2) set_data( t2, /*index*/ ctx2[1] ); if ((!current || dirty & /*sequence*/ 1) && t3_value !== (t3_value = /*sequence*/ ctx2[0].moduleName ? ` (${/*sequence*/ ctx2[0].moduleName})` : "")) set_data(t3, t3_value); if (!current || dirty & /*$status, CONSTANTS*/ 4) { toggle_class( a0, "sequence-done-show", /*$status*/ ctx2[2] > CONSTANTS.STATUS.RUNNING ); } if (!current || dirty & /*$status, CONSTANTS*/ 4) { toggle_class( a1, "sequence-done-hide", /*$status*/ ctx2[2] > CONSTANTS.STATUS.RUNNING ); } if (dirty & /*sequence*/ 1) { each_value = /*sequence*/ ctx2[0].sections; let i; for (i = 0; i < each_value.length; i += 1) { const child_ctx = get_each_context$1(ctx2, each_value, i); if (each_blocks[i]) { each_blocks[i].p(child_ctx, dirty); transition_in(each_blocks[i], 1); } else { each_blocks[i] = create_each_block$1(child_ctx); each_blocks[i].c(); transition_in(each_blocks[i], 1); each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor); } } group_outros(); for (i = each_value.length; i < each_blocks.length; i += 1) { out(i); } check_outros(); } }, i(local) { if (current) return; transition_in(sequencestatus.$$.fragment, local); for (let i = 0; i < each_value.length; i += 1) { transition_in(each_blocks[i]); } current = true; }, o(local) { transition_out(sequencestatus.$$.fragment, local); each_blocks = each_blocks.filter(Boolean); for (let i = 0; i < each_blocks.length; i += 1) { transition_out(each_blocks[i]); } current = false; }, d(detaching) { if (detaching) detach(div); destroy_component(sequencestatus); if (detaching) detach(t6); destroy_each(each_blocks, detaching); if (detaching) detach(each_1_anchor); mounted = false; run_all(dispose); } }; } function instance$2($$self, $$props, $$invalidate) { let $status; let { sequence } = $$props; let { index } = $$props; const status = sequence.status; component_subscribe($$self, status, (value) => $$invalidate(2, $status = value)); const click_handler = () => { SequenceManager.RunningSequences.delete(sequence.id); }; const click_handler_1 = () => { sequence._abort(); }; $$self.$$set = ($$props2) => { if ("sequence" in $$props2) $$invalidate(0, sequence = $$props2.sequence); if ("index" in $$props2) $$invalidate(1, index = $$props2.index); }; return [sequence, index, $status, status, click_handler, click_handler_1]; } let Sequence$2 = class Sequence2 extends SvelteComponent { constructor(options) { super(); init(this, options, instance$2, create_fragment$2, safe_not_equal, { sequence: 0, index: 1 }); } }; const Sequences_svelte_svelte_type_style_lang = ""; function get_each_context(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[7] = list[i][0]; child_ctx[8] = list[i][1]; child_ctx[10] = i; return child_ctx; } function create_else_block(ctx) { let div0; let button0; let t1; let button1; let t3; let div1; let each_blocks = []; let each_1_lookup = /* @__PURE__ */ new Map(); let current; let mounted; let dispose; let each_value = ( /*runningSequences*/ ctx[0] ); const get_key = (ctx2) => ( /*id*/ ctx2[7] ); for (let i = 0; i < each_value.length; i += 1) { let child_ctx = get_each_context(ctx, each_value, i); let key = get_key(child_ctx); each_1_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx)); } return { c() { div0 = element("div"); button0 = element("button"); button0.textContent = `${localize("SEQUENCER.Sequences.ClearFinished")}`; t1 = space(); button1 = element("button"); button1.textContent = `${localize("SEQUENCER.Sequences.StopAll")}`; t3 = space(); div1 = element("div"); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } attr(button0, "type", "button"); attr(button0, "class", "svelte-ese-1ismzan"); attr(button1, "type", "button"); attr(button1, "class", "svelte-ese-1ismzan"); attr(div0, "class", "sequence-button-header svelte-ese-1ismzan"); attr(div1, "class", "running-sequences svelte-ese-1ismzan"); }, m(target, anchor) { insert(target, div0, anchor); append(div0, button0); append(div0, t1); append(div0, button1); insert(target, t3, anchor); insert(target, div1, anchor); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].m(div1, null); } current = true; if (!mounted) { dispose = [ listen( button0, "click", /*click_handler*/ ctx[3] ), listen( button1, "click", /*click_handler_1*/ ctx[4] ) ]; mounted = true; } }, p(ctx2, dirty) { if (dirty & /*runningSequences*/ 1) { each_value = /*runningSequences*/ ctx2[0]; group_outros(); each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx2, each_value, each_1_lookup, div1, outro_and_destroy_block, create_each_block, null, get_each_context); check_outros(); } }, i(local) { if (current) return; for (let i = 0; i < each_value.length; i += 1) { transition_in(each_blocks[i]); } current = true; }, o(local) { for (let i = 0; i < each_blocks.length; i += 1) { transition_out(each_blocks[i]); } current = false; }, d(detaching) { if (detaching) detach(div0); if (detaching) detach(t3); if (detaching) detach(div1); for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].d(); } mounted = false; run_all(dispose); } }; } function create_if_block(ctx) { let div; let h2; return { c() { div = element("div"); h2 = element("h2"); h2.textContent = `${localize("SEQUENCER.Sequences.NoSequences")}`; attr(div, "class", "no-sequences"); }, m(target, anchor) { insert(target, div, anchor); append(div, h2); }, p: noop, i: noop, o: noop, d(detaching) { if (detaching) detach(div); } }; } function create_each_block(key_1, ctx) { let first; let sequence; let current; sequence = new Sequence$2({ props: { sequence: ( /*sequence*/ ctx[8] ), index: ( /*index*/ ctx[10] + 1 ) } }); return { key: key_1, first: null, c() { first = empty(); create_component(sequence.$$.fragment); this.first = first; }, m(target, anchor) { insert(target, first, anchor); mount_component(sequence, target, anchor); current = true; }, p(new_ctx, dirty) { ctx = new_ctx; const sequence_changes = {}; if (dirty & /*runningSequences*/ 1) sequence_changes.sequence = /*sequence*/ ctx[8]; if (dirty & /*runningSequences*/ 1) sequence_changes.index = /*index*/ ctx[10] + 1; sequence.$set(sequence_changes); }, i(local) { if (current) return; transition_in(sequence.$$.fragment, local); current = true; }, o(local) { transition_out(sequence.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(first); destroy_component(sequence, detaching); } }; } function create_fragment$1(ctx) { let div; let current_block_type_index; let if_block; let current; const if_block_creators = [create_if_block, create_else_block]; const if_blocks = []; function select_block_type(ctx2, dirty) { if (!/*runningSequences*/ ctx2[0].length) return 0; return 1; } current_block_type_index = select_block_type(ctx); if_block = if_blocks[current_block_type_index] = if_block_creators[current_block_type_index](ctx); return { c() { div = element("div"); if_block.c(); attr(div, "class", "sequence-container svelte-ese-1ismzan"); }, m(target, anchor) { insert(target, div, anchor); if_blocks[current_block_type_index].m(div, null); current = true; }, p(ctx2, [dirty]) { let previous_block_index = current_block_type_index; current_block_type_index = select_block_type(ctx2); if (current_block_type_index === previous_block_index) { if_blocks[current_block_type_index].p(ctx2, dirty); } else { group_outros(); transition_out(if_blocks[previous_block_index], 1, 1, () => { if_blocks[previous_block_index] = null; }); check_outros(); if_block = if_blocks[current_block_type_index]; if (!if_block) { if_block = if_blocks[current_block_type_index] = if_block_creators[current_block_type_index](ctx2); if_block.c(); } else { if_block.p(ctx2, dirty); } transition_in(if_block, 1); if_block.m(div, null); } }, i(local) { if (current) return; transition_in(if_block); current = true; }, o(local) { transition_out(if_block); current = false; }, d(detaching) { if (detaching) detach(div); if_blocks[current_block_type_index].d(); } }; } function instance$1($$self, $$props, $$invalidate) { let runningSequences; let $RunningSequences; const RunningSequences = SequenceManager.RunningSequences; component_subscribe($$self, RunningSequences, (value) => $$invalidate(2, $RunningSequences = value)); onDestroy(() => { SequenceManager.RunningSequences.clearFinishedSequences(); }); const click_handler = () => { SequenceManager.RunningSequences.clearFinishedSequences(); }; const click_handler_1 = () => { SequenceManager.RunningSequences.stopAll(); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*$RunningSequences*/ 4) { Object.values($RunningSequences); } if ($$self.$$.dirty & /*$RunningSequences*/ 4) { $$invalidate(0, runningSequences = Object.entries($RunningSequences)); } }; return [ runningSequences, RunningSequences, $RunningSequences, click_handler, click_handler_1 ]; } class Sequences extends SvelteComponent { constructor(options) { super(); init(this, options, instance$1, create_fragment$1, safe_not_equal, {}); } } function create_default_slot(ctx) { let tabs_1; let updating_activeTab; let t; let switch_instance; let switch_instance_anchor; let current; function tabs_1_activeTab_binding(value) { ctx[4](value); } let tabs_1_props = { tabs: ( /*tabs*/ ctx[3] ) }; if ( /*activeTab*/ ctx[1] !== void 0 ) { tabs_1_props.activeTab = /*activeTab*/ ctx[1]; } tabs_1 = new Tabs({ props: tabs_1_props }); binding_callbacks.push(() => bind(tabs_1, "activeTab", tabs_1_activeTab_binding)); var switch_value = ( /*component*/ ctx[2] ); function switch_props(ctx2) { return {}; } if (switch_value) { switch_instance = construct_svelte_component(switch_value, switch_props()); } return { c() { create_component(tabs_1.$$.fragment); t = space(); if (switch_instance) create_component(switch_instance.$$.fragment); switch_instance_anchor = empty(); }, m(target, anchor) { mount_component(tabs_1, target, anchor); insert(target, t, anchor); if (switch_instance) mount_component(switch_instance, target, anchor); insert(target, switch_instance_anchor, anchor); current = true; }, p(ctx2, dirty) { const tabs_1_changes = {}; if (!updating_activeTab && dirty & /*activeTab*/ 2) { updating_activeTab = true; tabs_1_changes.activeTab = /*activeTab*/ ctx2[1]; add_flush_callback(() => updating_activeTab = false); } tabs_1.$set(tabs_1_changes); if (switch_value !== (switch_value = /*component*/ ctx2[2])) { if (switch_instance) { group_outros(); const old_component = switch_instance; transition_out(old_component.$$.fragment, 1, 0, () => { destroy_component(old_component, 1); }); check_outros(); } if (switch_value) { switch_instance = construct_svelte_component(switch_value, switch_props()); create_component(switch_instance.$$.fragment); transition_in(switch_instance.$$.fragment, 1); mount_component(switch_instance, switch_instance_anchor.parentNode, switch_instance_anchor); } else { switch_instance = null; } } }, i(local) { if (current) return; transition_in(tabs_1.$$.fragment, local); if (switch_instance) transition_in(switch_instance.$$.fragment, local); current = true; }, o(local) { transition_out(tabs_1.$$.fragment, local); if (switch_instance) transition_out(switch_instance.$$.fragment, local); current = false; }, d(detaching) { destroy_component(tabs_1, detaching); if (detaching) detach(t); if (detaching) detach(switch_instance_anchor); if (switch_instance) destroy_component(switch_instance, detaching); } }; } function create_fragment(ctx) { let applicationshell; let updating_elementRoot; let current; function applicationshell_elementRoot_binding(value) { ctx[5](value); } let applicationshell_props = { $$slots: { default: [create_default_slot] }, $$scope: { ctx } }; if ( /*elementRoot*/ ctx[0] !== void 0 ) { applicationshell_props.elementRoot = /*elementRoot*/ ctx[0]; } applicationshell = new ApplicationShell({ props: applicationshell_props }); binding_callbacks.push(() => bind(applicationshell, "elementRoot", applicationshell_elementRoot_binding)); return { c() { create_component(applicationshell.$$.fragment); }, m(target, anchor) { mount_component(applicationshell, target, anchor); current = true; }, p(ctx2, [dirty]) { const applicationshell_changes = {}; if (dirty & /*$$scope, component, activeTab*/ 134) { applicationshell_changes.$$scope = { dirty, ctx: ctx2 }; } if (!updating_elementRoot && dirty & /*elementRoot*/ 1) { updating_elementRoot = true; applicationshell_changes.elementRoot = /*elementRoot*/ ctx2[0]; add_flush_callback(() => updating_elementRoot = false); } applicationshell.$set(applicationshell_changes); }, i(local) { if (current) return; transition_in(applicationshell.$$.fragment, local); current = true; }, o(local) { transition_out(applicationshell.$$.fragment, local); current = false; }, d(detaching) { destroy_component(applicationshell, detaching); } }; } function instance($$self, $$props, $$invalidate) { let component; const { application } = getContext("#external"); let { elementRoot } = $$props; let tabs = [ { value: "player", label: localize("SEQUENCER.Player.Title"), icon: "fas fa-play-circle", component: Player }, { value: "manager", label: localize("SEQUENCER.Manager.Title"), icon: "fas fa-film", component: Manager }, { value: "sequences", label: localize("SEQUENCER.Sequences.Title"), icon: "fas fa-play", component: Sequences }, { value: "howto", label: localize("SEQUENCER.HowTo.Title"), icon: "fas fa-chalkboard-teacher", component: HowTo } ]; let activeTab = application.options.tab ?? "manager"; function tabs_1_activeTab_binding(value) { activeTab = value; $$invalidate(1, activeTab); } function applicationshell_elementRoot_binding(value) { elementRoot = value; $$invalidate(0, elementRoot); } $$self.$$set = ($$props2) => { if ("elementRoot" in $$props2) $$invalidate(0, elementRoot = $$props2.elementRoot); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*activeTab*/ 2) { $$invalidate(2, component = tabs.find((tab) => tab.value === activeTab).component); } }; return [ elementRoot, activeTab, component, tabs, tabs_1_activeTab_binding, applicationshell_elementRoot_binding ]; } class Effects_ui_shell extends SvelteComponent { constructor(options) { super(); init(this, options, instance, create_fragment, safe_not_equal, { elementRoot: 0 }); } get elementRoot() { return this.$$.ctx[0]; } set elementRoot(elementRoot) { this.$$set({ elementRoot }); flush(); } } class EffectsUIApp extends SvelteApplication { static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { title: game.i18n.localize("SEQUENCER.ManagerUI"), classes: ["dialog"], width: "auto", height: "auto", top: 65, left: 120, resizable: false, svelte: { class: Effects_ui_shell, target: document.body } }); } static getActiveApp() { return Object.values(ui.windows).find((app) => { return app instanceof this && app._state > Application.RENDER_STATES.CLOSED; }); } static async show(options = {}) { const existingApp = this.getActiveApp(); if (existingApp) return existingApp.render(false, { focus: true }); return new Promise((resolve) => { options.resolve = resolve; new this(options).render(true, { focus: true }); }); } } function registerSettings() { game.settings.register(CONSTANTS.MODULE_NAME, "enable-fix-pixi", { name: "SEQUENCER.Setting.EnablePixiFix.Title", hint: "SEQUENCER.Setting.EnablePixiFix.Label", scope: "client", config: true, default: false, requiresReload: true, type: Boolean }); game.settings.register(CONSTANTS.MODULE_NAME, "enable-global-fix-pixi", { name: "SEQUENCER.Setting.EnableGlobalPixiFix.Title", hint: "SEQUENCER.Setting.EnableGlobalPixiFix.Label", scope: "client", config: true, default: false, requiresReload: true, type: Boolean }); game.settings.register(CONSTANTS.MODULE_NAME, "enable-above-ui-screenspace", { name: "SEQUENCER.Setting.EnableAboveUIScreenspace.Title", hint: "SEQUENCER.Setting.EnableAboveUIScreenspace.Label", scope: "client", config: true, default: true, requiresReload: true, type: Boolean }); game.settings.register(CONSTANTS.MODULE_NAME, "debug", { name: "SEQUENCER.Setting.Debug.Title", hint: "SEQUENCER.Setting.Debug.Label", scope: "client", config: true, default: false, type: Boolean }); game.settings.register(CONSTANTS.MODULE_NAME, "showSidebarTools", { name: "SEQUENCER.Setting.ShowTools.Title", hint: "SEQUENCER.Setting.ShowTools.Label", scope: "client", config: true, default: true, requiresReload: true, type: Boolean }); game.settings.register(CONSTANTS.MODULE_NAME, "showTokenSidebarTools", { name: "SEQUENCER.Setting.ShowTokenTools.Title", hint: "SEQUENCER.Setting.ShowTokenTools.Label", scope: "client", config: true, default: true, requiresReload: true, type: Boolean }); game.settings.register(CONSTANTS.MODULE_NAME, "effectsEnabled", { name: "SEQUENCER.Setting.EnableEffects.Title", hint: "SEQUENCER.Setting.EnableEffects.Label", scope: "client", config: true, default: true, requiresReload: true, type: Boolean }); game.settings.register(CONSTANTS.MODULE_NAME, "soundsEnabled", { name: "SEQUENCER.Setting.EnableSounds.Title", hint: "SEQUENCER.Setting.EnableSounds.Label", scope: "client", config: true, default: true, requiresReload: true, type: Boolean }); game.settings.register(CONSTANTS.MODULE_NAME, "user-effect-opacity", { name: "SEQUENCER.Setting.ExternalEffectOpacity.Title", hint: "SEQUENCER.Setting.ExternalEffectOpacity.Label", scope: "client", config: true, default: 50, type: Number, range: { min: 0, max: 100, step: 1 } }); game.settings.register(CONSTANTS.MODULE_NAME, "db-list-view", { scope: "client", config: false, default: false, type: Boolean }); const permissionLevels = [ game.i18n.localize("SEQUENCER.Permission.Player"), game.i18n.localize("SEQUENCER.Permission.Trusted"), game.i18n.localize("SEQUENCER.Permission.Assistant"), game.i18n.localize("SEQUENCER.Permission.GM") ]; game.settings.register(CONSTANTS.MODULE_NAME, "permissions-effect-create", { name: "SEQUENCER.Setting.Permission.EffectCreate.Title", hint: "SEQUENCER.Setting.Permission.EffectCreate.Label", scope: "world", config: true, default: 0, type: Number, choices: permissionLevels, requiresReload: true }); game.settings.register(CONSTANTS.MODULE_NAME, "permissions-effect-delete", { name: "SEQUENCER.Setting.Permission.EffectDelete.Title", hint: "SEQUENCER.Setting.Permission.EffectDelete.Label", scope: "world", config: true, default: 2, type: Number, choices: permissionLevels, requiresReload: true }); game.settings.register(CONSTANTS.MODULE_NAME, "permissions-sound-create", { name: "SEQUENCER.Setting.Permission.SoundCreate.Title", hint: "SEQUENCER.Setting.Permission.SoundCreate.Label", scope: "world", config: true, default: 0, type: Number, choices: permissionLevels, requiresReload: true }); game.settings.register(CONSTANTS.MODULE_NAME, "permissions-preload", { name: "SEQUENCER.Setting.Permission.PreloadClients.Title", hint: "SEQUENCER.Setting.Permission.PreloadClients.Label", scope: "world", config: true, default: 1, type: Number, choices: permissionLevels, requiresReload: true }); game.settings.register(CONSTANTS.MODULE_NAME, "permissions-sidebar-tools", { name: "SEQUENCER.Setting.Permission.UseSidebarTools.Title", hint: "SEQUENCER.Setting.Permission.UseSidebarTools.Label", scope: "world", config: true, default: 0, type: Number, choices: permissionLevels, requiresReload: true }); game.settings.register(CONSTANTS.MODULE_NAME, "effectPresets", { scope: "client", default: {}, type: Object }); Hooks.on("getSceneControlButtons", (controls) => { if (!game.settings.get(CONSTANTS.MODULE_NAME, "showSidebarTools")) return; const selectTool = { icon: "fas fa-expand", name: "select-effect", title: "SEQUENCER.SidebarButtons.Select", visible: user_can_do("permissions-effect-create") && user_can_do("permissions-sidebar-tools") }; const playTool = { icon: "fas fa-play", name: "play-effect", title: "SEQUENCER.SidebarButtons.Play", visible: user_can_do("permissions-effect-create") && user_can_do("permissions-sidebar-tools"), onClick: () => { EffectsUIApp.show({ inFocus: true, tab: "player" }); } }; const viewer = { icon: "fas fa-film", name: "effectviewer", title: "SEQUENCER.SidebarButtons.Manager", button: true, visible: user_can_do("permissions-effect-create") && user_can_do("permissions-sidebar-tools"), onClick: () => { EffectsUIApp.show({ inFocus: true, tab: "manager" }); } }; const database = { icon: "fas fa-database", name: "effectdatabase", title: "SEQUENCER.SidebarButtons.Database", button: true, visible: user_can_do("permissions-sidebar-tools"), onClick: () => { DatabaseViewerApp.show(); } }; controls.push({ name: CONSTANTS.MODULE_NAME, title: "Sequencer Layer", icon: "fas fa-list-ol", layer: "sequencerInterfaceLayer", visible: user_can_do("permissions-effect-create") && user_can_do("permissions-sidebar-tools"), activeTool: "select-effect", tools: [selectTool, playTool, database, viewer] }); if (!game.settings.get(CONSTANTS.MODULE_NAME, "showTokenSidebarTools")) return; const bar = controls.find((c) => c.name === "token"); bar.tools.push(database); bar.tools.push(viewer); }); debug("Sequencer | Registered settings"); } async function migrateSettings() { const oldScreenspaceSetting = game.settings.storage.get("client").getItem("sequencer.disable-above-ui-screenspace"); if (oldScreenspaceSetting) { const value = oldScreenspaceSetting === "true"; game.settings.storage.get("client").removeItem("sequencer.disable-above-ui-screenspace"); await game.settings.set( CONSTANTS.MODULE_NAME, "enable-above-ui-screenspace", !value ); } } function registerLayers() { CONFIG.Canvas.layers = foundry.utils.mergeObject(Canvas.layers, { sequencerEffects: { layerClass: BaseEffectsLayer, group: "primary" }, sequencerInterfaceLayer: { layerClass: SequencerInterfaceLayer, group: "interface" }, sequencerEffectsUILayer: { layerClass: UIEffectsLayer, group: "interface" } }); if (!Object.is(Canvas.layers, CONFIG.Canvas.layers)) { const layers = Canvas.layers; Object.defineProperty(Canvas, "layers", { get: function() { return foundry.utils.mergeObject(layers, CONFIG.Canvas.layers); } }); } debug("Registered Layers"); } const hotkeys = { get _ready() { return canvas.ready && canvas.sequencerInterfaceLayer.active; }, playTool: { playManySequencedDown: () => { if (!hotkeys._ready) return; EffectPlayer.playManySequenced = true; }, playManySequencedUp: () => { if (!hotkeys._ready) return; EffectPlayer.playManySequenced = false; if (!EffectPlayer.isActive) return; EffectPlayer.playManyUp(); }, playManyDown: () => { if (!hotkeys._ready) return; EffectPlayer.playMany = true; }, playManyUp: () => { if (!hotkeys._ready) return; EffectPlayer.playMany = false; if (!EffectPlayer.isActive) return; EffectPlayer.playManyUp(); }, attachToDown: () => { if (!hotkeys._ready) return; PlayerSettings.attachTo.store.set(true); PlayerSettings.stretchToAttach.store.set(true); }, attachToUp: () => { if (!hotkeys._ready) return; PlayerSettings.attachTo.store.set(false); PlayerSettings.stretchToAttach.store.set(false); } }, selectTool: { snapToGridDown: () => { if (!hotkeys._ready) return; SelectionManager.snapToGrid = true; }, snapToGridUp: () => { if (!hotkeys._ready) return; SelectionManager.snapToGrid = false; }, attachToTargetDown: () => { if (!hotkeys._ready) return; if (!SelectionManager.isActive) return; PlayerSettings.attachTo.store.set(true); PlayerSettings.stretchToAttach.store.set(true); }, attachToTargetUp: () => { if (!hotkeys._ready) return; PlayerSettings.attachTo.store.set(false); PlayerSettings.stretchToAttach.store.set(false); }, deleteDown: () => { if (!hotkeys._ready) return; SelectionManager.delete(); } } }; function registerHotkeys() { game.keybindings.register(CONSTANTS.MODULE_NAME, "play-tool-hotkey-control", { name: "SEQUENCER.Hotkeys.PlayTool.Control", editable: [{ key: "ControlLeft" }], onDown: hotkeys.playTool.playManySequencedDown, onUp: hotkeys.playTool.playManySequencedUp }); game.keybindings.register(CONSTANTS.MODULE_NAME, "play-tool-hotkey-shift", { name: "SEQUENCER.Hotkeys.PlayTool.Shift", editable: [{ key: "ShiftLeft" }], onDown: hotkeys.playTool.playManyDown, onUp: hotkeys.playTool.playManyUp }); game.keybindings.register(CONSTANTS.MODULE_NAME, "play-tool-hotkey-alt", { name: "SEQUENCER.Hotkeys.PlayTool.Alt", editable: [{ key: "AltLeft" }], onDown: hotkeys.playTool.attachToDown, onUp: hotkeys.playTool.attachToDown }); game.keybindings.register( CONSTANTS.MODULE_NAME, "select-tool-hotkey-control", { name: "SEQUENCER.Hotkeys.SelectTool.Control", editable: [{ key: "ControlLeft" }], onDown: hotkeys.selectTool.snapToGridDown, onUp: hotkeys.selectTool.snapToGridUp } ); game.keybindings.register(CONSTANTS.MODULE_NAME, "select-tool-hotkey-alt", { name: "SEQUENCER.Hotkeys.SelectTool.Alt", editable: [{ key: "AltLeft" }], onDown: hotkeys.selectTool.attachToTargetDown, onUp: hotkeys.selectTool.attachToTargetUp }); game.keybindings.register( CONSTANTS.MODULE_NAME, "select-tool-hotkey-delete", { name: "SEQUENCER.Hotkeys.SelectTool.Delete", editable: [{ key: "Delete" }], onDown: hotkeys.selectTool.deleteDown } ); } async function registerTypes(register) { fetch("modules/sequencer/typings/types.d.ts").then((response) => response.text()).then((content) => register("sequencer/types.d.ts", content)); } const presetMap = /* @__PURE__ */ new Map(); class SequencerPresets { /** * Adds a preset that can then be used in sequences * * @param {string} inName * @param {Function} inFunction * @param {boolean} [overwrite=false] overwrite * @returns {Map} */ static add(inName, inFunction, overwrite = false) { if (typeof inName !== "string") { throw custom_error( "Sequencer", `SequencerPresets | inName must be of type string` ); } if (!is_function$1(inFunction)) { throw custom_error( "Sequencer", `SequencerPresets | inFunction must be of type function` ); } if (presetMap.get(inName) && !overwrite) { throw custom_error( "Sequencer", `SequencerPresets | Preset "${inName}" already exists` ); } presetMap.set(inName, inFunction); debug(`Sequencer | Presets | Added "${inName}" preset`); return presetMap; } /** * Retrieves all presets * * @returns {Map} */ static getAll() { return presetMap; } /** * Retrieves preset based on its name * * @param {string} name * @returns {Function} */ static get(name) { return presetMap.get(name); } } class Section { constructor(inSequence) { this.sequence = inSequence; this._applyTraits(); this._sectionStatus = writable$1(CONSTANTS.STATUS.READY); this._playIf = true; this._waitUntilFinished = false; this._async = false; this._waitUntilFinishedDelay = [0, 0]; this._repetitions = 1; this._currentRepetition = 0; this._repeatDelayMin = 0; this._repeatDelayMax = 0; this._repeatDelay = 0; this._delayMin = 0; this._delayMax = 0; this._basicDelay = 0; this._duration = false; } static niceName = "Section"; /** * @protected */ get _shouldAsync() { return this._async || this._waitAnyway; } /** * @protected */ get shouldWaitUntilFinished() { return this._waitUntilFinished || this._waitAnyway; } /** * @protected */ get _waitAnyway() { return (this._async || this._waitUntilFinished) && this._isLastRepetition || this._isLastRepetition && this._isLastSection; } /** * @protected */ get _isLastSection() { return this.sequence.sections.length - 1 === this.sequence.sections.indexOf(this); } /** ------------------------------------------------------------------------------------------------------------------------------ * * Methods below this point should NOT be overridden by child instances of the class, they are integral to the sequence functioning * ------------------------------------------------------------------------------------------------------------------------------- */ /** * @protected */ get _isLastRepetition() { return this._repetitions === 1 || this._repetitions === this._currentRepetition + 1; } /** * @protected */ get _currentWaitTime() { let waitUntilFinishedDelay = this._waitAnyway ? random_int_between(...this._waitUntilFinishedDelay) : 0; return waitUntilFinishedDelay + this._repeatDelay; } /** * Method overwritten by inheriting classes, which is called just before the "run" method is called (see below) * * @returns {Promise} * @protected */ async preRun() { } /** * Method overwritten by inheriting classes, which is called when this section is executed by the Sequence * * @returns {Promise} * @protected */ async run() { } /** * Method overwritten by inheriting classes, which is called when this section is serialized by the Sequence * * @returns {object} * @private */ async _serialize() { return { async: this._async, delay: [this._delayMin, this._delayMax], waitUntilFinished: this._waitUntilFinished, waitUntilFinishedDelay: this._waitUntilFinishedDelay, repetitions: this._repetitions, repetitionsDelay: [this._repeatDelayMin, this._repeatDelayMax] }; } _deserialize(data) { this._async = data.async; this._waitUntilFinished = data.waitUntilFinished; this._waitUntilFinishedDelay = data.waitUntilFinishedDelay; this._repetitions = data.repetitions; this._repeatDelayMin = data.repetitionsDelay[0]; this._repeatDelayMax = data.repetitionsDelay[1]; return this; } /** * Method overwritten by inheriting classes, which stores data or prepares data before the Sequence executes it (see EffectsSection) * * @protected */ async _initialize() { } /** * Method overwritten by inheriting classes. Inheriting classes uses the following to apply traits to themselves: * - Object.assign(this.constructor.prototype, trait) * * @protected */ _applyTraits() { } /** * Causes the section to be repeated n amount of times, with an optional delay. If given inRepeatDelayMin * and inRepeatDelayMax, a random repetition delay will be picked for every repetition * * @param {number} inRepetitions * @param {number} inRepeatDelayMin * @param {number} inRepeatDelayMax * @returns {Section} this */ repeats(inRepetitions, inRepeatDelayMin = 0, inRepeatDelayMax) { if (!is_real_number(inRepetitions)) throw this.sequence._customError( this, "repeats", "inRepetitions must be of type number" ); if (!is_real_number(inRepeatDelayMin)) throw this.sequence._customError( this, "repeats", "repeatDelayMin must be of type number" ); if (inRepeatDelayMax && !is_real_number(inRepeatDelayMax)) { throw this.sequence._customError( this, "repeats", "repeatDelayMax must be of type number" ); } this._repetitions = inRepetitions; this._repeatDelayMin = Math.min( inRepeatDelayMin, inRepeatDelayMax ?? inRepeatDelayMin ); this._repeatDelayMax = Math.max( inRepeatDelayMin, inRepeatDelayMax ?? inRepeatDelayMin ); return this; } /** * Causes the effect or sound to not play, and skip all delays, repetitions, waits, etc. If you pass a function, * the function should return something false-y if you do not want the effect or sound to play. * * @param {boolean|function} inCondition * @returns {Section} this */ playIf(inCondition) { this._playIf = inCondition; return this; } /** * Causes the section to finish running before starting the next section. * * @param {number|boolean} [minDelay=0] minDelay * @param {number/null} [maxDelay=null] maxDelay * @returns {Section} this */ waitUntilFinished(minDelay = 0, maxDelay = null) { if (minDelay === false) return this; if (!is_real_number(minDelay)) throw this.sequence._customError( this, "waitUntilFinished", "minDelay must be of type number" ); if (maxDelay !== null && !is_real_number(maxDelay)) throw this.sequence._customError( this, "waitUntilFinished", "maxDelay must be of type number" ); this._waitUntilFinished = true; this._waitUntilFinishedDelay = [ Math.min(minDelay, maxDelay ?? minDelay), Math.max(minDelay, maxDelay ?? minDelay) ]; return this; } /** * Causes each effect or sound to finish playing before the next one starts playing. This differs from * .waitUntilFinished() in the sense that this is for each repetition, whilst .waitUntilFinished() is * for the entire section. * * @returns {Section} this */ async() { this._async = true; return this; } /** * Delays the effect or sound from being played for a set amount of milliseconds. If given a second number, a * random delay between the two numbers will be generated. * * @param {number} [msMin=1] minMs * @param {number} [msMax=1] maxMs * @returns {Section} this */ delay(msMin, msMax) { if (!is_real_number(msMin)) throw this.sequence._customError( this, "delay", "msMin must be of type number" ); if (msMax && !is_real_number(msMax)) throw this.sequence._customError( this, "delay", "msMax must be of type number" ); this._delayMin = Math.min(msMin, msMax ?? msMin); this._delayMax = Math.max(msMin, msMax ?? msMin); return this; } /** * Overrides the duration of this section * * @param {number} inDuration * @returns {Section} this */ duration(inDuration) { if (!is_real_number(inDuration)) throw this.sequence._customError( this, "duration", "inDuration must be of type number" ); this._duration = inDuration; return this; } /** * Applies a preset to the current section * * @param {string} presetName * @param {*} args * @returns {Section|FunctionSection|EffectSection|AnimationSection|SoundSection} */ preset(presetName, ...args) { if (typeof presetName !== "string") { throw this.sequence._customError( this, "name", `inName must be of type string` ); } const preset = SequencerPresets.get(presetName); if (!preset) { custom_warning( "Sequencer", `preset | Could not find preset with name "${presetName}"` ); return this; } return preset(this, ...args); } /** * @protected */ async _shouldPlay() { return is_function$1(this._playIf) ? await this._playIf() : this._playIf; } /** * @protected */ _validateLocation(inLocation) { inLocation = validate_document(inLocation); if (typeof inLocation === "string") { inLocation = get_object_from_scene(inLocation) ?? inLocation; } if (typeof inLocation === "string") { inLocation = safe_str(inLocation); } return inLocation; } /** * @protected */ async _execute() { if (!await this._shouldPlay()) { this.sectionStatus = CONSTANTS.STATUS.SKIPPED; return; } this._basicDelay = random_float_between(this._delayMin, this._delayMax); return new Promise(async (resolve) => { setTimeout(async () => { this.sectionStatus = CONSTANTS.STATUS.RUNNING; for (let i = 0; i < this._repetitions; i++) { if (get_store_value(this.sectionStatus) === CONSTANTS.STATUS.ABORTED) { resolve(); return; } this._currentRepetition = i; this._repeatDelay = i !== this._repetitions - 1 ? random_float_between( this._repeatDelayMin, this._repeatDelayMax ) : 0; await this.preRun(); if (this._shouldAsync) { await this.run(); } else { this.run(); } if (this._repetitions > 1 && i !== this._repetitions - 1) { await this._delayBetweenRepetitions(); } } resolve(); }, this._basicDelay); }).then(() => { this.sectionStatus = CONSTANTS.STATUS.COMPLETE; }); } set sectionStatus(inStatus) { this._sectionStatus.update((currentStatus) => { if (currentStatus === CONSTANTS.STATUS.READY || currentStatus === CONSTANTS.STATUS.RUNNING && inStatus !== CONSTANTS.STATUS.ABORTED) { return inStatus; } return currentStatus; }); } get sectionStatus() { return this._sectionStatus; } _abortSection() { this.sectionStatus = CONSTANTS.STATUS.ABORTED; } /** * @protected */ async _delayBetweenRepetitions() { let self = this; return new Promise((resolve) => { setTimeout(resolve, self._repeatDelay); }); } } class FunctionSection extends Section { constructor(inSequence, inFunc) { super(inSequence); if (!is_function$1(inFunc)) this._customError( "create", "The given function needs to be an actual function" ); this._func = inFunc; this._waitUntilFinished = inFunc.constructor.name === "AsyncFunction"; } static niceName = "Function"; /** * @returns {Promise} */ async run() { debug("Running function"); await this._func(); } /** * @returns {Promise} * @private */ async _execute() { await this.run(); } } const animation = { /** * Base properties */ _animations: null, /** * Animates a property on the target of the animation. * * @param {string} inTarget * @param {string} inPropertyName * @param {object} inOptions * @param {Number} inOptions.from - a single number from which to animate * @param {Number} inOptions.to - a single number to which to animate * @param {Number} inOptions.duration - how long in ms the animation should take * @param {Number} inOptions.delay - inserts a delay in ms before the animation starts * @param {String} inOptions.ease - what type of easing the animation should use * @param {Boolean} inOptions.gridUnits - if animating width or height, this will set it to work in the scene's grid units * @param {Boolean} inOptions.fromEnd - makes this animation play from the end, like fadeOut, scaleOut, etc * * @returns this */ animateProperty(inTarget, inPropertyName, inOptions = {}) { if (!this._animations) this._animations = []; const result = validateAnimation( inTarget, inPropertyName, inOptions ); if (typeof result === "string") { throw this.sequence._customError(this, "animateProperty", result); } this._animations.push(result); return this; }, /** * Loops a property between a set of values on the target * * @param {string} inTarget * @param {string} inPropertyName * @param {object} inOptions * @param {Number} inOptions.from - a single number from which to loop * @param {Number} inOptions.to - a single number to which to loop * @param {Number} inOptions.values - an array of values to loop between * @param {Number} inOptions.duration - how long in ms the loop should take * @param {Number} inOptions.loops - how many loops in total this animation should go through - if none are specified, the loop is indefinite * @param {Number} inOptions.delay - inserts a delay in ms before the animation starts * @param {String} inOptions.ease - what type of easing the animation should use * @param {Boolean} inOptions.pingPong - sets whether loop should interpolate to the first value after it reaches the first value, or simply set it to the first value * @param {Boolean} inOptions.gridUnits - if animating width or height, this will set it to work in the scene's grid units * * @returns this */ loopProperty(inTarget, inPropertyName, inOptions = {}) { if (!this._animations) this._animations = []; const result = validateLoopingAnimation( inTarget, inPropertyName, inOptions ); if (typeof result === "string") { throw this.sequence._customError(this, "loopProperty", result); } this._animations.push(result); return this; } }; const audio = { /** * Base properties */ _volume: null, _fadeInAudio: null, _fadeOutAudio: null, /** * Sets the volume of the sound. * * @param {number} inVolume * @returns this */ volume(inVolume) { if (!is_real_number(inVolume)) throw this.sequence._customError( this, "volume", "inVolume must be of type number" ); this._volume = Math.max(0, Math.min(1, inVolume)); return this; }, /** * Causes the animated section to fade in its audio (if any) when played * * @param {number} duration How long the fade should be * @param {object} [options] Additional options, such as easing and delay * @returns this */ fadeInAudio(duration, options = {}) { if (typeof options !== "object") throw this.sequence._customError( this, "fadeInAudio", "options must be of type object" ); options = foundry.utils.mergeObject( { ease: "linear", delay: 0 }, options ); if (!is_real_number(duration)) throw this.sequence._customError( this, "fadeInAudio", "duration must be of type number" ); if (typeof options.ease !== "string") throw this.sequence._customError( this, "fadeInAudio", "options.ease must be of type string" ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "fadeInAudio", "options.delay must be of type number" ); this._fadeInAudio = { duration, ease: options.ease, delay: options.delay }; return this; }, /** * Causes the audio to fade out at the end of the animated section's duration * * @param {number} duration How long the fade should be * @param {object} [options] Additional options, such as easing and delay * @returns this */ fadeOutAudio(duration, options = {}) { if (typeof options !== "object") throw this.sequence._customError( this, "fadeOutAudio", "options must be of type object" ); options = foundry.utils.mergeObject( { ease: "linear", delay: 0 }, options ); if (!is_real_number(duration)) throw this.sequence._customError( this, "fadeOutAudio", "duration must be of type number" ); if (typeof options.ease !== "string") throw this.sequence._customError( this, "fadeOutAudio", "ease must be of type string" ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "fadeOutAudio", "delay must be of type number" ); this._fadeOutAudio = { duration, ease: options.ease, delay: options.delay }; return this; } }; const files = { /** * Base properties */ _file: "", _fileOptions: false, _baseFolder: "", _mustache: null, /** * Declares which file to be played. This may also be an array of paths, which will be randomly picked from each * time the section is played. * * @param {string|array} inFile * @returns this */ file(inFile) { this._file = inFile; return this; }, /** * Defines the base folder that will prepend to the file path. This is mainly just useful to make the file * path easier to manage. * * @param {string} inBaseFolder * @returns this */ baseFolder(inBaseFolder) { if (typeof inBaseFolder !== "string") throw this.sequence._customError( this, "baseFolder", "inBaseFolder must be of type string" ); this._baseFolder = inBaseFolder + (inBaseFolder.endsWith("/") ? "" : "/"); return this; }, /** * Sets the Mustache of the filepath. This is applied after the randomization of the filepath, if available. * * @param {object} inMustache * @returns this */ setMustache(inMustache) { if (typeof inMustache !== "object") throw this.sequence._customError( this, "setMustache", "inMustache must be of type object" ); this._mustache = inMustache; return this; }, async _determineFile(inFile) { if (!Array.isArray(inFile) && typeof inFile === "object") { return this._validateCustomRange(inFile); } if (Array.isArray(inFile)) inFile = random_array_element(inFile, { recurse: true }); inFile = this._applyMustache(inFile); if (Sequencer.Database.entryExists(inFile)) { return this._determineDatabaseFile(inFile); } const determinedFile = await this._processFile(inFile); return { file: determinedFile, forcedIndex: false, customRange: false }; }, async _processFile(inFile) { inFile = this._applyMustache(inFile); inFile = this._applyBaseFolder(inFile); inFile = await this._applyWildcard(inFile); if (Array.isArray(inFile)) inFile = random_array_element(inFile, { recurse: true }); return inFile; }, async _validateCustomRange(inFile) { const finalFiles = {}; const validRanges = Object.keys(SequencerFileRangeFind.ftToDistanceMap); for (const [range, rangeFile] of Object.entries(inFile)) { if (!validRanges.includes(range)) { throw this.sequence._customError( this, "file", `a file-distance key map must only contain the following keys: ${validRanges.join( ", " )}` ); } finalFiles[range] = await this._processFile(rangeFile); } return { file: finalFiles, forcedIndex: false, customRange: true }; }, _determineDatabaseFile(inFile) { const entries = Sequencer.Database.getEntry(inFile); const entry = Array.isArray(entries) ? random_array_element(entries) : entries; const match = inFile.match(/(\d)+$/); return { file: entry, forcedIndex: match ? Number(match[1]) : false, customRange: false }; }, _applyBaseFolder(inFile) { if (Array.isArray(inFile)) return inFile.map((file) => this._applyBaseFolder(file)); return inFile.startsWith(this._baseFolder) ? inFile : this._baseFolder + inFile; }, _applyMustache(inFile) { if (!this._mustache) return inFile; let template = Handlebars.compile(inFile); return template(this._mustache); }, async _applyWildcard(inFile) { if (!inFile.includes("*")) return inFile; if (Array.isArray(inFile)) return inFile.map(async (file) => await this._applyWildcard(file)); inFile = this._applyBaseFolder(inFile); return getFiles(inFile, { applyWildCard: true, softFail: this.sequence.softFail }); } }; const moves = { /** * Base properties */ _moveTowards: null, _moveSpeed: null, /** * Sets the location to move the target object to * * @param {object|string} inTarget * @param {object} options * @returns this */ moveTowards(inTarget, options = {}) { options = foundry.utils.mergeObject( { ease: "linear", delay: 0, rotate: true, cacheLocation: false }, options ); if (typeof options.ease !== "string") throw this.sequence._customError( this, "moveTowards", "options.ease must be of type string" ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "moveTowards", "options.delay must be of type number" ); if (typeof options.rotate !== "boolean") throw this.sequence._customError( this, "moveTowards", "options.rotate must be of type boolean" ); if (typeof options.cacheLocation !== "boolean") throw this.sequence._customError( this, "moveTowards", "options.cacheLocation must be of type boolean" ); options.target = this._validateLocation(inTarget); if (!options.target) throw this.sequence._customError( this, "moveTowards", "could not find position of given object" ); options.target = options.cacheLocation ? get_object_position(options.cacheLocation, { measure: true }) : options.target; this._moveTowards = options; return this; }, /** * Sets the speed (pixels per frame) to move the target object * * @param {number} inSpeed * @returns this */ moveSpeed(inSpeed) { if (!is_real_number(inSpeed)) throw this.sequence._customError( this, "moveSpeed", "inSpeed must be of type number" ); this._moveSpeed = inSpeed; return this; } }; const opacity = { /** * Base properties */ _opacity: null, _fadeIn: null, _fadeOut: null, /** * Sets the opacity of the effect. If used with ._fadeIn() and/or ._fadeOut(), this defines what the effect will fade to/from * * @param {number} inOpacity * @returns this */ opacity(inOpacity) { if (!is_real_number(inOpacity)) throw this.sequence._customError( this, "opacity", "inOpacity must be of type number" ); this._opacity = inOpacity; return this; }, /** * Causes the effect to fade in when played * * @param {number} duration How long the fade should be * @param {object} [options] Additional options, such as easing and delay * @returns this */ fadeIn(duration, options = {}) { if (typeof options !== "object") throw this.sequence._customError( this, "fadeIn", "options must be of type object" ); options = foundry.utils.mergeObject( { ease: "linear", delay: 0 }, options ); if (!is_real_number(duration)) throw this.sequence._customError( this, "fadeIn", "duration must be of type number" ); if (typeof options.ease !== "string") throw this.sequence._customError( this, "fadeIn", "options.ease must be of type string" ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "fadeIn", "options.delay must be of type number" ); this._fadeIn = { duration, ease: options.ease, delay: options.delay }; return this; }, /** * Causes the effect to fade out at the end of the effect's duration * * @param {number} duration How long the fade should be * @param {object} [options] Additional options, such as easing and delay * @returns this */ fadeOut(duration, options = {}) { if (typeof options !== "object") throw this.sequence._customError( this, "fadeOut", "options must be of type object" ); options = foundry.utils.mergeObject( { ease: "linear", delay: 0 }, options ); if (!is_real_number(duration)) throw this.sequence._customError( this, "fadeOut", "duration must be of type number" ); if (typeof options.ease !== "string") throw this.sequence._customError( this, "fadeOut", "ease must be of type string" ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "fadeOut", "delay must be of type number" ); this._fadeOut = { duration, ease: options.ease, delay: options.delay }; return this; } }; const rotation = { /** * Base properties */ _angle: null, _rotateIn: null, _rotateOut: null, _randomRotation: null, _rotateTowards: null, /** * The object gets a random rotation, which means it should not be used with .stretchTo() * * @param {boolean} [inBool=true] inBool * @returns this */ randomRotation(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "randomRotation", "inBool must be of type boolean" ); this._randomRotation = inBool; return this; }, /** * Sets the rotation of the object, which is added on top of the calculated rotation after .rotateTowards() or .randomRotation() * * @param {number} inRotation * @returns this */ rotate(inRotation) { if (!is_real_number(inRotation)) throw this.sequence._customError( this, "opacity", "inRotation must be of type number" ); this._angle = inRotation; return this; }, /** * Causes the object to rotate when it starts playing * * @param {number} degrees * @param {number} duration * @param {object} [options] options * @returns this */ rotateIn(degrees, duration, options = {}) { if (typeof options !== "object") throw this.sequence._customError( this, "rotateIn", "options must be of type object" ); options = foundry.utils.mergeObject( { ease: "linear", delay: 0 }, options ); if (!is_real_number(degrees)) throw this.sequence._customError( this, "rotateOut", "degrees must be of type number" ); if (!is_real_number(duration)) throw this.sequence._customError( this, "rotateOut", "duration must be of type number" ); if (typeof options.ease !== "string") throw this.sequence._customError( this, "rotateIn", "options.ease must be of type string" ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "rotateIn", "options.delay must be of type number" ); this._rotateIn = { value: degrees, duration, ease: options.ease, delay: options.delay }; return this; }, /** * Causes the object to rotate at the end of the effect's duration * * @param {number} degrees * @param {number} duration * @param {object} [options] options * @returns this */ rotateOut(degrees, duration, options = {}) { if (typeof options !== "object") throw this.sequence._customError( this, "rotateOut", "options must be of type object" ); options = foundry.utils.mergeObject( { ease: "linear", delay: 0 }, options ); if (!is_real_number(degrees)) throw this.sequence._customError( this, "rotateOut", "degrees must be of type number" ); if (!is_real_number(duration)) throw this.sequence._customError( this, "rotateOut", "duration must be of type number" ); if (typeof options.ease !== "string") throw this.sequence._customError( this, "rotateOut", "options.ease must be of type string" ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "rotateOut", "options.delay must be of type number" ); this._rotateOut = { value: degrees, duration, ease: options.ease, delay: options.delay }; return this; } }; const scale = { _scaleMin: null, _scaleMax: null, _scaleIn: null, _scaleOut: null, /** * A method that can take the following: * - A number to set the scale uniformly * - An object with x and y for non-uniform scaling * - Two numbers which the Sequencer will randomly pick a uniform scale between * * @param {number|object} inScaleMin * @param {number} [inScaleMax] inScaleMax * @returns this */ scale(inScaleMin, inScaleMax) { if (!is_real_number(inScaleMin) && typeof inScaleMin !== "object") throw this.sequence._customError( this, "scale", "inScale must be of type number or object" ); if (is_real_number(inScaleMin)) { if (inScaleMax && !is_real_number(inScaleMax)) { throw this.sequence._customError( this, "scale", "if inScaleMin is a number, inScaleMax must also be of type number" ); } } this._scaleMin = inScaleMin; this._scaleMax = inScaleMax ?? false; return this; }, /** * Causes the effect to scale when it starts playing * * @param {number|object} scale * @param {number} duration * @param {object} [options] options * @returns this */ scaleIn(scale2, duration, options = {}) { if (typeof options !== "object") throw this.sequence._customError( this, "scaleIn", "options must be of type object" ); options = foundry.utils.mergeObject( { ease: "linear", delay: 0 }, options ); if (!is_real_number(duration)) throw this.sequence._customError( this, "scaleIn", "duration must be of type number" ); if (!is_real_number(scale2) && typeof scale2 !== "object") throw this.sequence._customError( this, "scaleIn", "scale must be of type number or object" ); if (typeof options.ease !== "string") throw this.sequence._customError( this, "scaleIn", "options.ease must be of type string" ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "scaleIn", "options.delay must be of type number" ); this._scaleIn = { value: scale2, duration, ease: options.ease, delay: options.delay }; return this; }, /** * Causes the effect to scale at the end of the effect's duration * * @param {number|object} scale * @param {number} duration * @param {object} [options] options * @returns this */ scaleOut(scale2, duration, options = {}) { if (typeof options !== "object") throw this.sequence._customError( this, "scaleOut", "options must be of type object" ); options = foundry.utils.mergeObject( { ease: "linear", delay: 0 }, options ); if (!is_real_number(duration)) throw this.sequence._customError( this, "scaleOut", "duration must be of type number" ); if (!is_real_number(scale2) && typeof scale2 !== "object") throw this.sequence._customError( this, "scaleOut", "scale must be of type number or object" ); if (typeof options.ease !== "string") throw this.sequence._customError( this, "scaleOut", "options.ease must be of type string" ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "scaleOut", "options.delay must be of type number" ); this._scaleOut = { value: scale2, duration, ease: options.ease, delay: options.delay }; return this; } }; const time = { _hasTime: true, _isRange: false, _startTime: null, _startPerc: null, _endTime: null, _endPerc: null, /** * Sets the start and end time of the section, playing only that range * * @param {number} inMsStart * @param {number} inMsEnd * @returns this */ timeRange(inMsStart, inMsEnd) { if (!is_real_number(inMsStart)) throw this.sequence._customError( this, "timeRange", "inMsStart must be of type number" ); if (!is_real_number(inMsEnd)) throw this.sequence._customError( this, "timeRange", "inMsEnd must be of type number" ); this._startTime = inMsStart; this._endTime = inMsEnd; this._isRange = true; return this; }, /** * Sets the start time of the section. * * @param {number} inMs * @returns this */ startTime(inMs) { if (!is_real_number(inMs)) throw this.sequence._customError( this, "startTime", "inMs must be of type number" ); this._startTime = inMs; this._startPerc = false; this._isRange = false; return this; }, /** * Sets the start time of the section based on a percentage from its total duration. * * @param {number} inPercentage * @returns this */ startTimePerc(inPercentage) { if (!is_real_number(inPercentage)) throw this.sequence._customError( this, "startTimePerc", "inPercentage must be of type number" ); this._startTime = inPercentage; this._startPerc = true; this._isRange = false; return this; }, /** * Sets the ending time of the section (from the end). * * @param {number} inMs * @returns this */ endTime(inMs) { if (!is_real_number(inMs)) throw this.sequence._customError( this, "endTime", "inMs must be of type number" ); this._endTime = inMs; this._endPerc = false; this._isRange = false; return this; }, /** * Sets the ending time of the section based on a percentage from the total duration. * * @param {number} inPercentage * @returns this */ endTimePerc(inPercentage) { if (!is_real_number(inPercentage)) throw this.sequence._customError( this, "endTimePerc", "inPercentage must be of type number" ); this._endTime = inPercentage; this._endPerc = true; this._isRange = false; return this; } }; const users = { _users: null, _addUser(inUser) { if (!this._users) this._users = []; if (typeof inUser !== "string") throw this.sequence._customError( this, "_addUser", "inUser must be of type string" ); if (!game.users.has(inUser)) { if (game.users.getName(inUser)) { inUser = game.users.getName(inUser).id; } else { throw this.sequence._customError( this, "_addUser", `user with id or name "${inUser}" does not exist!` ); } } if (!this._users.includes(inUser)) this._users.push(inUser); }, _deleteUser(inUser) { if (!this._users) this._users = []; if (this._users.includes(inUser)) { let index = this._users.indexOf(inUser); this._users.splice(index, 1); } }, /** * Causes section to be executed only locally, and not push to other connected clients. * * @param {boolean} inLocally * @returns this */ locally(inLocally = true) { if (inLocally) this._addUser(game.userId); else this._deleteUser(game.userId); return this; }, /** * Causes the section to be executed for only a set of users. * * @param {string|User|array} inUsers * @returns this */ forUsers(inUsers) { if (!Array.isArray(inUsers)) { if (typeof inUsers !== "string") throw this.sequence._customError( this, "forUsers", "inUser must be of type string" ); inUsers = [inUsers]; } inUsers.forEach((u) => this._addUser(u)); return this; } }; const filter = { _filters: null, _addFilter(inFilterName, inData, inName = false) { if (!this._filters) this._filters = []; this._filters.push({ className: inFilterName, name: inName, data: inData }); }, _testFilter(inFilterName, inData) { let filter2 = new filters[inFilterName](inData); if (!filter2.isValid) throw this.sequence._customError( this, "filter", `Could not create ${inFilterName} filter - data is malformed!` ); }, filter(inFilterName, inData = {}, inName = "") { if (typeof inFilterName !== "string") throw this.sequence._customError( this, "filter", `inFilterName must be of type string` ); if (!Object.keys(filters).includes(inFilterName)) throw this.sequence._customError( this, "filter", `"${inFilterName}" does not exist` ); this._testFilter(inFilterName, inData); this._addFilter(inFilterName, inData, inName); return this; } }; const tint = { _tint: null, /** * Tints the target of this section by the color given to the * * @param {number|string} inColor * @returns this */ tint(inColor) { if (!is_real_number(inColor) && typeof inColor !== "string") throw this.sequence._customError( this, "tint", `inColor must be of type string (hexadecimal) or number (decimal)!` ); this._tint = parseColor(inColor); return this; } }; const location = { /** * Base properties */ _source: null, /** * A smart method that can take a reference to an object, or a direct on the canvas to play the effect at, * or a string reference (see .name()) * * @param {Object|String} inLocation * @param {Object} inOptions * @returns {EffectSection} */ atLocation(inLocation, inOptions = {}) { if (!(typeof inLocation === "object" || typeof inLocation === "string")) { throw this.sequence._customError( this, "atLocation", `inLocation is invalid, and must be of type of object, string, placeable object, or document` ); } if (typeof inOptions !== "object") throw this.sequence._customError( this, "atLocation", `inOptions must be of type object` ); inOptions = foundry.utils.mergeObject( { cacheLocation: false, offset: false, randomOffset: false, gridUnits: false, local: false }, inOptions ); inLocation = this._validateLocation(inLocation); if (inLocation === void 0) throw this.sequence._customError( this, "atLocation", "could not find position of given object" ); if (typeof inOptions.cacheLocation !== "boolean") throw this.sequence._customError( this, "atLocation", "inOptions.cacheLocation must be of type boolean" ); if (!(typeof inOptions.randomOffset === "boolean" || is_real_number(inOptions.randomOffset))) throw this.sequence._customError( this, "atLocation", "inOptions.randomOffset must be of type boolean or number" ); this._temporaryEffect = this._temporaryEffect || (inLocation instanceof foundry.abstract.Document ? !is_UUID(inLocation?.uuid) : false); if (inOptions.offset) { const offsetData = this._validateOffset( "atLocation", inOptions.offset, inOptions ); this._offset = { source: offsetData, target: this._offset?.target ?? false }; } this._randomOffset = { source: inOptions.randomOffset, target: this._randomOffset?.target ?? false }; this._source = inOptions.cacheLocation && typeof inLocation !== "string" ? get_object_canvas_data(inLocation) : inLocation; return this; } }; const offset = { _offset: null, _randomOffset: null, _validateOffset(functionName, inOffset, inOptions = {}) { inOffset = foundry.utils.mergeObject( { x: 0, y: 0 }, inOffset ); inOptions = foundry.utils.mergeObject( { gridUnits: false, local: false }, inOptions ); if (typeof inOptions.gridUnits !== "boolean") throw this.sequence._customError( this, functionName, "inOptions.gridUnits must be of type boolean" ); if (typeof inOptions.local !== "boolean") throw this.sequence._customError( this, functionName, "inOptions.local must be of type boolean" ); if (!is_real_number(inOffset.x)) throw this.sequence._customError( this, functionName, `inOffset.x must be of type number!` ); if (!is_real_number(inOffset.y)) throw this.sequence._customError( this, functionName, `inOffset.y must be of type number!` ); return { ...inOffset, ...inOptions }; } }; const text = { _text: null, /** * Creates a text element, attached to the sprite. The options for the text are available here: * https://pixijs.io/pixi-text-style/ * * @param {String} inText * @param {Object} inOptions * @returns {EffectSection} */ text(inText, inOptions = {}) { if (typeof inText !== "string") throw this.sequence._customError( this, "text", "inText must be of type string" ); this._text = foundry.utils.mergeObject( { text: inText }, inOptions ); return this; } }; const traits = { animation, audio, files, moves, opacity, rotation, scale, time, users, filter, tint, location, offset, text }; class EffectSection extends Section { constructor(inSequence, inFile = "") { super(inSequence); this._deserializedData = null; this._file = inFile; this._text = null; this._source = null; this._stretchTo = null; this._attachTo = null; this._from = null; this._origin = null; this._anchor = null; this._spriteAnchor = null; this._randomOffset = null; this._missed = null; this._private = null; this._randomMirrorX = null; this._randomMirrorY = null; this._mirrorX = null; this._mirrorY = null; this._playbackRate = null; this._template = null; this._overrides = []; this._name = null; this._zIndex = null; this._offset = null; this._spriteOffset = null; this._size = null; this._persist = null; this._persistOptions = null; this._zeroSpriteRotation = null; this._extraEndDuration = null; this._noLoop = null; this._tilingTexture = null; this._snapToGrid = null; this._scaleToObject = null; this._screenSpace = null; this._screenSpaceAboveUI = null; this._screenSpaceAnchor = null; this._screenSpacePosition = null; this._screenSpaceScale = null; this._elevation = null; this._masks = []; this._tiedDocuments = []; this._selfMask = false; this._temporaryEffect = false; this._spriteRotation = 0; this._randomSpriteRotation = false; this._isRangedEffect = null; this._offsetLegacy = null; this._randomOffsetLegacy = null; this._aboveLighting = null; this._aboveInterface = null; this._spriteScaleMin = 1; this._spriteScaleMax = null; this._isometric = null; this._shapes = []; this._xray = null; this._playEffect = true; } static niceName = "Effect"; /** * @private */ get _target() { return this._stretchTo || this._rotateTowards || this._moveTowards || false; } static debounceWarning() { custom_warning( "Sequencer", "Effect | This user does not have permissions to play effects. This can be configured in Sequencer's module settings." ); } /** * Causes the effect's position to be stored and can then be used with .atLocation(), .stretchTowards(), * and .rotateTowards() to refer to previous effects' locations * * @param {String} inName * @returns {EffectSection} */ name(inName) { if (typeof inName !== "string") throw this.sequence._customError( this, "name", "inName must be of type string" ); this._name = safe_str(inName); return this; } /** * Causes the effect to persist indefinitely on the canvas until _ended via SequencerEffectManager.endAllEffects() or * name the effect with .name() and then end it through SequencerEffectManager.endEffect() * * @param {Boolean} [inBool=true] inBool * @param {Object} [inOptions={}] inOptions * @returns {EffectSection} */ persist(inBool = true, inOptions = {}) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "persist", "inBool must be of type boolean" ); if (typeof inOptions !== "object") throw this.sequence._customError( this, "persist", `inOptions must be of type object` ); inOptions = foundry.utils.mergeObject( { id: randomID(), persistTokenPrototype: false }, inOptions ); if (typeof inOptions.persistTokenPrototype !== "boolean") throw this.sequence._customError( this, "persist", "inOptions.persistTokenPrototype must be of type boolean" ); this._persist = inBool; this._persistOptions = inOptions; return this; } /** * Causes the effect to become temporary, which means it will not be stored in the flags of any object, * even if it .persist() is called * * @param {Boolean} inBool * @returns {EffectSection} */ temporary(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "temporary", "inBool must be of type boolean" ); this._temporaryEffect = inBool || this._temporaryEffect; return this; } /** * Sets the effect's playback rate. A playback rate of 2.0 would make it play 2x as fast, 0.5 would make * it play half as fast. * * @param {Number} inNumber * @returns {EffectSection} */ playbackRate(inNumber = 1) { if (!is_real_number(inNumber)) throw this.sequence._customError( this, "playbackRate", "inNumber must be of type number" ); this._playbackRate = inNumber; return this; } /** * Causes the effect to target a location close to the .stretchTowards() location, but not on it. * * @param {Boolean} [inBool=true] inBool * @returns {EffectSection} */ missed(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "missed", "inBool must be of type boolean" ); this._missed = inBool; return this; } /** * Adds a function that will run at the end of the effect serialization step, but before it is played. Allows direct * modifications of effect's data. For example, it could be manipulated to change which file will be used based * on the distance to the target. * * @param {Function} inFunc * @returns {EffectSection} */ addOverride(inFunc) { if (!is_function$1(inFunc)) throw this.sequence._customError( this, "addOverride", "The given function needs to be an actual function." ); this._overrides.push(inFunc); return this; } /** * A smart method that can take a reference to an object, or a direct on the canvas to attach an effect to, * or a string reference (see .name()) * * @param {Object|String} inObject * @param {Object} inOptions * @returns {EffectSection} */ attachTo(inObject, inOptions = {}) { if (!(typeof inObject === "object" || typeof inObject === "string")) { throw this.sequence._customError( this, "attachTo", `inObject is invalid, and must be of type of object, string, placeable object, or document` ); } if (typeof inOptions !== "object") throw this.sequence._customError( this, "attachTo", `inOptions must be of type object` ); inOptions = foundry.utils.mergeObject( { align: "center", edge: "on", bindVisibility: true, bindAlpha: true, bindElevation: true, followRotation: true, offset: false, randomOffset: false, gridUnits: false, local: false }, inOptions ); const validatedObject = this._validateLocation(inObject); if (validatedObject === void 0) throw this.sequence._customError( this, "attachTo", "could not find given object" ); let isValidObject = true; if (typeof inObject === "string") { isValidObject = validatedObject instanceof Token || validatedObject instanceof TokenDocument || validatedObject instanceof Tile || validatedObject instanceof TileDocument || validatedObject instanceof Drawing || validatedObject instanceof DrawingDocument || validatedObject instanceof MeasuredTemplate || validatedObject instanceof MeasuredTemplateDocument || validatedObject instanceof CanvasEffect; if (!isValidObject) { this.sequence._showWarning( this, "attachTo", "Only Tokens, Tiles, Drawings, and MeasuredTemplates may have attached effects - will play effect on target's location" ); } } const aligns = Object.keys(alignments); if (typeof inOptions.align !== "string" || !aligns.includes(inOptions.align)) { throw this.sequence._customError( this, "attachTo", `inOptions.align must be of type string, one of: ${aligns.join(", ")}` ); } if (typeof inOptions.edge !== "string" || !(inOptions.edge === "on" || inOptions.edge === "inner" || inOptions.edge === "outer")) { throw this.sequence._customError( this, "attachTo", `inOptions.edge must of type string with the value of either "on", "inner", or "outer"` ); } if (typeof inOptions.bindVisibility !== "boolean") throw this.sequence._customError( this, "attachTo", `inOptions.bindVisibility must be of type boolean` ); if (typeof inOptions.followRotation !== "boolean") throw this.sequence._customError( this, "attachTo", `inOptions.followRotation must be of type boolean` ); if (typeof inOptions.bindAlpha !== "boolean") throw this.sequence._customError( this, "attachTo", "inOptions.bindAlpha must be of type boolean" ); if (typeof inOptions.bindElevation !== "boolean") throw this.sequence._customError( this, "attachTo", "inOptions.bindElevation must be of type boolean" ); if (!(typeof inOptions.randomOffset === "boolean" || is_real_number(inOptions.randomOffset))) throw this.sequence._customError( this, "attachTo", "inOptions.randomOffset must be of type boolean or number" ); this._source = validatedObject; this._temporaryEffect = this._temporaryEffect || (validatedObject instanceof foundry.abstract.Document || validatedObject instanceof MeasuredTemplate ? !is_UUID(validatedObject?.uuid) : this._temporaryEffect || false); if (inOptions.offset) { const offsetData = this._validateOffset( "attachTo", inOptions.offset, inOptions ); this._offset = { source: offsetData, target: this._offset?.target ?? false }; } this._randomOffset = { source: inOptions.randomOffset, target: this._randomOffset?.target ?? false }; this._attachTo = { active: isValidObject, align: inOptions.align, edge: inOptions.edge, bindVisibility: inOptions.bindVisibility, bindAlpha: inOptions.bindAlpha, bindElevation: inOptions.bindElevation, followRotation: inOptions.followRotation }; return this; } /** * Causes the effect to be rotated and stretched towards an object, or a direct on the canvas to play the effect at, or a string reference (see .name()) * This effectively calculates the proper X scale for the effect to reach the target * * @param {Object|String} inLocation * @param {Object} inOptions * @returns {EffectSection} */ stretchTo(inLocation, inOptions = {}) { if (!(typeof inLocation === "object" || typeof inLocation === "string")) { throw this.sequence._customError( this, "stretchTo", `inLocation is invalid, and must be of type of object, string, placeable object, or document` ); } if (typeof inOptions !== "object") throw this.sequence._customError( this, "stretchTo", `inOptions must be of type object` ); inOptions = foundry.utils.mergeObject( { cacheLocation: false, attachTo: false, onlyX: false, tiling: false, offset: false, randomOffset: false, gridUnits: false, local: false, requiresLineOfSight: false, hideLineOfSight: false }, inOptions ); const validatedObject = this._validateLocation(inLocation); if (validatedObject === void 0) throw this.sequence._customError( this, "stretchTo", "could not find position of given object" ); if (typeof inOptions.cacheLocation !== "boolean") throw this.sequence._customError( this, "stretchTo", "inOptions.cacheLocation must be of type boolean" ); if (typeof inOptions.attachTo !== "boolean") throw this.sequence._customError( this, "stretchTo", "inOptions.attachTo must be of type boolean" ); if (typeof inOptions.onlyX !== "boolean") throw this.sequence._customError( this, "stretchTo", "inOptions.onlyX must be of type boolean" ); if (typeof inOptions.tiling !== "boolean") throw this.sequence._customError( this, "stretchTo", "inOptions.tiling must be of type boolean" ); if (!(typeof inOptions.randomOffset === "boolean" || is_real_number(inOptions.randomOffset))) throw this.sequence._customError( this, "stretchTo", "inOptions.randomOffset must be of type boolean or number" ); if (inOptions.cacheLocation && inOptions.attachTo) { throw this.sequence._customError( this, "stretchTo", "cacheLocation and attachTo cannot both be true - pick one or the other" ); } if (typeof inOptions.requiresLineOfSight !== "boolean") { throw this.sequence._customError( this, "stretchTo", "requiresLineOfSight must be of type boolean" ); } if (!inOptions.attachTo && inOptions.requiresLineOfSight) { throw this.sequence._customError( this, "stretchTo", "requiresLineOfSight requires that attachTo is true" ); } if (typeof inOptions.hideLineOfSight !== "boolean") { throw this.sequence._customError( this, "stretchTo", "hideLineOfSight must be of type boolean" ); } if (!inOptions.requiresLineOfSight && inOptions.hideLineOfSight) { throw this.sequence._customError( this, "stretchTo", "hideLineOfSight requires that requiresLineOfSight is true" ); } if (inOptions.tiling) this.tilingTexture(); this._temporaryEffect = this._temporaryEffect || (validatedObject instanceof foundry.abstract.Document ? !is_UUID(validatedObject?.uuid) : this._temporaryEffect || false); if (inOptions.offset) { const offsetData = this._validateOffset( "stretchTo", inOptions.offset, inOptions ); this._offset = { source: this._offset?.source ?? false, target: offsetData }; } this._randomOffset = { source: this._randomOffset?.source ?? false, target: inOptions.randomOffset }; this._stretchTo = { target: inOptions.cacheLocation ? get_object_canvas_data(validatedObject, { measure: true }) : validatedObject, attachTo: inOptions.attachTo, onlyX: inOptions.onlyX, requiresLineOfSight: inOptions.requiresLineOfSight, hideLineOfSight: inOptions.hideLineOfSight }; return this; } /** * Sets the location to rotate the object to * * @param {object|string} inLocation * @param {object} inOptions * @returns this */ rotateTowards(inLocation, inOptions = {}) { if (!(typeof inLocation === "object" || typeof inLocation === "string")) { throw this.sequence._customError( this, "inLocation", `inLocation is invalid, and must be of type of object, string, placeable object, or document` ); } inOptions = foundry.utils.mergeObject( { rotationOffset: 0, cacheLocation: false, attachTo: false, offset: false, randomOffset: false, local: false, gridUnits: false }, inOptions ); if (!is_real_number(inOptions.rotationOffset)) throw this.sequence._customError( this, "rotateTowards", "inOptions.rotationOffset must be of type number" ); if (typeof inOptions.attachTo !== "boolean") throw this.sequence._customError( this, "rotateTowards", "inOptions.attachTo must be of type boolean" ); if (typeof inOptions.cacheLocation !== "boolean") throw this.sequence._customError( this, "rotateTowards", "inOptions.cacheLocation must be of type boolean" ); const validatedObject = this._validateLocation(inLocation); if (!validatedObject) throw this.sequence._customError( this, "rotateTowards", "could not find position of given object" ); this._temporaryEffect = this._temporaryEffect || (validatedObject instanceof foundry.abstract.Document ? !is_UUID(validatedObject?.uuid) : this._temporaryEffect || false); if (inOptions.offset) { const offsetData = this._validateOffset( "attachTo", inOptions.offset, inOptions ); this._offset = { source: offsetData, target: this._offset?.target ?? false }; } this._randomOffset = { source: inOptions.randomOffset, target: this._randomOffset?.target ?? false }; this._rotateTowards = { target: inOptions.cacheLocation ? get_object_canvas_data(validatedObject, { measure: true }) : validatedObject, rotationOffset: inOptions.rotationOffset, cacheLocation: inOptions.cacheLocation, attachTo: inOptions.attachTo }; return this; } /** * Create an effect based on the given object, effectively copying the object as an effect. Useful when you want to do some effect magic on tokens or tiles. * * @param {Object} inObject * @param {Object} inOptions * @returns {EffectSection} */ from(inObject, inOptions = {}) { if (!(inObject instanceof Token || inObject instanceof Tile || inObject instanceof TokenDocument || inObject instanceof TileDocument)) { throw this.sequence._customError( this, "from", "inObject must be of type Token, Tile, TokenDocument, or TileDocument" ); } if (typeof inOptions !== "object") throw this.sequence._customError( this, "from", `inOptions must be of type object` ); inObject = inObject.document ?? inObject; if (!inObject?.texture?.src) throw this.sequence._customError( this, "from", "could not find the image for the given object" ); inOptions = foundry.utils.mergeObject( { cacheLocation: false, offset: false, randomOffset: false, local: false, gridUnits: false }, inOptions ); if (typeof inOptions.cacheLocation !== "boolean") throw this.sequence._customError( this, "from", "inOptions.cacheLocation must be of type boolean" ); if (!(typeof inOptions.randomOffset === "boolean" || is_real_number(inOptions.randomOffset))) throw this.sequence._customError( this, "from", "inOptions.randomOffset must be of type boolean or number" ); this._temporaryEffect = this._temporaryEffect || (inObject instanceof foundry.abstract.Document ? !is_UUID(inObject?.uuid) : this._temporaryEffect || false); if (inOptions.offset) { const offsetData = this._validateOffset( "attachTo", inOptions.offset, inOptions ); this._offset = { source: offsetData, target: this._offset?.target ?? false }; } this._randomOffset = { source: inOptions.randomOffset, target: this._randomOffset?.target ?? false }; this._from = { object: inObject, options: inOptions }; return this; } shape(inType, inOptions = {}) { if (typeof inType !== "string") throw this.sequence._customError( this, "shape", "type must be of type string" ); if (!Object.values(CONSTANTS.SHAPES).includes(inType)) { throw this.sequence._customError( this, "shape", "type must be one of: " + Object.values(CONSTANTS.SHAPES).join(", ") ); } if (inType === CONSTANTS.SHAPES.POLY) { if (!Array.isArray(inOptions.points)) { throw this.sequence._customError( this, "shape", "if creating polygon, inOptions.points must be of type array" ); } inOptions.points = inOptions.points.map((point) => { if (Array.isArray(point)) { if (!is_real_number(point[0]) || !is_real_number(point[1])) { throw this.sequence._customError( this, "shape", "inOptions.points must be an array, containing an array of two numbers or objects with x and y number properties" ); } return point; } if (typeof point === "object") { if (!is_real_number(point?.x) || !is_real_number(point?.y)) { throw this.sequence._customError( this, "shape", "inOptions.points must be an array, containing an array of two numbers or objects with x and y number properties" ); } return [point.x, point.y]; } }); } else if (inType === CONSTANTS.SHAPES.CIRC) { if (typeof inOptions.radius !== "number") { throw this.sequence._customError( this, "shape", "if creating circle, inOptions.radius must be of type number" ); } } else if (inType === CONSTANTS.SHAPES.RECT || inType === CONSTANTS.SHAPES.RREC || inType === CONSTANTS.SHAPES.ELIP) { if (inOptions.width ^ inOptions.height) { inOptions.width = inOptions.width ?? inOptions.height; inOptions.height = inOptions.height ?? inOptions.width; } if (typeof inOptions.width !== "number") { throw this.sequence._customError( this, "shape", `if creating rectangle, rounded rectangle, or an ellipse, inOptions.width must be of type number` ); } if (typeof inOptions.height !== "number") { throw this.sequence._customError( this, "shape", "if creating rectangle, rounded rectangle, or an ellipse, inOptions.height must be of type number" ); } if (inType === CONSTANTS.SHAPES.RREC && typeof inOptions.radius !== "number") { throw this.sequence._customError( this, "shape", "if creating rounded border rectangle, inOptions.radius must be of type number" ); } } if (inOptions.gridUnits !== void 0 && typeof inOptions.gridUnits !== "boolean") { throw this.sequence._customError( this, "shape", "inOptions.gridUnits must be of type boolean" ); } if (inOptions.name && typeof inOptions.name !== "string") { throw this.sequence._customError( this, "shape", "inOptions.name must be of type string" ); } if (inOptions.fillColor && !is_real_number(inOptions.fillColor) && typeof inOptions.fillColor !== "string") { throw this.sequence._customError( this, "shape", "inOptions.fillColor must be of type string (hexadecimal) or number (decimal)" ); } else { inOptions.fillColor = parseColor(inOptions.fillColor).decimal; } if (inOptions.fillAlpha && !is_real_number(inOptions.fillAlpha)) { throw this.sequence._customError( this, "shape", "inOptions.fillAlpha must be of type number" ); } if (inOptions.alpha && !is_real_number(inOptions.alpha)) { throw this.sequence._customError( this, "shape", "inOptions.alpha must be of type number" ); } if (inOptions.lineSize && !is_real_number(inOptions.lineSize)) { throw this.sequence._customError( this, "shape", "inOptions.lineSize must be of type number" ); } if (inOptions.lineColor && !is_real_number(inOptions.lineColor) && typeof inOptions.lineColor !== "string") { throw this.sequence._customError( this, "shape", "inOptions.lineColor must be of type string (hexadecimal) or number (decimal)" ); } else { inOptions.lineColor = parseColor(inOptions.lineColor).decimal; } if (inOptions.offset) { inOptions.offset = this._validateOffset( "shape", inOptions.offset, inOptions.offset ); } if (inOptions.texture !== void 0 && typeof inOptions.texture !== "string") { throw this.sequence._customError( this, "shape", "inOptions.texture must be of type string" ); } if (inOptions.isMask !== void 0 && typeof inOptions.isMask !== "boolean") { throw this.sequence._customError( this, "shape", "inOptions.isMask must be of type boolean" ); } this._shapes.push({ ...inOptions, type: inType }); return this; } /** * Causes the effect to be offset relative to its location based on a given vector * * @param {Object} inOffset * @param {Object} inOptions * @returns {EffectSection} */ offset(inOffset, inOptions = {}) { this.sequence._showWarning( this, "offset", "This method is becoming deprecated, please use the secondary offset option in atLocation, attachTo, stretchTo instead.", true ); if (inOffset === void 0) throw this.sequence._customError( this, "offset", "inOffset must not be undefined" ); if (typeof inOptions !== "object") throw this.sequence._customError( this, "offset", "options must be of type object" ); this._offsetLegacy = this._validateOffset("offset", inOffset, inOptions); return this; } /** * Causes the effect's sprite to be offset relative to its location based on a given vector * * @param {Object} inOffset * @param {Object} inOptions * @returns {EffectSection} */ spriteOffset(inOffset, inOptions = {}) { if (inOffset === void 0) throw this.sequence._customError( this, "spriteOffset", "inOffset must not be undefined" ); if (typeof inOptions !== "object") throw this.sequence._customError( this, "spriteOffset", "options must be of type object" ); this._spriteOffset = this._validateOffset( "spriteOffset", inOffset, inOptions ); return this; } /** * Causes the final effect location to be snapped to the grid * * @param {Boolean} inBool * @returns {EffectSection} */ snapToGrid(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "snapToGrid", "inBool must be of type boolean" ); this._snapToGrid = inBool; return this; } /** * Causes the effect to be scaled to the target object's width * * @param {Number} inScale * @param {Object} inOptions * @returns {EffectSection} */ scaleToObject(inScale = 1, inOptions = {}) { if (!is_real_number(inScale)) throw this.sequence._customError( this, "scaleToObject", `inScale must be of type number!` ); if (typeof inOptions !== "object") throw this.sequence._customError( this, "scaleToObject", "inOptions must be of type object" ); inOptions = foundry.utils.mergeObject( { scale: inScale, considerTokenScale: false, uniform: false }, inOptions ); if (typeof inOptions.uniform !== "boolean") throw this.sequence._customError( this, "scaleToObject", "inBool must be of type boolean" ); this._scaleToObject = inOptions; return this; } /** * Sets the width and the height of the effect in pixels, this size is set before any scaling * * @param {Number|Object<{width: {Number}, height: {Number}}>} inSize * @param {Object} inOptions * @returns {EffectSection} */ size(inSize, inOptions = {}) { if (!is_real_number(inSize) && typeof inSize !== "object") throw this.sequence._customError( this, "size", "inSize must be of type number or object" ); if (typeof inOptions !== "object") throw this.sequence._customError( this, "size", "inOptions must be of type object" ); if (is_real_number(inSize)) { inSize = { width: inSize, height: inSize }; } if (inSize.width === void 0 ^ inSize.height === void 0) { if (inSize.width) { if (!is_real_number(inSize.width)) throw this.sequence._customError( this, "size", "inSize.width must be of type number or string 'auto'" ); inSize["height"] = "auto"; } else { if (!is_real_number(inSize.height)) throw this.sequence._customError( this, "size", "inSize.height must be of type number or string 'auto'" ); inSize["width"] = "auto"; } } inOptions = foundry.utils.mergeObject( { gridUnits: false }, inOptions ); if (!is_real_number(inSize.width) && inSize.width !== "auto") throw this.sequence._customError( this, "size", "inSize.width must be of type number or string 'auto'" ); if (!is_real_number(inSize.height) && inSize.height !== "auto") throw this.sequence._customError( this, "size", "inSize.height must be of type number or string 'auto'" ); if (typeof inOptions.gridUnits !== "boolean") throw this.sequence._customError( this, "size", "inOptions.gridUnits must be of type boolean" ); this._size = { width: inSize.width ?? canvas.grid.size, height: inSize.height ?? canvas.grid.size, ...inOptions }; return this; } /** * This scales the sprite of the effect, and this method can take the following: * - A number to set the scale uniformly * - An object with x and y for non-uniform scaling * - Two numbers which the Sequencer will randomly pick a uniform scale between * * @param {number|object} inScaleMin * @param {number} [inScaleMax] inScaleMax * @returns this */ spriteScale(inScaleMin, inScaleMax) { if (!is_real_number(inScaleMin) && typeof inScaleMin !== "object") throw this.sequence._customError( this, "spriteScale", "inScale must be of type number or object" ); if (is_real_number(inScaleMin)) { if (inScaleMax && !is_real_number(inScaleMax)) { throw this.sequence._customError( this, "spriteScale", "if inScaleMin is a number, inScaleMax must also be of type number" ); } } this._spriteScaleMin = inScaleMin; this._spriteScaleMax = inScaleMax ?? false; return this; } /** * This defines the internal padding of this effect. Gridsize determines the internal grid size of this effect which will determine how big it is on the canvas * relative to the canvas's grid size. Start and end point defines padding at the left and right of the effect * * @param {Number} gridSize * @param {Number} startPoint * @param {Number} endPoint * @returns {EffectSection} */ template({ gridSize, startPoint, endPoint } = {}) { if (gridSize && !is_real_number(gridSize)) throw this.sequence._customError( this, "template", "gridSize must be of type number" ); if (startPoint && !is_real_number(startPoint)) throw this.sequence._customError( this, "template", "startPoint must be of type number" ); if (endPoint && !is_real_number(endPoint)) throw this.sequence._customError( this, "template", "endPoint must be of type number" ); if (!gridSize && !startPoint && !endPoint) throw this.sequence._customError( this, "template", "You need to define at least one parameter!" ); if (!this._template) this._template = {}; if (gridSize) this._template["gridSize"] = gridSize; if (startPoint) this._template["startPoint"] = startPoint; if (endPoint) this._template["endPoint"] = endPoint; return this; } /** * This makes the texture of the effect tile, effectively repeat itself within the sprite's dimensions * * @param {Object|Number} scale * @param {Object} position * @returns {EffectSection} */ tilingTexture(scale2 = { x: 1, y: 1 }, position = { x: 0, y: 0 }) { if (is_real_number(scale2)) { scale2 = { x: scale2, y: scale2 }; } scale2 = { x: scale2?.x ?? 1, y: scale2?.y ?? 1 }; if (!is_real_number(scale2.x)) throw this.sequence._customError( this, "tilingTexture", `scale.x must be of type number!` ); if (!is_real_number(scale2.y)) throw this.sequence._customError( this, "tilingTexture", `scale.y must be of type number!` ); position = { x: position?.x ?? 0, y: position?.y ?? 0 }; if (!is_real_number(position.x)) throw this.sequence._customError( this, "tilingTexture", `position.x must be of type number!` ); if (!is_real_number(position.y)) throw this.sequence._customError( this, "tilingTexture", `position.y must be of type number!` ); this._tilingTexture = { scale: scale2, position }; return this; } /** * Anchors the sprite's container according to the given x and y coordinates, or uniformly based on a single number * * @param {Number|Object} inAnchor * @returns {EffectSection} */ anchor(inAnchor) { if (is_real_number(inAnchor)) { inAnchor = { x: inAnchor, y: inAnchor }; } inAnchor = { x: inAnchor?.x ?? 0.5, y: inAnchor?.y ?? 0.5 }; if (!is_real_number(inAnchor.x)) throw this.sequence._customError( this, "anchor", `inAnchor.x must be of type number!` ); if (!is_real_number(inAnchor.y)) throw this.sequence._customError( this, "anchor", `inAnchor.y must be of type number!` ); this._anchor = inAnchor; return this; } /** * Anchors the sprite according to the given x and y coordinates, or uniformly based on a single number * * @param {Number|Object} inAnchor * @returns {EffectSection} */ spriteAnchor(inAnchor) { if (is_real_number(inAnchor)) { inAnchor = { x: inAnchor, y: inAnchor }; } inAnchor = { x: inAnchor?.x ?? 0.5, y: inAnchor?.y ?? 0.5 }; if (!is_real_number(inAnchor.x)) throw this.sequence._customError( this, "anchor", `inAnchor.x must be of type number!` ); if (!is_real_number(inAnchor.y)) throw this.sequence._customError( this, "anchor", `inAnchor.y must be of type number!` ); this._spriteAnchor = inAnchor; return this; } /** * Centers the sprite, effectively giving it an anchor of {x: 0.5, y: 0.5} * * Note: If this is used, it will override the anchor set by Aim Towards, which sets the sprite's anchor to the * outermost edge of the location the sprite is played at * * @returns {EffectSection} */ center() { this.anchor(0.5); return this; } /** * The sprite gets a random offset on its target location, usually within the object's bounds. The optional parameter * scales how much offset should be added. Defaults to 1.0, which covers the entire target position, 0.5 would cover half. * * @param {Number} inOffsetScale * @returns {EffectSection} */ randomOffset(inOffsetScale = 1) { this.sequence._showWarning( this, "randomOffset", "This method has been deprecated, please use randomOffset as a second parameter on atLocation, stretchTo, etc.", true ); if (!is_real_number(inOffsetScale)) throw this.sequence._customError( this, "randomOffset", "inBool must be of type number" ); this._randomOffsetLegacy = inOffsetScale; return this; } /** * The sprite gets a randomized flipped X scale. If the scale on that axis was 1, it can * become 1 or -1, effectively mirroring the sprite on its horizontal axis * * @param {Boolean} inBool * @returns {EffectSection} */ randomizeMirrorX(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "randomizeMirrorX", "inBool must be of type boolean" ); this._randomMirrorX = inBool; return this; } /** * The sprite gets a randomized flipped Y scale. If the scale on that axis was 1, it can * become 1 or -1, effectively mirroring the sprite on its vertical axis * * @param {Boolean} inBool * @returns {EffectSection} */ randomizeMirrorY(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "randomizeMirrorY", "inBool must be of type boolean" ); this._randomMirrorY = inBool; return this; } /** * The sprite gets a flipped X scale. If the scale on that axis was 1, it will become 1 or -1, effectively * mirroring the sprite on its horizontal axis * * @param {Boolean} inBool * @returns {EffectSection} */ mirrorX(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "mirrorX", "inBool must be of type boolean" ); this._mirrorX = inBool; return this; } /** * The sprite gets a flipped Y scale. If the scale on that axis was 1, it will become 1 or -1, effectively * mirroring the sprite on its vertical axis * * @param {Boolean} inBool * @returns {EffectSection} */ mirrorY(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "mirrorY", "inBool must be of type boolean" ); this._mirrorY = inBool; return this; } /** * Causes the effect to play beneath most tokens * * @param {Boolean} inBool * @returns {EffectSection} */ belowTokens(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "belowTokens", "inBool must be of type boolean" ); if (!inBool) return this; return this.elevation(0, { absolute: true }); } /** * Causes the effect to play beneath most tiles * * @param {Boolean} inBool * @returns {EffectSection} */ belowTiles(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "belowTokens", "inBool must be of type boolean" ); if (!inBool) return this; return this.elevation(-1, { absolute: true }); } /** * Causes the effect to be played on top of the vision mask * * @param {Boolean} inBool * @returns {EffectSection} */ aboveLighting(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "aboveLighting", "inBool must be of type boolean" ); this._aboveLighting = inBool; return this; } /** * Causes the effect to be played on top of interface * * @param {Boolean} inBool * @returns {EffectSection} */ aboveInterface(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "aboveInterface", "inBool must be of type boolean" ); this._aboveInterface = inBool; return this; } /** * Changes the effect's elevation * * @param {Number} inElevation * @param {Object} inOptions * @returns {EffectSection} */ elevation(inElevation, inOptions = {}) { if (typeof inElevation !== "number") throw this.sequence._customError( this, "elevation", "inElevation must be of type number" ); if (typeof inOptions !== "object") throw this.sequence._customError( this, "elevation", `inOptions must be of type object` ); inOptions = foundry.utils.mergeObject( { elevation: 1, absolute: false }, inOptions ); if (typeof inOptions.absolute !== "boolean") throw this.sequence._customError( this, "elevation", "inOptions.absolute must be of type boolean" ); this._elevation = { elevation: inElevation, absolute: inOptions.absolute }; return this; } /** * Sets the zIndex of the effect, potentially displaying it on top of other effects the same elevation * * @param {Number} inZIndex * @returns {EffectSection} */ zIndex(inZIndex) { if (!is_real_number(inZIndex)) throw this.sequence._customError( this, "zIndex", "inZIndex must be of type number" ); this._zIndex = inZIndex; return this; } /** * This method only modifies .persist()ed effects and causes them to not immediately end, but stick around for the given duration passed to this method. * * @param {Number} inExtraDuration * @returns {EffectSection} */ extraEndDuration(inExtraDuration) { if (!is_real_number(inExtraDuration)) throw this.sequence._customError( this, "extraEndDuration", "inExtraDuration must be of type number" ); this._extraEndDuration = inExtraDuration; return this; } /** * Rotates the sprite * * @param {Number} inAngle * @returns {EffectSection} */ spriteRotation(inAngle) { if (!is_real_number(inAngle)) throw this.sequence._customError( this, "spriteRotation", "inAngle must be of type number" ); this._spriteRotation = inAngle; return this; } /** * Rotates the sprite * * @param {Boolean} inBool * @returns {EffectSection} */ randomSpriteRotation(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "randomSpriteRotation", "inBool must be of type boolean" ); this._randomSpriteRotation = inBool; return this; } /** * Causes the effect to not rotate should its container rotate * * @param {Boolean} [inBool=true] inBool * @returns {EffectSection} */ zeroSpriteRotation(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "zeroSpriteRotation", "inBool must be of type boolean" ); this._zeroSpriteRotation = inBool; return this; } /** * If the effect would loop due to its duration or persistence, this causes it not to * * @param {Boolean} [inBool=true] inBool * @returns {EffectSection} */ noLoop(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "noLoop", "inBool must be of type boolean" ); this._noLoop = inBool; return this; } /** * Causes the effect to not show up in the Effect Manager UI - DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING * * @param inBool * @returns {EffectSection} */ private(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "private", "inBool must be of type boolean" ); this._private = inBool; return this; } /** * Causes the effect to be played in screen space instead of world space (where tokens are) * * @param {Boolean} [inBool=true] inBool * @returns {EffectSection} */ screenSpace(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "screenSpace", "inBool must be of type boolean" ); this._screenSpace = inBool; this._screenSpaceAnchor = this._screenSpaceAnchor ?? { x: 0.5, y: 0.5 }; return this; } /** * Causes the effect to be played above all of the UI elements * * @param {Boolean} [inBool=true] inBool * @returns {EffectSection} */ screenSpaceAboveUI(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "screenSpaceAboveUI", "inBool must be of type boolean" ); this._screenSpaceAboveUI = inBool; return this; } /** * Positions the effect in a screen space position, offset from its .screenSpaceAnchor() * * @param {Object} inPosition * @returns {EffectSection} */ screenSpacePosition(inPosition) { inPosition = { x: inPosition?.x ?? 0, y: inPosition?.y ?? 0 }; if (!is_real_number(inPosition.x)) throw this.sequence._customError( this, "screenSpacePosition", `inPosition.x must be of type number!` ); if (!is_real_number(inPosition.y)) throw this.sequence._customError( this, "screenSpacePosition", `inPosition.y must be of type number!` ); this._screenSpacePosition = inPosition; return this; } /** * Anchors the sprite according to the given x and y coordinates, or uniformly based on a single number in screen space * * @param {Number|Object} inAnchor * @returns {EffectSection} */ screenSpaceAnchor(inAnchor) { if (is_real_number(inAnchor)) { inAnchor = { x: inAnchor, y: inAnchor }; } inAnchor = { x: inAnchor?.x ?? 0.5, y: inAnchor?.y ?? 0.5 }; if (!is_real_number(inAnchor.x)) throw this.sequence._customError( this, "screenSpaceAnchor", `inAnchor.x must be of type number!` ); if (!is_real_number(inAnchor.y)) throw this.sequence._customError( this, "screenSpaceAnchor", `inAnchor.y must be of type number!` ); this._screenSpaceAnchor = inAnchor; return this; } /** * Sets up various properties relating to scale of the effect on the screen * * @param {Object} inOptions * @returns {EffectSection} */ screenSpaceScale(inOptions) { if (typeof inOptions !== "object") throw this.sequence._customError( this, "screenSpaceScale", `inOptions must be of type object` ); inOptions = foundry.utils.mergeObject( { x: 1, y: 1, fitX: false, fitY: false, ratioX: false, ratioY: false }, inOptions ); if (!is_real_number(inOptions.x)) throw this.sequence._customError( this, "screenSpaceScale", `inOptions.x must be of type number!` ); if (!is_real_number(inOptions.y)) throw this.sequence._customError( this, "screenSpaceScale", `inOptions.y must be of type number!` ); if (typeof inOptions.fitX !== "boolean") throw this.sequence._customError( this, "screenSpaceScale", "inOptions.fitX must be of type boolean" ); if (typeof inOptions.fitY !== "boolean") throw this.sequence._customError( this, "screenSpaceScale", "inOptions.fitY must be of type boolean" ); if (typeof inOptions.ratioX !== "boolean") throw this.sequence._customError( this, "screenSpaceScale", "inOptions.ratioX must be of type boolean" ); if (typeof inOptions.ratioY !== "boolean") throw this.sequence._customError( this, "screenSpaceScale", "inOptions.ratioY must be of type boolean" ); if (inOptions.ratioX && inOptions.ratioY) throw this.sequence._customError( this, "screenSpaceScale", "both ratioX and ratioY cannot be true, one axis must fit or be set directly" ); this._screenSpaceScale = inOptions; return this; } /** * This is for adding extra information to an effect, like the origin of the effect in the form of the item's uuid. * The method accepts a string or a Document that has an UUID. * * @param {string|document} inOrigin * @returns {Section} */ origin(inOrigin) { inOrigin = validate_document(inOrigin); if (inOrigin instanceof foundry.abstract.Document) { inOrigin = inOrigin?.uuid; if (!inOrigin) throw this.sequence._customError( this, "origin", "could not find the UUID for the given Document" ); } if (typeof inOrigin !== "string") throw this.sequence._customError( this, "origin", "inOrigin must be of type string" ); this._origin = inOrigin; return this; } /** * Ties the effect to any number of documents in Foundry - if those get deleted, the effect is ended. * * @param {String|PlaceableObject|foundry.abstract.Document|Array} inDocuments * @returns {EffectSection} */ tieToDocuments(inDocuments) { if (!Array.isArray(inDocuments)) { inDocuments = [inDocuments]; } for (let doc of inDocuments) { if (typeof doc !== "string" && !(doc instanceof PlaceableObject) && !(doc instanceof foundry.abstract.Document)) { throw this.sequence._customError( this, "tieToDocument", "inOrigin must be of type string, PlaceableObject, or Document, or an array thereof" ); } if (typeof doc === "string") { const obj = fromUuidSync(doc); if (!obj) throw this.sequence._customError( this, "tieToDocument", `could not find document with UUID "${doc}"` ); } else { doc = validate_document(doc); if (doc instanceof foundry.abstract.Document) { doc = doc?.uuid; if (!doc) throw this.sequence._customError( this, "tieToDocument", "could not find the UUID for the given object" ); } } this._tiedDocuments.push(doc); } return this; } /** * Masks the effect to the given object or objects. If no object is given, the effect will be masked to the source * of the effect. * * @param {Token/TokenDocument/Tile/TileDocument/Drawing/DrawingDocument/MeasuredTemplate/MeasuredTemplateDocument/Array} inObject * @returns {Section} */ mask(inObject) { if (!inObject) { this._selfMask = true; return this; } if (Array.isArray(inObject)) { for (let obj of inObject) { this.mask(obj); } return this; } const validatedObject = this._validateLocation(inObject); const isValidObject = validatedObject instanceof TokenDocument || validatedObject instanceof TileDocument || validatedObject instanceof DrawingDocument || validatedObject instanceof MeasuredTemplateDocument; if (!isValidObject) { throw this.sequence._customError( this, "mask", "A foundry object was provided, but only Tokens, Tiles, Drawings, and MeasuredTemplates may be used to create effect masks" ); } this._masks.push(get_object_identifier(validatedObject)); return this; } /** * Causes the effect to be visible through walls * * @param inBool * @returns {EffectSection} */ xray(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "xray", "inBool must be of type boolean" ); this._xray = inBool; return this; } /** * Configures the isometric configuration of this effect * * @param inOptions * @returns {EffectSection} */ isometric(inOptions = {}) { if (typeof inOptions !== "object") throw this.sequence._customError( this, "isometric", `inOptions must be of type object` ); inOptions = foundry.utils.mergeObject( { overlay: false }, inOptions ); if (typeof inOptions?.overlay !== "boolean") throw this.sequence._customError( this, "isometric", "inOptions.overlay must be of type boolean" ); this._isometric = inOptions; return this; } /** * @private */ _expressWarnings() { if (this._stretchTo && this._anchor?.x) { this.sequence._showWarning( this, "stretchTo", "you have called .stretchTo() and .anchor() - stretchTo will manually set the X axis of the anchor and may not behave like you expect.", true ); } if (this._stretchTo && this._scaleToObject) { throw this.sequence._customError( this, "stretchTo", "You're trying to stretch towards an object, while scaling to fit another??? Make up your mind!" ); } if (this._stretchTo && this._randomRotation) { throw this.sequence._customError( this, "stretchTo", "You're trying to stretch towards an object, while trying to randomly rotate the effect? What?" ); } if (this._stretchTo && this._moveTowards) { throw this.sequence._customError( this, "stretchTo", "You're trying to stretch towards an object, while moving towards it? You're insane." ); } if (this._attachTo && this._stretchTo?.attachTo && (this._startTime || this._endTime) && this._isRangedEffect) { throw this.sequence._customError( this, "stretchTo", "Dual-attached range-finding effects combined while using any of the time methods is stable - modern web browsers cannot handle it and it may crash them, so this feature has been disabled." ); } const source = this._getSourceObject(); const target = this._getTargetObject(); if (!this._screenSpace && this._persistOptions?.persistTokenPrototype && this._masks.filter((uuid) => uuid !== source).length > 0) { this.sequence._showWarning( this, "persist", "You have applied persistTokenPrototype with multiple masks from objects in the scene - these will not be persisted across scenes!", true ); } if (!source && !target && !this._screenSpace) { throw this.sequence._customError( this, "play", "Could not determine where to play the effect!" ); } } /** * @OVERRIDE */ async preRun() { if (this._from) { this._file = this._file || this._from.object?.texture?.src; if (this._source === null) { this._source = this._validateLocation(this._from.object); } if (this._size === null) { const size = get_object_dimensions(this._from.object); this._size = { width: size?.width ?? canvas.grid.size, height: size?.height ?? canvas.grid.size, gridUnits: false }; } if (this._mirrorX === null && (this._from.object.mirrorX || this._from.object?.tile && this._from.object?.tile.scale.x < 0)) { this._mirrorX = true; } if (this._mirrorY === null && (this._from.object.mirrorY || this._from.object?.tile && this._from.object?.tile.scale.y < 0)) { this._mirrorY = true; } if (this._angle === null && this._from.object?.rotation) { this._angle = -this._from.object.rotation; } this._randomOffset = { source: this._randomOffset?.source ?? this._from.options.randomOffset, target: this._randomOffset?.target ?? false }; } } /** * @OVERRIDE * @returns {Promise} */ async run() { if (!user_can_do("permissions-effect-create") || !this._playEffect) { if (!user_can_do("permissions-effect-create")) { debounce(EffectSection.debounceWarning, 1e3); } return new Promise((resolve) => { resolve(); }); } if (!this._deserializedData) this._expressWarnings(); const data = await this._sanitizeEffectData(); if (Hooks.call("preCreateSequencerEffect", data) === false) return; let push = !(data?.users?.length === 1 && data?.users?.includes(game.userId)) && !this.sequence.localOnly; let canvasEffectData = await Sequencer.EffectManager.play(data, push); let totalDuration = this._currentWaitTime; if (this._persist) { totalDuration += await canvasEffectData.promise; } else { totalDuration += await canvasEffectData.duration; } await new Promise((resolve) => setTimeout(resolve, totalDuration)); } /** * @private */ _applyTraits() { Object.assign(this.constructor.prototype, traits.files); Object.assign(this.constructor.prototype, traits.audio); Object.assign(this.constructor.prototype, traits.moves); Object.assign(this.constructor.prototype, traits.opacity); Object.assign(this.constructor.prototype, traits.rotation); Object.assign(this.constructor.prototype, traits.scale); Object.assign(this.constructor.prototype, traits.time); Object.assign(this.constructor.prototype, traits.users); Object.assign(this.constructor.prototype, traits.animation); Object.assign(this.constructor.prototype, traits.filter); Object.assign(this.constructor.prototype, traits.tint); Object.assign(this.constructor.prototype, traits.location); Object.assign(this.constructor.prototype, traits.offset); Object.assign(this.constructor.prototype, traits.text); } /** * @private */ async _initialize() { if (this._name) { if (!this.sequence.nameOffsetMap) { this.sequence.nameOffsetMap = {}; } if (!this.sequence.nameOffsetMap[this._name]) { const source = this._getSourceObject(); const target = this._getTargetObject(); if (this._offsetLegacy && !this._offset) { this._offset = { source: !target ? this._offsetLegacy : false, target: !!target ? this._offsetLegacy : false }; } if (this._randomOffsetLegacy && !this._randomOffset) { this._randomOffset = { source: !target ? this._randomOffsetLegacy : false, target: !!target ? this._randomOffsetLegacy : false }; } this.sequence.nameOffsetMap[this._name] = { seed: `${this._name}-${randomID()}`, source, target, randomOffset: this._randomOffset, missed: this._missed, offset: this._offset, repetitions: this._repetitions, twister: {} }; } } if (!this._file && !this._from && !this._text && !this._shapes.length && this.sequence.softFail) { this._playEffect = false; return; } let fileData = this._file ? await this._determineFile(this._file) : { file: this._file, forcedIndex: false, customRange: false }; this._isRangedEffect = fileData?.file?.rangeFind; if (fileData.customRange || fileData.file?.dbPath) return; let exists = false; try { exists = await SequencerFileCache.srcExists(fileData.file); } catch (err) { } if (!exists) { if (this.sequence.softFail) { this._playEffect = false; return; } throw this.sequence._customError( this, "Play", `Could not find file:
${fileData.file}` ); } } /** * @private */ _getSourceObject() { if (!this._source || typeof this._source !== "object") return this._source; if (this._source?.cachedLocation || !this._attachTo) { return get_object_canvas_data(this._source); } return get_object_identifier(this._source) ?? get_object_canvas_data(this._source); } /** * @private */ _getTargetObject() { if (!this._target?.target) return this._target; if (typeof this._target.target !== "object") return this._target.target; if (this._target?.target?.cachedLocation || !(this._stretchTo?.attachTo || this._rotateTowards?.attachTo)) { return get_object_canvas_data(this._target.target, true); } return get_object_identifier(this._target.target) ?? get_object_canvas_data(this._target.target, true); } /** * @private */ async _sanitizeEffectData() { if (this._deserializedData) { this._deserializedData.creationTimestamp = +new Date(); this._deserializedData.remote = true; return this._deserializedData; } const { file, forcedIndex, customRange } = this._file && this._playEffect ? await this._determineFile(this._file) : { file: this._file, forcedIndex: false, customRange: false }; const source = this._getSourceObject(); const target = this._getTargetObject(); if (this._offsetLegacy) { this._offset = { source: !target && this._offset?.source ? this._offsetLegacy : this._offset?.source, target: !!target && this._offset?.target ? this._offsetLegacy : this._offset?.target }; } if (this._randomOffsetLegacy) { this._randomOffset = { source: !target && this._randomOffset?.source ? this._randomOffsetLegacy : this._randomOffset?.source, target: !!target && this._randomOffset?.target ? this._randomOffsetLegacy : this._randomOffset?.target }; } if (this._selfMask) { this._masks.push( get_object_identifier(this._source) ?? get_object_canvas_data(this._source) ); } let sceneId = game.user.viewedScene; if (is_UUID(source)) { const potentialSceneId = source.split(".")[1]; if (game.scenes.get(potentialSceneId)) { sceneId = potentialSceneId; } } else if (is_UUID(target)) { const potentialSceneId = target.split(".")[1]; if (game.scenes.get(potentialSceneId)) { sceneId = potentialSceneId; } } let data = foundry.utils.duplicate({ /** * Core properties */ _id: randomID(), flagVersion: flagManager.latestFlagVersion, sequenceId: this.sequence.id, creationTimestamp: +new Date(), sceneId, creatorUserId: game.userId, moduleName: this.sequence.moduleName, users: this._users ? Array.from(this._users) : false, name: this._name, origin: this._origin, index: this.sequence.effectIndex, repetition: this._currentRepetition, private: this._private, temporary: this._temporaryEffect, tiedDocuments: Array.from(new Set(this._tiedDocuments)), /** * Source/target properties */ source, target, rotateTowards: this._rotateTowards, stretchTo: this._stretchTo ? { attachTo: this._stretchTo.attachTo, onlyX: this._stretchTo.onlyX, requiresLineOfSight: this._stretchTo.requiresLineOfSight, hideLineOfSight: this._stretchTo.hideLineOfSight } : false, moveTowards: this._moveTowards ? { ease: this._moveTowards.ease, rotate: this._moveTowards.rotate } : false, attachTo: this._attachTo, missed: this._missed, /** * Sprite properties */ file: file?.dbPath ?? file, customRange, forcedIndex, text: this._text, tilingTexture: this._tilingTexture, masks: Array.from(new Set(this._masks)), shapes: this._shapes, volume: this._volume, isometric: this._isometric, // Transforms scale: this._getCalculatedScale("scale"), spriteScale: this._getCalculatedScale("spriteScale"), angle: this._angle, size: this._size, offset: this._offset, anchor: this._anchor, spriteOffset: this._spriteOffset, spriteAnchor: this._spriteAnchor, template: this._template, zeroSpriteRotation: this._zeroSpriteRotation, randomOffset: this._randomOffset, randomRotation: this._randomRotation, scaleToObject: this._scaleToObject, elevation: this._elevation, aboveLighting: this._aboveLighting, aboveInterface: this._aboveInterface, xray: this._xray, // Appearance zIndex: this._zIndex, opacity: is_real_number(this._opacity) ? this._opacity : 1, filters: this._filters, noLoop: this._noLoop, spriteRotation: this._spriteRotation, randomSpriteRotation: this._randomSpriteRotation, tint: this._tint?.decimal, flipX: this._mirrorX || this._randomMirrorX && Math.random() < 0.5, flipY: this._mirrorY || this._randomMirrorY && Math.random() < 0.5, /** * Time properties */ duration: this._duration, persist: this._persist, persistOptions: this._persistOptions, playbackRate: this._playbackRate, extraEndDuration: this._extraEndDuration, time: this._startTime || this._endTime ? { start: is_real_number(this._startTime) ? { value: this._startTime, isPerc: this._startPerc } : false, end: is_real_number(this._endTime) ? { value: this._endTime, isPerc: this._endPerc } : false, isRange: this._isRange } : false, /** * Animation properties */ moves: this._moveTowards, moveSpeed: this._moveSpeed, fadeIn: this._fadeIn, fadeOut: this._fadeOut, scaleIn: this._scaleIn, scaleOut: this._scaleOut, rotateIn: this._rotateIn, rotateOut: this._rotateOut, fadeInAudio: this._fadeInAudio, fadeOutAudio: this._fadeOutAudio, animations: this._animations, /** * Screenspace properties */ screenSpace: this._screenSpace, screenSpaceAboveUI: this._screenSpaceAboveUI, screenSpaceAnchor: this._screenSpaceAnchor, screenSpacePosition: this._screenSpacePosition, screenSpaceScale: this._screenSpaceScale, nameOffsetMap: this.sequence.nameOffsetMap }); for (let override of this._overrides) { data = await override(this, data); } if ((typeof data.file !== "string" || data.file === "") && !data.text && !data.shapes && !data.customRange) { throw this.sequence._customError( this, "file", "an effect must have a file, text, or have a shape!" ); } return data; } async _serialize() { const data = await super._serialize(); await this.preRun(); const sectionData = await this._sanitizeEffectData(); return { ...data, type: "effect", sectionData }; } async _deserialize(data) { this._deserializedData = data.sectionData; return super._deserialize(data); } /** * @private */ _getCalculatedScale(type) { const min = this["_" + type + "Min"]; const max = this["_" + type + "Max"]; let scale2 = min; if (is_real_number(min)) { if (max && is_real_number(max)) { scale2 = random_float_between(min, max); } scale2 = { x: scale2, y: scale2 }; } return { x: scale2?.x ?? 1, y: scale2?.y ?? 1 }; } } class SoundSection extends Section { constructor(inSequence, inFile = "") { super(inSequence); this._file = inFile; this._volume = 0.8; this._overrides = []; } static niceName = "Sound"; /** * Adds a function that will run at the end of the sound serialization step, but before it is played. Allows direct * modifications of sound's data. * * @param {function} inFunc * @returns {SoundSection} */ addOverride(inFunc) { if (!is_function$1(inFunc)) throw this.sequence._customError( this, "addOverride", "The given function needs to be an actual function." ); this._overrides.push(inFunc); return this; } /** * @private */ _applyTraits() { Object.assign(this.constructor.prototype, traits.files); Object.assign(this.constructor.prototype, traits.audio); Object.assign(this.constructor.prototype, traits.time); Object.assign(this.constructor.prototype, traits.users); } /** * @OVERRIDE * @returns {Promise} */ async run() { const playData = await this._sanitizeSoundData(); if (!playData.play && this.sequence.softFail) { return new Promise((reject2) => { reject2(); }); } if (!playData?.play) { this.sequence._customError( this, "Play", `File not found: ${playData.src}` ); return new Promise((reject2) => reject2()); } if (Hooks.call("preCreateSequencerSound", playData) === false) return; let push = !(playData?.users?.length === 1 && playData?.users?.includes(game.userId)) && !this.sequence.localOnly; SequencerAudioHelper.play(playData, push); await new Promise( (resolve) => setTimeout(resolve, this._currentWaitTime + playData.duration) ); } /** * @returns {Promise} * @private */ async _sanitizeSoundData() { if (this._deserializedData) { return this._deserializedData; } if (!this._file) { return { play: false, src: false }; } let { file, forcedIndex } = await this._determineFile(this._file); if (!file) { return { play: false, src: false }; } if (file instanceof SequencerFileBase) { file.forcedIndex = forcedIndex; if (file.timeRange) { [this._startTime, this._endTime] = file.timeRange; this._isRange = true; } file = file.getFile(); } let soundFile = await AudioHelper.preloadSound(file); if (!soundFile || soundFile.failed) { return { play: false, src: this._file }; } let duration = soundFile.duration * 1e3; let startTime = (this._startTime ? !this._startPerc ? this._startTime : this._startTime * duration : 0) / 1e3; if (this._endTime) { duration = !this._endPerc ? Number( this._isRange ? this._endTime - this._startTime : duration - this._endTime ) : this._endTime * duration; } let data = { id: randomID(), play: true, src: file, loop: this._duration > duration, volume: this._volume, fadeIn: this._fadeInAudio, fadeOut: this._fadeOutAudio, startTime, duration: this._duration || duration, sceneId: game.user.viewedScene, users: this._users ? Array.from(this._users) : null }; for (let override of this._overrides) { data = await override(this, data); } if (typeof data.src !== "string" || data.src === "") { throw this.sequence._customError( this, "file", "a sound must have a src of type string!" ); } return data; } async _serialize() { const data = await super._serialize(); const sectionData = await this._sanitizeSoundData(); return { ...data, type: "sound", sectionData }; } async _deserialize(data) { this._deserializedData = data.sectionData; return super._deserialize(data); } } class AnimationSection extends Section { constructor(inSequence, inTarget) { super(inSequence); this._teleportTo = false; this._originObject = false; this._moveSpeed = 23; this._offset = { x: 0, y: 0 }; this._closestSquare = false; this._snapToGrid = false; this._hide = void 0; if (inTarget) this.on(inTarget); } static niceName = "Animation"; /** * Sets the target object to be animated * * @param {object|string} inTarget * @returns {AnimationSection} */ on(inTarget) { inTarget = this._validateLocation(inTarget); if (!inTarget) throw this.sequence._customError( this, "on", "could not find position of given object" ); this._originObject = this._validateLocation(inTarget); return this; } /** * Sets the location to teleport the target object to * * @param {object|string} inTarget * @param {object} options * @returns {AnimationSection} */ teleportTo(inTarget, options = {}) { options = foundry.utils.mergeObject( { delay: 0, relativeToCenter: false }, options ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "teleportTo", "options.delay must be of type number" ); inTarget = this._validateLocation(inTarget); if (!inTarget) throw this.sequence._customError( this, "teleportTo", "could not find position of given object" ); options.target = this._validateLocation(inTarget); this._teleportTo = options; return this; } /** * Sets the location to rotate the object to * * @param {object|string} inLocation * @param {object} options * @returns this */ rotateTowards(inLocation, options = {}) { options = foundry.utils.mergeObject( { duration: 0, ease: "linear", delay: 0, rotationOffset: 0, towardsCenter: true, cacheLocation: false }, options ); if (!is_real_number(options.duration)) throw this.sequence._customError( this, "rotateTowards", "options.duration must be of type number" ); if (typeof options.ease !== "string") throw this.sequence._customError( this, "rotateTowards", "options.ease must be of type string" ); if (!is_real_number(options.delay)) throw this.sequence._customError( this, "rotateTowards", "options.delay must be of type number" ); if (!is_real_number(options.rotationOffset)) throw this.sequence._customError( this, "rotateTowards", "options.rotationOffset must be of type number" ); if (typeof options.towardsCenter !== "boolean") throw this.sequence._customError( this, "rotateTowards", "options.towardsCenter must be of type boolean" ); if (typeof options.cacheLocation !== "boolean") throw this.sequence._customError( this, "rotateTowards", "options.cacheLocation must be of type boolean" ); options.target = this._validateLocation(inLocation); if (!options.target) throw this.sequence._customError( this, "rotateTowards", "could not find position of given object" ); options.target = options.cacheLocation ? get_object_position(options.target, { measure: true }) : options.target; this._rotateTowards = options; return this; } /** * Causes the movement or teleportation to be offset in the X and/or Y axis * * @param {object} inOffset * @returns {AnimationSection} */ offset(inOffset) { inOffset = foundry.utils.mergeObject({ x: 0, y: 0 }, inOffset); this._offset = this._validateLocation(inOffset); return this; } /** * Causes the movement or teleportation to pick the closest non-intersecting square, if the target is a token or tile * * @param {boolean} inBool * @returns {AnimationSection} */ closestSquare(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "closestSquare", "inBool must be of type boolean" ); this._closestSquare = inBool; return this; } /** * Causes the final location to be snapped to the grid * * @param {boolean} inBool * @returns {AnimationSection} */ snapToGrid(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "snapToGrid", "inBool must be of type boolean" ); this._snapToGrid = inBool; return this; } /** * Causes the object to become hidden * * @param {boolean} inBool * @returns {AnimationSection} */ hide(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "hide", "inBool must be of type boolean" ); this._hide = inBool; return this; } /** * Causes the object to become visible * * @param {boolean} inBool * @returns {AnimationSection} */ show(inBool = true) { if (typeof inBool !== "boolean") throw this.sequence._customError( this, "show", "inBool must be of type boolean" ); this._hide = !inBool; return this; } /** * @private */ async run() { return this._runAnimate(); } /** * @private */ _applyTraits() { Object.assign(this.constructor.prototype, traits.moves); Object.assign(this.constructor.prototype, traits.opacity); Object.assign(this.constructor.prototype, traits.rotation); Object.assign(this.constructor.prototype, traits.audio); Object.assign(this.constructor.prototype, traits.tint); } /** * @private */ async _updateObject(obj, updates, animate = false, animation2 = {}) { const uuid = obj?.uuid ?? obj?.document?.uuid; await sequencerSocket.executeAsGM( SOCKET_HANDLERS.UPDATE_DOCUMENT, uuid, updates, { animate, animation: animation2 } ); } /** * @private */ async _waitForTokenRefresh(obj) { let token; if (obj instanceof Token) { token = obj; } if (obj instanceof TokenDocument) { token = obj.object; } if (token == null || token.mesh != null) { return; } let refreshTokenID; return await new Promise((resolve) => { refreshTokenID = Hooks.on("refreshToken", async (tokenDoc) => { if (tokenDoc.document.actorId !== token.document.actorId || !token.mesh) return; Hooks.off("refreshToken", refreshTokenID); resolve(); }); }); } /** * @private */ async _execute() { if (!await this._shouldPlay()) return; let self = this; this._basicDelay = random_float_between(this._delayMin, this._delayMax); return new Promise(async (resolve) => { setTimeout(async () => { await this._waitForTokenRefresh(this._originObject); if (this._shouldAsync) { await self.run(); } else { self.run(); } resolve(); }, this._basicDelay); }); } /** * @private */ _getClosestSquare(origin, target) { let originLoc = get_object_position(origin, { exact: true }); let targetLoc = get_object_position(target, { exact: true }); let originDimensions = get_object_dimensions(origin); let targetDimensions = get_object_dimensions(target); let originBottom = Math.max( originDimensions.width - canvas.grid.size, canvas.grid.size ); let originRight = Math.max( originDimensions.height - canvas.grid.size, canvas.grid.size ); let ray = new Ray(originLoc, targetLoc); let dx = ray.dx; let dy = ray.dy; if (dx > 0 && Math.abs(dx) > originRight) { dx -= originDimensions.width; } else if (dx < 0 && Math.abs(dx) > targetDimensions.width) { dx += targetDimensions.height; } else { dx = 0; } if (dy > 0 && Math.abs(dy) > originBottom) { dy -= originDimensions.height; } else if (dy < 0 && Math.abs(dy) > targetDimensions.height) { dy += targetDimensions.height; } else { dy = 0; } const pos = { x: originLoc.x + dx, y: originLoc.y + dy, elevation: targetLoc?.elevation }; if (!pos.elevation) { delete pos["elevation"]; } return pos; } /** * @private */ _snapLocationToGrid(inLocation) { return canvas.grid.getSnappedPosition(inLocation.x, inLocation.y); } /** * This needs a rewrite, jeesus. */ async _runAnimate() { let animData = { attributes: [], maxFPS: 1e3 / game.settings.get("core", "maxFPS"), lastTimespan: performance.now(), totalDt: 0 }; let overallDuration = this._duration ? this._duration : 0; const originLocation = get_object_position(this._originObject, { exact: true }); if (this._rotateTowards) { let offset2 = (this._angle ? this._angle : 0) + this._rotateTowards.rotationOffset; let targetLoc = this._moveTowards?.target || this._teleportTo?.target || this._originObject; targetLoc = this._closestSquare ? this._getClosestSquare(this._originObject, targetLoc) : get_object_position(targetLoc, { exact: true }); targetLoc.x += this._offset.x; targetLoc.y += this._offset.y; if (this._snapToGrid) { targetLoc = this._snapLocationToGrid(targetLoc); } if (this._originObject instanceof TokenDocument) { setTimeout(async () => { let startLocation = this._originObject.object?.center ?? targetLoc; let targetLocation = this._rotateTowards.target?.object ?? this._rotateTowards.target; if (this._rotateTowards.towardsCenter) { targetLocation = targetLocation?.center ?? targetLocation; } let ray = new Ray(startLocation, targetLocation); let angle = Math.normalizeDegrees(ray.angle * 180 / Math.PI - 90); angle += offset2; await this._updateObject( this._originObject, { rotation: angle }, this._rotateTowards.duration > 0, { duration: this._rotateTowards.duration, easing: this._rotateTowards.ease } ); }, this._rotateTowards.delay ?? 0); } else { animData.attributes.push({ name: "rotationTowards", offset: offset2, origin: this._originObject, originLocation: targetLoc, target: this._rotateTowards.target?.object ?? this._rotateTowards.target, towardsCenter: this._rotateTowards.towardsCenter, from: false, to: false, progress: 0, done: false, duration: this._rotateTowards.duration, durationDone: 0, delay: this._rotateTowards.delay, ease: this._rotateTowards.ease }); } let rotateDuration = this._rotateTowards.duration + this._rotateTowards.delay; overallDuration = overallDuration > rotateDuration ? overallDuration : rotateDuration; } if (this._fadeIn) { let to = is_real_number(this._opacity) ? this._opacity : 1; if (this._originObject instanceof TokenDocument) { setTimeout(async () => { await this._updateObject(this._originObject, { alpha: to }, true, { duration: this._fadeIn.duration, easing: this._fadeIn.ease }); }, this._fadeIn.delay ?? 0); } else { animData.attributes.push({ name: "alpha", from: 0, to, progress: 0, done: false, duration: this._fadeIn.duration, durationDone: 0, delay: this._fadeIn.delay, ease: this._fadeIn.ease }); } let fadeDuration = this._fadeIn.duration + this._fadeIn.delay; overallDuration = overallDuration > fadeDuration ? overallDuration : fadeDuration; } if (this._fadeInAudio && this._originObject?.video?.volume !== void 0) { let to = is_real_number(this._volume) ? this._volume : 1; animData.attributes.push({ name: "video.volume", from: 0, to, progress: 0, done: false, duration: this._fadeInAudio.duration, durationDone: 0, delay: this._fadeInAudio.delay, ease: this._fadeInAudio.ease }); let fadeDuration = this._fadeInAudio.duration + this._fadeInAudio.delay; overallDuration = overallDuration > fadeDuration ? overallDuration : fadeDuration; } if (this._rotateIn) { let from = this._angle ? this._angle : this._originObject.rotation; let to = this._rotateIn.value; if (Math.abs(from - to) > 180) { if (to < 0) { to += 360; } else if (from > to) { from -= 360; } } if (this._originObject instanceof TokenDocument) { setTimeout(async () => { if (this._originObject.rotation !== from) { await this._updateObject( this._originObject, { rotation: from }, false ); } await this._updateObject(this._originObject, { rotation: to }, true, { duration: this._rotateIn.duration, easing: this._rotateIn.ease }); }, this._rotateIn.delay ?? 0); } else { animData.attributes.push({ name: "rotation", from, to, progress: 0, done: false, duration: this._rotateIn.duration, durationDone: 0, delay: this._rotateIn.delay, ease: this._rotateIn.ease }); } let rotateDuration = this._rotateIn.duration + this._rotateIn.delay; overallDuration = overallDuration > rotateDuration ? overallDuration : rotateDuration; } if (this._moveTowards) { let originLocation2 = get_object_position(this._originObject, { exact: true }); let targetLocation = this._closestSquare ? this._getClosestSquare(this._originObject, this._moveTowards.target) : get_object_position(this._moveTowards.target, { exact: true }); targetLocation.x += this._offset.x; targetLocation.y += this._offset.y; targetLocation.elevation = targetLocation?.elevation ?? this._originObject?.elevation; if (this._moveTowards.relativeToCenter) { const dimensions = get_object_dimensions(this._originObject); targetLocation.x -= dimensions.width / 2; targetLocation.y -= dimensions.height / 2; if (this._snapToGrid) { targetLocation.x -= 0.01; targetLocation.y -= 0.01; } } if (this._snapToGrid) { targetLocation = this._snapLocationToGrid(targetLocation); } let originalDx = targetLocation.x - originLocation2.x; let originalDy = targetLocation.y - originLocation2.y; let originalDistance = Math.sqrt( originalDx * originalDx + originalDy * originalDy ); let duration = this._duration ? this._duration : originalDistance / this._moveSpeed * animData.maxFPS; let moveDuration = duration + this._moveTowards.delay; overallDuration = overallDuration > moveDuration ? overallDuration : moveDuration; if (this._originObject instanceof TokenDocument) { setTimeout(async () => { await this._updateObject(this._originObject, targetLocation, true, { movementSpeed: this._moveSpeed, duration, easing: this._moveTowards.ease }); }, this._moveTowards.delay ?? 0); } else { if (!(duration === 0 || originalDistance === 0)) { animData.attributes.push({ name: "position", origin: originLocation2, target: targetLocation, originalDistance, currentDistance: 0, progress: 0, speed: 0, duration, done: false, ease: this._moveTowards.ease, delay: this._moveTowards.delay }); } } } if (this._fadeOut) { let from = is_real_number(this._opacity) ? this._opacity : this._originObject.alpha ?? 1; if (this._originObject instanceof TokenDocument) { setTimeout(async () => { await this._updateObject(this._originObject, { alpha: 0 }, true, { duration: this._fadeOut.duration, easing: this._fadeOut.ease }); }, this._fadeOut.delay ?? 0); } else { animData.attributes.push({ name: "alpha", from, to: 0, progress: 0, done: false, duration: this._fadeOut.duration, durationDone: 0, delay: overallDuration - this._fadeOut.duration, ease: this._fadeOut.ease }); } let fadeOutDuration = this._fadeOut.duration + this._fadeOut.delay; overallDuration = overallDuration > fadeOutDuration ? overallDuration : fadeOutDuration; } if (this._fadeOutAudio && this._originObject?.video?.volume !== void 0) { let from = is_real_number(this._volume) ? this._volume : this._originObject.video.volume; animData.attributes.push({ name: "video.volume", from, to: 0, progress: 0, done: false, duration: this._fadeOutAudio.duration, durationDone: 0, delay: overallDuration - this._fadeOutAudio.duration, ease: this._fadeOutAudio.ease }); let fadeOutAudioDuration = this._fadeOutAudio.duration + this._fadeOutAudio.delay; overallDuration = overallDuration > fadeOutAudioDuration ? overallDuration : fadeOutAudioDuration; } if (this._rotateOut) { let from = this._rotateOut.value; let to = this._angle ? this._angle : this._originObject.rotation; if (this._rotateIn) from += this._rotateIn.value; if (Math.abs(from - to) > 180) { if (to < 0) { to += 360; } else if (from > to) { from -= 360; } } if (this._originObject instanceof TokenDocument) { setTimeout(async () => { if (this._originObject.rotation !== from) { await this._updateObject( this._originObject, { rotation: from }, false ); } await this._updateObject(this._originObject, { rotation: to }, true, { duration: this._rotateOut.duration, easing: this._rotateOut.ease }); }, this._rotateOut.delay ?? 0); } else { animData.attributes.push({ name: "rotation", from, to, progress: 0, done: false, duration: this._rotateOut.duration, durationDone: 0, delay: overallDuration - this._rotateOut.duration, ease: this._rotateOut.ease }); } let rotateOutDuration = this._rotateOut.duration + this._rotateOut.delay; overallDuration = overallDuration > rotateOutDuration ? overallDuration : rotateOutDuration; } if (this._teleportTo) { setTimeout(async () => { let targetLocation = this._closestSquare ? this._getClosestSquare(this._originObject, this._teleportTo.target) : get_object_position(this._teleportTo.target, { exact: true }); if (targetLocation.x === void 0) targetLocation.x = originLocation.x; if (targetLocation.y === void 0) targetLocation.y = originLocation.y; if (targetLocation.elevation === void 0) targetLocation.elevation = originLocation.elevation; targetLocation.x += this._offset.x; targetLocation.y += this._offset.y; targetLocation.elevation = targetLocation?.elevation ?? this._originObject?.elevation; const dimensions = get_object_dimensions(this._originObject); if (this._teleportTo.relativeToCenter) { targetLocation.x -= dimensions.width / 2; targetLocation.y -= dimensions.height / 2; if (this._snapToGrid) { targetLocation.x -= 0.01; targetLocation.y -= 0.01; } } if (this._snapToGrid) { targetLocation.x -= dimensions.width / 2; targetLocation.y -= dimensions.height / 2; targetLocation = this._snapLocationToGrid(targetLocation); } await this._updateObject(this._originObject, targetLocation, false); }, this._teleportTo.delay); if (overallDuration <= this._teleportTo.delay) { this._waitUntilFinished = true; } overallDuration = overallDuration > this._teleportTo.delay ? overallDuration : this._teleportTo.delay; } let updateAttributes = {}; if (is_real_number(this._angle) && !this._rotateIn && !this._rotateOut) { updateAttributes["rotation"] = this._angle; } if (is_real_number(this._opacity) && !this._fadeIn && !this._fadeOut) { updateAttributes["alpha"] = this._opacity; } if (is_real_number(this._volume) && !this._fadeInAudio && !this._fadeOutAudio && this._originObject?.video?.volume !== void 0) { updateAttributes["video.volume"] = this._volume; } if (this._tint) { updateAttributes["tint"] = this._tint.hexadecimal; } if (this._hide !== void 0) { updateAttributes["hidden"] = this._hide; } if (Object.keys(updateAttributes).length) { await wait$1(1); await this._updateObject(this._originObject, updateAttributes); await wait$1(1); } return new Promise(async (resolve) => { this._animate(animData, resolve); setTimeout( resolve, Math.max(0, overallDuration + this._currentWaitTime + animData.maxFPS) ); }); } /** * @private */ async _animate(animData, resolve, timespan) { if (timespan) { let animatedAttributes = {}; let dt = timespan - animData.lastTimespan; if (dt >= animData.maxFPS) { animData.totalDt += dt; for (let attribute of animData.attributes) { if (attribute.done) continue; if (animData.totalDt < attribute.delay) continue; if (attribute.name === "position") { attribute.speed = attribute.originalDistance / (attribute.duration / dt); attribute.currentDistance += attribute.speed; attribute.progress = attribute.currentDistance / attribute.originalDistance; let x = interpolate( attribute.origin.x, attribute.target.x, attribute.progress, attribute.ease ); let y = interpolate( attribute.origin.y, attribute.target.y, attribute.progress, attribute.ease ); if (attribute.currentDistance >= attribute.originalDistance) { x = attribute.target.x; y = attribute.target.y; attribute.done = true; } animatedAttributes["x"] = x; animatedAttributes["y"] = y; } else { if (attribute.name === "rotationTowards" && !attribute.from && !attribute.to) { let target = attribute.target; if (this._rotateTowards.towardsCenter) target = target?.center ?? target; let ray = new Ray(attribute.originLocation, target); let angle = ray.angle * 180 / Math.PI - 90; angle += attribute.offset; attribute.from = attribute.origin.rotation; attribute.to = angle; if (Math.abs(attribute.from - attribute.to) > 180) { if (attribute.to < 0) { attribute.to += 360; } else if (attribute.from > attribute.to) { attribute.from -= 360; } } attribute.name = "rotation"; } attribute.durationDone += dt; attribute.progress = attribute.durationDone / attribute.duration; let val = interpolate( attribute.from, attribute.to, attribute.progress, attribute.ease ); if (attribute.progress >= 1) { val = attribute.to; attribute.done = true; } animatedAttributes[attribute.name] = val; } } if (Object.keys(animatedAttributes).length > 0) { await this._updateObject(this._originObject, animatedAttributes); } animData.attributes = animData.attributes.filter((a) => !a.done); if (animData.attributes.length === 0) return; animData.lastTimespan = timespan; } } let self = this; requestAnimationFrame(function(timespan2) { self._animate(animData, resolve, timespan2); }); } } class ScrollingTextSection extends Section { constructor(inSequence, target, text2, textOptions) { super(inSequence); this._deserializedData = null; this._source = null; this._text = ""; this._duration = 2e3; this._distance = null; this._jitter = 0; this._anchor = null; this._direction = null; this._seed = get_hash(randomID()); if (target) { this.atLocation(target); } if (text2) { this.text(text2, textOptions); } } static niceName = "Scrolling Text"; /** * @private */ _applyTraits() { Object.assign(this.constructor.prototype, traits.users); Object.assign(this.constructor.prototype, traits.location); Object.assign(this.constructor.prototype, traits.offset); Object.assign(this.constructor.prototype, traits.text); } direction(inDirection) { if (!(typeof inDirection === "string" || is_real_number(inDirection))) { throw this.sequence._customError( this, "direction", "inDirection must be of type string (CONST.TEXT_ANCHOR_POINTS) or number" ); } if (typeof inDirection === "string" && !CONST.TEXT_ANCHOR_POINTS[inDirection]) { throw this.sequence._customError( this, "direction", `${inDirection} does not exist in CONST.TEXT_ANCHOR_POINTS!` ); } else if (typeof inDirection === "string") { this._direction = CONST.TEXT_ANCHOR_POINTS[inDirection]; } else { this._direction = inDirection; } return this; } anchor(inAnchor) { if (!(typeof inAnchor === "string" || is_real_number(inAnchor))) { throw this.sequence._customError( this, "direction", "inAnchor must be of type string (CONST.TEXT_ANCHOR_POINTS) or number" ); } if (typeof inAnchor === "string" && !CONST.TEXT_ANCHOR_POINTS[inAnchor] || is_real_number(inAnchor) && !Object.values(CONST.TEXT_ANCHOR_POINTS).includes(inAnchor)) { throw this.sequence._customError( this, "direction", `${inAnchor} does not exist in CONST.TEXT_ANCHOR_POINTS!` ); } else if (typeof inAnchor === "string") { this._anchor = CONST.TEXT_ANCHOR_POINTS[inAnchor]; } else { this._anchor = inAnchor; } return this; } jitter(inJitter) { if (!(is_real_number(inJitter) && inJitter >= 0 && inJitter <= 1)) { throw this.sequence._customError( this, "jitter", "inJitter must be of type number between 0 and 1" ); } this._jitter = inJitter; return this; } async run() { const data = await this._sanitizeTextData(); if (Hooks.call("preCreateScrollingText", data) === false) return; const push = !(data?.users?.length === 1 && data?.users?.includes(game.userId)) && !this.sequence.localOnly; const duration = SequencerFoundryReplicator.playScrollingText(data, push); await new Promise( (resolve) => setTimeout(resolve, this._currentWaitTime + duration) ); } _getSourceObject() { if (!this._source || typeof this._source !== "object") return this._source; return get_object_identifier(this._source) ?? get_object_canvas_data(this._source); } async _sanitizeTextData() { if (this._deserializedData) { return this._deserializedData; } return { sceneId: game.user.viewedScene, seed: this._seed, sequenceId: this.sequence.id, creatorUserId: game.userId, users: this._users ? Array.from(this._users) : false, moduleName: this.sequence.moduleName, source: this._getSourceObject(), offset: this._offset?.source ?? false, randomOffset: this._randomOffset?.source ?? false, content: this._text?.text ?? "", options: { anchor: this._anchor, direction: this._direction, duration: this._duration, distance: this._distance, jitter: this._jitter, ...this._text } }; } async _serialize() { const data = await super._serialize(); const sectionData = await this._sanitizeTextData(); return { ...data, type: "scrollingText", sectionData }; } async _deserialize(data) { this._deserializedData = data.sectionData; return super._deserialize(data); } } class CanvasPanSection extends Section { constructor(inSequence, target, duration, scale2) { super(inSequence); this._deserializedData = null; this._source = null; this._duration = duration ?? 250; this._speed = null; this._scale = scale2 ?? 1; this._lockView = null; this._shake = null; this._seed = get_hash(randomID()); if (target) { this.atLocation(target); } } static niceName = "Canvas Pan"; /** * @private */ _applyTraits() { Object.assign(this.constructor.prototype, traits.users); Object.assign(this.constructor.prototype, traits.location); Object.assign(this.constructor.prototype, traits.offset); } /** * Sets the speed (pixels per frame) of the canvas pan * * @param {number} inSpeed * @returns this */ speed(inSpeed) { if (!is_real_number(inSpeed)) throw this.sequence._customError( this, "speed", "inSpeed must be of type number" ); if (inSpeed < 0) { throw this.sequence._customError( this, "speed", "inSpeed must be greater or equal to 0" ); } this._speed = inSpeed; return this; } /** * Sets the zoom level of the canvas pan * * @param {number} inScale * @returns this */ scale(inScale) { if (!is_real_number(inScale)) throw this.sequence._customError( this, "scale", "inScale must be of type number" ); if (inScale <= 0) { throw this.sequence._customError( this, "scale", "inScale must be greater than 0" ); } this._scale = inScale; return this; } /** * Locks the canvas at the given location for the given duration * * @param {number} inDuration * @returns this */ lockView(inDuration) { if (!is_real_number(inDuration)) throw this.sequence._customError( this, "lockView", "inDuration must be of type number" ); if (inDuration < 0) { throw this.sequence._customError( this, "lockView", "inDuration must be greater or equal to 0" ); } this._lockView = inDuration; return this; } /** * Shakes the canvas * * @param {number} duration * @param {number} strength * @param {number} frequency * @param {number} fadeInDuration * @param {number} fadeOutDuration * @param {boolean} rotation * @returns this */ shake({ duration = 250, strength = 20, frequency = 10, fadeInDuration = 0, fadeOutDuration = 200, rotation: rotation2 = true } = {}) { if (!is_real_number(duration)) { throw this.sequence._customError( this, "shake", "duration must be of type number" ); } if (!is_real_number(strength)) { throw this.sequence._customError( this, "shake", "strength must be of type number" ); } if (!is_real_number(strength)) { throw this.sequence._customError( this, "shake", "frequency must be of type number" ); } if (!is_real_number(fadeInDuration)) { throw this.sequence._customError( this, "shake", "fadeInDuration must be of type number" ); } if (!is_real_number(fadeOutDuration)) { throw this.sequence._customError( this, "shake", "fadeOutDuration must be of type number" ); } if (typeof rotation2 !== "boolean") { throw this.sequence._customError( this, "shake", "rotation must be of type boolean" ); } this._shake = { duration, strength, frequency, fadeInDuration, fadeOutDuration, rotation: rotation2 }; return this; } async run() { const data = await this._sanitizeData(); if (Hooks.call("preCanvasPan", data) === false) return; const push = !(data?.users?.length === 1 && data?.users?.includes(game.userId)) && !this.sequence.localOnly; const duration = SequencerFoundryReplicator.panCanvas(data, push); await new Promise( (resolve) => setTimeout(resolve, this._currentWaitTime + duration) ); } _getSourceObject() { if (!this._source || typeof this._source !== "object") return this._source; return get_object_identifier(this._source) ?? get_object_canvas_data(this._source); } async _sanitizeData() { if (this._deserializedData) { return this._deserializedData; } return { sceneId: game.user.viewedScene, seed: this._seed, sequenceId: this.sequence.id, creatorUserId: game.userId, users: this._users ? Array.from(this._users) : false, moduleName: this.sequence.moduleName, source: this._getSourceObject(), offset: this._offset?.source ?? false, randomOffset: this._randomOffset?.source ?? false, duration: this._duration, speed: this._speed, scale: this._scale, lockView: this._lockView, shake: this._shake }; } async _serialize() { const data = await super._serialize(); const sectionData = await this._sanitizeData(); return { ...data, type: "canvasPan", sectionData }; } async _deserialize(data) { this._deserializedData = data.sectionData; return super._deserialize(data); } } class WaitSection extends Section { constructor(inSequence, msMin, msMax) { super(inSequence); this._waitUntilFinished = true; this._waitDuration = random_int_between(msMin, Math.max(msMin, msMax)); } static niceName = "Wait"; /** * @returns {Promise} */ async run() { debug("Running wait"); await new Promise(async (resolve) => { setTimeout(resolve, this._waitDuration); }); } /** * @returns {Promise} * @private */ async _execute() { await this.run(); } async _serialize() { const data = await super._serialize(); return { ...data, type: "wait", sectionData: { waitDuration: this._waitDuration } }; } async _deserialize(data) { this._waitDuration = data.sectionData.waitDuration; return super._deserialize(data); } } let Sequence$1 = class Sequence3 { constructor(options = { moduleName: "Sequencer", softFail: false }, softFail = false) { this.id = randomID(); this.moduleName = typeof options === "string" ? options : options?.moduleName ?? "Sequencer"; this.softFail = options?.softFail ?? softFail; this.sections = []; this.nameOffsetMap = false; this.effectIndex = 0; this.sectionToCreate = void 0; this.localOnly = false; this._status = writable$1(CONSTANTS.STATUS.READY); return sequence_proxy_wrap(this); } /** * Plays all of this sequence's sections * * @returns {Promise} */ async play({ remote = false } = {}) { if (remote) { this.localOnly = true; const data = await this.toJSON(); sequencerSocket.executeForOthers( SOCKET_HANDLERS.RUN_SEQUENCE_LOCALLY, data ); return new Sequence3().fromJSON(data).play(); } Hooks.callAll("createSequencerSequence", this); debug("Initializing sections"); for (let section of this.sections) { await section._initialize(); } SequenceManager.RunningSequences.add(this.id, this); this.effectIndex = 0; debug("Playing sections"); this.status = CONSTANTS.STATUS.RUNNING; const promises = []; for (let section of this.sections) { if (section instanceof EffectSection) this.effectIndex++; if (section.shouldWaitUntilFinished) { promises.push(await section._execute()); } else { promises.push(section._execute()); } if (get_store_value(this.status) === CONSTANTS.STATUS.ABORTED) { continue; } if (!section._isLastSection) { await new Promise((resolve) => setTimeout(resolve, 1)); } } return Promise.allSettled(promises).then(() => { Hooks.callAll("endedSequencerSequence"); debug("Finished playing sections"); this.status = CONSTANTS.STATUS.COMPLETE; }); } /** * Creates a section that will run a function. * * @param {function} inFunc * @returns {Sequence} this */ thenDo(inFunc) { const func2 = section_proxy_wrap(new FunctionSection(this, inFunc)); this.sections.push(func2); return func2; } /** * Creates a section that will run a macro based on a name or a direct reference to a macro. * * @param {string|Macro} inMacro * @param {object} args * @returns {Sequence} this */ macro(inMacro, ...args) { let macro; let compendium = false; if (typeof inMacro === "string") { if (inMacro.startsWith("Compendium")) { let packArray = inMacro.split("."); let pack = game.packs.get(`${packArray[1]}.${packArray[2]}`); if (!pack) { if (this.softFail) { return this; } throw custom_error( this.moduleName, `macro - Compendium '${packArray[1]}.${packArray[2]}' was not found` ); } macro = packArray; compendium = pack; } else { macro = game.macros.getName(inMacro); if (!macro) { if (this.softFail) { return this; } throw custom_error( this.moduleName, `macro - Macro '${inMacro}' was not found` ); } } } else if (inMacro instanceof Macro) { macro = inMacro; } else { throw custom_error( this.moduleName, `macro - inMacro must be of instance string or Macro` ); } if (isNewerVersion(game.version, "11")) { args = args.length ? args?.[0] : {}; if (typeof args !== "object") { throw custom_error( this.moduleName, `macro - Secondary argument must be an object` ); } } else if (args && args.length && !game.modules.get("advanced-macros")?.active) { custom_warning( this.moduleName, `macro - Supplying macros with arguments require the advanced-macros module to be active`, true ); } const func2 = section_proxy_wrap( new FunctionSection( this, async () => { if (compendium) { const macroData = (await compendium.getDocuments()).find((i) => i.name === macro[3])?.toObject(); if (!macroData) { if (this.softFail) { return; } throw custom_error( this.moduleName, `macro - Macro '${macro[3]}' was not found in compendium '${macro[1]}.${macro[2]}'` ); } macro = new Macro(macroData); macro.ownership.default = CONST.DOCUMENT_PERMISSION_LEVELS.OWNER; } if (isNewerVersion(game.version, "11")) { await macro.execute(args); } else { const version = game.modules.get("advanced-macros")?.version; const bugAdvancedMacros = game.modules.get("advanced-macros")?.active && isNewerVersion( version.startsWith("v") ? version.slice(1) : version, "1.18.2" ) && !isNewerVersion( version.startsWith("v") ? version.slice(1) : version, "1.19.1" ); if (bugAdvancedMacros) { await macro.execute([...args]); } else { await macro.execute(...args); } } }, true ) ); this.sections.push(func2); return this; } /** * Creates an effect section. Until you call .then(), .effect(), .sound(), or .wait(), you'll be working on the Effect section. * * @param {string} [inFile] inFile * @returns {Section} */ effect(inFile = "") { const effect = section_proxy_wrap(new EffectSection(this, inFile)); this.sections.push(effect); return effect; } /** * Creates a sound section. Until you call .then(), .effect(), .sound(), or .wait(), you'll be working on the Sound section. * * @param {string} [inFile] inFile * @returns {Section} */ sound(inFile = "") { const sound = section_proxy_wrap(new SoundSection(this, inFile)); this.sections.push(sound); return sound; } /** * Creates an animation. Until you call .then(), .effect(), .sound(), or .wait(), you'll be working on the Animation section. * * @param {Token|Tile} [inTarget=false] inTarget * @returns {AnimationSection} */ animation(inTarget) { const animation2 = section_proxy_wrap( new AnimationSection(this, inTarget) ); this.sections.push(animation2); return animation2; } /** * Creates a scrolling text. Until you call .then(), .effect(), .sound(), or .wait(), you'll be working on the Scrolling Text section. * * @param {Object|String|Boolean} [inTarget=false] inTarget * @param {String|Boolean} [inText=false] inText * @param {Object} [inTextOptions={}] inTextOptions * @returns {AnimationSection} */ scrollingText(inTarget = false, inText = false, inTextOptions = {}) { const scrolling = section_proxy_wrap( new ScrollingTextSection(this, inTarget, inText, inTextOptions) ); this.sections.push(scrolling); return scrolling; } /** * Pans the canvas text. Until you call any other sections you'll be working on the Canvas Pan section. * * @param {Object|String|Boolean} [inTarget=false] inTarget * @param {Boolean|Number} inDuration * @param {Boolean|Number} inSpeed * @returns {AnimationSection} */ canvasPan(inTarget = false, inDuration = null, inSpeed = null) { const panning = section_proxy_wrap( new CanvasPanSection(this, inTarget) ); this.sections.push(panning); return panning; } /** * Causes the sequence to wait after the last section for as many milliseconds as you pass to this method. If given * a second number, a random wait time between the two given numbers will be generated. * * @param {number} [msMin=1] minMs * @param {number} [msMax=1] maxMs * @returns {Sequence} this */ wait(msMin = 1, msMax = 1) { if (msMin < 1) throw custom_error( this.moduleName, `wait - Wait ms cannot be less than 1` ); if (msMax < 1) throw custom_error( this.moduleName, `wait - Max wait ms cannot be less than 1` ); const section = section_proxy_wrap(new WaitSection(this, msMin, msMax)); this.sections.push(section); return this; } /** * Applies a preset to the sequence * * @param {string} presetName * @param {*} args * @returns {Sequence|FunctionSection|EffectSection|AnimationSection|SoundSection} */ preset(presetName, ...args) { if (typeof presetName !== "string") { throw this._customError(this, "name", `inName must be of type string`); } const preset = SequencerPresets.get(presetName); if (!preset) { custom_warning( "Sequencer", `preset | Could not find preset with name "${presetName}"` ); return this; } const lastSection = this.sections[this.sections.length - 1]; return preset(lastSection, ...args); } /** * Adds the sections from a given Sequence to this Sequence * * @param {Sequence|FunctionSection|EffectSection|AnimationSection|SoundSection} inSequence * @returns {Sequence} this */ addSequence(inSequence) { if (inSequence instanceof Section) inSequence = inSequence.sequence; if (!(inSequence instanceof Sequence3)) { throw custom_error( this.moduleName, `addSequence - could not find the sequence from the given parameter` ); } const newSections = inSequence.sections.map((section) => { const newSection = Object.assign( Object.create(Object.getPrototypeOf(section)), section ); newSection.sequence = this; return newSection; }); this.sections = this.sections.concat(newSections); return this; } async toJSON() { const data = { options: { moduleName: this.moduleName, softFail: this.softFail }, sections: [] }; for (const section of this.sections) { const sectionData = await section._serialize(); if (!sectionData.type) { throw new Error( `Sequencer | toJson | ${section.constructor.name} does not support serialization!` ); } data.sections.push(sectionData); } return data; } fromJSON(data) { this.moduleName = data.options.moduleName; this.softFail = data.options.softFail; this.localOnly = true; for (const section of data.sections) { this[section.type]()._deserialize(section); } return this; } _createCustomSection(...args) { const func2 = section_proxy_wrap( new this.sectionToCreate(this, ...args) ); this.sectionToCreate = void 0; this.sections.push(func2); return func2; } _showWarning(self, func2, warning, notify) { custom_warning( this.moduleName, `${self.constructor.name.replace("Section", "")} | ${func2} - ${warning}`, notify ); } _customError(self, func2, error) { return custom_error( this.moduleName, `${self.constructor.name.replace("Section", "")} | ${func2} - ${error}` ); } set status(inStatus) { this._status.update((currentStatus) => { if (currentStatus === CONSTANTS.STATUS.READY || currentStatus === CONSTANTS.STATUS.RUNNING) { return inStatus; } return currentStatus; }); } get status() { return this._status; } _abort() { this.status = CONSTANTS.STATUS.ABORTED; for (const section of this.sections) { section._abortSection(); } } }; const SequencerPreloader = { _usersToRespond: /* @__PURE__ */ new Set(), _clientsDone: [], _resolve: () => { }, /** * Caches provided file(s) locally, vastly improving loading speed of those files. * * @param {Array|String} inSrcs * @param {Boolean} showProgressBar * @returns {Promise} */ preload(inSrcs, showProgressBar = false) { if (Array.isArray(inSrcs)) { inSrcs.forEach((str) => { if (typeof str !== "string") { throw custom_error( "Sequencer", "preload | each entry in inSrcs must be of type string" ); } }); } else if (typeof inSrcs !== "string") { throw custom_error( "Sequencer", "preload | inSrcs must be of type string or array of strings" ); } if (typeof showProgressBar !== "boolean") { throw custom_error( "Sequencer", "preload | showProgressBar must be of type of boolean" ); } const srcs = this._cleanSrcs(inSrcs); if (srcs.length === 0) return; return this._preloadLocal(srcs, showProgressBar); }, /** * Causes each connected client (including the caller) to fetch and cache the provided file(s) locally, vastly * improving loading speed of those files. * * @param {Array|String} inSrcs * @param {Boolean} showProgressBar * @returns {Promise} */ preloadForClients(inSrcs, showProgressBar = false) { if (Array.isArray(inSrcs)) { inSrcs.forEach((str) => { if (typeof str !== "string") { throw custom_error( "Sequencer", "preloadForClients | each entry in inSrcs must be of type string" ); } }); } else if (typeof inSrcs !== "string") { throw custom_error( "Sequencer", "preloadForClients | inSrcs must be of type string or array of strings" ); } if (typeof showProgressBar !== "boolean") { throw custom_error( "Sequencer", "preloadForClients | showProgressBar must be of type of boolean" ); } const srcs = this._cleanSrcs(inSrcs); if (srcs.length === 0) return; if (!user_can_do("permissions-preload")) { custom_warning( "Sequencer", "preloadForClients - You do not have permission to force other clients to preload. Preloading locally instead." ); return this._preloadLocal(srcs, showProgressBar); } const promise2 = new Promise((resolve) => { this._resolve = resolve; }); this._usersToRespond = new Set( game.users.filter((user) => user.active).map((user) => user.id) ); sequencerSocket.executeForEveryone( SOCKET_HANDLERS.PRELOAD, game.user.id, srcs, showProgressBar ); return promise2; }, async respond(inSenderId, inSrcs, showProgressBar) { const numFilesFailedToLoad = await this._preloadLocal( inSrcs, showProgressBar ); return sequencerSocket.executeAsUser( SOCKET_HANDLERS.PRELOAD_RESPONSE, inSenderId, game.user.id, numFilesFailedToLoad ); }, handleResponse(inUserId, numFilesFailedToLoad) { this._usersToRespond.delete(inUserId); this._clientsDone.push({ userId: inUserId, numFilesFailedToLoad }); if (this._usersToRespond.size > 0) return; this._clientsDone.forEach((user) => { if (user.numFilesFailedToLoad > 0) { debug( `${game.users.get(user.userId).name} preloaded files, failed to preload ${user.numFilesFailedToLoad} files` ); } else { debug( `${game.users.get(user.userId).name} preloaded files successfully` ); } }); debug(`All clients responded to file preloads`); this._resolve(); this._usersToRespond = /* @__PURE__ */ new Set(); this._clientsDone = []; this._resolve = () => { }; }, /** * Filters and cleans up file paths given to the preload methods * * @private */ _cleanSrcs(inSrcs) { if (!Array.isArray(inSrcs)) { inSrcs = [inSrcs]; } if (inSrcs.length === 0) { custom_warning("Sequencer", "You need to provide files to preload"); return []; } inSrcs = make_array_unique( inSrcs.filter(Boolean).map((src) => { if (Sequencer.Database.entryExists(src)) { return Sequencer.Database.getAllFileEntries(src); } return src; }) ).deepFlatten(); if (inSrcs.length >= 750) { custom_warning( "Sequencer", "You are preloading over 750 files, you are most likely preloading more files than the system can cache.", true ); } return inSrcs; }, /** * The method that actually preloads files locally, with an optional progress bar * * @private */ _preloadLocal(inSrcs, showProgressBar) { let startTime = performance.now(); let numFilesToLoad = inSrcs.length; debug(`Preloading ${numFilesToLoad} files...`); if (showProgressBar) LoadingBar.init( `Sequencer - Preloading ${numFilesToLoad} files`, numFilesToLoad ); return new Promise(async (resolve) => { let numFilesFailedToLoad = 0; for (let src of inSrcs) { const blob = await SequencerFileCache.loadFile(src, true); if (showProgressBar) LoadingBar.incrementProgress(); if (!blob) { numFilesFailedToLoad++; } } const timeTaken = (performance.now() - startTime) / 1e3; let failedToLoad = ` (${numFilesFailedToLoad} failed to load)`; debug( `Preloading ${numFilesToLoad} files took ${timeTaken}s` + failedToLoad ); resolve(numFilesFailedToLoad); }); } }; class SequencerSectionManager { constructor() { this.externalSections = {}; } /** * Registers a class by a name that will then be available through the Sequencer * * @param {String} inModuleName * @param {String} inMethodName * @param {Class} inClass * @param {Boolean} overwrite * @returns {Boolean} Whether the registration succeeded */ registerSection(inModuleName, inMethodName, inClass, overwrite = false) { if (!(inClass.prototype instanceof Section)) { throw custom_error( inModuleName, `inClass must be instance of Sequencer.BaseSection` ); } let coreMethods = Object.getOwnPropertyNames(Sequence.prototype).filter( (method) => { return !method.startsWith("_") && method !== "constructor"; } ); if (coreMethods.includes(inMethodName)) { throw custom_error( inModuleName, `${inMethodName} is an existing protected method of the Sequence class - please register with another method name!` ); } if (this.externalSections[inMethodName] && !overwrite) { throw custom_error( inModuleName, `${inMethodName} is already a registered Section with the class ${this.externalSections[inMethodName].constructor.name}` ); } coreMethods = coreMethods.concat(Object.keys(this.externalSections)); const clashingMethods = Object.getOwnPropertyNames( inClass.prototype ).filter((method) => coreMethods.includes(method)); if (clashingMethods.length) { let errors = clashingMethods.join(", "); throw custom_error( inModuleName, `${inMethodName} cannot contain the following methods: ${errors}
These methods are existing methods on the Sequence or from already registered Sections. Please rename these methods to avoid conflicts.` ); } debug( `SectionManager | Successfully registered ${inMethodName} with Sequencer!` ); this.externalSections[inMethodName] = inClass; return true; } } let libWrapper = void 0; const TGT_SPLIT_RE = new RegExp( `([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|"([^"\\\\]|\\\\.)+?")\\])`, "g" ); const TGT_CLEANUP_RE = new RegExp(`(^\\['|'\\]$|^\\["|"\\]$)`, "g"); Hooks.once("init", () => { if (globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) { libWrapper = globalThis.libWrapper; return; } libWrapper = class { static get is_fallback() { return true; } static get WRAPPER() { return "WRAPPER"; } static get MIXED() { return "MIXED"; } static get OVERRIDE() { return "OVERRIDE"; } static register(package_id, target, fn, type = "MIXED", { chain = void 0, bind: bind2 = [] } = {}) { const is_setter = target.endsWith("#set"); target = !is_setter ? target : target.slice(0, -4); const split = target.match(TGT_SPLIT_RE).map((x) => x.replace(/\\(.)/g, "$1").replace(TGT_CLEANUP_RE, "")); const root_nm = split.splice(0, 1)[0]; let obj, fn_name; if (split.length == 0) { obj = globalThis; fn_name = root_nm; } else { const _eval = eval; fn_name = split.pop(); obj = split.reduce( (x, y) => x[y], globalThis[root_nm] ?? _eval(root_nm) ); } let iObj = obj; let descriptor = null; while (iObj) { descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name); if (descriptor) break; iObj = Object.getPrototypeOf(iObj); } if (!descriptor || descriptor?.configurable === false) throw new Error( `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.` ); let original = null; const wrapper = chain ?? (type.toUpperCase?.() != "OVERRIDE" && type != 3) ? function(...args) { return fn.call(this, original.bind(this), ...bind2, ...args); } : function(...args) { return fn.call(this, ...bind2, ...args); }; if (!is_setter) { if (descriptor.value) { original = descriptor.value; descriptor.value = wrapper; } else { original = descriptor.get; descriptor.get = wrapper; } } else { if (!descriptor.set) throw new Error( `libWrapper Shim: '${target}' does not have a setter` ); original = descriptor.set; descriptor.set = wrapper; } descriptor.configurable = true; Object.defineProperty(obj, fn_name, descriptor); } }; }); function registerLibwrappers() { const override = isNewerVersion(game.version, "11") ? "PIXI.BaseImageResource.prototype.upload" : "PIXI.resources.BaseImageResource.prototype.upload"; libWrapper.register(CONSTANTS.MODULE_NAME, override, PIXIUPLOAD); } function PIXIUPLOAD(wrapped, ...args) { let baseTexture = args[1]; if (baseTexture.sequencer_patched || !game.settings.get(CONSTANTS.MODULE_NAME, "enable-global-fix-pixi")) { return wrapped(...args); } let source = args[3]; source = source || this.source; const isVideo = !!source.videoWidth; if (isVideo) { baseTexture.alphaMode = PIXI.ALPHA_MODES.PREMULTIPLIED_ALPHA; baseTexture.sequencer_patched = true; } return wrapped(...args); } async function runMigrations() { const sortedMigrations = Object.entries(migrations).sort((a, b) => { return isNewerVersion(b[0], a[0]) ? -1 : 1; }); for (const [version, migration] of sortedMigrations) { try { await migration(version); } catch (err) { custom_warning( "Sequencer", `Something went wrong when migrating to version ${version}. Please check the console for the error!`, true ); console.error(err); } } } function getSequencerEffectTokens(version, tokenFilter = false) { return Array.from(game.scenes).map((scene) => [ scene, Array.from(scene.tokens).filter((token) => token.isOwner).filter((token, index) => { if (tokenFilter) { return tokenFilter(token, index); } const effects = getProperty(token, CONSTANTS.EFFECTS_FLAG) ?? []; const effectsOutOfDate = effects.filter( (e) => isNewerVersion(version, e[1].flagVersion) ); return effectsOutOfDate.length; }) ]).filter(([_, tokens]) => tokens.length); } function getSequencerEffectActors(version, actorFilter = false) { return Array.from(game.actors).filter((actor) => actor.isOwner).filter((actor, index) => { if (actorFilter) { return actorFilter(actor, index); } const effects = getProperty(actor, CONSTANTS.EFFECTS_FLAG) ?? []; const effectsOutOfDate = effects.filter( (e) => isNewerVersion(version, e[1].flagVersion) ); return effectsOutOfDate.length; }); } const migrations = { "3.0.0": async (version) => { const actorsToUpdate = getSequencerEffectActors(version, (actor) => { const effects = getProperty(actor, "prototypeToken." + CONSTANTS.EFFECTS_FLAG) ?? []; const effectsOutOfDate = effects.filter( (e) => isNewerVersion(version, e[1].flagVersion) ); return effectsOutOfDate.length; }); const actorUpdateArray = actorsToUpdate.map((actor) => { const effectsToMoveFromTokenPrototype = getProperty( actor.prototypeToken, CONSTANTS.EFFECTS_FLAG ).filter(([_, effect]) => { return effect.persistOptions?.persistTokenPrototype; }).map(([id, effect]) => { effect.flagVersion = version; return [id, effect]; }); const effectsToKeepOnTokenPrototype = getProperty( actor.prototypeToken, CONSTANTS.EFFECTS_FLAG ).filter(([_, effect]) => { return !effect.persistOptions?.persistTokenPrototype; }).map(([id, effect]) => { effect.flagVersion = version; return [id, effect]; }); return { _id: actor.id, [CONSTANTS.EFFECTS_FLAG]: effectsToMoveFromTokenPrototype, ["prototypeToken." + CONSTANTS.EFFECTS_FLAG]: effectsToKeepOnTokenPrototype }; }); const tokensOnScenes = getSequencerEffectTokens(version, (t) => { let actor; try { actor = t.actor; } catch (err) { return false; } const effects = getProperty(t, CONSTANTS.EFFECTS_FLAG) ?? []; const prototypeTokenEffects = getProperty(actor, "prototypeToken." + CONSTANTS.EFFECTS_FLAG) ?? []; const effectsOutOfDate = effects.filter( (e) => isNewerVersion(version, e[1].flagVersion) ); const prototypeEffectsOutOfDate = prototypeTokenEffects.filter( (e) => isNewerVersion(version, e[1].flagVersion) ); return t.actorLink && (effectsOutOfDate.length || prototypeEffectsOutOfDate.length); }); for (const [scene, tokens] of tokensOnScenes) { const updates = []; for (const token of tokens) { const effectsToKeepOnToken = getProperty(token, CONSTANTS.EFFECTS_FLAG).filter(([_, effect]) => { return !effect.persistOptions?.persistTokenPrototype; }).map(([id, effect]) => { effect.flagVersion = version; return [id, effect]; }); updates.push({ _id: token.id, [CONSTANTS.EFFECTS_FLAG]: effectsToKeepOnToken }); } if (updates.length) { debug( `Sequencer | Updated ${updates.length} tokens' effects on scene ${scene.id} to version ${version}` ); await scene.updateEmbeddedDocuments("Token", updates); } } if (actorUpdateArray.length) { debug( `Sequencer | Updated ${actorUpdateArray.length} actors' effects to version ${version}` ); await Actor.updateDocuments(actorUpdateArray); } } }; let moduleValid = false; let moduleReady = false; let canvasReady = false; Hooks.once("init", async function() { if (!game.modules.get("socketlib")?.active) return; moduleValid = true; CONSTANTS.INTEGRATIONS.ISOMETRIC.ACTIVE = false; initializeModule(); registerSocket(); }); Hooks.once("socketlib.ready", registerSocket); Hooks.once("ready", async function() { if (!game.modules.get("socketlib")?.active) { ui.notifications.error( "Sequencer requires the SocketLib module to be active and will not work without it!", { console: true } ); return; } for (const [name, func2] of Object.entries(easeFunctions)) { if (!CanvasAnimation[name]) { CanvasAnimation[name] = func2; } } if (game.user.isGM) { await runMigrations(); await migrateSettings(); await PlayerSettings.migrateOldPresets(); } SequencerFoundryReplicator.registerHooks(); InteractionManager.initialize(); }); Hooks.on("canvasTearDown", () => { canvasReady = false; SequencerEffectManager.tearDownPersists(); }); const setupModule = debounce(() => { if (!moduleValid) return; if (!moduleReady) { moduleReady = true; debug("Ready to go!"); Hooks.callAll("sequencer.ready"); Hooks.callAll("sequencerReady"); } if (!canvasReady) { canvasReady = true; SequencerEffectManager.initializePersistentEffects(); } }, 25); Hooks.on("canvasReady", () => { setTimeout(() => { setupModule(); }, 450); }); Hooks.on("refreshToken", setupModule); Hooks.on("refreshDrawing", setupModule); Hooks.on("refreshTile", setupModule); Hooks.on("refreshMeasuredTemplate", setupModule); function initializeModule() { window.Sequence = Sequence$1; window.Sequencer = { Player: EffectPlayer, Presets: SequencerPresets, Database: SequencerDatabase, DatabaseViewer: DatabaseViewerApp, Preloader: SequencerPreloader, EffectManager: SequencerEffectManager, SectionManager: new SequencerSectionManager(), registerEase, BaseSection: Section, CONSTANTS: { EASE }, Helpers: { wait: wait$1, clamp, interpolate, random_float_between, random_int_between, shuffle_array, random_array_element, random_object_element, make_array_unique } }; registerSettings(); registerLayers(); registerHotkeys(); registerLibwrappers(); SequencerAboveUILayer.setup(); SequencerEffectManager.setup(); } Hooks.once("monaco-editor.ready", registerTypes); //# sourceMappingURL=module.js.map