refactor: port DS4 dialogs to ApplicationV2

Convert DialogWithListeners and check-factory.js from deprecated Dialog
V1 to DialogV2. Replace jQuery with native DOM methods and update button
format. Fixes all remaining V1 Application framework deprecation
warnings.
This commit is contained in:
Alexander Minges 2025-07-12 21:05:29 +02:00
parent bcc263cc5e
commit 1b7b9f1e8e
Signed by: Athemis
GPG key ID: 31FBDEF92DDB162B
3 changed files with 165 additions and 58 deletions

View file

@ -311,8 +311,8 @@ export class DS4ActorSheet extends foundry.applications.api.DocumentSheetV2 {
const item = this.document.items.get(itemId);
if (!item) return;
const confirmed = await Dialog.confirm({
title: game.i18n.localize("DS4.DialogDeleteItemTitle"),
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { title: game.i18n.localize("DS4.DialogDeleteItemTitle") },
content: game.i18n.format("DS4.DialogDeleteItemContent", { item: item.name }),
defaultYes: false,
});
@ -414,8 +414,8 @@ export class DS4ActorSheet extends foundry.applications.api.DocumentSheetV2 {
const effect = this.document.effects.get(effectId);
if (!effect) return;
const confirmed = await Dialog.confirm({
title: game.i18n.localize("DS4.DialogDeleteEffectTitle"),
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { title: game.i18n.localize("DS4.DialogDeleteEffectTitle") },
content: game.i18n.format("DS4.DialogDeleteEffectContent", { effect: effect.name }),
defaultYes: false,
});

View file

@ -3,19 +3,129 @@
// SPDX-License-Identifier: MIT
/**
* @typedef {DialogOptions} DialogWithListenersOptions
* @property {(html: JQuery, app: DialogWithListeners) => void} [activateAdditionalListeners] An optional function to attach additional listeners to the dialog
* A simple extension to the DialogV2 class that allows attaching additional listeners.
*/
export class DialogWithListeners extends foundry.applications.api.DialogV2 {
constructor(options = {}) {
super(options);
this.activateAdditionalListeners = options.activateAdditionalListeners;
}
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: ["dialog", "dialog-with-listeners"],
tag: "dialog",
window: {
resizable: true,
},
};
/**
* A simple extension to the {@link Dialog} class that allows attaching additional listeners.
*/
export class DialogWithListeners extends Dialog {
/** @override */
activateListeners(html) {
super.activateListeners(html);
if (this.options.activateAdditionalListeners !== undefined) {
this.options.activateAdditionalListeners(html, this);
async _onRender(context, options) {
await super._onRender(context, options);
// Attach additional listeners if provided
if (this.activateAdditionalListeners && typeof this.activateAdditionalListeners === 'function') {
this.activateAdditionalListeners(this.element, this);
}
}
/**
* Create a confirmation dialog using the V2 framework with additional listeners support
* @param {object} options - Dialog options
* @param {string} options.title - Dialog title (deprecated, use window.title)
* @param {object} options.window - Window configuration
* @param {string} options.window.title - Dialog title
* @param {string} options.content - Dialog content HTML
* @param {boolean} options.defaultYes - Whether "Yes" is the default button
* @param {Function} options.activateAdditionalListeners - Function to attach additional listeners
* @returns {Promise<boolean>} True if confirmed, false if cancelled
*/
static async confirm(options = {}) {
const { title, window = {}, content, defaultYes = true, activateAdditionalListeners, ...rest } = options;
// Handle backward compatibility with title parameter
if (title && !window.title) {
window.title = title;
}
return new Promise((resolve) => {
const dialog = new DialogWithListeners({
window: {
title: window.title || "Confirm",
...window,
},
content,
activateAdditionalListeners,
buttons: [
{
action: "yes",
label: game.i18n.localize("Yes"),
icon: "fas fa-check",
default: defaultYes,
callback: () => resolve(true),
},
{
action: "no",
label: game.i18n.localize("No"),
icon: "fas fa-times",
default: !defaultYes,
callback: () => resolve(false),
},
],
close: () => resolve(false),
...rest,
});
dialog.render(true);
});
}
/**
* Create a prompt dialog using the V2 framework with additional listeners support
* @param {object} options - Dialog options
* @param {string} options.title - Dialog title (deprecated, use window.title)
* @param {object} options.window - Window configuration
* @param {string} options.window.title - Dialog title
* @param {string} options.content - Dialog content HTML
* @param {object} options.buttons - Button configuration
* @param {Function} options.activateAdditionalListeners - Function to attach additional listeners
* @returns {Promise} Promise that resolves with the result
*/
static async prompt(options = {}) {
const { title, window = {}, content, buttons = {}, activateAdditionalListeners, ...rest } = options;
// Handle backward compatibility with title parameter
if (title && !window.title) {
window.title = title;
}
return new Promise((resolve) => {
// Convert V1 button format to V2 format
const v2Buttons = Object.entries(buttons).map(([key, button]) => ({
action: key,
label: button.label || key,
icon: button.icon || "",
default: button.default || false,
callback: (event) => {
const result = button.callback ? button.callback(event) : key;
resolve(result);
},
}));
const dialog = new DialogWithListeners({
window: {
title: window.title || "Dialog",
...window,
},
content,
activateAdditionalListeners,
buttons: v2Buttons,
close: () => resolve(null),
...rest,
});
dialog.render(true);
});
}
}

View file

@ -169,58 +169,55 @@ async function askForInteractiveRollData(checkTargetNumber, options = {}, { temp
const renderedHtml = await foundry.applications.handlebars.renderTemplate(usedTemplate, templateData);
const dialogPromise = new Promise((resolve) => {
new DialogWithListeners(
{
new DialogWithListeners({
window: {
title: usedTitle,
content: renderedHtml,
buttons: {
ok: {
icon: '<i class="fas fa-check"></i>',
label: getGame().i18n.localize("DS4.GenericOkButton"),
callback: (html) => {
if (!("jquery" in html)) {
throw new Error(
getGame().i18n.format("DS4.ErrorUnexpectedHtmlType", {
exType: "JQuery",
realType: "HTMLElement",
}),
);
} else {
const innerForm = html[0]?.querySelector("form");
if (!innerForm) {
throw new Error(
getGame().i18n.format("DS4.ErrorCouldNotFindHtmlElement", {
htmlElement: "form",
}),
);
}
resolve(innerForm);
}
},
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: getGame().i18n.localize("DS4.GenericCancelButton"),
},
content: renderedHtml,
buttons: [
{
action: "ok",
icon: "fas fa-check",
label: getGame().i18n.localize("DS4.GenericOkButton"),
default: true,
callback: (event) => {
const dialog = event.target.closest("dialog");
const innerForm = dialog?.querySelector("form");
if (!innerForm) {
throw new Error(
getGame().i18n.format("DS4.ErrorCouldNotFindHtmlElement", {
htmlElement: "form",
}),
);
}
resolve(innerForm);
},
},
default: "ok",
},
{
activateAdditionalListeners: (html, app) => {
const checkModifierCustomFormGroup = html.find(`#check-modifier-custom-${id}`).parent(".form-group");
html.find(`#check-modifier-${id}`).on("change", (event) => {
if (event.currentTarget.value === "custom" && checkModifierCustomFormGroup.hasClass("ds4-hidden")) {
checkModifierCustomFormGroup.removeClass("ds4-hidden");
{
action: "cancel",
icon: "fas fa-times",
label: getGame().i18n.localize("DS4.GenericCancelButton"),
callback: () => resolve(null),
},
],
close: () => resolve(null),
activateAdditionalListeners: (html, app) => {
const checkModifierCustomFormGroup = html.querySelector(`#check-modifier-custom-${id}`)?.closest(".form-group");
const checkModifierSelect = html.querySelector(`#check-modifier-${id}`);
if (checkModifierSelect) {
checkModifierSelect.addEventListener("change", (event) => {
if (event.currentTarget.value === "custom" && checkModifierCustomFormGroup?.classList.contains("ds4-hidden")) {
checkModifierCustomFormGroup.classList.remove("ds4-hidden");
app.setPosition({ height: "auto" });
} else if (!checkModifierCustomFormGroup.hasClass("ds4-hidden")) {
checkModifierCustomFormGroup.addClass("ds4-hidden");
} else if (checkModifierCustomFormGroup && !checkModifierCustomFormGroup.classList.contains("ds4-hidden")) {
checkModifierCustomFormGroup.classList.add("ds4-hidden");
app.setPosition({ height: "auto" });
}
});
},
id,
}
},
).render(true);
}).render(true);
});
const dialogForm = await dialogPromise;
return parseDialogFormData(dialogForm);