diff --git a/dnd5e.css b/dnd5e.css index 381d4077b9..4fa15f0ba0 100644 --- a/dnd5e.css +++ b/dnd5e.css @@ -1433,6 +1433,10 @@ height: calc(var(--form-field-height) + 5px); line-height: 1.5em; } +.dnd5e.advancement.flow form[data-type="ItemChoice"] .item-name .item-delete { + flex: 0 0 20px; + margin-inline-end: 1px; +} .dnd5e.advancement.flow input.error { outline: 2px solid red; } diff --git a/less/advancement.less b/less/advancement.less index 7e6e415f92..e9c702883e 100644 --- a/less/advancement.less +++ b/less/advancement.less @@ -168,6 +168,13 @@ } } + form[data-type="ItemChoice"] { + .item-name .item-delete { + flex: 0 0 20px; + margin-inline-end: 1px; + } + } + input.error { outline: 2px solid red; } diff --git a/module/advancement/types/item-choice.mjs b/module/advancement/types/item-choice.mjs index 1f262df743..ffb9f2c1c7 100644 --- a/module/advancement/types/item-choice.mjs +++ b/module/advancement/types/item-choice.mjs @@ -15,6 +15,7 @@ export class ItemChoiceAdvancement extends Advancement { return foundry.utils.mergeObject(super.metadata, { defaults: { configuration: { + hint: "", choices: {}, allowDrops: true, pool: [] @@ -104,6 +105,32 @@ export class ItemChoiceConfig extends AdvancementConfig { * @extends {AdvancementFlow} */ export class ItemChoiceFlow extends AdvancementFlow { + constructor(...args) { + super(...args); + + /** + * Set of selected UUIDs. + * @type {Set} + */ + this.selected = new Set( + this.retainedData?.items.map(i => foundry.utils.getProperty(i, "flags.dnd5e.sourceId")) + ?? Object.values(this.advancement.data.value[this.level] ?? {}) + ); + + /** + * Cached items from the advancement's pool. + * @type {Array} + */ + this.pool = undefined; + + /** + * List of dropped items. + * @type {Array} + */ + this.dropped = []; + } + + /* -------------------------------------------- */ /** @inheritdoc */ static get defaultOptions() { @@ -117,26 +144,23 @@ export class ItemChoiceFlow extends AdvancementFlow { /** @inheritdoc */ async getData() { - const maxChoices = this.advancement.data.configuration.choices[this.level]; - const added = this.retainedData?.items.map(i => foundry.utils.getProperty(i, "flags.dnd5e.sourceId")) - ?? this.advancement.data.value[this.level]; - const checked = new Set(Object.values(added ?? {})); - - const allUuids = new Set([...this.advancement.data.configuration.pool, ...checked]); - const items = await Promise.all(Array.from(allUuids).map(async uuid => { - const item = await fromUuid(uuid); - item.checked = checked.has(uuid); - return item; - })); - - return foundry.utils.mergeObject(super.getData(), { - choices: { - max: maxChoices, - current: checked.size, - full: checked.size >= maxChoices - }, - items + this.pool ??= await Promise.all(this.advancement.data.configuration.pool.map(fromUuid)); + + const max = this.advancement.data.configuration.choices[this.level]; + const choices = { max, current: this.selected.size, full: this.selected.size >= max }; + + // TODO: Make any choices made at previous levels unavailable + // TODO: Make any items already on actor unavailable? + // TODO: Make sure selected works properly with retained data + // TODO: Ensure everything is populated properly when going forward and backward through steps + + const items = [...this.pool, ...this.dropped]; + items.forEach(i => { + i.checked = this.selected.has(i.uuid); + i.disabled = !i.checked && choices.full; }); + + return foundry.utils.mergeObject(super.getData(), { choices, items }); } /* -------------------------------------------- */ @@ -145,37 +169,16 @@ export class ItemChoiceFlow extends AdvancementFlow { activateListeners(html) { super.activateListeners(html); html.find("a[data-uuid]").click(this._onClickFeature.bind(this)); + html.find(".item-delete").click(this._onItemDelete.bind(this)); } /* -------------------------------------------- */ /** @inheritdoc */ _onChangeInput(event) { - this._calculateCurrent(); - } - - /* -------------------------------------------- */ - - /** - * Adjust the currently selected number to reflect for and enable/disable inputs as necessary. - */ - _calculateCurrent() { - // Recalculate the currently selected number - const current = Array.from(this.form.querySelectorAll("input")).reduce((current, i) => { - return ( (i.type === "hidden") || i.checked ) ? current + 1 : current; - }, 0); - this.form.dataset.current = current; - - // Enabled/disabled non-selected checkboxes - const checkboxes = this.form.querySelectorAll("input[type='checkbox']"); - if ( current >= parseInt(this.form.dataset.max) ) { - checkboxes.forEach(c => c.disabled = !c.checked); - } else { - checkboxes.forEach(c => c.disabled = false); - } - - // Update the current value listed in the UI - this.form.querySelector(".current").innerText = current; + if ( event.target.checked ) this.selected.add(event.target.name); + else this.selected.delete(event.target.name); + this.render(); } /* -------------------------------------------- */ @@ -195,28 +198,24 @@ export class ItemChoiceFlow extends AdvancementFlow { /* -------------------------------------------- */ /** - * Handle deleting an existing Item entry from the Advancement. - * @param {Event} event The originating click event. - * @returns {Promise} The updated parent Item after the application re-renders. - * @private + * Handle deleting a dropped item. + * @param {Event} event The originating click event. + * @protected */ async _onItemDelete(event) { - // event.preventDefault(); - // const uuidToDelete = event.currentTarget.closest("[data-item-uuid]")?.dataset.itemUuid; - // if ( !uuidToDelete ) return; - // const items = foundry.utils.getProperty(this.advancement.data.configuration, this.options.dropKeyPath); - // const updates = { configuration: this.prepareConfigurationUpdate({ - // [this.options.dropKeyPath]: items.filter(uuid => uuid !== uuidToDelete) - // }) }; - // await this.advancement.update(updates); - // this.render(); + event.preventDefault(); + const uuidToDelete = event.currentTarget.closest(".item-name")?.querySelector("input")?.name; + if ( !uuidToDelete ) return; + this.dropped.findSplice(i => i.uuid === uuidToDelete); + this.selected.delete(uuidToDelete); + this.render(); } /* -------------------------------------------- */ /** @inheritdoc */ async _onDrop(event) { - if ( parseInt(this.form.dataset.current) >= parseInt(this.form.dataset.max) ) return false; + if ( this.selected.size >= this.advancement.data.configuration.choices[this.level] ) return false; // Try to extract the data let data; @@ -228,20 +227,20 @@ export class ItemChoiceFlow extends AdvancementFlow { if ( data.type !== "Item" ) return false; const item = await Item.implementation.fromDropData(data); + item.dropped = true; - const existingItems = Array.from(this.form.querySelectorAll("input")).map(i => i.name); + // If the item is already been marked as selected, no need to go further + if ( this.selected.has(item.uuid) ) return false; - // Check to ensure the dropped item doesn't already exist on the actor + // TODO: Check to ensure the dropped item doesn't already exist on the actor - // If the item already exists, simply check its checkbox - if ( existingItems.includes(item.uuid) ) { - const existingInput = this.form.querySelector(`input[name='${item.uuid}']`); - existingInput.checked = true; - this._calculateCurrent(); - return false; - } + // Mark the item as selected + this.selected.add(item.uuid); + + // If the item doesn't already exist in the pool, add it + if ( !this.pool.find(i => i.uuid === item.uuid) ) this.dropped.push(item); - // TODO: Add a new input + this.render(); } /* -------------------------------------------- */ diff --git a/templates/advancement/item-choice-config.html b/templates/advancement/item-choice-config.html index 52a0dba19c..32e1c5b156 100644 --- a/templates/advancement/item-choice-config.html +++ b/templates/advancement/item-choice-config.html @@ -2,6 +2,11 @@ {{log this}}
{{> "systems/dnd5e/templates/advancement/parts/advancement-controls.html"}} +
+ + +
+
diff --git a/templates/advancement/item-choice-flow.html b/templates/advancement/item-choice-flow.html index 6257a3352f..202f13bbb4 100644 --- a/templates/advancement/item-choice-flow.html +++ b/templates/advancement/item-choice-flow.html @@ -2,8 +2,13 @@ data-current="{{choices.current}}" data-max="{{choices.max}}">

{{{title}}}

-

Chosen: {{choices.current}} of {{choices.max}}

+ {{#if advancement.data.configuration.hint}} +

{{advancement.data.configuration.hint}}

+ {{/if}} +

Chosen: {{choices.current}} of {{choices.max}}

+ + {{log this}}
{{#each items}}
@@ -13,10 +18,12 @@

{{this.name}}

{{#if this.dropped}} - + + + {{else}} - + {{/if}}