From c014e17da21e40ac277fd11a0ea0a100d9a95841 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 20 Nov 2025 22:43:14 -0700 Subject: [PATCH] Implement the request cancellation --- module/sockets/query/cancel.mjs | 18 ++++++++++++ module/utils/QueryManager.mjs | 50 +++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 module/sockets/query/cancel.mjs diff --git a/module/sockets/query/cancel.mjs b/module/sockets/query/cancel.mjs new file mode 100644 index 0000000..654b21d --- /dev/null +++ b/module/sockets/query/cancel.mjs @@ -0,0 +1,18 @@ +import { DialogManager } from "../../utils/DialogManager.mjs"; + +export async function queryCancel(payload) { + const { id } = payload; + + if (!id) { + ui.notifications.error(game.i18n.format( + `taf.notifs.error.malformed-socket-payload`, + { + event: `query.cancel`, + details: `A request ID must be provided`, + }), + ); + return; + }; + + await DialogManager.close(id); +}; diff --git a/module/utils/QueryManager.mjs b/module/utils/QueryManager.mjs index d6ce677..09839a0 100644 --- a/module/utils/QueryManager.mjs +++ b/module/utils/QueryManager.mjs @@ -1,7 +1,14 @@ +import { filePath } from "../consts.mjs"; +import { Logger } from "./Logger.mjs"; +import { QueryStatus } from "../apps/QueryStatus.mjs"; + /** - * An object containing information about the current status for all users involved - * with the data request. - * @typedef {Record} UserStatus + * An object containing information about the current status for all + * users involved with the data request. + * @typedef {Record< + * string, + * "finished" | "waiting" | "cancelled" | "disconnected" | "unprompted" + * >} UserStatus */ /** @@ -16,9 +23,13 @@ * @property {object} config The data used to create the initial config */ -import { filePath } from "../consts.mjs"; -import { Logger } from "./Logger.mjs"; -import { QueryStatus } from "../apps/QueryStatus.mjs"; +/** + * This internal API is used in order to prevent the query.notify event + * from being fired off in situations where the user hasn't responded, + * wasn't part of the query, or has already been notified. + * @type {Set} + */ +export const respondedToQueries = new Set(); /** @type {Map} */ const queries = new Map(); @@ -26,13 +37,13 @@ const queries = new Map(); /** @type {Map} */ const promises = new Map(); -async function sendBasicNotification(userID, answers) { +async function sendBasicNotification(requestID, userID, answers) { const content = await foundry.applications.handlebars.renderTemplate( filePath(`templates/query-response.hbs`), { answers }, ); - await notify(userID, content, { includeGM: false }); + await notify(requestID, userID, content, { includeGM: false }); }; export function has(requestID) { @@ -138,10 +149,19 @@ export async function requery(requestID, users) { export async function addResponse(requestID, userID, answers) { const data = queries.get(requestID); - data.responses[userID] = answers; - data.status[userID] = `finished`; - await data.onSubmit?.(userID, answers); + // User closed the popup manually + if (answers == null) { + data.status[userID] = `unprompted`; + } + + // User submitted the answers as expected + else { + data.responses[userID] = answers; + data.status[userID] = `finished`; + await data.onSubmit?.(requestID, userID, answers); + }; + await maybeResolve(requestID); }; @@ -180,10 +200,14 @@ async function maybeResolve(requestID) { }; }; -export async function notify(userID, content, { includeGM = false } = {}) { +export async function notify(requestID, userID, content, { includeGM = false } = {}) { + // Prevent sending notifications for not-your queries + if (!queries.has(requestID)) { return }; + game.socket.emit(`system.taf`, { event: `query.notify`, payload: { + id: requestID, userID, content, includeGM, @@ -196,6 +220,7 @@ export async function finish(requestID) { if (!queries.has(requestID)) { return }; const query = queries.get(requestID); + query.app?.close(); query.resolve(query.responses); queries.delete(requestID); promises.delete(requestID); @@ -211,6 +236,7 @@ export async function cancel(requestID) { if (!queries.has(requestID)) { return }; const query = queries.get(requestID); + query.app?.close(); query.resolve(null); queries.delete(requestID); promises.delete(requestID);