Working on an update for DialogManager#ask that allows multiple inputs in the Dialog

This commit is contained in:
Oliver-Akins 2024-09-02 00:48:32 -06:00
parent 7a6fa6d10d
commit 7b1db343ce
5 changed files with 124 additions and 45 deletions

View file

@ -1,17 +1,29 @@
async function rollDice() { async function rollDice() {
const statBase = await DialogManager.ask({
question: `How many dice to roll?`,
initialValue: 2,
inputType: `number`,
});
if (!statBase) {
return;
}
const sidesOnDice = 6; const sidesOnDice = 6;
const successThreshold = 4;
const answers = await DialogManager.ask({
id: `eat-the-reich-dice-pool`,
question: `Set up your dice pool:`,
inputs: [
{
inputType: `number`,
defaultValue: 2,
label: `Number of Dice`,
autofocus: true,
},
{
inputType: `number`,
defaultValue: 4,
label: `Success Threshold (d${sidesOnDice} >= X)`,
},
{
inputType: `checkbox`,
defaultValue: true,
label: `Enable Criticals`,
},
],
});
const [ statBase, successThreshold, critsEnabled ] = Object.values(answers);
let successes = 0; let successes = 0;
const results = []; const results = [];
@ -22,14 +34,17 @@ async function rollDice() {
if (r.total >= successThreshold) { if (r.total >= successThreshold) {
successes++; successes++;
} }
if (r.total === sidesOnDice) { if (r.total === sidesOnDice && critsEnabled) {
successes++; successes++;
} }
} }
await ChatMessage.create({ const m = new ChatMessage({
title: `Dice Pool`,
content: `Rolled: ${results.join(`, `)}<br>Successes: ${successes}`, content: `Rolled: ${results.join(`, `)}<br>Successes: ${successes}`,
}); });
m.applyRollMode(game.settings.get(`core`, `rollMode`));
ui.chat.postOne(m);
} }
rollDice() rollDice()

View file

@ -79,35 +79,68 @@ export class DialogManager {
/** /**
* Asks the user to provide a simple piece of information, this is primarily * Asks the user to provide a simple piece of information, this is primarily
* intended to be used within macros so that it can have better info gathering * intended to be used within macros so that it can have better info gathering
* as needed. * as needed. This returns an object of input labels to the value the user
* input for that label, if there is only one input, this will return the value
* without an object wrapper, allowing for easier access.
*/ */
static async ask(data, opts = {}) { static async ask(data, opts = {}) {
if (!data.question) { if (!data.id) {
throw new Error(`Asking the user for input must contain a question`); throw new Error(`Asking the user for input must contain an ID`);
}
if (!data.inputs.length) {
throw new Error(`Must include at least one input specification when prompting the user`);
} }
data.inputType ??= `text`;
data.initialValue ??= ``;
opts.title ??= `System Question`; let autofocusClaimed = false;
for (const i of data.inputs) {
i.id ??= foundry.utils.randomID(16);
i.inputType ??= `text`;
// Only ever allow one input to claim autofocus
i.autofocus &&= !autofocusClaimed;
autofocusClaimed ||= i.autofocus;
// Set the value's attribute name if it isn't specified explicitly
if (!i.valueAttribute) {
switch (i.inputType) {
case `checkbox`:
i.valueAttribute = `checked`;
break;
default:
i.valueAttribute = `value`;
};
};
};
opts.jQuery = true; opts.jQuery = true;
data.default ??= `confirm`;
data.title ??= `System Question`;
const content = await renderTemplate( data.content = await renderTemplate(
`systems/${game.system.id}/templates/Dialogs/ask.hbs`, `systems/${game.system.id}/templates/Dialogs/ask.hbs`,
data, data,
); );
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DialogManager.createOrFocus( DialogManager.createOrFocus(
data.question, data.id,
{ {
content, ...data,
buttons: { buttons: {
confirm: { confirm: {
label: `Confirm`, label: `Confirm`,
callback: (html) => { callback: (html) => {
const element = html.find(`.user-input`)[0]; const answers = {};
/*
Retrieve the answer for every input provided using the ID
determined during initial data prep, and assign the value
to the property of the label in the object.
*/
for (const i of data.inputs) {
const element = html.find(`#${i.id}`)[0];
let value = element.value; let value = element.value;
switch (data.inputType) { switch (i.inputType) {
case `number`: case `number`:
value = parseFloat(value); value = parseFloat(value);
break; break;
@ -116,12 +149,19 @@ export class DialogManager {
break; break;
} }
Logger.debug(`Ask response: ${value} (type: ${typeof value})`); Logger.debug(`Ask response: ${value} (type: ${typeof value})`);
answers[i.label] = value;
if (data.inputs.length === 1) {
resolve(value); resolve(value);
return;
}
}
resolve(answers);
}, },
}, },
cancel: { cancel: {
label: `Cancel`, label: `Cancel`,
callback: () => reject(), callback: () => reject(`User cancelled the prompt`),
}, },
}, },
}, },

View file

@ -1,3 +1,12 @@
.dialog-content:not(:only-child) { .dialog-content:not(:only-child) {
margin-bottom: 8px; margin-bottom: 8px;
} }
.dialog-content {
p {
margin: 0;
}
.prompt {
margin-top: 8px;
}
}

View file

@ -2,7 +2,7 @@
"id": "taf", "id": "taf",
"title": "Text-Based Actors", "title": "Text-Based Actors",
"description": "", "description": "",
"version": "1.0.0", "version": "1.1.0",
"download": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/dotdungeon.zip", "download": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/dotdungeon.zip",
"manifest": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/system.json", "manifest": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/system.json",
"url": "https://github.com/Oliver-Akins/Text-Actors-Foundry", "url": "https://github.com/Oliver-Akins/Text-Actors-Foundry",

View file

@ -1,12 +1,27 @@
<div> <div class="ask-dialog">
<p> <p>
{{question}} {{ question }}
</p> </p>
<input {{#each inputs as | i | }}
type="{{inputType}}" <div class="prompt">
value="{{initialValue}}" <label
checked="{{initialValue}}" for="{{i.id}}"
class="user-input" class="prompt__label"
autofocus
> >
{{ i.label }}
</label>
<input
type="{{i.inputType}}"
id="{{i.id}}"
class="patrons__input"
{{i.valueAttribute}}="{{i.defaultValue}}"
{{#if i.autofocus}}autofocus{{/if}}
>
{{#if i.details}}
<p class="prompt__details">
{{{ i.details }}}
</p>
{{/if}}
</div>
{{/each}}
</div> </div>