Get user re-querying working when they disconnect and improve the user status
This commit is contained in:
parent
1bf6cbbd45
commit
bb095a9b4e
5 changed files with 148 additions and 36 deletions
|
|
@ -18,6 +18,9 @@ export class QueryStatus extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
window: {
|
window: {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
},
|
},
|
||||||
|
actions: {
|
||||||
|
promptUser: this.promptUser,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
|
@ -53,10 +56,6 @@ export class QueryStatus extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
this._prepareUsers(ctx);
|
this._prepareUsers(ctx);
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
case `controls`: {
|
|
||||||
this._prepareControls(ctx);
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
|
|
@ -74,14 +73,25 @@ export class QueryStatus extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
name: user.name,
|
name: user.name,
|
||||||
active: user.active,
|
active: user.active,
|
||||||
answers: query.responses[userID] ?? null,
|
answers: query.responses[userID] ?? null,
|
||||||
|
status: query.status[userID],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
ctx.users = users;
|
ctx.users = users;
|
||||||
};
|
};
|
||||||
|
|
||||||
async _prepareControls(ctx) {};
|
|
||||||
// #endregion Lifecycle
|
// #endregion Lifecycle
|
||||||
|
|
||||||
// #region Actions
|
// #region Actions
|
||||||
|
/** @this {QueryStatus} */
|
||||||
|
static async promptUser($e, element) {
|
||||||
|
const userID = element.closest(`[data-user-id]`)?.dataset.userId;
|
||||||
|
if (!userID) { return };
|
||||||
|
QueryManager.requery(this.requestID, [ userID ]);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @this {QueryStatus} */
|
||||||
|
static async cancelRequest() {};
|
||||||
|
|
||||||
|
/** @this {QueryStatus} */
|
||||||
|
static async finishEarly() {};
|
||||||
// #endregion Actions
|
// #endregion Actions
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { QueryManager } from "../utils/QueryManager.mjs";
|
import { QueryManager } from "../utils/QueryManager.mjs";
|
||||||
|
|
||||||
Hooks.on(`userConnected`, (user) => {
|
Hooks.on(`userConnected`, (user, connected) => {
|
||||||
QueryManager.userActivity(user.id);
|
if (user.isSelf) { return };
|
||||||
|
QueryManager.userActivity(user.id, connected);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef QueryData
|
* @typedef QueryData
|
||||||
* @property {string[]} users
|
* @property {string[]} users
|
||||||
|
|
@ -5,6 +11,9 @@
|
||||||
* @property {Record<string, object>} responses
|
* @property {Record<string, object>} responses
|
||||||
* @property {(() => Promise<void>)|null} onSubmit
|
* @property {(() => Promise<void>)|null} onSubmit
|
||||||
* @property {QueryStatus|null} app
|
* @property {QueryStatus|null} app
|
||||||
|
* @property {UserStatus} status
|
||||||
|
* @property {object} request The data used to form the initial request
|
||||||
|
* @property {object} config The data used to create the initial config
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { filePath } from "../consts.mjs";
|
import { filePath } from "../consts.mjs";
|
||||||
|
|
@ -29,6 +38,7 @@ export class QueryManager {
|
||||||
return this.#queries.has(requestID);
|
return this.#queries.has(requestID);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @returns {Omit<QueryData, "resolve"|"onSubmit"|"app">} */
|
||||||
static get(requestID) {
|
static get(requestID) {
|
||||||
if (!this.#queries.has(requestID)) { return null };
|
if (!this.#queries.has(requestID)) { return null };
|
||||||
const query = this.#queries.get(requestID);
|
const query = this.#queries.get(requestID);
|
||||||
|
|
@ -36,6 +46,7 @@ export class QueryManager {
|
||||||
|
|
||||||
delete cloned.onSubmit;
|
delete cloned.onSubmit;
|
||||||
delete cloned.resolve;
|
delete cloned.resolve;
|
||||||
|
delete cloned.app;
|
||||||
|
|
||||||
return foundry.utils.deepFreeze(cloned);
|
return foundry.utils.deepFreeze(cloned);
|
||||||
};
|
};
|
||||||
|
|
@ -68,15 +79,29 @@ export class QueryManager {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
users ??= game.users
|
||||||
|
.filter(u => u.id !== game.user.id)
|
||||||
|
.map(u => u.id);
|
||||||
|
|
||||||
const promise = new Promise((resolve) => {
|
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(
|
this.#queries.set(
|
||||||
request.id,
|
request.id,
|
||||||
{
|
{
|
||||||
users: users ?? game.users.filter(u => u.id !== game.user.id).map(u => u.id),
|
users,
|
||||||
resolve,
|
request,
|
||||||
|
config,
|
||||||
responses: {},
|
responses: {},
|
||||||
|
resolve,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
app: null,
|
app: null,
|
||||||
|
status,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -90,15 +115,62 @@ export class QueryManager {
|
||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static async requery(requestID, users) {
|
||||||
|
const query = this.#queries.get(requestID);
|
||||||
|
if (!query) { return };
|
||||||
|
|
||||||
|
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` ] });
|
||||||
|
};
|
||||||
|
|
||||||
static async addResponse(requestID, userID, answers) {
|
static async addResponse(requestID, userID, answers) {
|
||||||
const data = this.#queries.get(requestID);
|
const data = this.#queries.get(requestID);
|
||||||
data.responses[userID] = answers;
|
data.responses[userID] = answers;
|
||||||
|
data.status[userID] = `finished`;
|
||||||
|
|
||||||
await data.onSubmit?.(userID, answers);
|
await data.onSubmit?.(userID, answers);
|
||||||
|
this.maybeResolve(requestID);
|
||||||
|
};
|
||||||
|
|
||||||
// Validate for responses from everyone
|
static async maybeResolve(requestID) {
|
||||||
if (data.users.length === Object.keys(data.responses).length) {
|
const data = this.#queries.get(requestID);
|
||||||
data.app.close();
|
|
||||||
|
// 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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure that we have a finished response from everyone prompted
|
||||||
|
if (data.users.length === finishedUserCount) {
|
||||||
|
data.app?.close();
|
||||||
data.resolve(data.responses);
|
data.resolve(data.responses);
|
||||||
} else {
|
} else {
|
||||||
data.app?.render({ parts: [ `users` ] });
|
data.app?.render({ parts: [ `users` ] });
|
||||||
|
|
@ -137,14 +209,21 @@ export class QueryManager {
|
||||||
query.app = app;
|
query.app = app;
|
||||||
};
|
};
|
||||||
|
|
||||||
static async userActivity(userID) {
|
static async userActivity(userID, connected) {
|
||||||
for (const query of this.#queries.values()) {
|
for (const [id, query] of this.#queries.entries()) {
|
||||||
if (query.users.includes(userID)) {
|
if (query.users.includes(userID)) {
|
||||||
query.app.render({ parts: [ `users` ] });
|
|
||||||
|
|
||||||
// TODO: if the user is connecting, we want to open
|
// Update the user's status to allow for the app to re-prompt them
|
||||||
// the ask modal on their browser so that they can
|
if (query.status[userID] !== `finished`) {
|
||||||
// actually fill in the data
|
if (connected) {
|
||||||
|
query.status[userID] = `unprompted`;
|
||||||
|
} else {
|
||||||
|
query.status[userID] = `disconnected`;
|
||||||
|
};
|
||||||
|
this.maybeResolve(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
query.app?.render({ parts: [ `users` ] });
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,17 @@
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: 1px solid yellowgreen;
|
border: 1px solid yellowgreen;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
|
|
||||||
|
> .user-summary {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,47 @@
|
||||||
<ul class="user-list">
|
<ul class="user-list">
|
||||||
{{#each users as | user |}}
|
{{#each users as | user |}}
|
||||||
<li style="--spinner-inner-colour: var(--user-color-{{user.id}})">
|
<li
|
||||||
<div class="grow">
|
style="--spinner-inner-colour: var(--user-color-{{user.id}})"
|
||||||
{{ user.name }}
|
data-user-id="{{ user.id }}"
|
||||||
|
>
|
||||||
|
<div class="user-summary">
|
||||||
|
<div class="grow">
|
||||||
|
{{ user.name }}
|
||||||
|
</div>
|
||||||
|
{{#if (eq user.status "cancelled")}}
|
||||||
|
<span>Cancelled by User</span>
|
||||||
|
{{else if (eq user.status "waiting")}}
|
||||||
|
<span class="loader"></span>
|
||||||
|
{{else if (eq user.status "disconnected")}}
|
||||||
|
<taf-icon
|
||||||
|
data-tooltip="taf.Apps.QueryStatus.user-disconnected-tooltip"
|
||||||
|
name="icons/disconnected"
|
||||||
|
var:size="40px"
|
||||||
|
var:stroke="currentColor"
|
||||||
|
var:fill="currentColor"
|
||||||
|
></taf-icon>
|
||||||
|
{{else if (eq user.status "unprompted")}}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-action="promptUser"
|
||||||
|
>
|
||||||
|
Send Request
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#if user.answers}}
|
{{#if (eq user.status "finished")}}
|
||||||
<div class="chip-list">
|
<div class="chip-list">
|
||||||
{{#each user.answers as | answer |}}
|
{{#each user.answers as | answer |}}
|
||||||
<span class="chip">
|
<span class="chip">
|
||||||
{{ @key }}
|
<span class="key">
|
||||||
|
{{ @key }}
|
||||||
|
</span>
|
||||||
<span class="value">
|
<span class="value">
|
||||||
{{ answer }}
|
{{ answer }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
{{else if user.active}}
|
|
||||||
<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}}
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue