Port End Session dialogs to DialogV2 for v13/v14

V1 Dialog and Dialog.confirm are deprecated in v13 and removed in v14.
Migrate the three sites in macros.js (attendance grid, single-session
confirm, multi-session pick) to foundry.applications.api.DialogV2, and
drop the jQuery used in their callbacks in favor of querySelector +
dataset/value on dialog.element. Use rejectClose: false to preserve
the prior null-on-close behavior.

Bump version to 0.2.0 and widen compatibility to verified 13, max 14
(min stays at 12 — DialogV2 has been available since v12).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Vassili Minaev 2026-05-12 18:21:33 -06:00
parent 0df05e0d0b
commit 43c630c1f7
2 changed files with 60 additions and 64 deletions

View file

@ -2,11 +2,11 @@
"id": "seitime-bridge", "id": "seitime-bridge",
"title": "Seitime Bridge", "title": "Seitime Bridge",
"description": "Pushes dnd5e character data from Foundry VTT to a Seitime Frappe site at session end. Companion to the st Frappe app.", "description": "Pushes dnd5e character data from Foundry VTT to a Seitime Frappe site at session end. Companion to the st Frappe app.",
"version": "0.1.0", "version": "0.2.0",
"compatibility": { "compatibility": {
"minimum": "12", "minimum": "12",
"verified": "12", "verified": "13",
"maximum": "13" "maximum": "14"
}, },
"authors": [ "authors": [
{ "name": "Vassili" } { "name": "Vassili" }

View file

@ -101,45 +101,44 @@ async function showAttendanceDialog(proposal) {
}) })
.join(""); .join("");
return new Promise((resolve) => { const result = await foundry.applications.api.DialogV2.wait({
new Dialog( window: { title: "Finalize Attendance" },
content: `
<p>Confirm each player's attendance for this session.</p>
<table style="width:100%; border-collapse:collapse;">
<thead>
<tr style="border-bottom:1px solid #ccc;">
<th style="text-align:left; padding:0.25em;">Player</th>
<th style="text-align:left; padding:0.25em;">Character(s)</th>
<th style="text-align:left; padding:0.25em; width:9em;">Status</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
`,
buttons: [
{ action: "cancel", label: "Cancel", callback: () => null },
{ {
title: "Finalize Attendance", action: "confirm",
content: ` label: "Confirm",
<p>Confirm each player's attendance for this session.</p> default: true,
<table style="width:100%; border-collapse:collapse;"> callback: (_event, _button, dialog) => {
<thead> const out = [];
<tr style="border-bottom:1px solid #ccc;"> dialog.element.querySelectorAll("select[data-player]").forEach((select) => {
<th style="text-align:left; padding:0.25em;">Player</th> out.push({
<th style="text-align:left; padding:0.25em;">Character(s)</th> player: select.dataset.player,
<th style="text-align:left; padding:0.25em; width:9em;">Status</th> status: select.value,
</tr> });
</thead> });
<tbody>${rows}</tbody> return out;
</table>
`,
buttons: {
cancel: { label: "Cancel", callback: () => resolve(null) },
confirm: {
label: "Confirm",
callback: (html) => {
const result = [];
html.find("select[data-player]").each(function () {
result.push({
player: $(this).data("player"),
status: $(this).val(),
});
});
resolve(result);
},
},
}, },
default: "confirm",
close: () => resolve(null),
}, },
{ width: 560 }, ],
).render(true); position: { width: 560 },
rejectClose: false,
}); });
return result ?? null;
} }
/** /**
@ -167,15 +166,14 @@ export async function pushAllSnapshots() {
async function pickSession(sessions) { async function pickSession(sessions) {
if (sessions.length === 1) { if (sessions.length === 1) {
const s = sessions[0]; const s = sessions[0];
const confirmed = await Dialog.confirm({ const confirmed = await foundry.applications.api.DialogV2.confirm({
title: "End Session", window: { title: "End Session" },
content: ` content: `
<p>End <b>${escapeHtml(s.session_title)}</b>?</p> <p>End <b>${escapeHtml(s.session_title)}</b>?</p>
<p>This will mark it Completed, schedule the next session, and push snapshots for all PCs.</p> <p>This will mark it Completed, schedule the next session, and push snapshots for all PCs.</p>
`, `,
yes: () => true, yes: { default: true },
no: () => false, rejectClose: false,
defaultYes: true,
}); });
return confirmed ? s.session_id : null; return confirmed ? s.session_id : null;
} }
@ -187,29 +185,27 @@ async function pickSession(sessions) {
) )
.join(""); .join("");
return new Promise((resolve) => { const value = await foundry.applications.api.DialogV2.wait({
new Dialog({ window: { title: "End Session" },
title: "End Session", content: `
content: ` <p>Multiple scheduled sessions match this Foundry world. Pick one to end:</p>
<p>Multiple scheduled sessions match this Foundry world. Pick one to end:</p> <select id="seitime-session-pick" style="width:100%; margin-bottom:0.5em;">
<select id="seitime-session-pick" style="width:100%; margin-bottom:0.5em;"> ${options}
${options} </select>
</select> `,
`, buttons: [
buttons: { { action: "cancel", label: "Cancel", callback: () => null },
cancel: { label: "Cancel", callback: () => resolve(null) }, {
end: { action: "end",
label: "End Session", label: "End Session",
callback: (html) => { default: true,
const value = html.find("#seitime-session-pick").val(); callback: (_event, _button, dialog) =>
resolve(value || null); dialog.element.querySelector("#seitime-session-pick")?.value || null,
},
},
}, },
default: "end", ],
close: () => resolve(null), rejectClose: false,
}).render(true);
}); });
return value ?? null;
} }
function summarizeAttendance(attendance) { function summarizeAttendance(attendance) {