From 8574a1d93ff53941902a04531697f278d614e36d Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Mon, 23 Jun 2025 14:27:27 -0300 Subject: [PATCH 01/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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}}