From 474177f7d088d52f8f6158483c030f297e337643 Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Wed, 25 Jun 2025 15:10:01 -0300 Subject: [PATCH] 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 = {};