Data Request API helper #10

Merged
Oliver merged 94 commits from feat/data-requests into main 2025-11-22 02:51:15 +00:00
4 changed files with 187 additions and 177 deletions
Showing only changes of commit df0c69c731 - Show all commits

View file

@ -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} */

View file

@ -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);
});

View file

@ -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);
};

View file

@ -20,28 +20,29 @@ import { filePath } from "../consts.mjs";
import { Logger } from "./Logger.mjs";
import { QueryStatus } from "../apps/QueryStatus.mjs";
/** @type {Map<string, QueryData>} */
const queries = new Map();
/** @type {Map<string, Promise>} */
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<string, QueryData>} */
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<QueryData, "resolve"|"onSubmit"|"app">} */
static get(requestID) {
if (!this.#queries.has(requestID)) { return null };
const query = this.#queries.get(requestID);
/** @returns {Omit<QueryData, "resolve"|"onSubmit"|"app">} */
export function get(requestID) {
if (!queries.has(requestID)) { return null };
const query = queries.get(requestID);
const cloned = foundry.utils.deepClone(query);
delete cloned.onSubmit;
@ -49,9 +50,9 @@ export class QueryManager {
delete cloned.app;
return foundry.utils.deepFreeze(cloned);
};
};
static async query(
export async function query(
request,
{
onSubmit = sendBasicNotification,
@ -59,7 +60,7 @@ export class QueryManager {
showStatusApp = true,
...config
} = {},
) {
) {
if (!request.id) {
ui.notifications.error(game.i18n.localize(`taf.notifs.error.missing-id`));
return;
@ -75,7 +76,7 @@ export class QueryManager {
},
});
if (this.#promises.has(request.id)) {
if (promises.has(request.id)) {
return null;
};
@ -91,7 +92,7 @@ export class QueryManager {
status[user] = game.users.get(user).active ? `waiting` : `unprompted`;
};
this.#queries.set(
queries.set(
request.id,
{
users,
@ -109,14 +110,14 @@ export class QueryManager {
if (showStatusApp) {
const app = new QueryStatus({ requestID: request.id });
app.render({ force: true });
this.#queries.get(request.id).app = app;
queries.get(request.id).app = app;
};
return promise;
};
};
static async requery(requestID, users) {
const query = this.#queries.get(requestID);
export async function requery(requestID, users) {
const query = queries.get(requestID);
if (!query) { return };
game.socket.emit(`system.taf`, {
@ -133,26 +134,26 @@ export class QueryManager {
query.status[user] = `waiting`;
};
query.app?.render({ parts: [ `users` ] });
};
};
static async addResponse(requestID, userID, answers) {
const data = this.#queries.get(requestID);
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);
this.maybeResolve(requestID);
};
await maybeResolve(requestID);
};
static async maybeResolve(requestID) {
const data = this.#queries.get(requestID);
async function maybeResolve(requestID) {
const query = queries.get(requestID);
// Determine how many users are considered "finished"
Oliver marked this conversation as resolved Outdated

Add check to ensure that the query isn't undefined

Add check to ensure that the query isn't undefined
let finishedUserCount = 0;
for (const user of data.users) {
const hasApp = data.app != null;
for (const user of query.users) {
const hasApp = query.app != null;
switch (data.status[user]) {
switch (query.status[user]) {
case `finished`: {
finishedUserCount++;
break;
@ -169,15 +170,15 @@ export class QueryManager {
};
// Ensure that we have a finished response from everyone prompted
if (data.users.length === finishedUserCount) {
data.app?.close();
data.resolve(data.responses);
if (query.users.length === finishedUserCount) {
query.app?.close();
query.resolve(query.responses);
} else {
data.app?.render({ parts: [ `users` ] });
};
query.app?.render({ parts: [ `users` ] });
};
};
static async notify(userID, content, { includeGM = false } = {}) {
export async function notify(userID, content, { includeGM = false } = {}) {
game.socket.emit(`system.taf`, {
event: `query.notify`,
payload: {
@ -186,31 +187,31 @@ export class QueryManager {
includeGM,
},
});
}
};
static async cancel(requestID) {
export async function cancel(requestID) {
// prevent cancelling other people's queries
if (!this.#queries.has(requestID)) { return };
if (!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 };
export async function setApplication(requestID, app) {
if (!queries.has(requestID)) { return };
if (!(app instanceof QueryStatus)) { return };
const query = this.#queries.get(requestID);
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 userActivity(userID, connected) {
for (const [id, query] of this.#queries.entries()) {
export async function userActivity(userID, connected) {
for (const [id, query] of queries.entries()) {
if (query.users.includes(userID)) {
// Update the user's status to allow for the app to re-prompt them
@ -220,11 +221,20 @@ export class QueryManager {
} else {
query.status[userID] = `disconnected`;
};
this.maybeResolve(id);
maybeResolve(id);
};
query.app?.render({ parts: [ `users` ] });
};
};
};
};
export const QueryManager = {
has, get,
query, requery,
addResponse,
notify,
cancel,
setApplication,
userActivity,
};