From 7b1db343cee7690bd9f06920bd8e3abd052f380f Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Mon, 2 Sep 2024 00:48:32 -0600 Subject: [PATCH] Working on an update for DialogManager#ask that allows multiple inputs in the Dialog --- scripts/macros/rollDice.mjs | 43 ++++++++++++------- src/utils/DialogManager.mjs | 82 +++++++++++++++++++++++++++---------- styles/v1/Dialog.scss | 9 ++++ system.json | 2 +- templates/Dialogs/ask.hbs | 33 +++++++++++---- 5 files changed, 124 insertions(+), 45 deletions(-) diff --git a/scripts/macros/rollDice.mjs b/scripts/macros/rollDice.mjs index c4011eb..b6bebf9 100644 --- a/scripts/macros/rollDice.mjs +++ b/scripts/macros/rollDice.mjs @@ -1,17 +1,29 @@ 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 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; const results = []; @@ -22,14 +34,17 @@ async function rollDice() { if (r.total >= successThreshold) { successes++; } - if (r.total === sidesOnDice) { + if (r.total === sidesOnDice && critsEnabled) { successes++; } } - await ChatMessage.create({ + const m = new ChatMessage({ + title: `Dice Pool`, content: `Rolled: ${results.join(`, `)}
Successes: ${successes}`, }); + m.applyRollMode(game.settings.get(`core`, `rollMode`)); + ui.chat.postOne(m); } rollDice() \ No newline at end of file diff --git a/src/utils/DialogManager.mjs b/src/utils/DialogManager.mjs index 56d1d9b..5dae6c8 100644 --- a/src/utils/DialogManager.mjs +++ b/src/utils/DialogManager.mjs @@ -79,49 +79,89 @@ export class DialogManager { /** * 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 - * 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 = {}) { - if (!data.question) { - throw new Error(`Asking the user for input must contain a question`); + if (!data.id) { + 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; + data.default ??= `confirm`; + data.title ??= `System Question`; - const content = await renderTemplate( + data.content = await renderTemplate( `systems/${game.system.id}/templates/Dialogs/ask.hbs`, data, ); return new Promise((resolve, reject) => { DialogManager.createOrFocus( - data.question, + data.id, { - content, + ...data, buttons: { confirm: { label: `Confirm`, callback: (html) => { - const element = html.find(`.user-input`)[0]; - let value = element.value; - switch (data.inputType) { - case `number`: - value = parseFloat(value); - break; - case `checkbox`: - value = element.checked; - break; + 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; + switch (i.inputType) { + case `number`: + value = parseFloat(value); + break; + case `checkbox`: + value = element.checked; + break; + } + Logger.debug(`Ask response: ${value} (type: ${typeof value})`); + answers[i.label] = value; + if (data.inputs.length === 1) { + resolve(value); + return; + } } - Logger.debug(`Ask response: ${value} (type: ${typeof value})`); - resolve(value); + + resolve(answers); }, }, cancel: { label: `Cancel`, - callback: () => reject(), + callback: () => reject(`User cancelled the prompt`), }, }, }, diff --git a/styles/v1/Dialog.scss b/styles/v1/Dialog.scss index 9522789..28f133f 100644 --- a/styles/v1/Dialog.scss +++ b/styles/v1/Dialog.scss @@ -1,3 +1,12 @@ .dialog-content:not(:only-child) { margin-bottom: 8px; } + +.dialog-content { + p { + margin: 0; + } + .prompt { + margin-top: 8px; + } +} diff --git a/system.json b/system.json index e9fde47..bf0ff7c 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "taf", "title": "Text-Based Actors", "description": "", - "version": "1.0.0", + "version": "1.1.0", "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", "url": "https://github.com/Oliver-Akins/Text-Actors-Foundry", diff --git a/templates/Dialogs/ask.hbs b/templates/Dialogs/ask.hbs index d08d8a2..a37f56e 100644 --- a/templates/Dialogs/ask.hbs +++ b/templates/Dialogs/ask.hbs @@ -1,12 +1,27 @@ -
+

- {{question}} + {{ question }}

- + {{#each inputs as | i | }} +
+ + + {{#if i.details}} +

+ {{{ i.details }}} +

+ {{/if}} +
+ {{/each}}