/** * GM-facing orchestration: the End Session flow and the bulk * snapshot push it depends on. Imported by main.js and exposed on * `globalThis.seitimeBridge` for use from Foundry macros. * * Recommended macro body (Script type, no arguments): * seitimeBridge.endSession(); */ import { MODULE_ID } from "./constants.js"; import { completeSession, listScheduledSessions, pushSnapshot, } from "./api.js"; function escapeHtml(s) { return String(s ?? "").replace( /[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c], ); } /** * Push snapshots for every PC in the world, in parallel. Returns a summary * with successful/failed counts and the names of any failures so the caller * can surface them. */ export async function pushAllSnapshots() { const pcs = game.actors.filter((a) => a.type === "character"); const results = await Promise.allSettled(pcs.map((a) => pushSnapshot(a))); const failed = results .map((r, i) => (r.status === "rejected" ? { name: pcs[i].name, error: r.reason?.message ?? String(r.reason) } : null)) .filter(Boolean); return { total: pcs.length, successful: pcs.length - failed.length, failed, }; } /** * Show the appropriate dialog for the number of candidates and resolve to * the chosen session_id, or null if the user cancels. */ async function pickSession(sessions) { if (sessions.length === 1) { const s = sessions[0]; const confirmed = await Dialog.confirm({ title: "End Session", content: `
End ${escapeHtml(s.session_title)}?
This will mark it Completed, schedule the next session, and push snapshots for all PCs.
`, yes: () => true, no: () => false, defaultYes: true, }); return confirmed ? s.session_id : null; } const options = sessions .map( (s) => ``, ) .join(""); return new Promise((resolve) => { new Dialog({ title: "End Session", content: `Multiple scheduled sessions match this Foundry world. Pick one to end:
`, buttons: { cancel: { label: "Cancel", callback: () => resolve(null) }, end: { label: "End Session", callback: (html) => { const value = html.find("#seitime-session-pick").val(); resolve(value || null); }, }, }, default: "end", close: () => resolve(null), }).render(true); }); } function buildSummaryHtml(completion, snapshotResult) { const parts = [ `${escapeHtml(completion.completed_session)} marked Completed.
`, ]; if (completion.next_session) { parts.push( `Next session scheduled: ${escapeHtml(completion.next_session)} (#${escapeHtml(completion.next_session_number)}).
`, ); } parts.push( `Snapshots pushed: ${snapshotResult.successful} of ${snapshotResult.total}.
`, ); if (snapshotResult.failed.length) { const items = snapshotResult.failed .map((f) => `Failed pushes: