From df0c69c73123ab934599d907d734393cf39c8380 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 19 Nov 2025 21:18:42 -0700 Subject: [PATCH] Update the way the QueryManager exports are structured to be more esm-y rather than Java-y --- module/apps/QueryStatus.mjs | 6 +- module/hooks/userConnected.mjs | 4 +- module/sockets/submitRequest.mjs | 6 +- module/utils/QueryManager.mjs | 348 ++++++++++++++++--------------- 4 files changed, 187 insertions(+), 177 deletions(-) diff --git a/module/apps/QueryStatus.mjs b/module/apps/QueryStatus.mjs index cc75d95..3715f60 100644 --- a/module/apps/QueryStatus.mjs +++ b/module/apps/QueryStatus.mjs @@ -1,6 +1,6 @@ import { __ID__, filePath } from "../consts.mjs"; +import { get as getQuery, requery } from "../utils/QueryManager.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { QueryManager } from "../utils/QueryManager.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -62,7 +62,7 @@ export class QueryStatus extends HandlebarsApplicationMixin(ApplicationV2) { }; async _prepareUsers(ctx) { - const query = QueryManager.get(this.requestID); + const query = getQuery(this.requestID); if (!query) { return }; const users = []; @@ -85,7 +85,7 @@ export class QueryStatus extends HandlebarsApplicationMixin(ApplicationV2) { static async promptUser($e, element) { const userID = element.closest(`[data-user-id]`)?.dataset.userId; if (!userID) { return }; - QueryManager.requery(this.requestID, [ userID ]); + requery(this.requestID, [ userID ]); }; /** @this {QueryStatus} */ diff --git a/module/hooks/userConnected.mjs b/module/hooks/userConnected.mjs index ed23837..ed50f34 100644 --- a/module/hooks/userConnected.mjs +++ b/module/hooks/userConnected.mjs @@ -1,6 +1,6 @@ -import { QueryManager } from "../utils/QueryManager.mjs"; +import { userActivity } from "../utils/QueryManager.mjs"; Hooks.on(`userConnected`, (user, connected) => { if (user.isSelf) { return }; - QueryManager.userActivity(user.id, connected); + userActivity(user.id, connected); }); diff --git a/module/sockets/submitRequest.mjs b/module/sockets/submitRequest.mjs index 91dfbc3..1d4ef0c 100644 --- a/module/sockets/submitRequest.mjs +++ b/module/sockets/submitRequest.mjs @@ -1,4 +1,4 @@ -import { QueryManager } from "../utils/QueryManager.mjs"; +import { addResponse, has as hasQuery } from "../utils/QueryManager.mjs"; export function submitRequest(payload, user) { const { @@ -17,6 +17,6 @@ export function submitRequest(payload, user) { return; }; - if (!QueryManager.has(id)) { return }; - QueryManager.addResponse(id, user.id, answers); + if (!hasQuery(id)) { return }; + addResponse(id, user.id, answers); }; diff --git a/module/utils/QueryManager.mjs b/module/utils/QueryManager.mjs index 9a4f892..4d3fb94 100644 --- a/module/utils/QueryManager.mjs +++ b/module/utils/QueryManager.mjs @@ -20,211 +20,221 @@ import { filePath } from "../consts.mjs"; import { Logger } from "./Logger.mjs"; import { QueryStatus } from "../apps/QueryStatus.mjs"; +/** @type {Map} */ +const queries = new Map(); + +/** @type {Map} */ +const promises = new Map(); + async function sendBasicNotification(userID, answers) { const content = await foundry.applications.handlebars.renderTemplate( filePath(`templates/query-response.hbs`), { answers }, ); - QueryManager.notify(userID, content, { includeGM: false }); + await notify(userID, content, { includeGM: false }); }; -export class QueryManager { - /** @type {Map} */ - static #queries = new Map(); - static #promises = new Map(); +export function has(requestID) { + return queries.has(requestID); +}; - static has(requestID) { - return this.#queries.has(requestID); +/** @returns {Omit} */ +export function get(requestID) { + if (!queries.has(requestID)) { return null }; + const query = queries.get(requestID); + const cloned = foundry.utils.deepClone(query); + + delete cloned.onSubmit; + delete cloned.resolve; + delete cloned.app; + + return foundry.utils.deepFreeze(cloned); +}; + +export async function query( + request, + { + onSubmit = sendBasicNotification, + users = null, + showStatusApp = true, + ...config + } = {}, +) { + if (!request.id) { + ui.notifications.error(game.i18n.localize(`taf.notifs.error.missing-id`)); + return; }; - /** @returns {Omit} */ - static get(requestID) { - if (!this.#queries.has(requestID)) { return null }; - const query = this.#queries.get(requestID); - const cloned = foundry.utils.deepClone(query); + game.socket.emit(`system.taf`, { + event: `query.prompt`, + payload: { + id: request.id, + users, + request, + config, + }, + }); - delete cloned.onSubmit; - delete cloned.resolve; - delete cloned.app; - - return foundry.utils.deepFreeze(cloned); + if (promises.has(request.id)) { + return null; }; - static async query( - request, - { - onSubmit = sendBasicNotification, - users = null, - showStatusApp = true, - ...config - } = {}, - ) { - if (!request.id) { - ui.notifications.error(game.i18n.localize(`taf.notifs.error.missing-id`)); - return; + users ??= game.users + .filter(u => u.id !== game.user.id) + .map(u => u.id); + + const promise = new Promise((resolve) => { + + /** @type {UserStatus} */ + const status = {}; + for (const user of users) { + status[user] = game.users.get(user).active ? `waiting` : `unprompted`; }; - game.socket.emit(`system.taf`, { - event: `query.prompt`, - payload: { - id: request.id, + queries.set( + request.id, + { users, request, config, + responses: {}, + resolve, + onSubmit, + app: null, + status, }, - }); + ); + }); - if (this.#promises.has(request.id)) { - return null; - }; - - users ??= game.users - .filter(u => u.id !== game.user.id) - .map(u => u.id); - - const promise = new Promise((resolve) => { - - /** @type {UserStatus} */ - const status = {}; - for (const user of users) { - status[user] = game.users.get(user).active ? `waiting` : `unprompted`; - }; - - this.#queries.set( - request.id, - { - users, - request, - config, - responses: {}, - resolve, - onSubmit, - app: null, - status, - }, - ); - }); - - if (showStatusApp) { - const app = new QueryStatus({ requestID: request.id }); - app.render({ force: true }); - this.#queries.get(request.id).app = app; - }; - - return promise; + if (showStatusApp) { + const app = new QueryStatus({ requestID: request.id }); + app.render({ force: true }); + queries.get(request.id).app = app; }; - static async requery(requestID, users) { - const query = this.#queries.get(requestID); - if (!query) { return }; + return promise; +}; - game.socket.emit(`system.taf`, { - event: `query.prompt`, - payload: { - id: requestID, - users, - request: query.request, - config: query.config, - }, - }); +export async function requery(requestID, users) { + const query = queries.get(requestID); + if (!query) { return }; - for (const user of users) { - query.status[user] = `waiting`; + game.socket.emit(`system.taf`, { + event: `query.prompt`, + payload: { + id: requestID, + users, + request: query.request, + config: query.config, + }, + }); + + for (const user of users) { + query.status[user] = `waiting`; + }; + query.app?.render({ parts: [ `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); + await maybeResolve(requestID); +}; + +async function maybeResolve(requestID) { + const query = queries.get(requestID); + + // Determine how many users are considered "finished" + let finishedUserCount = 0; + for (const user of query.users) { + const hasApp = query.app != null; + + switch (query.status[user]) { + case `finished`: { + finishedUserCount++; + break; + }; + case `cancelled`: + case `disconnected`: + case `unprompted`: { + if (!hasApp) { + finishedUserCount++; + }; + break; + }; }; + }; + + // Ensure that we have a finished response from everyone prompted + if (query.users.length === finishedUserCount) { + query.app?.close(); + query.resolve(query.responses); + } else { query.app?.render({ parts: [ `users` ] }); }; +}; - static async addResponse(requestID, userID, answers) { - const data = this.#queries.get(requestID); - data.responses[userID] = answers; - data.status[userID] = `finished`; +export async function notify(userID, content, { includeGM = false } = {}) { + game.socket.emit(`system.taf`, { + event: `query.notify`, + payload: { + userID, + content, + includeGM, + }, + }); +}; - await data.onSubmit?.(userID, answers); - this.maybeResolve(requestID); +export async function cancel(requestID) { + // prevent cancelling other people's queries + if (!queries.has(requestID)) { return }; + + game.socket.emit(`system.taf`, { + event: `query.cancel`, + payload: { id: requestID }, + }); +}; + +export async function setApplication(requestID, app) { + if (!queries.has(requestID)) { return }; + if (!(app instanceof QueryStatus)) { return }; + const query = queries.get(requestID); + if (query.app) { + Logger.error(`Cannot set an application for a query that has one already`); + return; }; + query.app = app; +}; - static async maybeResolve(requestID) { - const data = this.#queries.get(requestID); +export async function userActivity(userID, connected) { + for (const [id, query] of queries.entries()) { + if (query.users.includes(userID)) { - // Determine how many users are considered "finished" - let finishedUserCount = 0; - for (const user of data.users) { - const hasApp = data.app != null; - - switch (data.status[user]) { - case `finished`: { - finishedUserCount++; - break; - }; - case `cancelled`: - case `disconnected`: - case `unprompted`: { - if (!hasApp) { - finishedUserCount++; - }; - break; + // Update the user's status to allow for the app to re-prompt them + if (query.status[userID] !== `finished`) { + if (connected) { + query.status[userID] = `unprompted`; + } else { + query.status[userID] = `disconnected`; }; + maybeResolve(id); }; - }; - // Ensure that we have a finished response from everyone prompted - if (data.users.length === finishedUserCount) { - data.app?.close(); - data.resolve(data.responses); - } else { - data.app?.render({ parts: [ `users` ] }); - }; - }; - - static async notify(userID, content, { includeGM = false } = {}) { - game.socket.emit(`system.taf`, { - event: `query.notify`, - payload: { - userID, - content, - includeGM, - }, - }); - } - - static async cancel(requestID) { - // prevent cancelling other people's queries - if (!this.#queries.has(requestID)) { return }; - - game.socket.emit(`system.taf`, { - event: `query.cancel`, - payload: { id: requestID }, - }); - }; - - static async setApplication(requestID, app) { - if (!this.#queries.has(requestID)) { return }; - if (!(app instanceof QueryStatus)) { return }; - const query = this.#queries.get(requestID); - if (query.app) { - Logger.error(`Cannot set an application for a query that has one already`); - return; - }; - query.app = app; - }; - - static async userActivity(userID, connected) { - for (const [id, query] of this.#queries.entries()) { - if (query.users.includes(userID)) { - - // Update the user's status to allow for the app to re-prompt them - if (query.status[userID] !== `finished`) { - if (connected) { - query.status[userID] = `unprompted`; - } else { - query.status[userID] = `disconnected`; - }; - this.maybeResolve(id); - }; - - query.app?.render({ parts: [ `users` ] }); - }; + query.app?.render({ parts: [ `users` ] }); }; }; }; + +export const QueryManager = { + has, get, + query, requery, + addResponse, + notify, + cancel, + setApplication, + userActivity, +};