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
|