271 lines
9.4 KiB
JavaScript
271 lines
9.4 KiB
JavaScript
const validatorExpressions = {
|
|
noWhitespaceStart: /^[\S]+/,
|
|
};
|
|
const notificationIcons = {
|
|
danger: "fas fa-exclamation-circle",
|
|
warning: "fas fa-exclamation-triangle",
|
|
};
|
|
|
|
/**
|
|
* Hide (or show) all of an element's sibling elements
|
|
* @param {event} event - the event that triggered this
|
|
*/
|
|
function toggleHideAllSiblings(event, currentTarget) {
|
|
if (!event && !currentTarget) return;
|
|
if (!currentTarget) currentTarget = event.currentTarget;
|
|
|
|
const siblings = Array.from(currentTarget.parentNode.children).filter(
|
|
(item) => !item.isSameNode(currentTarget)
|
|
);
|
|
|
|
if (currentTarget.classList.contains("active")) {
|
|
siblings.forEach((el) => el.classList.remove("JTCS-hidden"));
|
|
} else {
|
|
siblings.forEach((el) => el.classList.add("JTCS-hidden"));
|
|
}
|
|
}
|
|
|
|
function fadeSheetOpacity(event, selector = ".window-content") {
|
|
event.preventDefault();
|
|
|
|
const windowContent = event.currentTarget.closest(selector);
|
|
const faded =
|
|
windowContent.classList.contains("fade") ||
|
|
windowContent.classList.contains("fade-all");
|
|
|
|
if (faded) {
|
|
windowContent.classList.remove("fade");
|
|
} else {
|
|
windowContent.classList.add("fade");
|
|
}
|
|
}
|
|
/**
|
|
* Insert a notification inline
|
|
* @param {*} event - the event that triggered this notification
|
|
* @param {*} ancestorSelector - the ancestor element into which we want to insert this notification element
|
|
* @param {Object} options - options to customize this notification
|
|
* @param {String} options.message - the notification message
|
|
* @param {String} options.notificationType - the type of notification, to affect its icon and styling
|
|
*/
|
|
async function renderInlineNotification(
|
|
event,
|
|
ancestorSelector = "formGroup",
|
|
options = {}
|
|
) {
|
|
let { notificationType, icon } = options;
|
|
if (!icon) {
|
|
if (notificationType) {
|
|
options.icon = notificationIcons[notificationType];
|
|
} else {
|
|
notificationType = "error";
|
|
options.icon = notificationIcons[notificationType];
|
|
}
|
|
}
|
|
const parentItem = event.currentTarget.closest(`.${ancestorSelector}`);
|
|
let template = game.JTCS.templates["notification-badge"];
|
|
let renderHTML = await renderTemplate(template, options);
|
|
parentItem.insertAdjacentHTML("beforeend", renderHTML);
|
|
}
|
|
|
|
function setAnimDefaults(animOptions) {
|
|
const defaultOptions = {
|
|
isFadeOut: false,
|
|
duration: 300,
|
|
onFadeOut: ($el, event) => {
|
|
$el.remove();
|
|
},
|
|
};
|
|
return mergeObject(defaultOptions, animOptions);
|
|
}
|
|
/**
|
|
*
|
|
* for fading objects in and out when they enter or exit the DOM
|
|
* @param {JQuery} $element - Jquery object representing element to fade
|
|
* @param {Object} options - the options object
|
|
* @param {Number} options.duration - default fade animation duration
|
|
* @param {Boolean} options.isFadeOut - a boolean determining whether or not this should fade in our out
|
|
* @param {Function} options.onFadeOut - the callback to handle what happens when the fade animation is complete
|
|
*/
|
|
async function fade($element, options = {}) {
|
|
const { duration, isFadeOut, onFadeOut, onCancel } = setAnimDefaults(options);
|
|
|
|
if ($element.length === 0) return;
|
|
|
|
let fadeAnim = $element[0].animate(
|
|
[
|
|
// keyframes
|
|
{ opacity: isFadeOut ? "100%" : "0%" },
|
|
{ opacity: isFadeOut ? "0%" : "100%" },
|
|
],
|
|
{
|
|
// timing options
|
|
duration: duration,
|
|
}
|
|
);
|
|
fadeAnim.addEventListener("finish", (event) => {
|
|
if (isFadeOut) {
|
|
onFadeOut($element, event);
|
|
}
|
|
});
|
|
fadeAnim.addEventListener("cancel", (event) => {
|
|
if (onCancel) {
|
|
onCancel($element, event);
|
|
}
|
|
});
|
|
return fadeAnim;
|
|
}
|
|
/**
|
|
* Handle the adding and removal of classes and triggering of animations to hide and fade elements
|
|
* @param {JQuery} $element - the JQuery object representing DOM Element we want to show or hide
|
|
*/
|
|
async function handleVisibilityTransitions($element) {
|
|
//if the class already has hidden, set it to fadeIn rather than out
|
|
const isFadeOut = $element.hasClass("JTCS-hidden") ? false : true;
|
|
|
|
//if we're fading in, remove the hidden class
|
|
if (!isFadeOut) $($element).removeClass("JTCS-hidden");
|
|
|
|
//set our fade animation options
|
|
let options = {
|
|
isFadeOut,
|
|
onFadeOut: ($element, event) => $element.addClass("JTCS-hidden"),
|
|
};
|
|
//handle the fade animation
|
|
//? Fade will handle the opacity, while our "JTCS-hidden" class handles everything else (transform, clip rect, position absolute, etc.)
|
|
fade($($element), options);
|
|
}
|
|
function toggleActiveStyles(event, el, useInitialTarget = true) {
|
|
if (!el) {
|
|
el = event.currentTarget;
|
|
if (useInitialTarget) {
|
|
//use target instead of currentTarget
|
|
el = event.target;
|
|
}
|
|
}
|
|
|
|
if (el.classList.contains("active")) {
|
|
el.classList.remove("active");
|
|
} else {
|
|
el.classList.add("active");
|
|
}
|
|
}
|
|
/**
|
|
* Turn off other elements that have active styles
|
|
* @param {HTMLEvent} event - the triggering event
|
|
* @param {Element} el - the element on which to apply the active styles
|
|
* @param {String} otherSelector - a selector to find other elements that are set to "active"
|
|
*/
|
|
function clearOtherActiveStyles(event, el, otherSelector, parentSelector) {
|
|
const parentItem = el.closest(parentSelector);
|
|
let others = Array.from(parentItem.querySelectorAll(otherSelector)).filter(
|
|
(item) => !item.isSameNode(el)
|
|
);
|
|
others = others.filter((other) => other.classList.contains("active"));
|
|
|
|
others.forEach((other) => toggleActiveStyles(event, other));
|
|
}
|
|
export const universalInterfaceActions = {
|
|
/**
|
|
*
|
|
* Show or hide another element
|
|
* @param {HTMLEvent} event - the event that provoked this
|
|
* @param {Object} options - options object
|
|
* @param {HTMLElement} options.parentItem - the parent item in which to find the element we want to hide/show
|
|
* @param {String} options.targetClassSelector - the class of the item we want to show
|
|
*/
|
|
toggleShowAnotherElement: (event, options) => {
|
|
let { parentItem, targetClassSelector, fadeIn = true } = options;
|
|
let el = event.currentTarget;
|
|
let targetID = el?.dataset.targetId;
|
|
let target;
|
|
if (targetID) {
|
|
target = parentItem.querySelector(`#${targetID}`);
|
|
} else {
|
|
target = parentItem.querySelector(`.${targetClassSelector}`);
|
|
}
|
|
if (target) {
|
|
if (fadeIn) {
|
|
handleVisibilityTransitions($(target));
|
|
} else {
|
|
$(target).removeClass("JTCS-hidden");
|
|
}
|
|
}
|
|
},
|
|
toggleActiveStyles: toggleActiveStyles,
|
|
clearOtherActiveStyles: clearOtherActiveStyles,
|
|
fadeSheetOpacity: fadeSheetOpacity,
|
|
toggleHideSelf: (event) => {
|
|
let el = event.currentTarget;
|
|
el.classList.toggle("JTCS-hidden");
|
|
},
|
|
toggleHideAncestor: (event, options) => {
|
|
let { ancestorSelector } = options;
|
|
let el = event.currentTarget;
|
|
el.closest(ancestorSelector).classList.toggle("JTCS-hidden");
|
|
// parentItem.classList.toggle("JTCS-hidden");
|
|
},
|
|
toggleHideAllSiblings,
|
|
scrollOtherElementIntoView: (event, options) => {
|
|
const { parentItem: $parentItem } = options;
|
|
let currentTarget = event.currentTarget;
|
|
let scrollTargetID = currentTarget.dataset.target;
|
|
let scrollTarget = $parentItem.find(`#${scrollTargetID}`);
|
|
scrollTarget[0].scrollIntoView();
|
|
clearOtherActiveStyles(
|
|
event,
|
|
currentTarget,
|
|
"[data-action='scrollTo']",
|
|
"#JTCSsettingsHeader"
|
|
);
|
|
toggleActiveStyles(event, currentTarget);
|
|
},
|
|
renderAnotherApp: async (appName, constructor) => {
|
|
//if global variable's not initialized, initialize it
|
|
if (!game[appName]) game[appName] = new constructor();
|
|
//if it's not rendered, render it
|
|
if (!game[appName].rendered) {
|
|
await game[appName].render(true);
|
|
// window[appName] = constructor;
|
|
} else {
|
|
//if it is rendered, bring it to the top
|
|
await game[appName].bringToTop();
|
|
}
|
|
},
|
|
renderInlineNotification: renderInlineNotification,
|
|
fade,
|
|
|
|
validateInput: async (
|
|
event,
|
|
validators = {
|
|
noWhitespaceStart: {
|
|
notificationType: "error",
|
|
message: "Please enter a value that doesn't start with a white space",
|
|
},
|
|
}
|
|
) => {
|
|
const { value } = event.currentTarget;
|
|
const validatorKeys = Object.keys(validators);
|
|
let allValid = true;
|
|
/// do validation here
|
|
let firstInvalidObject = {};
|
|
validatorKeys.forEach((key) => {
|
|
const regexp = validatorExpressions[key];
|
|
let isValid = regexp.test(value);
|
|
if (!isValid) {
|
|
//if one expression doesn't match
|
|
//set the full "allValid" boolean to false
|
|
allValid = false;
|
|
firstInvalidObject = validators[key];
|
|
let { notificationType: type } = firstInvalidObject;
|
|
firstInvalidObject.icon = notificationIcons[type];
|
|
}
|
|
});
|
|
|
|
//if one of the validators returns invalid, show a notification
|
|
if (!allValid) {
|
|
await renderInlineNotification(event, "form-group", firstInvalidObject);
|
|
}
|
|
return allValid;
|
|
},
|
|
};
|