From 634bb3d6443318b00fe615b69ced22efb8780cb0 Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Fri, 11 Jul 2025 17:49:31 -0300 Subject: [PATCH] FEAT: add inventory-fieldset-items-V2 and inventory-item-V2 partials for Actors --- .../sheets-configs/environment-settings.mjs | 53 +++-- .../applications/sheets/actors/adversary.mjs | 48 +---- .../applications/sheets/actors/character.mjs | 163 +++------------- .../applications/sheets/actors/companion.mjs | 54 +----- .../sheets/actors/environment.mjs | 44 +---- .../sheets/api/application-mixin.mjs | 100 +++++++--- module/applications/sheets/api/base-actor.mjs | 67 ++++++- module/helpers/utils.mjs | 12 ++ module/systemRegistration/handlebars.mjs | 4 + .../adversary-settings/features.hbs | 4 +- .../environment-settings/adversaries.hbs | 51 ++--- .../environment-settings/features.hbs | 4 +- templates/sheets/actors/adversary/effects.hbs | 22 ++- .../sheets/actors/adversary/features.hbs | 14 +- templates/sheets/actors/adversary/notes.hbs | 1 - templates/sheets/actors/adversary/sidebar.hbs | 80 ++++---- templates/sheets/actors/character/effects.hbs | 23 ++- .../sheets/actors/character/features.hbs | 22 ++- templates/sheets/actors/character/header.hbs | 12 +- .../sheets/actors/character/inventory.hbs | 38 ++-- templates/sheets/actors/character/loadout.hbs | 23 ++- templates/sheets/actors/character/sidebar.hbs | 81 ++++---- templates/sheets/actors/companion/details.hbs | 51 +++-- templates/sheets/actors/companion/effects.hbs | 15 +- .../sheets/actors/environment/features.hbs | 7 +- .../environment/potentialAdversaries.hbs | 12 +- .../partials/inventory-fieldset-items-V2.hbs | 59 ++++++ .../global/partials/inventory-item-V2.hbs | 183 ++++++++++++++++++ 28 files changed, 733 insertions(+), 514 deletions(-) create mode 100644 templates/sheets/global/partials/inventory-fieldset-items-V2.hbs create mode 100644 templates/sheets/global/partials/inventory-item-V2.hbs diff --git a/module/applications/sheets-configs/environment-settings.mjs b/module/applications/sheets-configs/environment-settings.mjs index 7422f5fc..c249d6d5 100644 --- a/module/applications/sheets-configs/environment-settings.mjs +++ b/module/applications/sheets-configs/environment-settings.mjs @@ -1,3 +1,4 @@ +import { getDocFromElement } from '../../helpers/utils.mjs'; import DHBaseActorSettings from '../sheets/api/actor-setting.mjs'; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ @@ -9,8 +10,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { actions: { addCategory: DHEnvironmentSettings.#addCategory, removeCategory: DHEnvironmentSettings.#removeCategory, - viewAdversary: this.#viewAdversary, - deleteAdversary: this.#deleteAdversary + deleteAdversary: DHEnvironmentSettings.#deleteAdversary }, dragDrop: [ { dragSelector: null, dropSelector: '.category-container' }, @@ -69,37 +69,30 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { await this.actor.update({ [`system.potentialAdversaries.-=${target.dataset.categoryId}`]: null }); } - static async #viewAdversary(_, button) { - const adversary = await foundry.utils.fromUuid(button.dataset.adversary); - if (!adversary) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.adversaryMissing')); - return; - } + /** + * + * @type {ApplicationClickAction} + * @returns + */ + static async #deleteAdversary(_event, target) { + const doc = getDocFromElement(target); + const { category } = target.dataset; + const path = `system.potentialAdversaries.${category}.adversaries`; - adversary.sheet.render({ force: true }); - } + const confirmed = await foundry.applications.api.DialogV2.confirm({ + window: { + title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { + type: game.i18n.localize('TYPES.Actor.adversary'), + name: doc.name + }) + }, + content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name }) + }); - static async #deleteAdversary(event, target) { - const adversaryKey = target.dataset.adversary; - const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`; - const property = foundry.utils.getProperty(this.actor, path); - const adversary = property.find(x => (x?.uuid ?? x) === adversaryKey); + if (!confirmed) return; - if (adversary) { - const confirmed = await foundry.applications.api.DialogV2.confirm({ - window: { - title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { - type: game.i18n.localize('TYPES.Actor.adversary'), - name: adversary.name - }) - }, - content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: adversary.name }) - }); - - if (!confirmed) return; - } - - const newAdversaries = property.filter(x => x && (x?.uuid ?? x) !== adversaryKey); + const adversaries = foundry.utils.getProperty(this.actor, path); + const newAdversaries = adversaries.filter(a => a.uuid !== doc.uuid); await this.actor.update({ [path]: newAdversaries }); } diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index bc3f1f54..1e3747f6 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -9,8 +9,6 @@ export default class AdversarySheet extends DHBaseActorSheet { window: { resizable: true }, actions: { reactionRoll: AdversarySheet.#reactionRoll, - useItem: this.useItem, - toChat: this.toChat }, window: { resizable: true @@ -28,7 +26,7 @@ export default class AdversarySheet extends DHBaseActorSheet { /** @inheritdoc */ static TABS = { primary: { - tabs: [{ id: 'features' }, { id: 'notes' }, { id: 'effects' }], + tabs: [{ id: 'features' }, { id: 'effects' }, { id: 'notes' }], initial: 'features', labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } @@ -80,12 +78,6 @@ export default class AdversarySheet extends DHBaseActorSheet { } } - getItem(element) { - const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId, - item = this.document.items.get(itemId); - return item; - } - /* -------------------------------------------- */ /* Application Clicks Actions */ /* -------------------------------------------- */ @@ -111,42 +103,4 @@ export default class AdversarySheet extends DHBaseActorSheet { this.actor.diceRoll(config); } - - /** - * - * @type {ApplicationClickAction} - */ - static async useItem(event) { - const action = this.getItem(event) ?? this.actor.system.attack; - action.use(event); - } - - /** - * - * @type {ApplicationClickAction} - */ - static async toChat(event, button) { - if (button?.dataset?.type === 'experience') { - const experience = this.document.system.experiences[button.dataset.uuid]; - const cls = getDocumentClass('ChatMessage'); - const systemData = { - name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'), - description: `${experience.name} ${experience.total.signedString()}` - }; - const msg = new cls({ - type: 'abilityUse', - user: game.user.id, - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/ability-use.hbs', - systemData - ) - }); - - cls.create(msg.toObject()); - } else { - const item = this.getItem(event) ?? this.document.system.attack; - item.toChat(this.document.id); - } - } } diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 804185d0..55fb616d 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -4,6 +4,7 @@ import { abilities } from '../../../config/actorConfig.mjs'; import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; +import { getDocFromElement } from "../../../helpers/utils.mjs"; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ @@ -23,8 +24,6 @@ export default class CharacterSheet extends DHBaseActorSheet { makeDeathMove: CharacterSheet.#makeDeathMove, levelManagement: CharacterSheet.#levelManagement, toggleEquipItem: CharacterSheet.#toggleEquipItem, - useItem: this.useItem, //TODO Fix this - toChat: this.toChat }, window: { resizable: true @@ -33,7 +32,7 @@ export default class CharacterSheet extends DHBaseActorSheet { contextMenus: [ { handler: CharacterSheet._getContextMenuOptions, - selector: '[data-item-id]', + selector: '[data-item-uuid]', options: { parentClassHooks: false, fixed: true @@ -97,20 +96,6 @@ export default class CharacterSheet extends DHBaseActorSheet { this._createSearchFilter(); } - /* -------------------------------------------- */ - - getItem(element) { - const listElement = (element.target ?? element).closest('[data-item-id]'); - const itemId = listElement.dataset.itemId; - - switch (listElement.dataset.type) { - case 'effect': - return this.document.effects.get(itemId); - default: - return this.document.items.get(itemId); - } - } - /* -------------------------------------------- */ /* Prepare Context */ /* -------------------------------------------- */ @@ -175,7 +160,7 @@ export default class CharacterSheet extends DHBaseActorSheet { * @protected */ async _prepareLoadoutContext(context, _options) { - context.listView = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList); + context.cardView = !game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList); } /** @@ -229,38 +214,21 @@ export default class CharacterSheet extends DHBaseActorSheet { * @protected */ static _getContextMenuOptions() { - /** - * Get the item from the element. - * @param {HTMLElement} el - * @returns {foundry.documents.Item?} - */ - const getItem = element => { - const listElement = (element.target ?? element).closest('[data-item-id]'); - const itemId = listElement.dataset.itemId; - - switch (listElement.dataset.type) { - case 'effect': - return this.document.effects.get(itemId); - default: - return this.document.items.get(itemId); - } - }; - return [ { name: 'DAGGERHEART.ACTORS.Character.contextMenu.useItem', icon: '', - condition: el => { - const item = getItem(el); - return !['class', 'subclass'].includes(item.type); + condition: target => { + const doc = getDocFromElement(target); + return typeof doc.use === 'function'; }, - callback: (button, event) => CharacterSheet.useItem.call(this, event, button) + callback: (target, event) => getDocFromElement(target).use(event) }, { name: 'DAGGERHEART.ACTORS.Character.contextMenu.equip', icon: '', - condition: el => { - const item = getItem(el); + condition: target => { + const item = getDocFromElement(target); return ['weapon', 'armor'].includes(item.type) && !item.system.equipped; }, callback: CharacterSheet.#toggleEquipItem.bind(this) @@ -269,7 +237,7 @@ export default class CharacterSheet extends DHBaseActorSheet { name: 'DAGGERHEART.ACTORS.Character.contextMenu.unequip', icon: '', condition: el => { - const item = getItem(el); + const item = getDocFromElement(el); return ['weapon', 'armor'].includes(item.type) && item.system.equipped; }, callback: CharacterSheet.#toggleEquipItem.bind(this) @@ -277,51 +245,38 @@ export default class CharacterSheet extends DHBaseActorSheet { { name: 'DAGGERHEART.ACTORS.Character.contextMenu.toLoadout', icon: '', - condition: el => { - const item = getItem(el); + condition: target => { + const item = getDocFromElement(target); return ['domainCard'].includes(item.type) && item.system.inVault; }, - callback: target => getItem(target).update({ 'system.inVault': false }) + callback: target => getDocFromElement(target).update({ 'system.inVault': false }) }, { name: 'DAGGERHEART.ACTORS.Character.contextMenu.toVault', icon: '', - condition: el => { - const item = getItem(el); + condition: target => { + const item = getDocFromElement(target); return ['domainCard'].includes(item.type) && !item.system.inVault; }, - callback: target => getItem(target).update({ 'system.inVault': true }) + callback: target => getDocFromElement(target).update({ 'system.inVault': true }) }, { name: 'DAGGERHEART.ACTORS.Character.contextMenu.sendToChat', icon: '', - callback: CharacterSheet.toChat.bind(this) + condition: target => { + return typeof getDocFromElement(target).toChat === 'function'; + }, + callback: (target) => getDocFromElement(target).toChat(this.document.id), }, { name: 'CONTROLS.CommonEdit', icon: '', - callback: target => getItem(target).sheet.render({ force: true }) + callback: target => getDocFromElement(target).sheet.render({ force: true }) }, { name: 'CONTROLS.CommonDelete', icon: '', - callback: async el => { - const item = getItem(el); - const confirmed = await foundry.applications.api.DialogV2.confirm({ - window: { - title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { - type: game.i18n.localize(`TYPES.${item.documentName}.${item.type}`), - name: item.name - }) - }, - content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { - name: item.name - }) - }); - if (!confirmed) return; - - item.delete(); - } + callback: async el => getDocFromElement(el).deleteDialog(), } ]; } @@ -417,7 +372,7 @@ export default class CharacterSheet extends DHBaseActorSheet { this.#filteredItems.inventory.search.clear(); for (const li of html.querySelectorAll('.inventory-item')) { - const item = this.document.items.get(li.dataset.itemId); + const item = getDocFromElement(li); const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name); if (matchesSearch) this.#filteredItems.inventory.search.add(item.id); const { menu } = this.#filteredItems.inventory; @@ -437,7 +392,7 @@ export default class CharacterSheet extends DHBaseActorSheet { this.#filteredItems.loadout.search.clear(); for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) { - const item = this.document.items.get(li.dataset.itemId); + const item = getDocFromElement(li); const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name); if (matchesSearch) this.#filteredItems.loadout.search.add(item.id); const { menu } = this.#filteredItems.loadout; @@ -488,7 +443,7 @@ export default class CharacterSheet extends DHBaseActorSheet { this.#filteredItems.inventory.menu.clear(); for (const li of html.querySelectorAll('.inventory-item')) { - const item = this.document.items.get(li.dataset.itemId); + const item = getDocFromElement(li); const matchesMenu = filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f)); @@ -509,7 +464,7 @@ export default class CharacterSheet extends DHBaseActorSheet { this.#filteredItems.loadout.menu.clear(); for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) { - const item = this.document.items.get(li.dataset.itemId); + const item = getDocFromElement(li); const matchesMenu = filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f)); @@ -577,13 +532,15 @@ export default class CharacterSheet extends DHBaseActorSheet { this.document.diceRoll(config); } + //TODO: redo toggleEquipItem method + + /** * Toggles the equipped state of an item (armor or weapon). * @type {ApplicationClickAction} */ static async #toggleEquipItem(_event, button) { - //TODO: redo this - const item = this.actor.items.get(button.closest('[data-item-id]')?.dataset.itemId); + const item = getDocFromElement(button); if (!item) return; if (item.system.equipped) { await item.update({ 'system.equipped': false }); @@ -631,9 +588,8 @@ export default class CharacterSheet extends DHBaseActorSheet { * Toggles whether an item is stored in the vault. * @type {ApplicationClickAction} */ - static async #toggleVault(event, button) { - const docId = button.closest('[data-item-id]')?.dataset.itemId; - const doc = this.document.items.get(docId); + static async #toggleVault(_event, button) { + const doc = getDocFromElement(button) await doc?.update({ 'system.inVault': !doc.system.inVault }); } @@ -645,61 +601,6 @@ export default class CharacterSheet extends DHBaseActorSheet { return CONFIG.ux.ContextMenu.triggerContextMenu(event); } - /** - * Use a item - * @type {ApplicationClickAction} - */ - static async useItem(event, button) { - const item = this.getItem(button); - if (!item) return; - - // Should dandle its actions. Or maybe they'll be separate buttons as per an Issue on the board - if (item.type === 'feature') { - item.use(event); - } else if (item instanceof ActiveEffect) { - item.toChat(this); - } else { - const wasUsed = await item.use(event); - if (wasUsed && item.type === 'weapon') { - Hooks.callAll(CONFIG.DH.HOOKS.characterAttack, {}); - } - } - } - - /** - * Send item to Chat - * @type {ApplicationClickAction} - */ - static async toChat(event, button) { - if (button?.dataset?.type === 'experience') { - const experience = this.document.system.experiences[button.dataset.uuid]; - const cls = getDocumentClass('ChatMessage'); - const systemData = { - name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'), - description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}` - }; - const msg = new cls({ - type: 'abilityUse', - user: game.user.id, - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/ability-use.hbs', - systemData - ) - }); - - cls.create(msg.toObject()); - } else { - const item = this.getItem(event); - if (!item) return; - item.toChat(this.document.id); - } - } - - async _onDragStart(_, event) { - super._onDragStart(event); - } - async _onDrop(event) { super._onDrop(event); this._onDropItem(event, TextEditor.getDragEventData(event)); diff --git a/module/applications/sheets/actors/companion.mjs b/module/applications/sheets/actors/companion.mjs index 042a4bfa..87cfda27 100644 --- a/module/applications/sheets/actors/companion.mjs +++ b/module/applications/sheets/actors/companion.mjs @@ -6,11 +6,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet { static DEFAULT_OPTIONS = { classes: ['actor', 'companion'], position: { width: 300 }, - actions: { - viewActor: this.viewActor, - useItem: this.useItem, - toChat: this.toChat - } + actions: {} }; static PARTS = { @@ -29,52 +25,4 @@ export default class DhCompanionSheet extends DHBaseActorSheet { labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } }; - - /* -------------------------------------------- */ - /* Application Clicks Actions */ - /* -------------------------------------------- */ - - static async viewActor(_, button) { - const target = button.closest('[data-item-uuid]'); - const actor = await foundry.utils.fromUuid(target.dataset.itemUuid); - if (!actor) return; - - actor.sheet.render(true); - } - - getAction(element) { - const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId, - item = this.document.system.actions.find(x => x.id === itemId); - return item; - } - - static async useItem(event) { - const action = this.getAction(event) ?? this.actor.system.attack; - action.use(event); - } - - static async toChat(event, button) { - if (button?.dataset?.type === 'experience') { - const experience = this.document.system.experiences[button.dataset.uuid]; - const cls = getDocumentClass('ChatMessage'); - const systemData = { - name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'), - description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}` - }; - const msg = new cls({ - type: 'abilityUse', - user: game.user.id, - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/ability-use.hbs', - systemData - ) - }); - - cls.create(msg.toObject()); - } else { - const item = this.getAction(event) ?? this.document.system.attack; - item.toChat(this.document.id); - } - } } diff --git a/module/applications/sheets/actors/environment.mjs b/module/applications/sheets/actors/environment.mjs index 84e5f180..c7022a42 100644 --- a/module/applications/sheets/actors/environment.mjs +++ b/module/applications/sheets/actors/environment.mjs @@ -9,10 +9,7 @@ export default class DhpEnvironment extends DHBaseActorSheet { position: { width: 500 }, - actions: { - useItem: this.useItem, - toChat: this.toChat - }, + actions: {}, dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }] }; @@ -76,45 +73,6 @@ export default class DhpEnvironment extends DHBaseActorSheet { /* -------------------------------------------- */ - getItem(element) { - const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId, - item = this.document.items.get(itemId); - return item; - } - - /* -------------------------------------------- */ - /* Application Clicks Actions */ - /* -------------------------------------------- */ - - /** - * - * @type {ApplicationClickAction} - */ - async viewAdversary(_, button) { - const target = button.closest('[data-item-uuid]'); - const adversary = await foundry.utils.fromUuid(target.dataset.itemUuid); - if (!adversary) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.adversaryMissing')); - return; - } - - adversary.sheet.render({ force: true }); - } - - static async useItem(event, button) { - const action = this.getItem(event); - if (!action) { - await this.viewAdversary(event, button); - } else { - action.use(event); - } - } - - static async toChat(event) { - const item = this.getItem(event); - item.toChat(this.document.id); - } - async _onDragStart(event) { const item = event.currentTarget.closest('.inventory-item'); diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 0e5f211d..4e3058db 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -1,5 +1,10 @@ const { HandlebarsApplicationMixin } = foundry.applications.api; -import { tagifyElement } from '../../../helpers/utils.mjs'; +import { getDocFromElement, tagifyElement } from '../../../helpers/utils.mjs'; +import DHActionConfig from '../../sheets-configs/action-config.mjs'; + +/** + * @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} + */ /** * @typedef {object} DragDropConfig @@ -73,7 +78,9 @@ export default function DHApplicationMixin(Base) { actions: { createDoc: DHSheetV2.#createDoc, editDoc: DHSheetV2.#editDoc, - deleteDoc: DHSheetV2.#deleteDoc + deleteDoc: DHSheetV2.#deleteDoc, + toChat: DHSheetV2.#toChat, + useItem: DHSheetV2.#useItem, }, contextMenus: [], dragDrop: [], @@ -162,14 +169,14 @@ export default function DHApplicationMixin(Base) { * @param {DragEvent} event * @protected */ - _onDragStart(event) {} + _onDragStart(event) { } /** * Handle drop event. * @param {DragEvent} event * @protected */ - _onDrop(event) {} + _onDrop(event) { } /* -------------------------------------------- */ /* Context Menu */ @@ -213,11 +220,10 @@ export default function DHApplicationMixin(Base) { /** * Create an embedded document. - * @param {PointerEvent} event - The originating click event - * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] + * @type {ApplicationClickAction} */ - static async #createDoc(event, button) { - const { documentClass, type } = button.dataset; + static async #createDoc(event, target) { + const { documentClass, type } = target.dataset; const parent = this.document; const cls = getDocumentClass(documentClass); @@ -234,39 +240,83 @@ export default function DHApplicationMixin(Base) { /** * Renders an embedded document. - * @param {PointerEvent} event - The originating click event - * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] + * @type {ApplicationClickAction} */ - static #editDoc(_event, button) { - const { type, docId } = button.dataset; - this.document.getEmbeddedDocument(type, docId, { strict: true }).sheet.render({ force: true }); + static #editDoc(_event, target) { + const doc = getDocFromElement(target); + + // TODO: REDO this + if (doc) return doc.sheet.render({ force: true }); + const { actionId } = target.closest('[data-action-id]').dataset; + const { actions, attack } = this.document.system; + const action = attack.id === actionId ? attack : actions?.find(a => a.id === actionId); + new DHActionConfig(action).render({ force: true }) } /** * Delete an embedded document. - * @param {PointerEvent} _event - The originating click event - * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] + * @type {ApplicationClickAction} */ - static async #deleteDoc(_event, button) { - const { type, docId } = button.dataset; - const document = this.document.getEmbeddedDocument(type, docId, { strict: true }); - const typeName = game.i18n.localize( - document.type === 'base' ? `DOCUMENT.${type}` : `TYPES.${type}.${document.type}` - ); + static async #deleteDoc(_event, target) { + const doc = getDocFromElement(target); + + // TODO: REDO this + if (doc) return await doc.deleteDialog() + + const { actionId } = target.closest('[data-action-id]').dataset; + const { actions, attack } = this.document.system; + if (attack.id === actionId) return; + const action = actions.find(a => a.id === actionId); const confirmed = await foundry.applications.api.DialogV2.confirm({ window: { title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { - type: typeName, - name: document.name + type: game.i18n.localize(`DAGGERHEART.GENERAL.Action.single`), + name: action.name }) }, - content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: document.name }) + content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name }) }); if (!confirmed) return; - await document.delete(); + return await this.document.update({ + 'system.actions': actions.filter((a) => a.id !== action.id) + }); } + + /** + * Send item to Chat + * @type {ApplicationClickAction} + */ + static async #toChat(_event, target) { + let doc = getDocFromElement(target); + + // TODO: REDO this + if (!doc) { + const { actionId } = target.closest('[data-action-id]').dataset; + const { actions, attack } = this.document.system; + doc = attack.id === actionId ? attack : actions?.find(a => a.id === actionId); + } + return doc.toChat(this.document.id); + } + + /** + * Use a item + * @type {ApplicationClickAction} + */ + static async #useItem(event, target) { + let doc = getDocFromElement(target); + + // TODO: REDO this + if (!doc) { + const { actionId } = target.closest('[data-action-id]').dataset; + const { actions, attack } = this.document.system; + doc = attack.id === actionId ? attack : actions?.find(a => a.id === actionId); + } + + await doc.use(event); + } + } return DHSheetV2; diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 7102fa1c..86c97b6b 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -21,11 +21,14 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { submitOnChange: true }, actions: { - openSettings: DHBaseActorSheet.#openSettings + openSettings: DHBaseActorSheet.#openSettings, + sendExpToChat: DHBaseActorSheet.#sendExpToChat, }, dragDrop: [] }; + /* -------------------------------------------- */ + /**@type {typeof DHBaseActorSettings}*/ #settingSheet; @@ -35,6 +38,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { return (this.#settingSheet ??= SheetClass ? new SheetClass({ document: this.document }) : null); } + /* -------------------------------------------- */ + /* Prepare Context */ + /* -------------------------------------------- */ + /**@inheritdoc */ async _prepareContext(_options) { const context = await super._prepareContext(_options); @@ -42,6 +49,41 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { return context; } + + /**@inheritdoc */ + async _preparePartContext(partId, context, options) { + context = await super._preparePartContext(partId, context, options); + switch (partId) { + case 'effects': + await this._prepareEffectsContext(context, options); + break; + } + return context; + } + + /** + * Prepare render context for the Effect part. + * @param {ApplicationRenderContext} context + * @param {ApplicationRenderOptions} options + * @returns {Promise} + * @protected + */ + async _prepareEffectsContext(context, _options) { + context.effects = { + actives: [], + inactives: [], + }; + + for (const effect of this.actor.allApplicableEffects()) { + const list = effect.active ? context.effects.actives : context.effects.inactives; + list.push(effect); + } + } + + /* -------------------------------------------- */ + /* Application Clicks Actions */ + /* -------------------------------------------- */ + /** * Open the Actor Setting Sheet * @type {ApplicationClickAction} @@ -49,4 +91,27 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { static async #openSettings() { await this.settingSheet.render({ force: true }); } + + /** + * Send Experience to Chat + * @type {ApplicationClickAction} + */ + static async #sendExpToChat(_, button) { + const experience = this.document.system.experiences[button.dataset.id]; + + const systemData = { + name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'), + description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}` + }; + + foundry.documents.ChatMessage.implementation.create({ + type: 'abilityUse', + user: game.user.id, + system: systemData, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/ui/chat/ability-use.hbs', + systemData + ) + }); + } } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 0bb2088e..3030bc08 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -314,3 +314,15 @@ export const updateActorTokens = async (actor, update) => { } } }; + +/** + * Retrieves a Foundry document associated with the nearest ancestor element + * that has a `data-item-uuid` attribute. + * @param {HTMLElement} element - The DOM element to start the search from. + * @returns {foundry.abstract.Document|null} The resolved document, or null if not found or invalid. + */ +export function getDocFromElement(element) { + const target = element.closest('[data-item-uuid]'); + return foundry.utils.fromUuidSync(target.dataset.itemUuid) ?? null; +} + diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index 75a5aff6..f2cc2e82 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -1,4 +1,8 @@ export const preloadHandlebarsTemplates = async function () { + foundry.applications.handlebars.loadTemplates({ + 'daggerheart.inventory-items': 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs', + 'daggerheart.inventory-item': 'systems/daggerheart/templates/sheets/global/partials/inventory-item-V2.hbs', + }) return foundry.applications.handlebars.loadTemplates([ 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs', 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs', diff --git a/templates/sheets-settings/adversary-settings/features.hbs b/templates/sheets-settings/adversary-settings/features.hbs index f232dae9..a287c2a8 100644 --- a/templates/sheets-settings/adversary-settings/features.hbs +++ b/templates/sheets-settings/adversary-settings/features.hbs @@ -16,8 +16,8 @@ {{feature.name}}
- - + +
{{/each}} diff --git a/templates/sheets-settings/environment-settings/adversaries.hbs b/templates/sheets-settings/environment-settings/adversaries.hbs index 4d8fa066..4a9ef08d 100644 --- a/templates/sheets-settings/environment-settings/adversaries.hbs +++ b/templates/sheets-settings/environment-settings/adversaries.hbs @@ -1,30 +1,33 @@ -
+
- {{#each document.system.potentialAdversaries as |category id|}} -
- {{category.label}} -
- - - - + {{#each document.system.potentialAdversaries as |category categoryId|}} +
+ {{category.label}} +
+ + + + +
+
+ {{#each category.adversaries as |adversary|}} +
+ {{> 'daggerheart.inventory-item' + item=adversary + type='adversary' + isActor=true + categoryAdversary=categoryId + }}
-
- {{#each category.adversaries as |adversary|}} -
- {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=adversary type='adversary' isActor=true categoryAdversary=@../key}} -
- {{/each}} -
-
- Drop Actors here -
-
+ {{/each}} +
+
+ Drop Actors here +
+
{{/each}}
\ No newline at end of file diff --git a/templates/sheets-settings/environment-settings/features.hbs b/templates/sheets-settings/environment-settings/features.hbs index f232dae9..aab68309 100644 --- a/templates/sheets-settings/environment-settings/features.hbs +++ b/templates/sheets-settings/environment-settings/features.hbs @@ -16,8 +16,8 @@ {{feature.name}}
- - + +
{{/each}} diff --git a/templates/sheets/actors/adversary/effects.hbs b/templates/sheets/actors/adversary/effects.hbs index 3d378802..13e761a9 100644 --- a/templates/sheets/actors/adversary/effects.hbs +++ b/templates/sheets/actors/adversary/effects.hbs @@ -1,8 +1,16 @@ -
- {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.GENERAL.activeEffects') type='effect'}} - {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.GENERAL.inactiveEffects') type='effect'}} +
+ {{> 'daggerheart.inventory-items' + title='DAGGERHEART.GENERAL.activeEffects' + type='effect' + isGlassy=true + collection=effects.actives + }} + + {{> 'daggerheart.inventory-items' + title='DAGGERHEART.GENERAL.inactiveEffects' + type='effect' + isGlassy=true + collection=effects.inactives + }}
\ No newline at end of file diff --git a/templates/sheets/actors/adversary/features.hbs b/templates/sheets/actors/adversary/features.hbs index 1a8d918e..69843c49 100644 --- a/templates/sheets/actors/adversary/features.hbs +++ b/templates/sheets/actors/adversary/features.hbs @@ -1,9 +1,11 @@ -
+
- {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize tabs.features.label) type='feature' values=document.system.features hideControls=true}} + {{> 'daggerheart.inventory-items' + title=tabs.features.label + type='feature' + collection=document.system.features + hideControls=true + }}
\ No newline at end of file diff --git a/templates/sheets/actors/adversary/notes.hbs b/templates/sheets/actors/adversary/notes.hbs index e7ae7342..663a484a 100644 --- a/templates/sheets/actors/adversary/notes.hbs +++ b/templates/sheets/actors/adversary/notes.hbs @@ -5,7 +5,6 @@ >
{{localize tabs.notes.label}} - {{log notes}} {{formInput notes.field value=notes.value enriched=notes.value toggled=true}}
\ No newline at end of file diff --git a/templates/sheets/actors/adversary/sidebar.hbs b/templates/sheets/actors/adversary/sidebar.hbs index b26c1b81..6b21e382 100644 --- a/templates/sheets/actors/adversary/sidebar.hbs +++ b/templates/sheets/actors/adversary/sidebar.hbs @@ -1,38 +1,36 @@
\ No newline at end of file diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index df275472..f700c66c 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -1,38 +1,35 @@
\ No newline at end of file diff --git a/templates/sheets/actors/environment/potentialAdversaries.hbs b/templates/sheets/actors/environment/potentialAdversaries.hbs index f39a1adf..9783972a 100644 --- a/templates/sheets/actors/environment/potentialAdversaries.hbs +++ b/templates/sheets/actors/environment/potentialAdversaries.hbs @@ -4,8 +4,16 @@ data-group='{{tabs.potentialAdversaries.group}}' >
- {{#each document.system.potentialAdversaries}} - {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=this.label type='adversary' isGlassy=true adversaries=this.adversaries}} + {{#each document.system.potentialAdversaries as |category categoryId|}} + {{> 'daggerheart.inventory-items' + title=category.label + type='adversary' + isGlassy=true + isActor=true + categoryAdversary=categoryId + hideControls=true + collection=category.adversaries + }} {{/each}}
\ No newline at end of file diff --git a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs new file mode 100644 index 00000000..ad940abe --- /dev/null +++ b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs @@ -0,0 +1,59 @@ +{{!-- +Inventory/Domain Card Section + +{{> 'daggerheart.inventory-items' }} + +Parameters: +- title {string} : Localization key used for the legend. +- collection {array} : Array of items to render. +- type {string} : The type of items in the list: +- isGlassy {boolean} : If true, applies the 'glassy' class to the fieldset. +- cardView {boolean} : If true and type is 'domainCard', renders using domain card layout. +- isActor {boolean} : Passed through to inventory-item partials. +- canCreate {boolean} : If true, show createDoc anchor on legend +- categoryAdversary {string} : Category adversary id. +- showLabels {boolean} : If true, show label-tags else show simple tags. +- hideTooltip {boolean} : If true, disables the tooltip on the item image. +- hideControls {boolean} : If true, hides the controls inside inventory-item partials. +- hideDescription {boolean} : If true, hides the item's description. +--}} + +
+ + {{localize title}} + {{#if canCreate}} + + + + {{/if }} + + {{#if (and cardView (eq type 'domainCard'))}} +
    + {{#each collection as |item|}} + + {{> 'systems/daggerheart/templates/sheets/global/partials/domain-card-item.hbs' + item=item + type=../type + }} + + {{/each}} +
+ {{else}} +
    + {{#each collection as |item|}} + + {{> 'daggerheart.inventory-item' + item=item + type=../type + hideControls=../hideControls + isActor=../isActor + categoryAdversary=../categoryAdversary + hideTooltip=../hideTooltip + showLabels=../showLabels + isAction=../isAction + }} + + {{/each}} +
+ {{/if}} +
\ No newline at end of file diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs new file mode 100644 index 00000000..453dff4b --- /dev/null +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -0,0 +1,183 @@ +{{!-- +{{> 'daggerheart.inventory-item' }} + +Parameters: +- type {string} : The type of items in the list +- isActor {boolean} : Passed through to inventory-item partials. +- categoryAdversary {string} : Category adversary id. +- hideLabels {boolean} : If true, hide label-tags else show label-tags. +- hideTags {boolean} : If true, hide simple-tags else show simple-tags. +- hideTooltip {boolean} : If true, disables the tooltip on the item image. +- hideControls {boolean} : If true, hides the controls inside inventory-item partials. +- hideDescription {boolean} : If true, hides the item's description. +--}} + +
  • + {{!-- Image --}} + + + {{!-- Name & Tags --}} +
    + {{!-- Item Name --}} +
    {{item.name}}
    + + {{!-- Weapon Block Start --}} + {{#if (eq type 'weapon')}} + {{#if (not hideTags)}} +
    + {{else if (not hideLabels)}} +
    +
    + {{localize (concat 'DAGGERHEART.CONFIG.Range.' item.system.attack.range '.short')}} + - + {{item.system.attack.damage.parts.0.value.dice}} + {{#if item.system.attack.damage.parts.0.value.bonus}} + + {{item.system.attack.damage.parts.0.value.bonus}} + {{/if}} + {{#with (lookup @root.config.GENERAL.damageTypes item.system.attack.damage.parts.0.type)}} + {{#each icon}}{{/each}} + {{/with}} +
    +
    + {{/if}} + {{/if}} + {{!-- Weapon Block End --}} + + {{!-- Armor Block Start --}} + {{#if (eq type 'armor')}} + {{#if (not hideTags)}} +
    +
    {{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}: {{item.system.baseScore}}
    +
    + {{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.base"}}: + {{item.system.baseThresholds.major}} / {{item.system.baseThresholds.severe}} +
    +
    + {{else if (not hideLabels)}} +
    +
    + {{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}: {{item.system.baseScore}} +
    +
    + {{/if}} + {{/if}} + {{!-- Armor Block End --}} + + {{!-- Domain Card Block Start --}} + {{#if (eq type 'domainCard')}} + {{#if (not hideTags)}} +
    +
    {{localize (concat 'DAGGERHEART.CONFIG.DomainCardTypes.' item.system.type)}}
    +
    {{localize (concat 'DAGGERHEART.GENERAL.Domain.' item.system.domain '.label')}}
    +
    + {{localize "DAGGERHEART.ITEMS.DomainCard.recallCost"}}: + {{item.system.recallCost}} +
    +
    + {{else if (not hideLabels)}} +
    +
    + {{localize (concat 'DAGGERHEART.CONFIG.DomainCardTypes.' item.system.type)}} - + {{localize (concat 'DAGGERHEART.GENERAL.Domain.' item.system.domain '.label')}} - + {{item.system.recallCost}} + +
    +
    + {{/if}} + {{/if}} + {{!-- Domain Card Block End --}} + + {{!-- Effect Block Start --}} + {{#if (eq type 'effect')}} + {{#if (not hideTags)}} +
    +
    + {{localize item.parent.system.metadata.label}}: {{item.parent.name}} +
    +
    + {{#if item.duration.duration}} + {{localize 'DAGGERHEART.EFFECTS.Duration.temporary'}} + {{else}} + {{localize 'DAGGERHEART.EFFECTS.Duration.passive'}} + {{/if}} +
    + {{#each item.statuses as |status|}} +
    {{localize (concat 'DAGGERHEART.CONFIG.Condition.' status '.name')}}
    + {{/each}} +
    + {{else if (not hideLabels)}} + {{!-- Empty --}} + {{/if}} + {{/if}} + {{!-- Effect Block End --}} + + {{!-- Action Block Start --}} + {{#if (eq type 'action')}} + {{#if (not hideTags)}} +
    +
    {{localize (concat 'DAGGERHEART.ACTIONS.TYPES.' item.type '.name')}}
    +
    {{localize (concat 'DAGGERHEART.CONFIG.ActionType.' item.actionType)}}
    +
    + {{else if (not hideLabels)}} + {{!-- Empty --}} + {{/if}} + {{/if}} + {{!-- Action Block End --}} +
    + + {{!-- Controls --}} + {{#unless hideControls}} +
    + {{#if isActor}} + + + + {{#if (eq type 'adversary')}} + + + + {{/if}} + {{else}} + {{#if (eq type 'weapon')}} + + + + {{else if (eq type 'armor')}} + + + + {{else if (eq type 'domainCard')}} + + + + {{/if}} + {{!-- I had to use the {{not}} helper because otherwise the function is called when rendering --}} + {{#unless (not item.toChat)}} + + + + {{/unless}} + + + + + {{/if}} +
    + {{else}} + + {{/unless}} + + {{!-- Description --}} + {{#unless hideDescription}} +
    + {{{item.system.description}}} +
    + {{/unless}} +
  • \ No newline at end of file