From 85f404825156437828a7611d2d8a5f12a24e2d2c Mon Sep 17 00:00:00 2001 From: esheyw Date: Fri, 19 Jan 2024 14:46:27 -0800 Subject: [PATCH] MHDialog tidyup, validator implementation, documentation --- README.md | 47 +++++++++- TODO | 5 +- lang/en.json | 22 +++-- scripts/classes/MHLDialog.mjs | 109 ++++++++++++++++++---- scripts/constants.mjs | 6 +- scripts/helpers/errorHelpers.mjs | 73 ++++++++++++--- scripts/helpers/otherHelpers.mjs | 13 +-- scripts/helpers/pf2eHelpers.mjs | 20 +++- scripts/helpers/targetHelpers.mjs | 3 +- scripts/init.mjs | 2 +- scripts/macros/fascinatingPerformance.mjs | 4 +- scripts/macros/lashingCurrents.mjs | 12 ++- scripts/settings.mjs | 1 + 13 files changed, 252 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 2038b9f..ea5cbdc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ It is a collection of macros and helper functions I've written for PF2e. It will [Patch Notes](https://github.com/esheyw/pf2e-macro-helper-library/blob/main/CHANGELOG.md) -## Existing Macros: +## Macros Macros are accessed via `game.pf2emhl.macros.` #### Fascinating Performance (`async fascinatingPerformance()`) Requires one token selected, and at least one target. Has handling for target limits depending on Performance rank, and will ignore any targets with an effect that contains both "Immun" and "Fascinating Performance" in its name, case-**in**sensitive. TODO: Build immunity effect, apply as appropriate (existing behaviour is a holdover from standalone macro) @@ -15,7 +15,7 @@ For recovering weapons hidden in flags by the old Lashing Currents macro origina #### Drop Held Torch (`async dropHeldTorch()`) *Requires Item Piles* Requires one token only selected, and a currently held torch. Creates an Item Pile containing the torch, removing it from the actor. If the torch was lit, apply that light to the resulting pile token. Significant generalization and improvements planned. -## Existing Helper Functions: +## Helper Functions Helpers are accessed via `game.pf2emhl.` --- @@ -97,3 +97,46 @@ Example image (produced via pickItemFromActor above): The purple text is the `indentifier`, which can be supplied to disambiguated things with duplicate names. Currently produces a single button per provided `thing`, regardless of thing count. **TODO**: implement select menu fallback for > configurable limit of items, improve styling generally. + +--- +## Classes +### `MHLDialog` +MHLDialog is designed to be a drop-in replacement for the foundry Dialog class, with a few improvements: +#### Defaults to `jQuery:false` in options +This is mostly personal preference, but it means that any callbacks you use with this class should assume they will be passed an HTMLElement instead of a jQuery object, unless you specify `jQuery:true` in your dialog options object. +#### Doesn't clobber the classes array +In base Dialog, the way Application handles merging the options object, if you specify `classes:["my-class"]` as part of your dialog options, it will overwrite the array entirely, removing the `"dialog"` class. MHLDialog includes a workaround for this, and adds its own class (`"mhldialog"`) to the list in addition to whatever you give it. +#### Handlebars as `content` +Supports passing either a path to a handlebars file (must have extension `.html` or `.hbs`), or an inline handlebars template string, as `content` in dialog data. The temlpate is compiled and then passed the contents of the `contentData` property, in addition to the `buttons` and `content` variables that the base class provides, as well as the `idPrefix` variable, which is set to `mhldialog-${this.appId}-`. This last allows [valid-by-html-rules](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id) `id` properties on your form inputs, and associated labels, eg: +```hbs +
+
+ +
+ +
+
+
+``` +#### Restricting form submission (required fields) +Supports passing a `validator` property along with the dialog data. This can either be: +- A function (that takes the root element (respecting the `jQuery` option, which MHLDialog defaults to `false`) of the dialog and returns a boolean) +- An array of strings equating to the `name`s of form elements that are not allowed to be empty +- A single string `name` (gets put into an array and treated as above) +If passed either non-function option, the default validator will produce a banner if validation is failed: ![](https://i.imgur.com/EfbNTWE.png) + +#### Static methods `MHLDialog.getFormData(html)` and `MHLDialog.getFormData(html)` +`getFormsData` takes in html (or jQuery), and, for each form in the data, runs that form through `new FormDataExtended`, and assigns the output to an object, with the key of the form's name, eg: +```js +{ + "formname1": { + "fieldname1":"value", + "fieldname2":"value" + }, + "formname2":{ + //etc + } +} +``` +If there is more than one form in `html`, and any forms lack a `name` attribute, will error. If there's only one form, if that form lacks a `name` attribute it will be default to just 'form'. +`getFormData` just calls `getFormsData` and returns the first form's data; the only difference between it and simple `(html) => new FormDataExtended(html).object` is `getFormsData`'s handling for multiple forms. Either function is suitable as a callback if you'd like to simple dump the form output and handle that separately (my preference over having all the logic in the callback). diff --git a/TODO b/TODO index b69fe91..a2fb669 100644 --- a/TODO +++ b/TODO @@ -3,7 +3,4 @@ - implement current column - foundry package release api - npm package setup, run link dev -- - or just a standalone script -- dupe check contentData in MHLDialog against reserved keys -- add submit validation callback to MHLDialog -- document MHLDialog \ No newline at end of file + - or just a standalone script \ No newline at end of file diff --git a/lang/en.json b/lang/en.json index 7b7d062..02abc89 100644 --- a/lang/en.json +++ b/lang/en.json @@ -14,10 +14,15 @@ "MalformedThing": "Provided thing lacked valid label or value." } }, - "Dialog" : { + "Dialog": { + "Warning": { + "RequiredFields":"This dialog requires one or more fields to be non-empty: {fields}" + }, "Error": { "TemplateFailure": "The template filepath or literal passed to MHLDialog failed to compile properly.", - "FormRequiresName": "One or more of the forms provided lacks a `name` property." + "FormRequiresName": "One or more of the forms provided lacks a `name` property.", + "ReservedKeys":"The contentData object must not contain any of the following reserved keys: {keys}", + "BadValidator":"The supplied validator must either be a function or an array of value names required to be non-empty." } }, "Macro": { @@ -39,6 +44,9 @@ } }, "LashingCurrents": { + "Info": { + "Removing": "Removing Lashing Currents rules from \"{name}\"." + }, "Error": { "NoneSelected": "No weapon selected.", "NoExistingFound": "No old-style Lashing Currents weapon found on the actor of selected token \"{name}\"." @@ -76,19 +84,21 @@ "NotAUser": "Provided user was not a foundry User." } }, - "Error": { + "Error": { "Generic": "You broke something.", - "BannerType":"Banner type must be one of \"info\", \"warn\", or \"error\".", - "BadErrorString": "Provided non-string to error handler localizer.", + "BannerType": "Banner type must be one of \"info\", \"warn\", or \"error\".", + "LogType": "Log type must be one of \"debug\", \"info\", \"warn\", or \"error\".", "InvalidType": "Invalid type \"{type}\" provided.", "Type": { + "Array": "\"{var}\" must be an Array{typestr}.", + "User": "\"{var}\" must be a User or the ID of one.", "Folder": "\"{var}\" must be a Folder document or the ID of one.", "Function": "\"{var}\" must be a Function.", "Number": "\"{var}\" must be a Number.", "String": "\"{var}\" must be a String." } }, - "Warning":{ + "Warning": { "LevelOutOfBounds": "Provided level ({level}) out of bounds! Defaulting to level 25." }, "Settings": { diff --git a/scripts/classes/MHLDialog.mjs b/scripts/classes/MHLDialog.mjs index 89bd822..f0e25f5 100644 --- a/scripts/classes/MHLDialog.mjs +++ b/scripts/classes/MHLDialog.mjs @@ -1,24 +1,77 @@ -import { fu } from "../constants.mjs"; -import { MHLError } from "../helpers/errorHelpers.mjs"; +import { COLOURS, fu } from "../constants.mjs"; +import { MHLError, isEmpty, localizedBanner, mhlog } from "../helpers/errorHelpers.mjs"; import { localize } from "../helpers/stringHelpers.mjs"; const PREFIX = "MHL.Dialog"; export class MHLDialog extends Dialog { constructor(data, options = {}) { - if ("submitValidator" in data) { - const { submitValidator } = data; - if (typeof submitValidator !== "function") + //validate the validator. TODO: add facility for list of non-empty inputs instead of function + if ("validator" in data) { + let validator = data.validator; + switch (typeof validator) { + case "function": + break; + case "string": + validator = [validator]; + case "object": + if (Array.isArray(validator) && validator.every((f) => typeof f === "string")) { + const fields = fu.deepClone(validator); + data.validator = (html) => { + const formValues = MHLDialog.getFormData(html); + const emptyFields = fields.filter((f) => isEmpty(formValues[f])); + if (emptyFields.length) { + const fieldsError = fields + .map((f) => + emptyFields.includes(f) + ? `${f}` + : f + ) + .join(", "); + localizedBanner( + `${PREFIX}.Warning.RequiredFields`, + { fields: fieldsError }, + { type: "warn", log: { formValues }, console: false } + ); + return false; + } + return true; + }; + break; + } + default: + throw MHLError(`${PREFIX}.Error.BadValidator`, null, { func: "MHLDialog: ", log: { validator } }); + } + } + //make sure contentData doesnt have reserved keys (just buttons and content afaict) + if ("contentData" in data) { + const contentData = data.contentData; + const disallowedKeys = ["buttons", "content"]; + if (!Object.keys(contentData).every((k) => !disallowedKeys.includes(k))) { throw MHLError( - `MHL.Error.Type.Function`, - { var: "submitValidator" }, - { func: "MHLDialog: ", log: { submitValidator } } + `${PREFIX}.Error.ReservedKeys`, + { keys: disallowedKeys.join(", ") }, + { func: "MHLDialog: ", log: { contentData } } ); + } + } + + // gotta work around Application nuking the classes array with mergeObject + let tempClasses; + if ("classes" in options && Array.isArray(options.classes)) { + tempClasses = fu.deepClone(options.classes); + delete options.classes; } super(data, options); + if (tempClasses) this.options.classes = [...new Set(this.options.classes.concat(tempClasses))]; + } + + #_validate() { + if (!("validator" in this.data)) return true; + return this.data.validator(this.options.jQuery ? this.element : this.element[0]); } getData() { return fu.mergeObject(super.getData(), { - idPrefix: `mhdialog-${this.appId}-`, + idPrefix: `mhldialog-${this.appId}-`, ...(this.data.contentData ?? {}), }); } @@ -26,28 +79,30 @@ export class MHLDialog extends Dialog { static get defaultOptions() { return fu.mergeObject(super.defaultOptions, { jQuery: false, - classes: ["mhldialog"], + classes: ["mhldialog", ...super.defaultOptions.classes], }); } submit(button, event) { - if (this.data?.submitValidator && !this.data.submitValidator(this.element[0])) return false; + if (!this.#_validate()) return false; super.submit(button, event); } async _renderInner(data) { - const originalContent = fu.deepClone(data.content); - if (/\.(hbs|html)$/.test(data.content)) { - data.content = await renderTemplate(originalContent, data); - } else { - data.content = Handlebars.compile(originalContent)(data); + if (data?.content) { + const originalContent = fu.deepClone(data.content); + if (/\.(hbs|html)$/.test(data.content)) { + data.content = await renderTemplate(originalContent, data); + } else { + data.content = Handlebars.compile(originalContent)(data); + } + data.content ||= localize(`${PREFIX}.Error.TemplateFailure`); } - data.content ||= localize(`${PREFIX}.Error.TemplateFailure`); return super._renderInner(data); } static getFormData(html) { - return Object.values(this.getFormsData(html))[0]; + return Object.values(MHLDialog.getFormsData(html))[0]; } static getFormsData(html) { @@ -58,8 +113,24 @@ export class MHLDialog extends Dialog { if (forms.length > 1 && !form?.name) { throw MHLError(`${PREFIX}.Error.FormRequiresName`, null, { func: "defaultFormCallback: ", log: { forms } }); } - out[form?.name ?? "form"] = new FormDataExtended(curr).object; //if there's only one and it doesnt have a name, give it a default + //if there's only one and it doesnt have a name, give it a default + out[form?.name ?? "form"] = new FormDataExtended(form).object; } return out; } + + static getLabelMap(html) { + html = html instanceof jQuery ? html[0] : html; + const named = html.querySelectorAll("[name][id]"); + if (!named.length) return {}; + const namedIDs = Array.from(named).map((e) => e.getAttribute("id")); + const labels = html.querySelectorAll("[for]"); + if (!labels.length) return {}; + return Array.from(labels).reduce((acc, curr) => { + const forAttr = curr.getAttribute("for"); + if (!namedIDs.includes(forAttr)) return acc; + acc[forAttr] = curr.innerText; + return acc; + }, {}); + } } diff --git a/scripts/constants.mjs b/scripts/constants.mjs index ab997db..482a407 100644 --- a/scripts/constants.mjs +++ b/scripts/constants.mjs @@ -1,4 +1,8 @@ export const MODULE = 'pf2e-macro-helper-library'; -export const NOTIFY = () => game.settings.get(MODULE,'notify-on-error'); export const PHYSICAL_ITEM_TYPES = ["armor", "backpack", "book", "consumable", "equipment", "shield", "treasure", "weapon"]; export const fu = foundry.utils; +export const CONSOLE_TYPES = ["debug","info","warn","error"]; +export const BANNER_TYPES = CONSOLE_TYPES.slice(1) +export const COLOURS = { + error: 'var(--color-level-error, red)' +} \ No newline at end of file diff --git a/scripts/helpers/errorHelpers.mjs b/scripts/helpers/errorHelpers.mjs index b82c8a3..d490b90 100644 --- a/scripts/helpers/errorHelpers.mjs +++ b/scripts/helpers/errorHelpers.mjs @@ -1,30 +1,75 @@ -import { NOTIFY } from "../constants.mjs"; +import { BANNER_TYPES, CONSOLE_TYPES } from "../constants.mjs"; +import { NOTIFY } from "../settings.mjs"; import { localize } from "./stringHelpers.mjs"; -export const PREFIX = "MHL"; + export function localizedError(str, data = {}, { notify = null, prefix = "", log = {} } = {}) { - notify ??= NOTIFY; + notify ??= NOTIFY(); let errorstr = "" + prefix; - if (typeof log === "object" && Object.keys(log).length) console.error(log); - errorstr += typeof str === "string" ? localize(str, data) : localize(`${PREFIX}.Error.Type.String`); + if (typeof log === "object" && Object.keys(log).length) mhlog(log, "error"); + if (typeof str !== "string") { + throw MHLError(`MHL.Error.Type.String`, { var: "str" }, { func: "localizedError", log: { str } }); + } + errorstr += localize(str, data); if (notify) ui.notifications.error(errorstr, { console: false }); return Error(errorstr); } -export function localizedBanner(str, data = {}, { notify = null, prefix = "", log = {}, type="info" } = {}) { - const func = 'localizedBanner'; - notify ??= NOTIFY; + +export function localizedBanner(str, data = {}, { notify = null, prefix = "", log = {}, type = "info", console=true} = {}) { + const func = "localizedBanner"; + notify ??= NOTIFY(); if (!notify) return false; - if (!['info','error','warn'].includes(type)) throw MHLError(`${PREFIX}.Error.BannerType`, null, {func, log:{type}}) + if (!BANNER_TYPES.includes(type)) throw MHLError(`MHL.Error.BannerType`, null, { func, log: { type } }); let bannerstr = "" + prefix; - if (typeof log === "object" && Object.keys(log).length) console[type](log); - if (typeof str !== "string") throw MHLError(`${PREFIX}.Error.Type.String`, {var:'str'}, {func,log:{str}}); + if (typeof log === "object" && Object.keys(log).length) mhlog(log,type); + if (typeof str !== "string") { + throw MHLError(`MHL.Error.Type.String`, { var: "str" }, { func, log: { str } }); + } bannerstr += localize(str, data); - return ui.notifications[type](bannerstr); + return ui.notifications[type](bannerstr, {console}); } + export function MHLError( str, data = {}, - { notify = null, prefix = "MacroHelperLibrary: ", log = {}, func = null } = {} + { notify = null, prefix = "MHL | ", log = {}, func = null } = {} ) { if (func && typeof func === "string") prefix += func; - return localizedError(str, data, { notify, prefix }); + return localizedError(str, data, { notify, prefix, log }); +} + +// taken from https://stackoverflow.com/a/32728075, slightly modernized +/** + * Checks if value is empty. Deep-checks arrays and objects + * Note: isEmpty([]) == true, isEmpty({}) == true, isEmpty([{0:false},"",0]) == true, isEmpty({0:1}) == false + * @param value + * @returns {boolean} + */ +export function isEmpty(value) { + const isEmptyObject = (a) => { + if (!Array.isArray(a)) { + // it's an Object, not an Array + const hasNonempty = Object.keys(a).some((e) => !isEmpty(a[e])); + return hasNonempty ? false : isEmptyObject(Object.keys(a)); + } + return !a.some((e) => !isEmpty(e)); + }; + return ( + value == false || + typeof value === "undefined" || + value == null || + (typeof value === "object" && isEmptyObject(value)) + ); +} + +export function log(loggable, type = null, prefix = null) { + type ??= "debug"; + if (!CONSOLE_TYPES.includes(type)) { + throw MHLError(`MHL.Error.LogTypes`, { types: BANNER_TYPES.join(", ") }, { func: "log: ", log: { type } }); + } + prefix ??= ""; + return console[type](prefix, loggable); +} + +export function mhlog(loggable, type = null) { + return log(loggable, type, "MacroHelperLibrary |"); } diff --git a/scripts/helpers/otherHelpers.mjs b/scripts/helpers/otherHelpers.mjs index 713d05d..e739a5d 100644 --- a/scripts/helpers/otherHelpers.mjs +++ b/scripts/helpers/otherHelpers.mjs @@ -1,6 +1,7 @@ import { fu } from "../constants.mjs"; import { MHLError } from "./errorHelpers.mjs"; import { prependIndefiniteArticle, capitalize } from "./stringHelpers.mjs"; +import { MHLDialog } from "../classes/MHLDialog.mjs"; //root: root folder of the structure to update //exemplar: document to copy ownership structure of @@ -48,18 +49,12 @@ export async function pickAThingDialog({ things = null, title = null, thingType acc[curr.value] = { label: buttonLabel }; return acc; }, {}); - dialogOptions = fu.mergeObject( - { - jQuery: false, - classes: ["pick-a-thing"], - }, - dialogOptions - ); + dialogOptions.classes ??= []; + dialogOptions.classes.push('pick-a-thing') const dialogData = { title: title ?? `Pick ${prependIndefiniteArticle(capitalize(thingType) ?? "Thing")}`, - // content: dialogStyle, buttons, close: () => false, }; - return await Dialog.wait(dialogData, dialogOptions); + return await MHLDialog.wait(dialogData, dialogOptions); } diff --git a/scripts/helpers/pf2eHelpers.mjs b/scripts/helpers/pf2eHelpers.mjs index c9656c2..f1af57d 100644 --- a/scripts/helpers/pf2eHelpers.mjs +++ b/scripts/helpers/pf2eHelpers.mjs @@ -102,7 +102,7 @@ export async function getAllFromAllowedPacks({ fetch = false, } = {}) { const PREFIX = "MHL.GetAllFromAllowedPacks"; - const func = "getAllFromAllowedPacks"; + const func = "getAllFromAllowedPacks: "; const browser = game.pf2e.compendiumBrowser; const validTypes = Object.keys(browser.settings); validTypes.push("all"); @@ -176,6 +176,7 @@ export async function getAllFromAllowedPacks({ if (fetch) { out.push( ...(await pack.getDocuments({ + //secret getDocuments query syntax {prop}__in: _id__in: filteredDocs.map((d) => d._id), })) ); @@ -185,3 +186,20 @@ export async function getAllFromAllowedPacks({ } return out; } +//TODO: Generalize before marking for export +function generateTraitsFlavour(traits = []) { + if (!Array.isArray(traits)) { + throw MHLError( + `MHL.Error.Type.Array`, + { var: "traits", typestr: " of trait slug strings" }, + { func: "generateTraitsFlavour: ", log: { traits } } + ); + } + return traits + .map((tag) => { + const label = game.i18n.localize(CONFIG.PF2E.actionTraits[tag]); + const tooltip = CONFIG.PF2E.traitsDescriptions[tag]; + return `${label}`; + }) + .join(""); +} diff --git a/scripts/helpers/targetHelpers.mjs b/scripts/helpers/targetHelpers.mjs index 4c49134..05d2c8e 100644 --- a/scripts/helpers/targetHelpers.mjs +++ b/scripts/helpers/targetHelpers.mjs @@ -13,8 +13,9 @@ export function oneTargetOnly(useFirst = false, user = game.user) { return targets.first(); } export function anyTargets(user = game.user) { + if (typeof user === 'string') user = game.users.get(user) ?? game.users.getName(user); if (!(user instanceof User)) { - throw MHLError(`MHL.User.Error.NotAUser`, null, { log: { user } }); + throw MHLError(`MHL.Error.Type.User`, {var:'user'}, { log: { user } }); } if (user.targets.size === 0) { throw MHLError(`${PREFIX}.Error.NotAnyTargetted`); diff --git a/scripts/init.mjs b/scripts/init.mjs index 2731cc6..c56ab99 100644 --- a/scripts/init.mjs +++ b/scripts/init.mjs @@ -16,4 +16,4 @@ Hooks.on("init", () => { globalThis.mh = game.pf2emhl; } registerSettings(); -}); +}); \ No newline at end of file diff --git a/scripts/macros/fascinatingPerformance.mjs b/scripts/macros/fascinatingPerformance.mjs index b083d0c..b0048c7 100644 --- a/scripts/macros/fascinatingPerformance.mjs +++ b/scripts/macros/fascinatingPerformance.mjs @@ -2,7 +2,7 @@ import { MHLError } from "../helpers/errorHelpers.mjs"; import { localize } from "../helpers/stringHelpers.mjs"; import { oneTokenOnly } from "../helpers/tokenHelpers.mjs"; import { anyTargets } from "../helpers/targetHelpers.mjs"; -import { NOTIFY } from "../constants.mjs"; +import { NOTIFY } from "../settings.mjs"; const PREFIX = `MHL.Macro.FascinatingPerformance`; export async function fascinatingPerformance() { const token = oneTokenOnly(); @@ -123,7 +123,7 @@ export async function fascinatingPerformance() { (i) => i.name.toLowerCase().includes("immun") && i.name.toLowerCase().includes("fascinating performance") ); if (immunityEffect) { - if (NOTIFY) ui.notifications.warn(localize(`${PREFIX}.Warning.TargetImmune`, { name: targetToken.name })); + if (NOTIFY()) ui.notifications.warn(localize(`${PREFIX}.Warning.TargetImmune`, { name: targetToken.name })); continue; } const extraRollOptions = []; diff --git a/scripts/macros/lashingCurrents.mjs b/scripts/macros/lashingCurrents.mjs index 724b571..70b4e6c 100644 --- a/scripts/macros/lashingCurrents.mjs +++ b/scripts/macros/lashingCurrents.mjs @@ -1,9 +1,9 @@ import { oneTokenOnly } from "../helpers/tokenHelpers.mjs"; import { pickItemFromActor } from "../helpers/pf2eHelpers.mjs"; -import { MHLError } from "../helpers/errorHelpers.mjs"; +import { MHLError, localizedBanner } from "../helpers/errorHelpers.mjs"; const PREFIX = "MHL.Macro.LashingCurrents"; export async function lashingCurrents() { - const func = 'lashingCurrents: '; + const func = "lashingCurrents: "; const token = oneTokenOnly(); const actor = token.actor; const FORBIDDEN_RUNES = ["bloodbane", "kinWarding"]; @@ -22,6 +22,7 @@ export async function lashingCurrents() { label: "Lashing Currents", group: "flail", traits: ["disarm", "finesse", "reach-10", "trip", "versatile-s"], + img: "icons/magic/water/waves-water-blue.webp", }, ]; const existingLC = await pickItemFromActor(actor, { @@ -34,7 +35,7 @@ export async function lashingCurrents() { held: true, itemType: "weapon", }); - if (!relicWeapon) throw MHLError(`${PREFIX}.Error.NoneSelected`, null, {func}); + if (!relicWeapon) throw MHLError(`${PREFIX}.Error.NoneSelected`, null, { func }); rules.push({ key: "Striking", selector: "lashing-currents-damage", @@ -56,7 +57,7 @@ export async function lashingCurrents() { }); } await relicWeapon.update({ "system.rules": rules.concat(relicWeapon.system.rules) }); - } else { + } else { const oldRules = existingLC.system.rules.filter( (r) => !( @@ -66,5 +67,6 @@ export async function lashingCurrents() { ) ); await existingLC.update({ "system.rules": oldRules }); + localizedBanner(`${PREFIX}.Info.Removing`, {name: existingLC.name}); } -} \ No newline at end of file +} diff --git a/scripts/settings.mjs b/scripts/settings.mjs index 2d9508b..3ae9ce2 100644 --- a/scripts/settings.mjs +++ b/scripts/settings.mjs @@ -10,3 +10,4 @@ export function registerSettings() { type: Boolean, }); } +export const NOTIFY = () => game.settings.get(MODULE, 'notify-on-error'); \ No newline at end of file