All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

34481 lines
1023 KiB

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("<br>", "\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("<br>", "\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("<br>", "\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<string>} [options.ignoreClasses] - Iterable list of classes to ignore elements.
*
* @param {Set<HTMLElement>} [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<string>} [options.ignoreClasses] - Iterable list of classes to ignore elements.
*
* @param {Set<HTMLElement>} [options.ignoreElements] - Set of elements to ignore.
*
* @param {string} [options.selectors] - Custom list of focusable selectors for `querySelectorAll`.
*
* @returns {Array<HTMLElement>} 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<string>} [options.ignoreClasses] - Iterable list of classes to ignore elements.
*
* @param {Set<HTMLElement>} [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<string>} [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<string, string>} 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<string>} 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<string, import('svelte/store').Writable>}
*/
#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<void>} */
#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<void>}
*/
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<string>} 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<Awaited<unknown>[]>} */
#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<Awaited<unknown>[]>|Promise<void>} 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<Position>|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<Position>|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<Position>|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<Position>|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<Position>|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<Position>|Iterable<{position: Position}>} position -
*
* @param {Iterable<string>} 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<string, PositionDataExtended>}
*/
#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<string>} [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<PositionDataExtended>} 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<ValidatorData|undefined>} 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<number>} 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<number>} 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<number>} 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(<Function>)`.
*
* 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<string, ApplicationStateData>} */
#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<ApplicationStateData>} 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<ApplicationShellExt>} 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<Array<number|SvelteComponent>>} 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<SvelteComponent>} 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>} 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 '<svelte:options accessors={true}/>'?
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<void>} 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.<JQuery>} 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<JQuery>} 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 : `<i class="${button.icon}" title="${title}"></i>`);
}
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 = `<i class="fas fa-arrows-alt-h"></i>`;
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 = `<i class="fas fa-play svelte-ese-uyryhb"></i>`;
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, "<mark>$&</mark>"));
}
};
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 = `<i class="fas fa-play svelte-ese-flzvpb"></i>`;
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, "<mark>$&</mark>"));
}
};
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 = `<source type="audio/ogg"/>`;
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<number>, promise: Promise<void>}>}
* @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<number>, promise: Promise<void>}[]>}
* @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<Application.Options>}
*/
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<Application.Options>}
*/
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<void>}
* @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<void>}
* @private
*/
async _initialize() {
await super._initialize(false);
await this._startEffect();
}
/**
* @OVERRIDE
* @returns {Promise<void>}
* @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<void>}
* @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<void>}
* @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<void>}
* @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: `<p><input type="text" placeholder="${game.i18n.localize(
"SEQUENCER.Player.CreateNewPresetInputLabel"
)}" id="newPresetName" style="width:100%;"></p>`,
buttons: {
okay: {
icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("SEQUENCER.OK"),
callback: async (html) => {
let name = html.find("#newPresetName").val();
if (name === "" || !name) {
name = false;
}
resolve(name);
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
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: `<p>${game.i18n.localize(
"SEQUENCER.Player.DefaultErrorContent"
)}</p>`,
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: `<p>${game.i18n.format(
"SEQUENCER.Player.OverwritePresetContent",
{ preset_name: presetName }
)}</p>`
});
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 = `<i class="fas fa-times"></i>`;
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 = `<i class="fas fa-times"></i>`;
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 = `<i class="fas fa-file-import svelte-ese-1ipnpu1"></i>`;
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 = `<i class="fas fa-download svelte-ese-1ipnpu1"></i>`;
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 = `<i class="fas fa-stop"></i>`;
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<string, Function>}
*/
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<string, Function>}
*/
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<void>}
* @protected
*/
async preRun() {
}
/**
* Method overwritten by inheriting classes, which is called when this section is executed by the Sequence
*
* @returns {Promise<void>}
* @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<void>}
*/
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<string|User>} 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<String|PlaceableObject|foundry.abstract.Document>} 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<void>}
*/
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:<br>${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<void>}
*/
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<void>}
*/
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<void>}
*/
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}<br>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