Data Request API helper #10

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

View file

@ -15,6 +15,9 @@
"PlayerSheet": "Player Sheet" "PlayerSheet": "Player Sheet"
}, },
"Apps": { "Apps": {
"QueryStatus": {
"user-disconnected-tooltip": "This user is not logged in to Foundry"
},
"TAFDocumentSheetConfig": { "TAFDocumentSheetConfig": {
"Sizing": "Sizing", "Sizing": "Sizing",
"Width": { "Width": {

View file

@ -1,4 +1,5 @@
import { __ID__, filePath } from "../consts.mjs"; import { __ID__, filePath } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { QueryManager } from "../utils/QueryManager.mjs"; import { QueryManager } from "../utils/QueryManager.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -10,15 +11,22 @@ export class QueryStatus extends HandlebarsApplicationMixin(ApplicationV2) {
__ID__, __ID__,
`QueryStatus`, `QueryStatus`,
], ],
position: {
width: 300,
height: `auto`,
},
window: {
resizable: true,
},
}; };
static PARTS = { static PARTS = {
users: { users: {
template: filePath(`templates/QueryStatus/users.hbs`), template: filePath(`templates/QueryStatus/users.hbs`),
}, },
// controls: { controls: {
// template: filePath(`templates/QueryStatus/controls.hbs`), template: filePath(`templates/QueryStatus/controls.hbs`),
// }, },
}; };
// #endregion Options // #endregion Options
@ -27,6 +35,10 @@ export class QueryStatus extends HandlebarsApplicationMixin(ApplicationV2) {
requestID, requestID,
...opts ...opts
}) { }) {
if (!requestID) {
Logger.error(`A requestID must be provided for QueryStatus applications`);
return null;
};
super(opts); super(opts);
this.requestID = requestID; this.requestID = requestID;
}; };
@ -60,7 +72,7 @@ export class QueryStatus extends HandlebarsApplicationMixin(ApplicationV2) {
users.push({ users.push({
id: userID, id: userID,
name: user.name, name: user.name,
colour: user.color, active: user.active,
answers: query.responses[userID] ?? null, answers: query.responses[userID] ?? null,
}); });
}; };

View file

@ -0,0 +1,5 @@
import { QueryManager } from "../utils/QueryManager.mjs";
Hooks.on(`userConnected`, (user) => {
QueryManager.userActivity(user.id);
});

View file

@ -1,2 +1,3 @@
import "./api.mjs"; import "./api.mjs";
import "./hooks/init.mjs"; import "./hooks/init.mjs";
import "./hooks/userConnected.mjs";

View file

@ -4,9 +4,12 @@
* @property {Function} resolve * @property {Function} resolve
* @property {Record<string, object>} responses * @property {Record<string, object>} responses
* @property {(() => Promise<void>)|null} onSubmit * @property {(() => Promise<void>)|null} onSubmit
* @property {QueryStatus|null} app
*/ */
import { filePath } from "../consts.mjs"; import { filePath } from "../consts.mjs";
import { Logger } from "./Logger.mjs";
import { QueryStatus } from "../apps/QueryStatus.mjs";
async function sendBasicNotification(userID, answers) { async function sendBasicNotification(userID, answers) {
const content = await foundry.applications.handlebars.renderTemplate( const content = await foundry.applications.handlebars.renderTemplate(
@ -34,7 +37,7 @@ export class QueryManager {
delete cloned.onSubmit; delete cloned.onSubmit;
delete cloned.resolve; delete cloned.resolve;
return cloned; return foundry.utils.deepFreeze(cloned);
}; };
static async query( static async query(
@ -42,7 +45,8 @@ export class QueryManager {
{ {
onSubmit = sendBasicNotification, onSubmit = sendBasicNotification,
users = null, users = null,
config = undefined, showStatusApp = true,
...config
} = {}, } = {},
) { ) {
if (!request.id) { if (!request.id) {
@ -68,13 +72,21 @@ export class QueryManager {
this.#queries.set( this.#queries.set(
request.id, request.id,
{ {
users: users ?? game.users.filter(u => u.id !== game.user.id), users: users ?? game.users.filter(u => u.id !== game.user.id).map(u => u.id),
resolve, resolve,
responses: {}, responses: {},
onSubmit, onSubmit,
app: null,
}, },
); );
}); });
if (showStatusApp) {
const app = new QueryStatus({ requestID: request.id });
app.render({ force: true });
this.#queries.get(request.id).app = app;
};
return promise; return promise;
}; };
@ -86,7 +98,10 @@ export class QueryManager {
// Validate for responses from everyone // Validate for responses from everyone
if (data.users.length === Object.keys(data.responses).length) { if (data.users.length === Object.keys(data.responses).length) {
data.app.close();
data.resolve(data.responses); data.resolve(data.responses);
} else {
data.app?.render({ parts: [ `users` ] });
}; };
}; };
@ -110,4 +125,27 @@ export class QueryManager {
payload: { id: requestID }, 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) {
for (const query of this.#queries.values()) {
if (query.users.includes(userID)) {
query.app.render({ parts: [ `users` ] });
// TODO: if the user is connecting, we want to open
// the ask modal on their browser so that they can
// actually fill in the data
};
};
};
}; };
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

View file

@ -0,0 +1,20 @@
.taf.QueryStatus {
.user-list {
display: flex;
flex-direction: column;
gap: 4px;
list-style-type: none;
margin: 0;
padding: 0;
li {
display: flex;
flex-direction: row;
align-items: center;
margin: 0;
border: 1px solid yellowgreen;
border-radius: 4px;
padding: 4px 8px;
}
}
}

View file

@ -18,8 +18,9 @@
.taf > .window-content span { .taf > .window-content span {
&.loader { &.loader {
width: 48px; --size: 40px;
height: 48px; width: var(--size);
height: var(--size);
border-radius: 50%; border-radius: 50%;
position: relative; position: relative;
animation: rotate 2s linear infinite; animation: rotate 2s linear infinite;
@ -38,7 +39,7 @@
&::after{ &::after{
inset: 8px; inset: 8px;
transform: rotate3d(90, 90, 0, 180deg ); transform: rotate3d(90, 90, 0, 180deg );
border-color: #FF3D00; /* This can be the user colour */ border-color: var(--spinner-inner-colour, #FF3D00);
} }
} }
} }

View file

@ -0,0 +1,3 @@
.taf > .window-content {
.grow { flex-grow: 1; }
}

View file

@ -9,6 +9,7 @@
@import url("./themes/light.css") layer(themes); @import url("./themes/light.css") layer(themes);
/* Elements */ /* Elements */
@import url("./elements/utils.css") layer(elements);
@import url("./elements/headers.css") layer(elements); @import url("./elements/headers.css") layer(elements);
@import url("./elements/hr.css") layer(elements); @import url("./elements/hr.css") layer(elements);
@import url("./elements/input.css") layer(elements); @import url("./elements/input.css") layer(elements);
@ -22,4 +23,5 @@
@import url("./Apps/Ask.css") layer(apps); @import url("./Apps/Ask.css") layer(apps);
@import url("./Apps/AttributeManager.css") layer(apps); @import url("./Apps/AttributeManager.css") layer(apps);
@import url("./Apps/PlayerSheet.css") layer(apps); @import url("./Apps/PlayerSheet.css") layer(apps);
@import url("./Apps/QueryStatus.css") layer(apps);
@import url("./Apps/TAFDocumentSheetConfig.css") layer(apps); @import url("./Apps/TAFDocumentSheetConfig.css") layer(apps);

View file

@ -0,0 +1,8 @@
<div>
<button data-action="">
Cancel Request
Oliver marked this conversation as resolved Outdated

Localizing this would be nice

Localizing this would be nice
</button>
<button data-action="">
Finish Request Early
Oliver marked this conversation as resolved Outdated

Localizing this would be nice

Localizing this would be nice
</button>
</div>

View file

@ -1,7 +1,9 @@
<ul> <ul class="user-list">
{{#each users as | user |}} {{#each users as | user |}}
<li> <li style="--spinner-inner-colour: var(--user-color-{{user.id}})">
{{ user.name }} <div class="grow">
{{ user.name }}
</div>
{{#if user.answers}} {{#if user.answers}}
<div class="chip-list"> <div class="chip-list">
{{#each user.answers as | answer |}} {{#each user.answers as | answer |}}
@ -13,8 +15,16 @@
</span> </span>
{{/each}} {{/each}}
</div> </div>
{{else}} {{else if user.active}}
<span class="loader"></span> <span class="loader"></span>
{{else}}
<taf-icon
data-tooltip="taf.Apps.QueryStatus.user-disconnected-tooltip"
name="icons/disconnected"
var:size="40px"
var:stroke="currentColor"
var:fill="currentColor"
></taf-icon>
{{/if}} {{/if}}
Oliver marked this conversation as resolved

Localize

Localize
</li> </li>
{{/each}} {{/each}}