From f1eaea524a38f8083107435df5b109d6275824c1 Mon Sep 17 00:00:00 2001 From: Jeff Hitchcock Date: Mon, 22 Jul 2024 09:38:26 -0700 Subject: [PATCH] [#2138] Rebuild RollConfigurationDialog using Application V2 --- lang/en.json | 4 +- module/applications/_module.mjs | 1 + module/applications/api/_module.mjs | 1 + module/applications/api/application.mjs | 22 ++ .../dice/roll-configuration-dialog.mjs | 199 ++++++++++++------ templates/dice/roll-buttons.hbs | 5 + templates/dice/roll-configuration-dialog.hbs | 24 --- templates/dice/roll-configuration.hbs | 9 + templates/dice/roll-formulas.hbs | 14 ++ 9 files changed, 192 insertions(+), 87 deletions(-) create mode 100644 module/applications/api/_module.mjs create mode 100644 module/applications/api/application.mjs create mode 100644 templates/dice/roll-buttons.hbs delete mode 100644 templates/dice/roll-configuration-dialog.hbs create mode 100644 templates/dice/roll-configuration.hbs create mode 100644 templates/dice/roll-formulas.hbs diff --git a/lang/en.json b/lang/en.json index 4a4bf8d021..cd7ba51a1a 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1308,7 +1308,9 @@ "DND5E.RitualAbbr": "R", "DND5E.Roll": "Roll", "DND5E.RollConfiguration": { - "Title": "Configure Roll" + "Title": "Configure Roll", + "Configuration": "Configuration", + "Rolls": "Rolls" }, "DND5E.RollExample": "e.g. 1d4", "DND5E.RollMode": "Roll Mode", diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index a439627786..199f3f5d39 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -1,5 +1,6 @@ export * as actor from "./actor/_module.mjs"; export * as advancement from "./advancement/_module.mjs"; +export * as api from "./api/_module.mjs"; export * as combat from "./combat/_module.mjs"; export * as components from "./components/_module.mjs"; export * as dice from "./dice/_module.mjs"; diff --git a/module/applications/api/_module.mjs b/module/applications/api/_module.mjs new file mode 100644 index 0000000000..5796353280 --- /dev/null +++ b/module/applications/api/_module.mjs @@ -0,0 +1 @@ +export { default as Application5e } from "./application.mjs"; diff --git a/module/applications/api/application.mjs b/module/applications/api/application.mjs new file mode 100644 index 0000000000..5e6bc9bd0c --- /dev/null +++ b/module/applications/api/application.mjs @@ -0,0 +1,22 @@ +const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; + +/** + * Base application from which all system applications should be based. + */ +export default class Application5e extends HandlebarsApplicationMixin(ApplicationV2) { + /** @override */ + static DEFAULT_OPTIONS = { + classes: ["dnd5e"] + }; + + /* -------------------------------------------- */ + /* Rendering */ + /* -------------------------------------------- */ + + /** @inheritDoc */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + context.CONFIG = CONFIG.DND5E; + return context; + } +} diff --git a/module/applications/dice/roll-configuration-dialog.mjs b/module/applications/dice/roll-configuration-dialog.mjs index da17bc5beb..b78a9c77f5 100644 --- a/module/applications/dice/roll-configuration-dialog.mjs +++ b/module/applications/dice/roll-configuration-dialog.mjs @@ -1,3 +1,5 @@ +import Application5e from "../api/application.mjs"; + /** * Dialog rendering options for a roll configuration dialog. * @@ -16,9 +18,9 @@ * @param {BasicRollMessageConfiguration} [message={}] Message configuration. * @param {BasicRollConfigurationDialogOptions} [options={}] Dialog rendering options. */ -export default class RollConfigurationDialog extends FormApplication { +export default class RollConfigurationDialog extends Application5e { constructor(config={}, message={}, options={}) { - super(null, options); + super(options); /** * Roll configuration. @@ -27,23 +29,51 @@ export default class RollConfigurationDialog extends FormApplication { Object.defineProperty(this, "config", { value: config, writable: false }); this.message = message; - this.object = this._buildRolls(foundry.utils.deepClone(this.config)); + this.rolls = this._buildRolls(foundry.utils.deepClone(this.config)); } /* -------------------------------------------- */ - /** @inheritDoc */ - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - template: "systems/dnd5e/templates/dice/roll-configuration-dialog.hbs", + /** @override */ + static DEFAULT_OPTIONS = { + classes: ["roll-configuration"], + tag: "form", + window: { title: "DND5E.RollConfiguration.Title", - classes: ["dnd5e", "dialog", "roll"], - width: 400, - submitOnChange: true, - closeOnSubmit: false, - jQuery: false, - rollType: CONFIG.Dice.BasicRoll - }); + icon: "fa-solid fa-dice", + minimizable: false + }, + form: { + handler: RollConfigurationDialog.#handleFormSubmission + }, + position: { + width: 400 + } + }; + + /* -------------------------------------------- */ + + /** @override */ + static PARTS = { + formulas: { + template: "systems/dnd5e/templates/dice/roll-formulas.hbs" + }, + configuration: { + template: "systems/dnd5e/templates/dice/roll-configuration.hbs" + }, + buttons: { + template: "systems/dnd5e/templates/dice/roll-buttons.hbs" + } + }; + + /* -------------------------------------------- */ + + /** + * Roll type to use when constructing the rolls. + * @type {BasicRoll} + */ + static get rollType() { + return CONFIG.Dice.BasicRoll; } /* -------------------------------------------- */ @@ -62,41 +92,81 @@ export default class RollConfigurationDialog extends FormApplication { * The rolls being configured. * @type {BasicRoll[]} */ - get rolls() { - return this.object; - } + rolls; - set rolls(rolls) { - this.object = rolls; + /* -------------------------------------------- */ + + /** + * Roll type to use when constructing the rolls. + * @type {BasicRoll} + */ + get rollType() { + return this.options.rollType ?? this.constructor.rollType; } /* -------------------------------------------- */ /* Rendering */ /* -------------------------------------------- */ + /** @inheritDoc */ + async _preparePartContext(partId, context, options) { + context = await super._preparePartContext(partId, context, options); + switch (partId) { + case "buttons": + return this._prepareButtonsContext(context, options); + case "configuration": + return this._prepareConfigurationContext(context, options); + case "formulas": + return this._prepareFormulasContext(context, options); + default: + return context; + } + } + + /* -------------------------------------------- */ + /** - * Buttons displayed in the configuration dialog. - * @returns {object} - * @protected + * Prepare the context for the buttons. + * @param {ApplicationRenderContext} context Shared context provided by _prepareContext. + * @param {HandlebarsRenderOptions} options Options which configure application rendering behavior. + * @returns {ApplicationRenderContext} */ - _getButtons() { - return { - roll: { label: game.i18n.localize("DND5E.Roll") } + _prepareButtonsContext(context, options) { + context.buttons = { + roll: { + icon: '', + label: game.i18n.localize("DND5E.Roll") + } }; + return context; } /* -------------------------------------------- */ - /** @inheritDoc */ - getData(options={}) { - return foundry.utils.mergeObject({ - CONFIG: CONFIG.DND5E, - rolls: this.rolls, - rollMode: this.message.rollMode ?? this.options.default?.rollMode, - rollModes: CONFIG.Dice.rollModes, - situational: this.rolls[0].data.situational, - buttons: this._getButtons() - }, super.getData(options)); + /** + * Prepare the context for the roll configuration section. + * @param {ApplicationRenderContext} context Shared context provided by _prepareContext. + * @param {HandlebarsRenderOptions} options Options which configure application rendering behavior. + * @returns {ApplicationRenderContext} + */ + _prepareConfigurationContext(context, options) { + context.rollMode = this.message.rollMode ?? this.options.default?.rollMode; + context.rollModesOptions = CONFIG.Dice.rollModes; + return context; + } + + /* -------------------------------------------- */ + + /** + * Prepare the context for the formulas list. + * @param {ApplicationRenderContext} context Shared context provided by _prepareContext. + * @param {HandlebarsRenderOptions} options Options which configure application rendering behavior. + * @returns {ApplicationRenderContext} + */ + _prepareFormulasContext(context, options) { + context.rolls = this.rolls; + context.situational = this.rolls[0].data.situational; + return context; } /* -------------------------------------------- */ @@ -106,12 +176,12 @@ export default class RollConfigurationDialog extends FormApplication { /** * Build a roll from the provided configuration objects. * @param {BasicRollProcessConfiguration} config Roll configuration data. - * @param {object} [formData={}] Any data entered into the rolling prompt. + * @param {FormDataExtended} [formData] Any data entered into the rolling prompt. * @returns {BasicRoll[]} * @internal */ - _buildRolls(config, formData={}) { - const RollType = this.options.rollType ?? CONFIG.Dice.BasicRoll; + _buildRolls(config, formData) { + const RollType = this.rollType; return config.rolls?.map((config, index) => RollType.create(this._buildConfig(config, formData, index))) ?? []; } @@ -120,7 +190,7 @@ export default class RollConfigurationDialog extends FormApplication { /** * Prepare individual configuration object before building a roll. * @param {BasicRollConfiguration} config Roll configuration data. - * @param {object} formData Any data entered into the rolling prompt. + * @param {FormDataExtended} [formData] Any data entered into the rolling prompt. * @param {number} index Index of the roll within all rolls being prepared. * @returns {BasicRollConfiguration} * @protected @@ -134,14 +204,14 @@ export default class RollConfigurationDialog extends FormApplication { * @memberof hookEvents * @param {RollConfigurationDialog} app Roll configuration dialog. * @param {BasicRollConfiguration} config Roll configuration data. - * @param {object} formData Any data entered into the rolling prompt. + * @param {FormDataExtended} [formData] Any data entered into the rolling prompt. * @param {number} index Index of the roll within all rolls being prepared. */ Hooks.callAll("dnd5e.buildRollConfig", this, config, formData, index); - if ( formData.situational && (config.situational !== false) ) { + if ( formData?.get("situational") && (config.situational !== false) ) { config.parts.push("@situational"); - config.data.situational = formData.situational; + config.data.situational = formData.get("situational"); } return config; @@ -164,37 +234,42 @@ export default class RollConfigurationDialog extends FormApplication { * Rebuild rolls based on an updated config and re-render the dialog. */ rebuild() { - this._onSubmit(new Event("change")); + this._onChangeForm(this.options.form, new Event("change")); } /* -------------------------------------------- */ /* Event Handling */ /* -------------------------------------------- */ - /** @inheritDoc */ - async close(options={}) { - this.options.resolve?.([]); - return super.close(options); + /** + * Handle submission of the dialog using the form buttons. + * @param {Event|SubmitEvent} event The form submission event. + * @param {HTMLFormElement} form The submitted form. + * @param {FormDataExtended} formData Data from the dialog. + * @private + */ + static async #handleFormSubmission(event, form, formData) { + const rolls = this._finalizeRolls(event.submitter?.dataset?.action); + await this.close({ dnd5e: { rolls } }); } - /* -------------------------------------------- */ + /* <><><><> <><><><> <><><><> <><><><> */ /** @inheritDoc */ - _updateObject(event, formData) { - if ( formData.rollMode ) this.message.rollMode = formData.rollMode; - - // If one of the buttons was clicked, finalize the roll, resolve the promise, and close - if ( event.type === "submit" ) { - const rolls = this._finalizeRolls(event.submitter?.dataset.action); - this.options.resolve?.(rolls); - this.close({ submit: false, force: true }); - } + _onChangeForm(formConfig, event) { + super._onChangeForm(formConfig, event); - // Otherwise, re-build the in-memory rolls and re-render the dialog - else { - this.rolls = this._buildRolls(foundry.utils.deepClone(this.config), formData); - this.render(); - } + const formData = new FormDataExtended(this.element); + if (formData.has("rollMode")) this.message.rollMode = formData.get("rollMode"); + this.rolls = this._buildRolls(foundry.utils.deepClone(this.config), formData); + this.render({ parts: ["formulas"] }); + } + + /* <><><><> <><><><> <><><><> <><><><> */ + + /** @override */ + _onClose(options = {}) { + this.options.resolve?.(options[game.system.id]?.rolls ?? []); } /* -------------------------------------------- */ diff --git a/templates/dice/roll-buttons.hbs b/templates/dice/roll-buttons.hbs new file mode 100644 index 0000000000..fccac592fa --- /dev/null +++ b/templates/dice/roll-buttons.hbs @@ -0,0 +1,5 @@ + diff --git a/templates/dice/roll-configuration-dialog.hbs b/templates/dice/roll-configuration-dialog.hbs deleted file mode 100644 index 039a9423bb..0000000000 --- a/templates/dice/roll-configuration-dialog.hbs +++ /dev/null @@ -1,24 +0,0 @@ -
- {{#each rolls}} -
- - -
- {{/each}} -
- - -
-
- - -
- - -
diff --git a/templates/dice/roll-configuration.hbs b/templates/dice/roll-configuration.hbs new file mode 100644 index 0000000000..40069e0b22 --- /dev/null +++ b/templates/dice/roll-configuration.hbs @@ -0,0 +1,9 @@ +
+ {{ localize "DND5E.RollConfiguration.Configuration" }} +
+ + +
+
diff --git a/templates/dice/roll-formulas.hbs b/templates/dice/roll-formulas.hbs new file mode 100644 index 0000000000..f2f3891611 --- /dev/null +++ b/templates/dice/roll-formulas.hbs @@ -0,0 +1,14 @@ +
+ {{ localize "DND5E.RollConfiguration.Rolls" }} + {{#each rolls}} +
+ + +
+ {{/each}} +
+ + +
+