Compare commits

..

No commits in common. "642d74c9f88f98355255b66e033ded4e33e912a2" and "0df05e0d0b5b6c00f50b9421ee8a56e4cdb91ecf" have entirely different histories.

4 changed files with 71 additions and 67 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.2.0", "version": "0.1.0",
"compatibility": { "compatibility": {
"minimum": "12", "minimum": "12",
"verified": "13", "verified": "12",
"maximum": "14" "maximum": "13"
}, },
"authors": [ "authors": [
{ "name": "Vassili" } { "name": "Vassili" }

View file

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

View file

@ -41,7 +41,7 @@ function scheduleSnapshotPush(actor) {
let debouncer = snapshotDebouncers.get(actor.id); let debouncer = snapshotDebouncers.get(actor.id);
if (!debouncer) { if (!debouncer) {
const debounceMs = game.settings.get(MODULE_ID, "snapshotDebounceSeconds") * 1000; const debounceMs = game.settings.get(MODULE_ID, "snapshotDebounceMs");
debouncer = foundry.utils.debounce((a) => { debouncer = foundry.utils.debounce((a) => {
pushSnapshot(a).catch((err) => { pushSnapshot(a).catch((err) => {
console.warn(`[${MODULE_ID}] snapshot push for ${a.name} failed:`, err); console.warn(`[${MODULE_ID}] snapshot push for ${a.name} failed:`, err);

View file

@ -14,11 +14,11 @@ import { MODULE_ID } from "./constants.js";
export function registerSettings() { export function registerSettings() {
game.settings.register(MODULE_ID, "frappeBaseUrl", { game.settings.register(MODULE_ID, "frappeBaseUrl", {
name: "Frappe Base URL", name: "Frappe Base URL",
hint: "Base URL of your Seitime Frappe site, e.g. https://seitimegames.com (no trailing slash).", hint: "Base URL of your Seitime Frappe site, e.g. https://seitime.vassi.li (no trailing slash).",
scope: "world", scope: "world",
config: true, config: true,
type: String, type: String,
default: "https://seitimegames.com", default: "",
}); });
game.settings.register(MODULE_ID, "sharedSecret", { game.settings.register(MODULE_ID, "sharedSecret", {
@ -50,14 +50,14 @@ export function registerSettings() {
range: { min: 0, max: 120, step: 1 }, range: { min: 0, max: 120, step: 1 },
}); });
game.settings.register(MODULE_ID, "snapshotDebounceSeconds", { game.settings.register(MODULE_ID, "snapshotDebounceMs", {
name: "Snapshot Debounce (seconds)", name: "Snapshot Debounce (ms)",
hint: "How long to wait after the last actor edit before pushing a snapshot. Combat causes frequent edits, so a few seconds is usually right.", hint: "How long to wait after the last actor edit before pushing a snapshot. Combat causes frequent edits, so a few seconds is usually right.",
scope: "world", scope: "world",
config: true, config: true,
type: Number, type: Number,
default: 5, default: 5000,
range: { min: 1, max: 60, step: 1 }, range: { min: 500, max: 60000, step: 500 },
}); });
game.settings.register(MODULE_ID, "snapshotAutoSync", { game.settings.register(MODULE_ID, "snapshotAutoSync", {