From 8574a1d93ff53941902a04531697f278d614e36d Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Mon, 23 Jun 2025 14:27:27 -0300 Subject: [PATCH 01/17] FEAT: improvement of DHApplicationMixin FEAT: create a new DHBaseItemSheet --- lang/en.json | 7 + module/applications/_module.mjs | 4 +- module/applications/sheets/api/_modules.mjs | 2 + .../sheets/api/application-mixin.mjs | 102 ++++++++++++++ module/applications/sheets/api/base-item.mjs | 129 ++++++++++++++++++ module/applications/sheets/items/armor.mjs | 22 +-- module/applications/sheets/items/class.mjs | 50 ++----- .../applications/sheets/items/consumable.mjs | 5 +- .../applications/sheets/items/domainCard.mjs | 5 +- module/applications/sheets/items/feature.mjs | 106 ++++++++------ .../sheets/items/miscellaneous.mjs | 5 +- module/applications/sheets/items/subclass.mjs | 65 ++------- module/applications/sheets/items/weapon.mjs | 21 ++- templates/sheets/items/feature/effects.hbs | 14 +- templates/sheets/items/feature/settings.hbs | 6 +- 15 files changed, 381 insertions(+), 162 deletions(-) create mode 100644 module/applications/sheets/api/_modules.mjs create mode 100644 module/applications/sheets/api/application-mixin.mjs create mode 100644 module/applications/sheets/api/base-item.mjs diff --git a/lang/en.json b/lang/en.json index 868ee74b..f19b69db 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1028,6 +1028,13 @@ } }, "Sheets": { + "TABS": { + "features": "Features", + "effects": "Effects", + "settings": "Settings", + "actions": "Actions", + "description": "Description" + }, "PC": { "Name": "Name", "Pronouns": "Pronouns", diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index 65eafe09..2c2847ee 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -12,4 +12,6 @@ export { default as DhpWeapon } from './sheets/items/weapon.mjs'; export { default as DhpArmor } from './sheets/items/armor.mjs'; export { default as DhpChatMessage } from './chatMessage.mjs'; export { default as DhpEnvironment } from './sheets/environment.mjs'; -export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs'; \ No newline at end of file +export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs'; + +export * as api from './sheets/api/_modules.mjs'; \ No newline at end of file diff --git a/module/applications/sheets/api/_modules.mjs b/module/applications/sheets/api/_modules.mjs new file mode 100644 index 00000000..d5ef290d --- /dev/null +++ b/module/applications/sheets/api/_modules.mjs @@ -0,0 +1,2 @@ +export {default as DHApplicationMixin} from "./application-mixin.mjs"; +export {default as DHBaseItemSheet} from "./base-item.mjs"; diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs new file mode 100644 index 00000000..d45cafe6 --- /dev/null +++ b/module/applications/sheets/api/application-mixin.mjs @@ -0,0 +1,102 @@ +const { HandlebarsApplicationMixin } = foundry.applications.api; + +/** + * @typedef {object} DragDropConfig + * @property {string|null} dragSelector - A CSS selector that identifies draggable elements. + * @property {string|null} dropSelector - A CSS selector that identifies drop targets. + */ + +/** + * @typedef {import("@client/applications/api/handlebars-application.mjs").HandlebarsRenderOptions} HandlebarsRenderOptions + * @typedef {foundry.applications.types.ApplicationConfiguration & HandlebarsRenderOptions & { dragDrop?: DragDropConfig[] }} DHSheetV2Configuration + */ + +/** + * @template {Constructor} BaseDocumentSheet + * @param {BaseDocumentSheet} Base - The base class to extend. + * @returns {BaseDocumentSheet} + */ +export default function DHApplicationMixin(Base) { + class DHSheetV2 extends HandlebarsApplicationMixin(Base) { + /** + * @param {DHSheetV2Configuration} [options={}] + */ + constructor(options = {}) { + super(options); + /** + * @type {foundry.applications.ux.DragDrop[]} + * @private + */ + this._dragDrop = this._createDragDropHandlers(); + } + + /** + * The default options for the sheet. + * @type {DHSheetV2Configuration} + */ + static DEFAULT_OPTIONS = { + classes: ['daggerheart', 'sheet', 'dh-style'], + position: { + width: 480, + height: 'auto' + }, + + dragDrop: [] + }; + + /* -------------------------------------------- */ + + /**@inheritdoc */ + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + this._dragDrop.forEach(d => d.bind(htmlElement)); + } + + /* -------------------------------------------- */ + /* Drag and Drop */ + /* -------------------------------------------- */ + + /** + * Creates drag-drop handlers from the configured options. + * @returns {foundry.applications.ux.DragDrop[]} + * @private + */ + _createDragDropHandlers() { + return this.options.dragDrop.map(d => { + d.callbacks = { + drop: this._onDrop.bind(this) + }; + return new foundry.applications.ux.DragDrop.implementation(d); + }); + } + + /** + * Handle drop event. + * @param {DragEvent} event + * @protected + */ + _onDrop(event) { } + + /* -------------------------------------------- */ + /* Prepare Context */ + /* -------------------------------------------- */ + + /** + * Prepare the template context. + * @param {object} options + * @param {string} [objectPath='document'] + * @returns {Promise} + * @inheritdoc + */ + async _prepareContext(options, objectPath = 'document') { + const context = await super._prepareContext(options); + context.config = CONFIG.daggerheart; + context.source = this[objectPath]; + context.fields = this[objectPath].schema.fields; + context.systemFields = this[objectPath].system ? this[objectPath].system.schema.fields : {}; + return context; + } + } + + return DHSheetV2; +} diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs new file mode 100644 index 00000000..e6fcf26a --- /dev/null +++ b/module/applications/sheets/api/base-item.mjs @@ -0,0 +1,129 @@ +import DHApplicationMixin from "./application-mixin.mjs"; + +const { ItemSheetV2 } = foundry.applications.sheets; + +/** + * A base item sheet extending {@link ItemSheetV2} via {@link DHApplicationMixin} + * @extends ItemSheetV2 + * @mixes DHSheetV2 + */ +export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { + /** @inheritDoc */ + static DEFAULT_OPTIONS = { + classes: ['item'], + position: { width: 600 }, + form: { + submitOnChange: true + }, + actions: { + addAction: DHBaseItemSheet.#addAction, + editAction: DHBaseItemSheet.#editAction, + removeAction: DHBaseItemSheet.#removeAction + } + }; + + /* -------------------------------------------- */ + + /** @inheritdoc */ + static TABS = { + primary: { + tabs: [ + { id: 'description' }, + { id: 'actions' }, + { id: 'settings' } + ], + initial: "description", + labelPrefix: "DAGGERHEART.Sheets.TABS" + } + } + + /* -------------------------------------------- */ + /* Application Clicks Actions */ + /* -------------------------------------------- */ + + /** + * Render a dialog prompting the user to select an action type. + * + * @returns {Promise} An object containing the selected action type. + */ + static async selectActionType() { + const content = await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), + title = 'Select Action Type', //useless var + type = 'form', + data = {}; //useless var + //TODO: use DialogV2 + return Dialog.prompt({ + title, + label: title, + content, + type, //this prop is useless + callback: html => { + const form = html[0].querySelector('form'), + fd = new foundry.applications.ux.FormDataExtended(form); + foundry.utils.mergeObject(data, fd.object, { inplace: true }); + // if (!data.name?.trim()) data.name = game.i18n.localize(SYSTEM.ACTIONS.actionTypes[data.type].name); + return data; + }, + rejectClose: false + }); + } + + /** + * Add a new action to the item, prompting the user for its type. + * @param {PointerEvent} _event - The originating click event + * @param {HTMLElement} _button - The capturing HTML element which defines the [data-action="addAction"] + */ + static async #addAction(_event, _button) { + const actionType = await DHBaseItemSheet.selectActionType(), + actionIndexes = this.document.system.actions.map(x => x._id.split('-')[2]).sort((a, b) => a - b); + try { + const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, + action = new cls( + { + // id: `${this.document.id}-Action-${actionIndexes.length > 0 ? actionIndexes[0] + 1 : 1}` + _id: foundry.utils.randomID(), + type: actionType.type, + name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name), + ...cls.getSourceConfig(this.document) + }, + { + parent: this.document + } + ); + await this.document.update({ 'system.actions': [...this.document.system.actions, action] }); + await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render( + true + ); + } catch (error) { + console.log(error); + } + } + + /** + * Edit an existing action on the item + * @param {PointerEvent} event - The originating click event + * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="editAction"] + */ + static async #editAction(_event, button) { + const action = this.document.system.actions[button.dataset.index]; + await new DHActionConfig(action).render(true); + } + + /** + * Remove an action from the item. + * @param {PointerEvent} event - The originating click event + * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] + */ + static async #removeAction(event, button) { + event.stopPropagation(); + await this.document.update({ + 'system.actions': this.document.system.actions.filter( + (_, index) => index !== Number.parseInt(button.dataset.index) + ) + }); + } + +} \ No newline at end of file diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index 32c482ba..4cc05fe2 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -1,14 +1,14 @@ -import { armorFeatures } from '../../../config/itemConfig.mjs'; +import DHBaseItemSheet from '../api/base-item.mjs'; import { tagifyElement } from '../../../helpers/utils.mjs'; -import DHItemSheetV2 from '../item.mjs'; -const { ItemSheetV2 } = foundry.applications.sheets; -export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) { +export default class ArmorSheet extends DHBaseItemSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['armor'], dragDrop: [{ dragSelector: null, dropSelector: null }] }; + /**@override */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/armor/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, @@ -23,6 +23,7 @@ export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) { } }; + /**@inheritdoc */ async _preparePartContext(partId, context) { super._preparePartContext(partId, context); @@ -35,15 +36,20 @@ export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) { return context; } + /**@inheritdoc */ _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); const featureInput = htmlElement.querySelector('.features-input'); - tagifyElement(featureInput, armorFeatures, this.onFeatureSelect.bind(this)); + tagifyElement(featureInput, CONFIG.daggerheart.ITEM.armorFeatures, ArmorSheet.onFeatureSelect.bind(this)); } - async onFeatureSelect(features) { - await this.document.update({ 'system.features': features.map(x => ({ value: x.value })) }); - this.render(true); + /** + * Callback function used by `tagifyElement`. + * @param {Array} selectedOptions - The currently selected tag objects. + */ + static async onFeatureSelect(selectedOptions ) { + await this.document.update({ 'system.features': selectedOptions .map(x => ({ value: x.value })) }); + this.render({force: false, parts: ["settings"]}); } } diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index a4659598..f55c2b5f 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -1,15 +1,14 @@ +import DHBaseItemSheet from '../api/base-item.mjs'; import { actionsTypes } from '../../../data/_module.mjs'; import { tagifyElement } from '../../../helpers/utils.mjs'; import DHActionConfig from '../../config/Action.mjs'; -import DaggerheartSheet from '../daggerheart-sheet.mjs'; -const { ItemSheetV2 } = foundry.applications.sheets; const { TextEditor } = foundry.applications.ux; -export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { +export default class ClassSheet extends DHBaseItemSheet { static DEFAULT_OPTIONS = { tag: 'form', - classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'class'], + classes: ['class'], position: { width: 700 }, actions: { removeSubclass: this.removeSubclass, @@ -23,11 +22,6 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { removeSecondaryWeapon: this.removeSecondaryWeapon, removeArmor: this.removeArmor }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false - }, dragDrop: [ { dragSelector: '.suggested-item', dropSelector: null }, { dragSelector: null, dropSelector: '.take-section' }, @@ -53,24 +47,17 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { } }; + /** @inheritdoc */ static TABS = { - features: { - active: true, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.Sheets.Class.Tabs.Features' - }, - settings: { - active: false, - cssClass: '', - group: 'primary', - id: 'settings', - icon: null, - label: 'DAGGERHEART.Sheets.Class.Tabs.settings' + primary: { + tabs: [ + { id: 'description' }, + { id: 'settings' }, + ], + initial: "description", + labelPrefix: "DAGGERHEART.Sheets.Feature.Tabs" } - }; + } _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); @@ -81,17 +68,10 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); context.domains = this.document.system.domains; - return context; } - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } onAddTag(e) { if (e.detail.index === 2) { @@ -157,9 +137,9 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { async selectActionType() { const content = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/views/actionType.hbs', - { types: SYSTEM.ACTIONS.actionTypes } - ), + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), title = 'Select Action Type', type = 'form', data = {}; diff --git a/module/applications/sheets/items/consumable.mjs b/module/applications/sheets/items/consumable.mjs index 815c6b9b..26fdd312 100644 --- a/module/applications/sheets/items/consumable.mjs +++ b/module/applications/sheets/items/consumable.mjs @@ -1,7 +1,6 @@ -import DHItemSheetV2 from '../item.mjs'; +import DHBaseItemSheet from '../api/base-item.mjs'; -const { ItemSheetV2 } = foundry.applications.sheets; -export default class ConsumableSheet extends DHItemSheetV2(ItemSheetV2) { +export default class ConsumableSheet extends DHBaseItemSheet { static DEFAULT_OPTIONS = { classes: ['consumable'], position: { width: 550 } diff --git a/module/applications/sheets/items/domainCard.mjs b/module/applications/sheets/items/domainCard.mjs index 17a83f95..285573cc 100644 --- a/module/applications/sheets/items/domainCard.mjs +++ b/module/applications/sheets/items/domainCard.mjs @@ -1,7 +1,6 @@ -import DHItemSheetV2 from '../item.mjs'; +import DHBaseItemSheet from '../api/base-item.mjs'; -const { ItemSheetV2 } = foundry.applications.sheets; -export default class DomainCardSheet extends DHItemSheetV2(ItemSheetV2) { +export default class DomainCardSheet extends DHBaseItemSheet { static DEFAULT_OPTIONS = { classes: ['domain-card'], position: { width: 450, height: 700 } diff --git a/module/applications/sheets/items/feature.mjs b/module/applications/sheets/items/feature.mjs index 40fa9b02..c312b7b6 100644 --- a/module/applications/sheets/items/feature.mjs +++ b/module/applications/sheets/items/feature.mjs @@ -1,17 +1,11 @@ -import DHItemSheetV2 from '../item.mjs'; - -const { ItemSheetV2 } = foundry.applications.sheets; -export default class FeatureSheet extends DHItemSheetV2(ItemSheetV2) { - constructor(options = {}) { - super(options); - - this.selectedEffectType = null; - } +import DHBaseItemSheet from '../api/base-item.mjs'; +export default class FeatureSheet extends DHBaseItemSheet { + /** @inheritDoc */ static DEFAULT_OPTIONS = { id: 'daggerheart-feature', classes: ['feature'], - position: { width: 600, height: 600 }, + position: { height: 600 }, window: { resizable: true }, actions: { addEffect: this.addEffect, @@ -19,6 +13,7 @@ export default class FeatureSheet extends DHItemSheetV2(ItemSheetV2) { } }; + /**@override */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/feature/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, @@ -37,58 +32,85 @@ export default class FeatureSheet extends DHItemSheetV2(ItemSheetV2) { } }; + /** + * Internally tracks the selected effect type from the select. + * @type {String} + * @private + */ + _selectedEffectType; + + /**@override */ static TABS = { - ...super.TABS, - effects: { - active: false, - cssClass: '', - group: 'primary', - id: 'effects', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Effects' + primary: { + tabs: [ + { id: 'description' }, + { id: 'actions' }, + { id: 'settings' }, + { id: 'effects' } + ], + initial: "description", + labelPrefix: "DAGGERHEART.Sheets.TABS" } }; + /**@inheritdoc*/ _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); - $(htmlElement).find('.effect-select').on('change', this.effectSelect.bind(this)); + if (partId === "effects") + htmlElement.querySelector('.effect-select')?.addEventListener('change', this._effectSelect.bind(this)); } + + /** + * Handles selection of a new effect type. + * @param {Event} event - Change Event + */ + _effectSelect(event) { + const value = event.currentTarget.value; + this._selectedEffectType = value; + this.render({ parts: ["effects"] }); + } + + /**@inheritdoc */ async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - context.generalConfig = SYSTEM.GENERAL; - context.itemConfig = SYSTEM.ITEM; - context.properties = SYSTEM.ACTOR.featureProperties; - context.dice = SYSTEM.GENERAL.diceTypes; - context.selectedEffectType = this.selectedEffectType; - context.effectConfig = SYSTEM.EFFECTS; + context.properties = CONFIG.daggerheart.ACTOR.featureProperties; + context.dice = CONFIG.daggerheart.GENERAL.diceTypes; + context.effectConfig = CONFIG.daggerheart.EFFECTS; + + context.selectedEffectType = this._selectedEffectType; return context; } - effectSelect(event) { - this.selectedEffectType = event.currentTarget.value; - this.render(true); - } - static async addEffect() { - if (!this.selectedEffectType) return; - - const { id, name, ...rest } = SYSTEM.EFFECTS.effectTypes[this.selectedEffectType]; - const update = { - [foundry.utils.randomID()]: { - type: this.selectedEffectType, + /** + * Adds a new effect to the item, based on the selected effect type. + * @param {PointerEvent} _event - The originating click event + * @param {HTMLElement} _target - The capturing HTML element which defines the [data-action] + * @returns + */ + static async addEffect(_event, _target) { + const type = this._selectedEffectType; + if (!type) return; + const { id, name, ...rest } = CONFIG.daggerheart.EFFECTS.effectTypes[type]; + await this.item.update({ + [`system.effects.${foundry.utils.randomID()}`]: { + type, value: '', ...rest } - }; - await this.item.update({ 'system.effects': update }); + }); } - static async removeEffect(_, button) { - const path = `system.effects.-=${button.dataset.effect}`; + /** + * Removes an effect from the item. + * @param {PointerEvent} _event - The originating click event + * @param {HTMLElement} target - The capturing HTML element which defines the [data-action] + * @returns + */ + static async removeEffect(_event, target) { + const path = `system.effects.-=${target.dataset.effect}`; await this.item.update({ [path]: null }); } } diff --git a/module/applications/sheets/items/miscellaneous.mjs b/module/applications/sheets/items/miscellaneous.mjs index dd22d216..4ef74b5c 100644 --- a/module/applications/sheets/items/miscellaneous.mjs +++ b/module/applications/sheets/items/miscellaneous.mjs @@ -1,7 +1,6 @@ -import DHItemSheetV2 from '../item.mjs'; +import DHBaseItemSheet from '../api/base-item.mjs'; -const { ItemSheetV2 } = foundry.applications.sheets; -export default class MiscellaneousSheet extends DHItemSheetV2(ItemSheetV2) { +export default class MiscellaneousSheet extends DHBaseItemSheet { static DEFAULT_OPTIONS = { classes: ['miscellaneous'], position: { width: 550 } diff --git a/module/applications/sheets/items/subclass.mjs b/module/applications/sheets/items/subclass.mjs index e6e9725f..823a953a 100644 --- a/module/applications/sheets/items/subclass.mjs +++ b/module/applications/sheets/items/subclass.mjs @@ -1,12 +1,10 @@ +import DHBaseItemSheet from '../api/base-item.mjs'; import { actionsTypes } from '../../../data/_module.mjs'; import DHActionConfig from '../../config/Action.mjs'; -import DhpApplicationMixin from '../daggerheart-sheet.mjs'; -const { ItemSheetV2 } = foundry.applications.sheets; -export default class SubclassSheet extends DhpApplicationMixin(ItemSheetV2) { +export default class SubclassSheet extends DHBaseItemSheet { static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'subclass'], + classes: ['subclass'], position: { width: 600 }, window: { resizable: false }, actions: { @@ -14,11 +12,6 @@ export default class SubclassSheet extends DhpApplicationMixin(ItemSheetV2) { editFeature: this.editFeature, deleteFeature: this.deleteFeature }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false - } }; static PARTS = { @@ -35,45 +28,17 @@ export default class SubclassSheet extends DhpApplicationMixin(ItemSheetV2) { } }; + /** @inheritdoc */ static TABS = { - description: { - active: true, - cssClass: '', - group: 'primary', - id: 'description', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Description' - }, - features: { - active: false, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Features' - }, - settings: { - active: false, - cssClass: '', - group: 'primary', - id: 'settings', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings' + primary: { + tabs: [ + { id: 'description' }, + { id: 'features' }, + { id: 'settings' } + ], + initial: "description", + labelPrefix: "DAGGERHEART.Sheets.TABS" } - }; - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.config = CONFIG.daggerheart; - context.tabs = super._getTabs(this.constructor.TABS); - - return context; - } - - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); } static addFeature(_, target) { @@ -93,9 +58,9 @@ export default class SubclassSheet extends DhpApplicationMixin(ItemSheetV2) { async #selectActionType() { const content = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/views/actionType.hbs', - { types: SYSTEM.ACTIONS.actionTypes } - ), + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), title = 'Select Action Type', type = 'form', data = {}; diff --git a/module/applications/sheets/items/weapon.mjs b/module/applications/sheets/items/weapon.mjs index b381e8bf..70f82f68 100644 --- a/module/applications/sheets/items/weapon.mjs +++ b/module/applications/sheets/items/weapon.mjs @@ -1,13 +1,14 @@ +import DHBaseItemSheet from '../api/base-item.mjs'; import { weaponFeatures } from '../../../config/itemConfig.mjs'; import { tagifyElement } from '../../../helpers/utils.mjs'; -import DHItemSheetV2 from '../item.mjs'; -const { ItemSheetV2 } = foundry.applications.sheets; -export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) { +export default class WeaponSheet extends DHBaseItemSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['weapon'] }; + /**@override */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/weapon/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, @@ -22,6 +23,7 @@ export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) { } }; + /**@inheritdoc */ async _preparePartContext(partId, context) { super._preparePartContext(partId, context); @@ -34,15 +36,20 @@ export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) { return context; } + /**@inheritdoc */ _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); const featureInput = htmlElement.querySelector('.features-input'); - tagifyElement(featureInput, weaponFeatures, this.onFeatureSelect.bind(this)); + tagifyElement(featureInput, weaponFeatures, WeaponSheet.onFeatureSelect.bind(this)); } - async onFeatureSelect(features) { - await this.document.update({ 'system.features': features.map(x => ({ value: x.value })) }); - this.render(true); + /** + * Callback function used by `tagifyElement`. + * @param {Array} selectedOptions - The currently selected tag objects. + */ + static async onFeatureSelect(selectedOptions) { + await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) }); + this.render({ force: false, parts: ["settings"] }); } } diff --git a/templates/sheets/items/feature/effects.hbs b/templates/sheets/items/feature/effects.hbs index 68fed719..52beb934 100755 --- a/templates/sheets/items/feature/effects.hbs +++ b/templates/sheets/items/feature/effects.hbs @@ -8,15 +8,15 @@ {{localize "DAGGERHEART.Sheets.Feature.effects.addEffect"}}
- - + +
- {{#each this.document.system.effects as |effect key|}} + {{#each document.system.effects as |effect key|}}
{{localize (concat 'DAGGERHEART.Effects.Types.' effect.type '.Name')}} @@ -32,8 +32,8 @@ {{selectOptions effect.applyLocationChoices selected=effect.appliesOn localize=true}} {{/if}} - {{#if (eq effect.valueType ../this.effectConfig.valueTypes.numberString.id)}} - {{#if (eq effect.type ../this.effectConfig.effectTypes.damage.id) }} + {{#if (eq effect.valueType @root.effectConfig.valueTypes.numberString.id)}} + {{#if (eq effect.type @root.effectConfig.effectTypes.damage.id) }} {{localize "DAGGERHEART.Sheets.Feature.effects.value"}} @@ -47,7 +47,7 @@ {{/if}} {{/if}} - {{#if (eq effect.valueType ../this.effectConfig.valueTypes.select.id)}} + {{#if (eq effect.valueType @root.effectConfig.valueTypes.select.id)}} {{localize effect.valueData.fromValue}} diff --git a/templates/sheets/items/feature/settings.hbs b/templates/sheets/items/feature/settings.hbs index cca672ea..dc5cb884 100755 --- a/templates/sheets/items/feature/settings.hbs +++ b/templates/sheets/items/feature/settings.hbs @@ -22,13 +22,13 @@ {{localize "DAGGERHEART.Sheets.Feature.ValueType.Title"}} {{localize "DAGGERHEART.Sheets.Feature.ValueType.Title"}} {{#if (eq document.system.featureType.type "dice")}} {{localize "DAGGERHEART.Sheets.Feature.ValueType.Dice"}} {{localize "DAGGERHEART.Feature.Max"}} @@ -36,7 +36,7 @@ {{localize "DAGGERHEART.Sheets.Feature.ValueType.Property"}} {{/if}}
From 6e0d82e4f977cbd64745684ef08a84595d91b5fc Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Tue, 24 Jun 2025 15:29:50 -0300 Subject: [PATCH 02/17] prettier refacor FIX: missing imports --- module/applications/sheets/api/base-item.mjs | 10 ++++----- module/applications/sheets/items/armor.mjs | 6 +++--- module/applications/sheets/items/feature.mjs | 21 +++++++------------ module/applications/sheets/items/subclass.mjs | 20 +++++++----------- module/applications/sheets/items/weapon.mjs | 2 +- 5 files changed, 23 insertions(+), 36 deletions(-) diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index e6fcf26a..b85f9696 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -1,4 +1,6 @@ import DHApplicationMixin from "./application-mixin.mjs"; +import { actionsTypes } from "../../../data/_module.mjs"; +import DHActionConfig from "../../config/Action.mjs"; const { ItemSheetV2 } = foundry.applications.sheets; @@ -77,13 +79,11 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { * @param {HTMLElement} _button - The capturing HTML element which defines the [data-action="addAction"] */ static async #addAction(_event, _button) { - const actionType = await DHBaseItemSheet.selectActionType(), - actionIndexes = this.document.system.actions.map(x => x._id.split('-')[2]).sort((a, b) => a - b); + const actionType = await DHBaseItemSheet.selectActionType() try { const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, action = new cls( { - // id: `${this.document.id}-Action-${actionIndexes.length > 0 ? actionIndexes[0] + 1 : 1}` _id: foundry.utils.randomID(), type: actionType.type, name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name), @@ -94,9 +94,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { } ); await this.document.update({ 'system.actions': [...this.document.system.actions, action] }); - await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render( - true - ); + await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render({force: true}); } catch (error) { console.log(error); } diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index 4cc05fe2..4098d376 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -48,8 +48,8 @@ export default class ArmorSheet extends DHBaseItemSheet { * Callback function used by `tagifyElement`. * @param {Array} selectedOptions - The currently selected tag objects. */ - static async onFeatureSelect(selectedOptions ) { - await this.document.update({ 'system.features': selectedOptions .map(x => ({ value: x.value })) }); - this.render({force: false, parts: ["settings"]}); + static async onFeatureSelect(selectedOptions) { + await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) }); + this.render({ force: false, parts: ['settings'] }); } } diff --git a/module/applications/sheets/items/feature.mjs b/module/applications/sheets/items/feature.mjs index c312b7b6..077186f1 100644 --- a/module/applications/sheets/items/feature.mjs +++ b/module/applications/sheets/items/feature.mjs @@ -42,25 +42,19 @@ export default class FeatureSheet extends DHBaseItemSheet { /**@override */ static TABS = { primary: { - tabs: [ - { id: 'description' }, - { id: 'actions' }, - { id: 'settings' }, - { id: 'effects' } - ], - initial: "description", - labelPrefix: "DAGGERHEART.Sheets.TABS" + tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'settings' }, { id: 'effects' }], + initial: 'description', + labelPrefix: 'DAGGERHEART.Sheets.TABS' } }; /**@inheritdoc*/ _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); - if (partId === "effects") + if (partId === 'effects') htmlElement.querySelector('.effect-select')?.addEventListener('change', this._effectSelect.bind(this)); } - /** * Handles selection of a new effect type. * @param {Event} event - Change Event @@ -68,7 +62,7 @@ export default class FeatureSheet extends DHBaseItemSheet { _effectSelect(event) { const value = event.currentTarget.value; this._selectedEffectType = value; - this.render({ parts: ["effects"] }); + this.render({ parts: ['effects'] }); } /**@inheritdoc */ @@ -83,12 +77,11 @@ export default class FeatureSheet extends DHBaseItemSheet { return context; } - /** * Adds a new effect to the item, based on the selected effect type. * @param {PointerEvent} _event - The originating click event * @param {HTMLElement} _target - The capturing HTML element which defines the [data-action] - * @returns + * @returns */ static async addEffect(_event, _target) { const type = this._selectedEffectType; @@ -107,7 +100,7 @@ export default class FeatureSheet extends DHBaseItemSheet { * Removes an effect from the item. * @param {PointerEvent} _event - The originating click event * @param {HTMLElement} target - The capturing HTML element which defines the [data-action] - * @returns + * @returns */ static async removeEffect(_event, target) { const path = `system.effects.-=${target.dataset.effect}`; diff --git a/module/applications/sheets/items/subclass.mjs b/module/applications/sheets/items/subclass.mjs index 823a953a..1aeb6063 100644 --- a/module/applications/sheets/items/subclass.mjs +++ b/module/applications/sheets/items/subclass.mjs @@ -11,7 +11,7 @@ export default class SubclassSheet extends DHBaseItemSheet { addFeature: this.addFeature, editFeature: this.editFeature, deleteFeature: this.deleteFeature - }, + } }; static PARTS = { @@ -31,15 +31,11 @@ export default class SubclassSheet extends DHBaseItemSheet { /** @inheritdoc */ static TABS = { primary: { - tabs: [ - { id: 'description' }, - { id: 'features' }, - { id: 'settings' } - ], - initial: "description", - labelPrefix: "DAGGERHEART.Sheets.TABS" + tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }], + initial: 'description', + labelPrefix: 'DAGGERHEART.Sheets.TABS' } - } + }; static addFeature(_, target) { if (target.dataset.type === 'action') this.addAction(target.dataset.level); @@ -58,9 +54,9 @@ export default class SubclassSheet extends DHBaseItemSheet { async #selectActionType() { const content = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/views/actionType.hbs', - { types: SYSTEM.ACTIONS.actionTypes } - ), + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), title = 'Select Action Type', type = 'form', data = {}; diff --git a/module/applications/sheets/items/weapon.mjs b/module/applications/sheets/items/weapon.mjs index 70f82f68..ab018943 100644 --- a/module/applications/sheets/items/weapon.mjs +++ b/module/applications/sheets/items/weapon.mjs @@ -50,6 +50,6 @@ export default class WeaponSheet extends DHBaseItemSheet { */ static async onFeatureSelect(selectedOptions) { await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) }); - this.render({ force: false, parts: ["settings"] }); + this.render({ force: false, parts: ['settings'] }); } } From 61ea8b85fb27f5378aaa75a75c0c1c844964d697 Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Wed, 25 Jun 2025 11:57:19 -0300 Subject: [PATCH 03/17] FEAT: add DHHeritageSheet --- module/applications/sheets/api/_modules.mjs | 5 +- .../sheets/api/application-mixin.mjs | 198 +++++++++------- module/applications/sheets/api/base-item.mjs | 221 +++++++++--------- .../sheets/api/heritage-sheet.mjs | 31 +++ module/applications/sheets/items/ancestry.mjs | 5 +- module/applications/sheets/items/class.mjs | 18 +- .../applications/sheets/items/community.mjs | 7 +- 7 files changed, 276 insertions(+), 209 deletions(-) create mode 100644 module/applications/sheets/api/heritage-sheet.mjs diff --git a/module/applications/sheets/api/_modules.mjs b/module/applications/sheets/api/_modules.mjs index d5ef290d..4316da2a 100644 --- a/module/applications/sheets/api/_modules.mjs +++ b/module/applications/sheets/api/_modules.mjs @@ -1,2 +1,3 @@ -export {default as DHApplicationMixin} from "./application-mixin.mjs"; -export {default as DHBaseItemSheet} from "./base-item.mjs"; +export { default as DHApplicationMixin } from './application-mixin.mjs'; +export { default as DHBaseItemSheet } from './base-item.mjs'; +export { default as DHHeritageSheet } from './heritage-sheet.mjs'; diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index d45cafe6..12b55bd6 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -17,86 +17,128 @@ const { HandlebarsApplicationMixin } = foundry.applications.api; * @returns {BaseDocumentSheet} */ export default function DHApplicationMixin(Base) { - class DHSheetV2 extends HandlebarsApplicationMixin(Base) { - /** - * @param {DHSheetV2Configuration} [options={}] - */ - constructor(options = {}) { - super(options); - /** - * @type {foundry.applications.ux.DragDrop[]} - * @private - */ - this._dragDrop = this._createDragDropHandlers(); - } + class DHSheetV2 extends HandlebarsApplicationMixin(Base) { + /** + * @param {DHSheetV2Configuration} [options={}] + */ + constructor(options = {}) { + super(options); + /** + * @type {foundry.applications.ux.DragDrop[]} + * @private + */ + this._dragDrop = this._createDragDropHandlers(); + } - /** - * The default options for the sheet. - * @type {DHSheetV2Configuration} - */ - static DEFAULT_OPTIONS = { - classes: ['daggerheart', 'sheet', 'dh-style'], - position: { - width: 480, - height: 'auto' - }, - - dragDrop: [] - }; - - /* -------------------------------------------- */ - - /**@inheritdoc */ - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - this._dragDrop.forEach(d => d.bind(htmlElement)); - } - - /* -------------------------------------------- */ - /* Drag and Drop */ - /* -------------------------------------------- */ - - /** - * Creates drag-drop handlers from the configured options. - * @returns {foundry.applications.ux.DragDrop[]} - * @private - */ - _createDragDropHandlers() { - return this.options.dragDrop.map(d => { - d.callbacks = { - drop: this._onDrop.bind(this) + /** + * The default options for the sheet. + * @type {DHSheetV2Configuration} + */ + static DEFAULT_OPTIONS = { + classes: ['daggerheart', 'sheet', 'dh-style'], + position: { + width: 480, + height: 'auto' + }, + actions: { + addEffect: DHSheetV2.#addEffect, + editEffect: DHSheetV2.#editEffect, + removeEffect: DHSheetV2.#removeEffect + }, + dragDrop: [] }; - return new foundry.applications.ux.DragDrop.implementation(d); - }); + + /* -------------------------------------------- */ + + /**@inheritdoc */ + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + this._dragDrop.forEach(d => d.bind(htmlElement)); + } + + /* -------------------------------------------- */ + /* Drag and Drop */ + /* -------------------------------------------- */ + + /** + * Creates drag-drop handlers from the configured options. + * @returns {foundry.applications.ux.DragDrop[]} + * @private + */ + _createDragDropHandlers() { + return this.options.dragDrop.map(d => { + d.callbacks = { + drop: this._onDrop.bind(this) + }; + return new foundry.applications.ux.DragDrop.implementation(d); + }); + } + + /** + * Handle drop event. + * @param {DragEvent} event + * @protected + */ + _onDrop(event) {} + + /* -------------------------------------------- */ + /* Prepare Context */ + /* -------------------------------------------- */ + + /** + * Prepare the template context. + * @param {object} options + * @param {string} [objectPath='document'] + * @returns {Promise} + * @inheritdoc + */ + async _prepareContext(options, objectPath = 'document') { + const context = await super._prepareContext(options); + context.config = CONFIG.daggerheart; + context.source = this[objectPath]; + context.fields = this[objectPath].schema.fields; + context.systemFields = this[objectPath].system ? this[objectPath].system.schema.fields : {}; + return context; + } + + /* -------------------------------------------- */ + /* Application Clicks Actions */ + /* -------------------------------------------- */ + + /** + * Renders an ActiveEffect's sheet sheet. + * @param {PointerEvent} event - The originating click event + * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] + */ + static async #addEffect() { + const cls = foundry.documents.ActiveEffect; + await cls.create( + { + name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize(cls.metadata.label) }) + }, + { parent: this.document } + ); + } + + /** + * Renders an ActiveEffect's sheet sheet. + * @param {PointerEvent} event - The originating click event + * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] + */ + static async #editEffect(_event, button) { + const effect = this.document.effects.get(button.dataset.effect); + effect.sheet.render({ force: true }); + } + + /** + * Delete an ActiveEffect from the item. + * @param {PointerEvent} _event - The originating click event + * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] + */ + static async #removeEffect(_event, button) { + await this.document.effects.get(button.dataset.effect).delete(); + } } - /** - * Handle drop event. - * @param {DragEvent} event - * @protected - */ - _onDrop(event) { } - - /* -------------------------------------------- */ - /* Prepare Context */ - /* -------------------------------------------- */ - - /** - * Prepare the template context. - * @param {object} options - * @param {string} [objectPath='document'] - * @returns {Promise} - * @inheritdoc - */ - async _prepareContext(options, objectPath = 'document') { - const context = await super._prepareContext(options); - context.config = CONFIG.daggerheart; - context.source = this[objectPath]; - context.fields = this[objectPath].schema.fields; - context.systemFields = this[objectPath].system ? this[objectPath].system.schema.fields : {}; - return context; - } - } - - return DHSheetV2; + return DHSheetV2; } diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index b85f9696..020b482d 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -1,6 +1,6 @@ -import DHApplicationMixin from "./application-mixin.mjs"; -import { actionsTypes } from "../../../data/_module.mjs"; -import DHActionConfig from "../../config/Action.mjs"; +import DHApplicationMixin from './application-mixin.mjs'; +import { actionsTypes } from '../../../data/_module.mjs'; +import DHActionConfig from '../../config/Action.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; @@ -10,118 +10,115 @@ const { ItemSheetV2 } = foundry.applications.sheets; * @mixes DHSheetV2 */ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { - /** @inheritDoc */ - static DEFAULT_OPTIONS = { - classes: ['item'], - position: { width: 600 }, - form: { - submitOnChange: true - }, - actions: { - addAction: DHBaseItemSheet.#addAction, - editAction: DHBaseItemSheet.#editAction, - removeAction: DHBaseItemSheet.#removeAction + /** @inheritDoc */ + static DEFAULT_OPTIONS = { + classes: ['item'], + position: { width: 600 }, + form: { + submitOnChange: true + }, + actions: { + addAction: DHBaseItemSheet.#addAction, + editAction: DHBaseItemSheet.#editAction, + removeAction: DHBaseItemSheet.#removeAction + } + }; + + /* -------------------------------------------- */ + + /** @inheritdoc */ + static TABS = { + primary: { + tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'settings' }], + initial: 'description', + labelPrefix: 'DAGGERHEART.Sheets.TABS' + } + }; + + /* -------------------------------------------- */ + /* Application Clicks Actions */ + /* -------------------------------------------- */ + + /** + * Render a dialog prompting the user to select an action type. + * + * @returns {Promise} An object containing the selected action type. + */ + static async selectActionType() { + const content = await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), + title = 'Select Action Type', //useless var + type = 'form', + data = {}; //useless var + //TODO: use DialogV2 + return Dialog.prompt({ + title, + label: title, + content, + type, //this prop is useless + callback: html => { + const form = html[0].querySelector('form'), + fd = new foundry.applications.ux.FormDataExtended(form); + foundry.utils.mergeObject(data, fd.object, { inplace: true }); + // if (!data.name?.trim()) data.name = game.i18n.localize(SYSTEM.ACTIONS.actionTypes[data.type].name); + return data; + }, + rejectClose: false + }); } - }; - /* -------------------------------------------- */ - - /** @inheritdoc */ - static TABS = { - primary: { - tabs: [ - { id: 'description' }, - { id: 'actions' }, - { id: 'settings' } - ], - initial: "description", - labelPrefix: "DAGGERHEART.Sheets.TABS" + /** + * Add a new action to the item, prompting the user for its type. + * @param {PointerEvent} _event - The originating click event + * @param {HTMLElement} _button - The capturing HTML element which defines the [data-action="addAction"] + */ + static async #addAction(_event, _button) { + const actionType = await DHBaseItemSheet.selectActionType(); + try { + const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, + action = new cls( + { + _id: foundry.utils.randomID(), + type: actionType.type, + name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name), + ...cls.getSourceConfig(this.document) + }, + { + parent: this.document + } + ); + await this.document.update({ 'system.actions': [...this.document.system.actions, action] }); + await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render({ + force: true + }); + } catch (error) { + console.log(error); + } } - } - /* -------------------------------------------- */ - /* Application Clicks Actions */ - /* -------------------------------------------- */ - - /** - * Render a dialog prompting the user to select an action type. - * - * @returns {Promise} An object containing the selected action type. - */ - static async selectActionType() { - const content = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/views/actionType.hbs', - { types: SYSTEM.ACTIONS.actionTypes } - ), - title = 'Select Action Type', //useless var - type = 'form', - data = {}; //useless var - //TODO: use DialogV2 - return Dialog.prompt({ - title, - label: title, - content, - type, //this prop is useless - callback: html => { - const form = html[0].querySelector('form'), - fd = new foundry.applications.ux.FormDataExtended(form); - foundry.utils.mergeObject(data, fd.object, { inplace: true }); - // if (!data.name?.trim()) data.name = game.i18n.localize(SYSTEM.ACTIONS.actionTypes[data.type].name); - return data; - }, - rejectClose: false - }); - } - - /** - * Add a new action to the item, prompting the user for its type. - * @param {PointerEvent} _event - The originating click event - * @param {HTMLElement} _button - The capturing HTML element which defines the [data-action="addAction"] - */ - static async #addAction(_event, _button) { - const actionType = await DHBaseItemSheet.selectActionType() - try { - const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, - action = new cls( - { - _id: foundry.utils.randomID(), - type: actionType.type, - name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name), - ...cls.getSourceConfig(this.document) - }, - { - parent: this.document - } - ); - await this.document.update({ 'system.actions': [...this.document.system.actions, action] }); - await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render({force: true}); - } catch (error) { - console.log(error); + /** + * Edit an existing action on the item + * @param {PointerEvent} _event - The originating click event + * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="editAction"] + */ + static async #editAction(_event, button) { + const action = this.document.system.actions[button.dataset.index]; + await new DHActionConfig(action).render({ force: true }); } - } - /** - * Edit an existing action on the item - * @param {PointerEvent} event - The originating click event - * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="editAction"] - */ - static async #editAction(_event, button) { - const action = this.document.system.actions[button.dataset.index]; - await new DHActionConfig(action).render(true); - } - - /** - * Remove an action from the item. - * @param {PointerEvent} event - The originating click event - * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] - */ - static async #removeAction(event, button) { - event.stopPropagation(); - await this.document.update({ - 'system.actions': this.document.system.actions.filter( - (_, index) => index !== Number.parseInt(button.dataset.index) - ) - }); - } - -} \ No newline at end of file + /** + * Remove an action from the item. + * @param {PointerEvent} event - The originating click event + * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] + */ + static async #removeAction(event, button) { + event.stopPropagation(); + await this.document.update({ + 'system.actions': this.document.system.actions.filter( + (_, index) => index !== Number.parseInt(button.dataset.index) + ) + }); + } +} diff --git a/module/applications/sheets/api/heritage-sheet.mjs b/module/applications/sheets/api/heritage-sheet.mjs new file mode 100644 index 00000000..4938f5b0 --- /dev/null +++ b/module/applications/sheets/api/heritage-sheet.mjs @@ -0,0 +1,31 @@ +import DHBaseItemSheet from './base-item.mjs'; + +export default class DHHeritageSheet extends DHBaseItemSheet { + /**@inheritdoc */ + static DEFAULT_OPTIONS = { + position: { width: 450, height: 700 } + }; + + /**@override */ + static PARTS = { + tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, + description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, + actions: { + template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs', + scrollable: ['.actions'] + }, + effects: { + template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs', + scrollable: ['.effects'] + } + }; + + /** @override*/ + static TABS = { + primary: { + tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'effects' }], + initial: 'description', + labelPrefix: 'DAGGERHEART.Sheets.TABS' + } + }; +} diff --git a/module/applications/sheets/items/ancestry.mjs b/module/applications/sheets/items/ancestry.mjs index 3d20df7a..eb03de99 100644 --- a/module/applications/sheets/items/ancestry.mjs +++ b/module/applications/sheets/items/ancestry.mjs @@ -1,7 +1,6 @@ -import DHHeritageSheetV2 from './heritage.mjs'; +import DHHeritageSheet from '../api/heritage-sheet.mjs'; -const { ItemSheetV2 } = foundry.applications.sheets; -export default class AncestrySheet extends DHHeritageSheetV2(ItemSheetV2) { +export default class AncestrySheet extends DHHeritageSheet { static DEFAULT_OPTIONS = { classes: ['ancestry'] }; diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index f55c2b5f..f91f70fa 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -50,14 +50,11 @@ export default class ClassSheet extends DHBaseItemSheet { /** @inheritdoc */ static TABS = { primary: { - tabs: [ - { id: 'description' }, - { id: 'settings' }, - ], - initial: "description", - labelPrefix: "DAGGERHEART.Sheets.Feature.Tabs" + tabs: [{ id: 'description' }, { id: 'settings' }], + initial: 'description', + labelPrefix: 'DAGGERHEART.Sheets.Feature.Tabs' } - } + }; _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); @@ -72,7 +69,6 @@ export default class ClassSheet extends DHBaseItemSheet { return context; } - onAddTag(e) { if (e.detail.index === 2) { ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.ClassCanOnlyHaveTwoDomains')); @@ -137,9 +133,9 @@ export default class ClassSheet extends DHBaseItemSheet { async selectActionType() { const content = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/views/actionType.hbs', - { types: SYSTEM.ACTIONS.actionTypes } - ), + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), title = 'Select Action Type', type = 'form', data = {}; diff --git a/module/applications/sheets/items/community.mjs b/module/applications/sheets/items/community.mjs index 890ff605..92c3731e 100644 --- a/module/applications/sheets/items/community.mjs +++ b/module/applications/sheets/items/community.mjs @@ -1,11 +1,12 @@ -import DHHeritageSheetV2 from './heritage.mjs'; +import DHHeritageSheet from '../api/heritage-sheet.mjs'; -const { ItemSheetV2 } = foundry.applications.sheets; -export default class CommunitySheet extends DHHeritageSheetV2(ItemSheetV2) { +export default class CommunitySheet extends DHHeritageSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['community'] }; + /**@inheritdoc */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' }, ...super.PARTS From 474177f7d088d52f8f6158483c030f297e337643 Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Wed, 25 Jun 2025 15:10:01 -0300 Subject: [PATCH 04/17] FEAT: add tafify config on DHApplicationMixin --- .../sheets/api/application-mixin.mjs | 76 +++++++++++++++++-- module/applications/sheets/items/armor.mjs | 21 +++-- module/applications/sheets/items/class.mjs | 30 +++----- 3 files changed, 89 insertions(+), 38 deletions(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 12b55bd6..663dd9eb 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -1,14 +1,32 @@ const { HandlebarsApplicationMixin } = foundry.applications.api; +import { tagifyElement } from '../../../helpers/utils.mjs'; /** * @typedef {object} DragDropConfig - * @property {string|null} dragSelector - A CSS selector that identifies draggable elements. - * @property {string|null} dropSelector - A CSS selector that identifies drop targets. - */ - -/** + * @property {string} [dragSelector] - A CSS selector that identifies draggable elements. + * @property {string} [dropSelector] - A CSS selector that identifies drop targets. + * + * @typedef {Object} TagOption + * @property {string} label + * @property {string} [src] + * + * @typedef {object} TagifyConfig + * @property {String} selector - The CSS selector for get the element to transform into a tag input + * @property {Record | (() => Record)} options - Available tag options as key-value pairs + * @property {TagChangeCallback} callback - Callback function triggered when tags change + * @property {TagifyOptions} [tagifyOptions={}] - Additional configuration for Tagify + * + * @callback TagChangeCallback + * @param {Array<{value: string, name: string, src?: string}>} selectedOptions - Current selected tags + * @param {{option: string, removed: boolean}} change - What changed (added/removed tag) + * @param {HTMLElement} inputElement - Original input element + * + * + * @typedef {Object} TagifyOptions + * @property {number} [maxTags] - Maximum number of allowed tags + * * @typedef {import("@client/applications/api/handlebars-application.mjs").HandlebarsRenderOptions} HandlebarsRenderOptions - * @typedef {foundry.applications.types.ApplicationConfiguration & HandlebarsRenderOptions & { dragDrop?: DragDropConfig[] }} DHSheetV2Configuration + * @typedef {foundry.applications.types.ApplicationConfiguration & HandlebarsRenderOptions & { dragDrop?: DragDropConfig[], tagifyConfigs?: TagifyConfig[] }} DHSheetV2Configuration */ /** @@ -45,7 +63,8 @@ export default function DHApplicationMixin(Base) { editEffect: DHSheetV2.#editEffect, removeEffect: DHSheetV2.#removeEffect }, - dragDrop: [] + dragDrop: [], + tagifyConfigs: [] }; /* -------------------------------------------- */ @@ -56,6 +75,49 @@ export default function DHApplicationMixin(Base) { this._dragDrop.forEach(d => d.bind(htmlElement)); } + /**@inheritdoc */ + async _onRender(context, options) { + await super._onRender(context, options); + this._createTagifyElements(this.options.tagifyConfigs); + } + + /** + * Creates Tagify elements from configuration objects + * @param {TagifyConfig[]} tagConfigs - Array of Tagify configuration objects + * @throws {TypeError} If tagConfigs is not an array + * @throws {Error} If required properties are missing in config objects + * @param {TagifyConfig[]} tagConfigs + */ + _createTagifyElements(tagConfigs) { + if (!Array.isArray(tagConfigs)) throw new TypeError('tagConfigs must be an array'); + + tagConfigs.forEach(config => { + try { + const { selector, options, callback, tagifyOptions = {} } = config; + + // Validate required fields + if (!selector || !options || !callback) { + console.warn('Invalid TagifyConfig - missing required properties', config); + return; + } + + // Find target element + const element = rootEl.querySelector(selector); + if (!element) { + console.warn(`Element not found with selector: ${selector}`); + return; + } + // Resolve dynamic options if function provided + const resolvedOptions = typeof options === 'function' ? options.call(this) : options; + + // Initialize Tagify + tagifyElement(element, resolvedOptions, callback.bind(this), tagifyOptions); + } catch (error) { + console.error('Error initializing Tagify:', error, config); + } + }); + } + /* -------------------------------------------- */ /* Drag and Drop */ /* -------------------------------------------- */ diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index 4098d376..dc3643fe 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -1,11 +1,17 @@ import DHBaseItemSheet from '../api/base-item.mjs'; -import { tagifyElement } from '../../../helpers/utils.mjs'; export default class ArmorSheet extends DHBaseItemSheet { /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['armor'], - dragDrop: [{ dragSelector: null, dropSelector: null }] + dragDrop: [{ dragSelector: null, dropSelector: null }], + tagifyConfigs: [ + { + selector: '.features-input', + options: () => CONFIG.daggerheart.ITEM.armorFeatures, + callback: ArmorSheet.#onFeatureSelect + } + ] }; /**@override */ @@ -36,20 +42,11 @@ export default class ArmorSheet extends DHBaseItemSheet { return context; } - /**@inheritdoc */ - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - const featureInput = htmlElement.querySelector('.features-input'); - tagifyElement(featureInput, CONFIG.daggerheart.ITEM.armorFeatures, ArmorSheet.onFeatureSelect.bind(this)); - } - /** * Callback function used by `tagifyElement`. * @param {Array} selectedOptions - The currently selected tag objects. */ - static async onFeatureSelect(selectedOptions) { + static async #onFeatureSelect(selectedOptions) { await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) }); - this.render({ force: false, parts: ['settings'] }); } } diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index f91f70fa..a12e89d2 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -1,6 +1,5 @@ import DHBaseItemSheet from '../api/base-item.mjs'; import { actionsTypes } from '../../../data/_module.mjs'; -import { tagifyElement } from '../../../helpers/utils.mjs'; import DHActionConfig from '../../config/Action.mjs'; const { TextEditor } = foundry.applications.ux; @@ -22,6 +21,13 @@ export default class ClassSheet extends DHBaseItemSheet { removeSecondaryWeapon: this.removeSecondaryWeapon, removeArmor: this.removeArmor }, + tagifyConfigs: [ + { + selector: 'domain-input', + choices: () => CONFIG.daggerheart.DOMAIN.domains, + callback: ClassSheet.#onDomainSelect + } + ], dragDrop: [ { dragSelector: '.suggested-item', dropSelector: null }, { dragSelector: null, dropSelector: '.take-section' }, @@ -56,28 +62,14 @@ export default class ClassSheet extends DHBaseItemSheet { } }; - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - const domainInput = htmlElement.querySelector('.domain-input'); - tagifyElement(domainInput, SYSTEM.DOMAIN.domains, this.onDomainSelect.bind(this)); - } - async _prepareContext(_options) { const context = await super._prepareContext(_options); context.domains = this.document.system.domains; return context; } - onAddTag(e) { - if (e.detail.index === 2) { - ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.ClassCanOnlyHaveTwoDomains')); - } - } - - async onDomainSelect(domains) { + static async #onDomainSelect(domains) { await this.document.update({ 'system.domains': domains.map(x => x.value) }); - this.render(true); } static async removeSubclass(_, button) { @@ -133,9 +125,9 @@ export default class ClassSheet extends DHBaseItemSheet { async selectActionType() { const content = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/views/actionType.hbs', - { types: SYSTEM.ACTIONS.actionTypes } - ), + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), title = 'Select Action Type', type = 'form', data = {}; From 5eab093ac4034140060f23662059895f7b7b59ba Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Wed, 25 Jun 2025 15:20:01 -0300 Subject: [PATCH 05/17] FIX: class tagify selector FEAT: add tagifyCOnfig on weapon sheet FIX: prettier format --- module/applications/sheets/items/ancestry.mjs | 2 + module/applications/sheets/items/class.mjs | 17 +- .../applications/sheets/items/consumable.mjs | 2 + .../applications/sheets/items/domainCard.mjs | 2 + module/applications/sheets/items/heritage.mjs | 147 ------------------ .../sheets/items/miscellaneous.mjs | 2 + module/applications/sheets/items/subclass.mjs | 2 + module/applications/sheets/items/weapon.mjs | 22 ++- 8 files changed, 30 insertions(+), 166 deletions(-) delete mode 100644 module/applications/sheets/items/heritage.mjs diff --git a/module/applications/sheets/items/ancestry.mjs b/module/applications/sheets/items/ancestry.mjs index eb03de99..3636ea62 100644 --- a/module/applications/sheets/items/ancestry.mjs +++ b/module/applications/sheets/items/ancestry.mjs @@ -1,10 +1,12 @@ import DHHeritageSheet from '../api/heritage-sheet.mjs'; export default class AncestrySheet extends DHHeritageSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['ancestry'] }; + /**@inheritdoc */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' }, ...super.PARTS diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index a12e89d2..6b23531c 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -23,7 +23,7 @@ export default class ClassSheet extends DHBaseItemSheet { }, tagifyConfigs: [ { - selector: 'domain-input', + selector: '.domain-input', choices: () => CONFIG.daggerheart.DOMAIN.domains, callback: ClassSheet.#onDomainSelect } @@ -62,14 +62,19 @@ export default class ClassSheet extends DHBaseItemSheet { } }; + /**@inheritdoc */ async _prepareContext(_options) { const context = await super._prepareContext(_options); context.domains = this.document.system.domains; return context; } - static async #onDomainSelect(domains) { - await this.document.update({ 'system.domains': domains.map(x => x.value) }); + /** + * Callback function used by `tagifyElement`. + * @param {Array} selectedOptions - The currently selected tag objects. + */ + static async #onDomainSelect(selectedOptions) { + await this.document.update({ 'system.domains': selectedOptions.map(x => x.value) }); } static async removeSubclass(_, button) { @@ -125,9 +130,9 @@ export default class ClassSheet extends DHBaseItemSheet { async selectActionType() { const content = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/views/actionType.hbs', - { types: SYSTEM.ACTIONS.actionTypes } - ), + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), title = 'Select Action Type', type = 'form', data = {}; diff --git a/module/applications/sheets/items/consumable.mjs b/module/applications/sheets/items/consumable.mjs index 26fdd312..f6fb1ba5 100644 --- a/module/applications/sheets/items/consumable.mjs +++ b/module/applications/sheets/items/consumable.mjs @@ -1,11 +1,13 @@ import DHBaseItemSheet from '../api/base-item.mjs'; export default class ConsumableSheet extends DHBaseItemSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['consumable'], position: { width: 550 } }; + /**@override */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/consumable/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, diff --git a/module/applications/sheets/items/domainCard.mjs b/module/applications/sheets/items/domainCard.mjs index 285573cc..d29c9d18 100644 --- a/module/applications/sheets/items/domainCard.mjs +++ b/module/applications/sheets/items/domainCard.mjs @@ -1,11 +1,13 @@ import DHBaseItemSheet from '../api/base-item.mjs'; export default class DomainCardSheet extends DHBaseItemSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['domain-card'], position: { width: 450, height: 700 } }; + /**@override */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/domainCard/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, diff --git a/module/applications/sheets/items/heritage.mjs b/module/applications/sheets/items/heritage.mjs deleted file mode 100644 index 44d950ca..00000000 --- a/module/applications/sheets/items/heritage.mjs +++ /dev/null @@ -1,147 +0,0 @@ -import { actionsTypes } from '../../../data/_module.mjs'; -import DHActionConfig from '../../config/Action.mjs'; -import DHItemMixin from '../item.mjs'; - -export default function DHHeritageMixin(Base) { - return class DHHeritageSheetV2 extends DHItemMixin(Base) { - static DEFAULT_OPTIONS = { - tag: 'form', - position: { width: 450, height: 700 }, - actions: { - addAction: this.addAction, - editAction: this.editAction, - removeAction: this.removeAction, - addEffect: this.addEffect, - editEffect: this.editEffect, - removeEffect: this.removeEffect - }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false - } - }; - - static PARTS = { - tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, - description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, - actions: { - template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs', - scrollable: ['.actions'] - }, - effects: { - template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs', - scrollable: ['.effects'] - } - }; - - static TABS = { - description: { - active: true, - cssClass: '', - group: 'primary', - id: 'description', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Description' - }, - actions: { - active: false, - cssClass: '', - group: 'primary', - id: 'actions', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Actions' - }, - effects: { - active: false, - cssClass: '', - group: 'primary', - id: 'effects', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Effects' - } - }; - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - - return context; - } - - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } - - async selectActionType() { - const content = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/views/actionType.hbs', - { types: SYSTEM.ACTIONS.actionTypes } - ), - title = 'Select Action Type', - type = 'form', - data = {}; - return Dialog.prompt({ - title, - label: title, - content, - type, - callback: html => { - const form = html[0].querySelector('form'), - fd = new foundry.applications.ux.FormDataExtended(form); - foundry.utils.mergeObject(data, fd.object, { inplace: true }); - return data; - }, - rejectClose: false - }); - } - - static async addAction() { - const actionType = await this.selectActionType(); - const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, - action = new cls( - { - _id: foundry.utils.randomID(), - type: actionType.type, - name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name), - ...cls.getSourceConfig(this.document) - }, - { - parent: this.document - } - ); - await this.document.update({ 'system.actions': [...this.document.system.actions, action] }); - } - - static async editAction(_, button) { - const action = this.document.system.actions[button.dataset.index]; - await new DHActionConfig(action).render(true); - } - - static async removeAction(event, button) { - event.stopPropagation(); - await this.document.update({ - 'system.actions': this.document.system.actions.filter( - (_, index) => index !== Number.parseInt(button.dataset.index) - ) - }); - } - - static async addEffect() { - await this.document.createEmbeddedDocuments('ActiveEffect', [ - { name: game.i18n.localize('DAGGERHEART.Feature.NewEffect') } - ]); - } - - static async editEffect(_, target) { - const effect = this.document.effects.get(target.dataset.effect); - effect.sheet.render(true); - } - - static async removeEffect(_, target) { - await this.document.effects.get(target.dataset.effect).delete(); - } - }; -} diff --git a/module/applications/sheets/items/miscellaneous.mjs b/module/applications/sheets/items/miscellaneous.mjs index 4ef74b5c..62df4e8c 100644 --- a/module/applications/sheets/items/miscellaneous.mjs +++ b/module/applications/sheets/items/miscellaneous.mjs @@ -1,11 +1,13 @@ import DHBaseItemSheet from '../api/base-item.mjs'; export default class MiscellaneousSheet extends DHBaseItemSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['miscellaneous'], position: { width: 550 } }; + /**@override */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/miscellaneous/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, diff --git a/module/applications/sheets/items/subclass.mjs b/module/applications/sheets/items/subclass.mjs index 1aeb6063..c12593ec 100644 --- a/module/applications/sheets/items/subclass.mjs +++ b/module/applications/sheets/items/subclass.mjs @@ -3,6 +3,7 @@ import { actionsTypes } from '../../../data/_module.mjs'; import DHActionConfig from '../../config/Action.mjs'; export default class SubclassSheet extends DHBaseItemSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['subclass'], position: { width: 600 }, @@ -14,6 +15,7 @@ export default class SubclassSheet extends DHBaseItemSheet { } }; + /**@override */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/subclass/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, diff --git a/module/applications/sheets/items/weapon.mjs b/module/applications/sheets/items/weapon.mjs index ab018943..50f30f0f 100644 --- a/module/applications/sheets/items/weapon.mjs +++ b/module/applications/sheets/items/weapon.mjs @@ -1,11 +1,16 @@ import DHBaseItemSheet from '../api/base-item.mjs'; -import { weaponFeatures } from '../../../config/itemConfig.mjs'; -import { tagifyElement } from '../../../helpers/utils.mjs'; export default class WeaponSheet extends DHBaseItemSheet { /**@inheritdoc */ static DEFAULT_OPTIONS = { - classes: ['weapon'] + classes: ['weapon'], + tagifyConfigs: [ + { + selector: '.features-input', + choices: () => CONFIG.daggerheart.ITEM.weaponFeatures, + callback: WeaponSheet.#onFeatureSelect + } + ] }; /**@override */ @@ -36,20 +41,11 @@ export default class WeaponSheet extends DHBaseItemSheet { return context; } - /**@inheritdoc */ - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - const featureInput = htmlElement.querySelector('.features-input'); - tagifyElement(featureInput, weaponFeatures, WeaponSheet.onFeatureSelect.bind(this)); - } - /** * Callback function used by `tagifyElement`. * @param {Array} selectedOptions - The currently selected tag objects. */ - static async onFeatureSelect(selectedOptions) { + static async #onFeatureSelect(selectedOptions) { await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) }); - this.render({ force: false, parts: ['settings'] }); } } From 47097e52514015832b5e560ebf39b2ce5f6e21db Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Wed, 25 Jun 2025 16:24:42 -0300 Subject: [PATCH 06/17] FIX: tagify bug rootEl not exist --- module/applications/sheets/api/application-mixin.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 663dd9eb..42e1615d 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -102,7 +102,7 @@ export default function DHApplicationMixin(Base) { } // Find target element - const element = rootEl.querySelector(selector); + const element = this.element.querySelector(selector); if (!element) { console.warn(`Element not found with selector: ${selector}`); return; From 678e45840f4048b866573ecdd544283d85acc3c1 Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Wed, 25 Jun 2025 17:31:59 -0300 Subject: [PATCH 07/17] FEAT: add effects tab on domainCard sheet --- module/applications/sheets/items/domainCard.mjs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/module/applications/sheets/items/domainCard.mjs b/module/applications/sheets/items/domainCard.mjs index d29c9d18..d7a70741 100644 --- a/module/applications/sheets/items/domainCard.mjs +++ b/module/applications/sheets/items/domainCard.mjs @@ -7,6 +7,15 @@ export default class DomainCardSheet extends DHBaseItemSheet { position: { width: 450, height: 700 } }; + /** @override */ + static TABS = { + primary: { + tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'settings' }, { id: 'effects' }], + initial: 'description', + labelPrefix: 'DAGGERHEART.Sheets.TABS' + } + }; + /**@override */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/domainCard/header.hbs' }, @@ -19,6 +28,10 @@ export default class DomainCardSheet extends DHBaseItemSheet { settings: { template: 'systems/daggerheart/templates/sheets/items/domainCard/settings.hbs', scrollable: ['.settings'] + }, + effects: { + template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs', + scrollable: ['.effects'] } }; } From 18040707640ee11e06f4166a80c941d6d89fec3a Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Wed, 25 Jun 2025 17:32:48 -0300 Subject: [PATCH 08/17] FEAT: simplify the class sheet --- module/applications/sheets/items/class.mjs | 224 +++++++++------------ templates/sheets/items/class/features.hbs | 9 +- templates/sheets/items/class/settings.hbs | 24 +-- templates/views/actionType.hbs | 1 + 4 files changed, 118 insertions(+), 140 deletions(-) diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index 6b23531c..06244972 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -5,21 +5,17 @@ import DHActionConfig from '../../config/Action.mjs'; const { TextEditor } = foundry.applications.ux; export default class ClassSheet extends DHBaseItemSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { - tag: 'form', classes: ['class'], position: { width: 700 }, actions: { - removeSubclass: this.removeSubclass, - viewSubclass: this.viewSubclass, + removeItemFromCollection: ClassSheet.#removeItemFromCollection, + removeSuggestedItem: ClassSheet.#removeSuggestedItem, + viewDoc: ClassSheet.#viewDoc, addFeature: this.addFeature, editFeature: this.editFeature, - deleteFeature: this.deleteFeature, - removeItem: this.removeItem, - viewItem: this.viewItem, - removePrimaryWeapon: this.removePrimaryWeapon, - removeSecondaryWeapon: this.removeSecondaryWeapon, - removeArmor: this.removeArmor + deleteFeature: this.deleteFeature }, tagifyConfigs: [ { @@ -40,9 +36,11 @@ export default class ClassSheet extends DHBaseItemSheet { ] }; + /**@override */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/class/header.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, + description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, features: { template: 'systems/daggerheart/templates/sheets/items/class/features.hbs', scrollable: ['.features'] @@ -56,9 +54,9 @@ export default class ClassSheet extends DHBaseItemSheet { /** @inheritdoc */ static TABS = { primary: { - tabs: [{ id: 'description' }, { id: 'settings' }], + tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }], initial: 'description', - labelPrefix: 'DAGGERHEART.Sheets.Feature.Tabs' + labelPrefix: 'DAGGERHEART.Sheets.TABS' } }; @@ -69,6 +67,8 @@ export default class ClassSheet extends DHBaseItemSheet { return context; } + /* -------------------------------------------- */ + /** * Callback function used by `tagifyElement`. * @param {Array} selectedOptions - The currently selected tag objects. @@ -77,119 +77,7 @@ export default class ClassSheet extends DHBaseItemSheet { await this.document.update({ 'system.domains': selectedOptions.map(x => x.value) }); } - static async removeSubclass(_, button) { - await this.document.update({ - 'system.subclasses': this.document.system.subclasses.filter(x => x.uuid !== button.dataset.subclass) - }); - } - - static async viewSubclass(_, button) { - const subclass = await fromUuid(button.dataset.subclass); - subclass.sheet.render(true); - } - - static async deleteFeature(_, button) { - await this.document.update({ - 'system.features': this.document.system.features.map(x => x.uuid).filter(x => x !== button.dataset.feature) - }); - } - - static async editFeature(_, button) { - const feature = await fromUuid(button.dataset.feature); - feature.sheet.render(true); - } - - static async removeItem(event, button) { - event.stopPropagation(); - const type = button.dataset.type; - const path = `system.inventory.${type}`; - await this.document.update({ - [path]: this.document.system.inventory[type].filter(x => x.uuid !== button.dataset.item) - }); - } - - static async viewItem(_, button) { - const item = await fromUuid(button.dataset.item); - item.sheet.render(true); - } - - static async removePrimaryWeapon(event) { - event.stopPropagation(); - await this.document.update({ 'system.characterGuide.suggestedPrimaryWeapon': null }, { diff: false }); - } - - static async removeSecondaryWeapon(event) { - event.stopPropagation(); - await this.document.update({ 'system.characterGuide.suggestedSecondaryWeapon': null }, { diff: false }); - } - - static async removeArmor(event) { - event.stopPropagation(); - await this.document.update({ 'system.characterGuide.suggestedArmor': null }, { diff: false }); - } - - async selectActionType() { - const content = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/views/actionType.hbs', - { types: SYSTEM.ACTIONS.actionTypes } - ), - title = 'Select Action Type', - type = 'form', - data = {}; - return Dialog.prompt({ - title, - label: title, - content, - type, - callback: html => { - const form = html[0].querySelector('form'), - fd = new foundry.applications.ux.FormDataExtended(form); - foundry.utils.mergeObject(data, fd.object, { inplace: true }); - - return data; - }, - rejectClose: false - }); - } - - getActionPath(type) { - return type === 'hope' ? 'hopeFeatures' : 'classFeatures'; - } - - static async addFeature(_, target) { - const actionPath = this.getActionPath(target.dataset.type); - const actionType = await this.selectActionType(); - const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, - action = new cls( - { - _id: foundry.utils.randomID(), - systemPath: actionPath, - type: actionType.type, - name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name), - ...cls.getSourceConfig(this.document) - }, - { - parent: this.document - } - ); - await this.document.update({ [`system.${actionPath}`]: [...this.document.system[actionPath], action] }); - } - - static async editFeature(_, target) { - const action = this.document.system[this.getActionPath(target.dataset.type)].find( - x => x._id === target.dataset.feature - ); - await new DHActionConfig(action).render(true); - } - - static async deleteFeature(_, target) { - const actionPath = this.getActionPath(target.dataset.type); - await this.document.update({ - [`system.${actionPath}`]: this.document.system[actionPath].filter( - action => action._id !== target.dataset.feature - ) - }); - } + /* -------------------------------------------- */ async _onDrop(event) { const data = TextEditor.getDragEventData(event); @@ -245,4 +133,92 @@ export default class ClassSheet extends DHBaseItemSheet { } } } + + /* -------------------------------------------- */ + /* Application Clicks Actions */ + /* -------------------------------------------- */ + + static async #removeItemFromCollection(_event, element) { + const { uuid, target } = element.dataset; + const prop = foundry.utils.getProperty(this.document.system, target); + await this.document.update({ [target]: prop.filter(i => i.uuid !== uuid) }); + } + + static async #removeSuggestedItem(_event, element) { + const { target } = element.dataset; + await this.document.update({ [`system.characterGuide.${target}`]: null }); + } + + static async #viewDoc(_event, button) { + const doc = await fromUuid(button.dataset.uuid); + doc.sheet.render({ force: true }); + } + + //TODO: redo this + async selectActionType() { + const content = await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), + title = 'Select Action Type', + type = 'form', + data = {}; + return Dialog.prompt({ + title, + label: title, + content, + type, + callback: html => { + const form = html[0].querySelector('form'), + fd = new foundry.applications.ux.FormDataExtended(form); + foundry.utils.mergeObject(data, fd.object, { inplace: true }); + + return data; + }, + rejectClose: false + }); + } + + //TODO: redo this + getActionPath(type) { + return type === 'hope' ? 'hopeFeatures' : 'classFeatures'; + } + + //TODO: redo this + static async addFeature(_, target) { + const actionPath = this.getActionPath(target.dataset.type); + const actionType = await this.selectActionType(); + const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, + action = new cls( + { + _id: foundry.utils.randomID(), + systemPath: actionPath, + type: actionType.type, + name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name), + ...cls.getSourceConfig(this.document) + }, + { + parent: this.document + } + ); + await this.document.update({ [`system.${actionPath}`]: [...this.document.system[actionPath], action] }); + } + + //TODO: redo this + static async editFeature(_, target) { + const action = this.document.system[this.getActionPath(target.dataset.type)].find( + x => x._id === target.dataset.feature + ); + await new DHActionConfig(action).render(true); + } + + //TODO: redo this + static async deleteFeature(_, target) { + const actionPath = this.getActionPath(target.dataset.type); + await this.document.update({ + [`system.${actionPath}`]: this.document.system[actionPath].filter( + action => action._id !== target.dataset.feature + ) + }); + } } diff --git a/templates/sheets/items/class/features.hbs b/templates/sheets/items/class/features.hbs index 8b615121..5d8d8a70 100644 --- a/templates/sheets/items/class/features.hbs +++ b/templates/sheets/items/class/features.hbs @@ -36,16 +36,17 @@
diff --git a/templates/sheets/items/class/settings.hbs b/templates/sheets/items/class/settings.hbs index 1333bc9a..1a211f2b 100644 --- a/templates/sheets/items/class/settings.hbs +++ b/templates/sheets/items/class/settings.hbs @@ -39,11 +39,11 @@ {{localize "DAGGERHEART.Sheets.Class.Guide.SuggestedPrimaryWeaponTitle"}}
{{#if document.system.characterGuide.suggestedPrimaryWeapon}} -
+
{{document.system.characterGuide.suggestedPrimaryWeapon.name}}
- +
{{/if}} @@ -54,11 +54,11 @@ {{localize "DAGGERHEART.Sheets.Class.Guide.SuggestedSecondaryWeaponTitle"}}
{{#if document.system.characterGuide.suggestedSecondaryWeapon}} -
+
{{document.system.characterGuide.suggestedSecondaryWeapon.name}}
- +
{{/if}} @@ -69,11 +69,11 @@ {{localize "DAGGERHEART.Sheets.Class.Guide.SuggestedArmorTitle"}}
{{#if document.system.characterGuide.suggestedArmor}} -
+
{{document.system.characterGuide.suggestedArmor.name}}
- +
{{/if}} @@ -87,11 +87,11 @@ {{localize "DAGGERHEART.Sheets.Class.Guide.Inventory.Take"}}
{{#each source.system.inventory.take}} -
+
{{this.name}}
- +
{{/each}} @@ -102,11 +102,11 @@ {{localize "DAGGERHEART.Sheets.Class.Guide.Inventory.ThenChoose"}}
{{#each source.system.inventory.choiceA}} -
+
{{this.name}}
- +
{{/each}} @@ -117,11 +117,11 @@ {{localize "DAGGERHEART.Sheets.Class.Guide.Inventory.AndEither"}}
{{#each source.system.inventory.choiceB}} -
+
{{this.name}}
- +
{{/each}} diff --git a/templates/views/actionType.hbs b/templates/views/actionType.hbs index 75dceeb7..fdffaabe 100644 --- a/templates/views/actionType.hbs +++ b/templates/views/actionType.hbs @@ -3,6 +3,7 @@ {{#each types}}
  • +
    {{localize tabs.settings.label}} - {{localize "DAGGERHEART.Sheets.Class.Evasion"}} - {{formField systemFields.evasion value=source.system.evasion}} + {{formGroup systemFields.hitPoints value=source.system.hitPoints localize=true}} + {{formGroup systemFields.evasion value=source.system.evasion localize=true}}
    From b4fff7b9e6b88eda14e56b0a3fe6df2beee76585 Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 28 Jun 2025 10:05:53 +0200 Subject: [PATCH 15/17] 174 character context menu (#189) * Set up the contextMenues properly * Fixed double item generation on drop --- lang/en.json | 11 + module/applications/sheets/character.mjs | 195 +++++++++++++----- module/documents/item.mjs | 28 +++ styles/chat.less | 1 + styles/daggerheart.css | 1 + templates/chat/ability-use.hbs | 1 - templates/sheets/actors/character/loadout.hbs | 2 +- templates/sheets/actors/character/sidebar.hbs | 2 +- .../global/partials/domain-card-item.hbs | 6 +- .../partials/inventory-fieldset-items.hbs | 2 +- .../sheets/global/partials/inventory-item.hbs | 10 +- 11 files changed, 197 insertions(+), 62 deletions(-) diff --git a/lang/en.json b/lang/en.json index 205a77e4..9a614b68 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1099,6 +1099,17 @@ "biography": "Biography", "effects": "Effects" }, + "ContextMenu": { + "UseItem": "Use", + "Equip": "Equip", + "Unequip": "Unequip", + "Edit": "Edit", + "Delete": "Delete", + "ToLoadout": "Send to Loadout", + "ToVault": "Send to Vault", + "Consume": "Consume Item", + "SendToChat": "Send To Chat" + }, "Armor": { "Title": "Active Armor" }, diff --git a/module/applications/sheets/character.mjs b/module/applications/sheets/character.mjs index 82679ccd..665c203e 100644 --- a/module/applications/sheets/character.mjs +++ b/module/applications/sheets/character.mjs @@ -43,10 +43,11 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { makeDeathMove: this.makeDeathMove, itemQuantityDecrease: (_, button) => this.setItemQuantity(button, -1), itemQuantityIncrease: (_, button) => this.setItemQuantity(button, 1), - useAbility: this.useAbility, + toChat: this.toChat, useAdvancementCard: this.useAdvancementCard, useAdvancementAbility: this.useAdvancementAbility, toggleEquipItem: this.toggleEquipItem, + toggleVault: this.toggleVault, levelManagement: this.levelManagement, editImage: this._onEditImage }, @@ -58,11 +59,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { submitOnChange: true, closeOnSubmit: false }, - dragDrop: [ - { dragSelector: null, dropSelector: '.weapon-section' }, - { dragSelector: null, dropSelector: '.armor-section' }, - { dragSelector: '.item-list .item', dropSelector: null } - ] + dragDrop: [] }; static PARTS = { @@ -214,6 +211,109 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { return { primary: primaryTabs, secondary: secondaryTabs }; } + async _onFirstRender(context, options) { + await super._onFirstRender(context, options); + + this._createContextMenues(); + } + + _createContextMenues() { + const allOptions = { + useItem: { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.UseItem', + icon: '', + callback: this.constructor.useItem.bind(this) + }, + equip: { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Equip', + icon: '', + condition: el => { + const item = foundry.utils.fromUuidSync(el.dataset.uuid); + return !item.system.equipped; + }, + callback: this.constructor.toggleEquipItem.bind(this) + }, + unequip: { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Unequip', + icon: '', + condition: el => { + const item = foundry.utils.fromUuidSync(el.dataset.uuid); + return item.system.equipped; + }, + callback: this.constructor.toggleEquipItem.bind(this) + }, + edit: { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Edit', + icon: '', + callback: this.constructor.viewObject.bind(this) + }, + delete: { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Delete', + icon: '', + callback: this.constructor.deleteItem.bind(this) + }, + sendToLoadout: { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToLoadout', + icon: '', + condition: el => { + const item = foundry.utils.fromUuidSync(el.dataset.uuid); + return item.system.inVault; + }, + callback: this.constructor.toggleVault.bind(this) + }, + sendToVault: { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToVault', + icon: '', + condition: el => { + const item = foundry.utils.fromUuidSync(el.dataset.uuid); + return !item.system.inVault; + }, + callback: this.constructor.toggleVault.bind(this) + }, + sendToChat: { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.SendToChat', + icon: '', + callback: this.constructor.toChat.bind(this) + } + }; + + const getMenuOptions = type => () => { + let menuItems = ['class', 'subclass'].includes(type) ? [] : [allOptions.useItem]; + switch (type) { + case 'weapon': + case 'armor': + menuItems.push(...[allOptions.equip, allOptions.unequip]); + break; + case 'domainCard': + menuItems.push(...[allOptions.sendToLoadout, allOptions.sendToVault]); + break; + } + menuItems.push(...[allOptions.sendToChat, allOptions.edit, allOptions.delete]); + + return menuItems; + }; + + const menuConfigs = [ + 'armor', + 'weapon', + 'miscellaneous', + 'consumable', + 'domainCard', + 'miscellaneous', + 'ancestry', + 'community', + 'class', + 'subclass' + ]; + menuConfigs.forEach(type => { + this._createContextMenu(getMenuOptions(type), `.${type}-context-menu`, { + eventName: 'click', + parentClassHooks: false, + fixed: true + }); + }); + } + _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); @@ -544,20 +644,14 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { (await game.packs.get('daggerheart.communities'))?.render(true); } - static useItem(event) { - const uuid = event.target.closest('[data-item-id]').dataset.itemId, - item = this.document.items.find(i => i.uuid === uuid); - item.use(event); + static useItem(element, button) { + const id = (button ?? element).closest('a').id, + item = this.document.items.get(id); + item.use({}); } - static async viewObject(_, button) { - const object = await fromUuid(button.dataset.value); - if (!object) return; - - const tab = button.dataset.tab; - if (tab && object.sheet._tabs) object.sheet._tabs[0].active = tab; - - if (object.sheet.editMode) object.sheet.editMode = false; + static async viewObject(element, button) { + const object = await fromUuid((button ?? element).dataset.uuid); object.sheet.render(true); } @@ -620,8 +714,8 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { this.render(); } - static async deleteItem(_, button) { - const item = await fromUuid($(button).closest('[data-item-id]')[0].dataset.itemId); + static async deleteItem(element, button) { + const item = await fromUuid((button ?? element).closest('a').dataset.uuid); await item.delete(); } @@ -655,35 +749,29 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { cls.create(msg.toObject()); } - static async useAbility(_, button) { - const item = await fromUuid(button.dataset.feature); - const type = button.dataset.type; + static async toChat(element, 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.description} ${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/chat/ability-use.hbs', + systemData + ) + }); - const cls = getDocumentClass('ChatMessage'); - const systemData = { - title: - type === 'ancestry' - ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.AncestryTitle') - : type === 'community' - ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle') - : game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'), - origin: this.document.id, - img: item.img, - name: item.name, - description: item.system.description, - actions: [] - }; - const msg = new cls({ - type: 'abilityUse', - user: game.user.id, - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/chat/ability-use.hbs', - systemData - ) - }); - - cls.create(msg.toObject()); + cls.create(msg.toObject()); + } else { + const item = await fromUuid((button ?? element).dataset.uuid); + item.toChat(this.document.id); + } } static async useAdvancementCard(_, button) { @@ -738,8 +826,9 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { cls.create(msg.toObject()); } - static async toggleEquipItem(_, button) { - const item = this.document.items.get(button.id); + static async toggleEquipItem(element, button) { + const id = (button ?? element).closest('a').id; + const item = this.document.items.get(id); if (item.system.equipped) { await item.update({ 'system.equipped': false }); return; @@ -763,6 +852,12 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { this.render(); } + static async toggleVault(element, button) { + const id = (button ?? element).closest('a').id; + const item = this.document.items.get(id); + await item.update({ 'system.inVault': !item.system.inVault }); + } + async _onDragStart(_, event) { super._onDragStart(event); } diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 54759542..ca86ce98 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -141,4 +141,32 @@ export default class DhpItem extends Item { // Display Item Card in chat return response; } + + async toChat(origin) { + const cls = getDocumentClass('ChatMessage'); + const systemData = { + title: + this.type === 'ancestry' + ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.AncestryTitle') + : this.type === 'community' + ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle') + : game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'), + origin: origin, + img: this.img, + name: this.name, + description: this.system.description, + actions: [] + }; + const msg = new cls({ + type: 'abilityUse', + user: game.user.id, + system: systemData, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/ability-use.hbs', + systemData + ) + }); + + cls.create(msg.toObject()); + } } diff --git a/styles/chat.less b/styles/chat.less index c1de3106..e075274f 100644 --- a/styles/chat.less +++ b/styles/chat.less @@ -283,6 +283,7 @@ h2 { width: 100%; text-align: center; + margin: 0; } } diff --git a/styles/daggerheart.css b/styles/daggerheart.css index c1824583..994293bf 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -1598,6 +1598,7 @@ .daggerheart.chat.domain-card .domain-card-title h2 { width: 100%; text-align: center; + margin: 0; } .daggerheart.chat.domain-card .ability-card-footer { display: flex; diff --git a/templates/chat/ability-use.hbs b/templates/chat/ability-use.hbs index 51d56ace..4ebaa293 100644 --- a/templates/chat/ability-use.hbs +++ b/templates/chat/ability-use.hbs @@ -1,6 +1,5 @@
    -
    {{title}}

    {{name}}

    diff --git a/templates/sheets/actors/character/loadout.hbs b/templates/sheets/actors/character/loadout.hbs index a03f393c..375581f3 100644 --- a/templates/sheets/actors/character/loadout.hbs +++ b/templates/sheets/actors/character/loadout.hbs @@ -23,6 +23,6 @@
    {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.Sheets.PC.Tabs.Loadout') type='domainCard' isGlassy=true cardView='list'}} - {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.Sheets.PC.Tabs.Vault') type='domainCard' isGlassy=true cardView='list'}} + {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.Sheets.PC.Tabs.Vault') type='domainCard' isVault=true isGlassy=true cardView='list'}}
    \ No newline at end of file diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index 8e4891dc..bba7b3e3 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -111,7 +111,7 @@
    {{/each}} diff --git a/templates/sheets/global/partials/domain-card-item.hbs b/templates/sheets/global/partials/domain-card-item.hbs index 9198c8af..55662833 100644 --- a/templates/sheets/global/partials/domain-card-item.hbs +++ b/templates/sheets/global/partials/domain-card-item.hbs @@ -1,5 +1,5 @@
  • - + diff --git a/templates/sheets/global/partials/inventory-fieldset-items.hbs b/templates/sheets/global/partials/inventory-fieldset-items.hbs index d37119d5..f327c268 100644 --- a/templates/sheets/global/partials/inventory-fieldset-items.hbs +++ b/templates/sheets/global/partials/inventory-fieldset-items.hbs @@ -3,7 +3,7 @@
      {{#each document.items as |item|}} {{#if (eq item.type ../type)}} - {{#unless (or (eq ../type 'ancestry') (eq item.type 'class') (eq item.type 'subclass'))}} + {{#unless (or (eq ../type 'ancestry') (eq item.type 'class') (eq item.type 'subclass') (and (eq ../type 'domainCard') (or (and item.system.inVault (not ../isVault)) (and (not item.system.inVault) ../isVault))))}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=item type=../type}} {{/unless}} {{/if}} diff --git a/templates/sheets/global/partials/inventory-item.hbs b/templates/sheets/global/partials/inventory-item.hbs index 093c804f..640a8415 100644 --- a/templates/sheets/global/partials/inventory-item.hbs +++ b/templates/sheets/global/partials/inventory-item.hbs @@ -1,5 +1,5 @@
    • - +
      {{item.name}}
      {{#if (eq type 'weapon')}} @@ -114,17 +114,17 @@ {{/if}} {{#if (eq type 'domainCard')}} {{#unless item.system.inVault}} - + {{else}} - + {{/unless}} {{/if}} - - + +
    • \ No newline at end of file From e3b9dcad2a57b051d93891f97557696c95388dd8 Mon Sep 17 00:00:00 2001 From: Murilo Brito <91566541+moliloo@users.noreply.github.com> Date: Sat, 28 Jun 2025 05:33:24 -0300 Subject: [PATCH 16/17] add death roll in character sheet (#191) --- module/applications/sheets/character.mjs | 1 - styles/less/actors/character/sidebar.less | 31 +++++++++++++++++-- templates/sheets/actors/character/sidebar.hbs | 6 +++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/module/applications/sheets/character.mjs b/module/applications/sheets/character.mjs index 665c203e..c2fc28a6 100644 --- a/module/applications/sheets/character.mjs +++ b/module/applications/sheets/character.mjs @@ -699,7 +699,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { static async makeDeathMove() { if (this.document.system.resources.hitPoints.value === this.document.system.resources.hitPoints.max) { await new DhpDeathMove(this.document).render(true); - await this.minimize(); } } diff --git a/styles/less/actors/character/sidebar.less b/styles/less/actors/character/sidebar.less index b3ac42e0..0a37aff4 100644 --- a/styles/less/actors/character/sidebar.less +++ b/styles/less/actors/character/sidebar.less @@ -12,12 +12,39 @@ background: transparent; } - img { + .portrait { + position: relative; height: 235px; width: 275px; border-bottom: 1px solid light-dark(@dark-blue, @golden); cursor: pointer; - object-fit: cover; + + img { + height: 235px; + width: 275px; + object-fit: cover; + } + + .death-roll-btn { + display: none; + } + + &.death-roll { + filter: grayscale(1); + + .death-roll-btn { + display: flex; + position: absolute; + top: 30%; + right: 30%; + font-size: 6rem; + color: @beige; + + &:hover { + text-shadow: 0 0 8px @beige; + } + } + } } .info-section { diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index bba7b3e3..4060caeb 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -1,5 +1,9 @@