From b743c9b62819b15f975b2a8b200e95932eea28cb Mon Sep 17 00:00:00 2001 From: Joaquin Pereyra Date: Wed, 2 Jul 2025 21:51:04 -0300 Subject: [PATCH] REFACTOR: prettier --- module/applications/sheets/character.mjs | 40 +-- module/applications/ux/_module.mjs | 2 +- module/applications/ux/filter-menu.mjs | 421 +++++++++++------------ 3 files changed, 226 insertions(+), 237 deletions(-) diff --git a/module/applications/sheets/character.mjs b/module/applications/sheets/character.mjs index f86cdc34..49d5035f 100644 --- a/module/applications/sheets/character.mjs +++ b/module/applications/sheets/character.mjs @@ -217,7 +217,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { this._createContextMenues(); this._createFilterMenus(); - } /** @inheritDoc */ @@ -474,11 +473,11 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { #filteredItems = { inventory: { search: new Set(), - menu: new Set(), + menu: new Set() }, loadout: { search: new Set(), - menu: new Set(), + menu: new Set() } }; @@ -571,7 +570,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { content: '.items-section', callback: this._onMenuFilterInventory.bind(this), target: '.filter-button', - filters: FilterMenu.invetoryFilters, + filters: FilterMenu.invetoryFilters }, { key: 'loadout', @@ -579,25 +578,23 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { content: '.items-section', callback: this._onMenuFilterLoadout.bind(this), target: '.filter-button', - filters: FilterMenu.cardsFilters, + filters: FilterMenu.cardsFilters } ]; menus.forEach(m => { const container = this.element.querySelector(m.container); this.#menu[m.key] = new FilterMenu(container, m.target, m.filters, m.callback, { - contentSelector: m.content, + contentSelector: m.content }); }); - } - /** * Callback when filters change - * @param {PointerEvent} event - * @param {HTMLElement} html - * @param {import('../ux/filter-menu.mjs').FilterItem[]} filters + * @param {PointerEvent} event + * @param {HTMLElement} html + * @param {import('../ux/filter-menu.mjs').FilterItem[]} filters */ _onMenuFilterInventory(event, html, filters) { this.#filteredItems.inventory.menu.clear(); @@ -605,21 +602,20 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { for (const li of html.querySelectorAll('.inventory-item')) { const item = this.document.items.get(li.dataset.itemId); - const matchesMenu = filters.length === 0 || - filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f)); + const matchesMenu = + filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f)); if (matchesMenu) this.#filteredItems.inventory.menu.add(item.id); const { search } = this.#filteredItems.inventory; li.hidden = !(search.has(item.id) && matchesMenu); } - } /** * Callback when filters change - * @param {PointerEvent} event - * @param {HTMLElement} html - * @param {import('../ux/filter-menu.mjs').FilterItem[]} filters + * @param {PointerEvent} event + * @param {HTMLElement} html + * @param {import('../ux/filter-menu.mjs').FilterItem[]} filters */ _onMenuFilterLoadout(event, html, filters) { this.#filteredItems.loadout.menu.clear(); @@ -627,14 +623,13 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) { const item = this.document.items.get(li.dataset.itemId); - const matchesMenu = filters.length === 0 || - filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f)); + const matchesMenu = + filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f)); if (matchesMenu) this.#filteredItems.loadout.menu.add(item.id); const { search } = this.#filteredItems.loadout; li.hidden = !(search.has(item.id) && matchesMenu); } - } /* -------------------------------------------- */ @@ -915,8 +910,9 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { const cls = getDocumentClass('ChatMessage'); const systemData = { name: game.i18n.localize('DAGGERHEART.General.Experience.Single'), - description: `${experience.description} ${experience.total < 0 ? experience.total : `+${experience.total}` - }` + description: `${experience.description} ${ + experience.total < 0 ? experience.total : `+${experience.total}` + }` }; const msg = new cls({ type: 'abilityUse', diff --git a/module/applications/ux/_module.mjs b/module/applications/ux/_module.mjs index 886358b9..a16a4d6f 100644 --- a/module/applications/ux/_module.mjs +++ b/module/applications/ux/_module.mjs @@ -1 +1 @@ -export { default as FilterMenu} from "./filter-menu.mjs"; \ No newline at end of file +export { default as FilterMenu } from './filter-menu.mjs'; diff --git a/module/applications/ux/filter-menu.mjs b/module/applications/ux/filter-menu.mjs index 4052cf18..0973a358 100644 --- a/module/applications/ux/filter-menu.mjs +++ b/module/applications/ux/filter-menu.mjs @@ -6,239 +6,232 @@ */ export default class FilterMenu extends foundry.applications.ux.ContextMenu { - /** - * Filter Menu - * @param {HTMLElement} container - Container element - * @param {string} selector - CSS selector for menu targets - * @param {Array} menuItems - Array of menu entries - * @param {Function} callback - Callback when filters change - * @param {Object} [options] - Additional options - */ - constructor(container, selector, menuItems, callback, options = {}) { - // Set default options - const mergedOptions = { - eventName: "click", - fixed: true, - ...options - }; + /** + * Filter Menu + * @param {HTMLElement} container - Container element + * @param {string} selector - CSS selector for menu targets + * @param {Array} menuItems - Array of menu entries + * @param {Function} callback - Callback when filters change + * @param {Object} [options] - Additional options + */ + constructor(container, selector, menuItems, callback, options = {}) { + // Set default options + const mergedOptions = { + eventName: 'click', + fixed: true, + ...options + }; - super(container, selector, menuItems, mergedOptions); + super(container, selector, menuItems, mergedOptions); - // Initialize filter states - this.menuItems = menuItems.map(item => ({ - ...item, - enabled: false, - })); + // Initialize filter states + this.menuItems = menuItems.map(item => ({ + ...item, + enabled: false + })); - this.callback = callback; - this.contentElement = container.querySelector(mergedOptions.contentSelector); + this.callback = callback; + this.contentElement = container.querySelector(mergedOptions.contentSelector); - const syntheticEvent = { - type: 'pointerdown', - bubbles: true, - cancelable: true, - pointerType: 'mouse', - isPrimary: true, - button: 0, - }; + const syntheticEvent = { + type: 'pointerdown', + bubbles: true, + cancelable: true, + pointerType: 'mouse', + isPrimary: true, + button: 0 + }; - this.callback(syntheticEvent, this.contentElement, this.getActiveFilterData()); - } - - /** @inheritdoc */ - async render(target, options = {}) { - await super.render(target, { ...options, animate: false }); - - // Create menu structure - const menu = document.createElement("menu"); - menu.className = "filter-menu"; - - // Group items by their group property - const groups = this.#groupItems(this.menuItems); - - // Create sections for each group - for (const [groupName, items] of Object.entries(groups)) { - if (!items.length) continue; - - const section = this.#createSection(groupName, items); - menu.appendChild(section); + this.callback(syntheticEvent, this.contentElement, this.getActiveFilterData()); } - // Update menu and set position - this.element.replaceChildren(menu); + /** @inheritdoc */ + async render(target, options = {}) { + await super.render(target, { ...options, animate: false }); - menu.addEventListener("click", this.#handleClick.bind(this)); + // Create menu structure + const menu = document.createElement('menu'); + menu.className = 'filter-menu'; - this._setPosition(this.element, target, options); + // Group items by their group property + const groups = this.#groupItems(this.menuItems); - if (options.animate !== false) await this._animate(true); - return this._onRender(options); - } + // Create sections for each group + for (const [groupName, items] of Object.entries(groups)) { + if (!items.length) continue; - /** - * Groups an array of items by their `group`. - * @param {Array} items - The array of items to group. Each item is expected to have an optional `group` property. - * @returns {Object>} An object where keys are group names and values are arrays of items belonging to each group. - */ - #groupItems(items) { - return items.reduce((groups, item) => { - const group = item.group ?? "_none"; - groups[group] = groups[group] || []; - groups[group].push(item); - return groups; - }, {}); - } - - /** - * Creates a DOM section element for a group of items with corresponding filter buttons. - * @param {string} groupName - The name of the group, used as the section label. - * @param {Array} items - The items to create buttons for. Each item should have: - * @returns {HTMLDivElement} The section DOM element containing the label and buttons. - */ - #createSection(groupName, items) { - const section = document.createElement("fieldset"); - section.className = "filter-section"; - - const header = document.createElement("legend"); - header.textContent = groupName; - section.appendChild(header); - - const buttons = document.createElement("div"); - buttons.className = "filter-buttons"; - - items.forEach(item => { - const button = document.createElement("button"); - button.className = `filter-button ${item.enabled ? "active" : ""}`; - button.textContent = item.name; - item.element = button; - buttons.appendChild(button); - }); - - section.appendChild(buttons); - return section; - } - - - /** - * Get filter data from active filters - * @returns {Array} Array of filter configurations - */ - getActiveFilterData() { - return this.menuItems.filter(item => item.enabled).map(item => item.filter); - } - - /** - * Handles click events on filter buttons. - * Toggles the active state of the clicked button and updates the corresponding item's `enabled` state. - * Then triggers the provided callback with the event, the content element, and the current active filter data. - * @param {PointerEvent} event - The click event triggered by interacting with a filter button. - * @returns {void} - */ - #handleClick(event) { - event.preventDefault(); - event.stopPropagation(); - - const button = event.target.closest(".filter-button"); - if (!button) return; - - const clickedItem = this.menuItems.find(item => item.element === button); - if (!clickedItem) return; - - const isActive = button.classList.toggle("active"); - clickedItem.enabled = isActive; - - const filters = this.getActiveFilterData(); - - if (filters.length > 0) { - this.target.classList.add("fa-beat", "active"); - } else { - this.target.classList.remove("fa-beat", "active"); - } - - this.callback(event, this.contentElement, filters); - - } - - /** - * Generate and return a sorted array of inventory filters. - * @returns {Array} An array of filter objects, sorted by name within each group. - */ - static get invetoryFilters() { - const { OPERATORS } = foundry.applications.ux.SearchFilter; - - const typesFilters = Object.entries(CONFIG.Item.dataModels) - .filter(([, { metadata }]) => metadata.isInventoryItem) - .map(([type, { metadata }]) => ({ - group: game.i18n.localize("Type"), - name: game.i18n.localize(metadata.label), - filter: { - field: "type", - operator: OPERATORS.EQUALS, - value: type + const section = this.#createSection(groupName, items); + menu.appendChild(section); } - })); - const burdenFilter = Object.values(CONFIG.daggerheart.GENERAL.burden).map(({ value, label }) => ({ - group: game.i18n.localize("DAGGERHEART.Sheets.Weapon.Burden"), - name: game.i18n.localize(label), - filter: { - field: "system.burden", - operator: OPERATORS.EQUALS, - value: value - } - })); + // Update menu and set position + this.element.replaceChildren(menu); - const damageTypeFilter = Object.values(CONFIG.daggerheart.GENERAL.damageTypes).map(({ id, abbreviation }) => ({ - group: "Damage Type", //TODO localize - name: game.i18n.localize(abbreviation), - filter: { - field: "system.damage.type", - operator: OPERATORS.EQUALS, - value: id - } - })); + menu.addEventListener('click', this.#handleClick.bind(this)); + this._setPosition(this.element, target, options); - return [ - ...game.i18n.sortObjects(typesFilters, "name"), - ...game.i18n.sortObjects(burdenFilter, "name"), - ...game.i18n.sortObjects(damageTypeFilter, "name"), - ]; - } + if (options.animate !== false) await this._animate(true); + return this._onRender(options); + } - /** - * Generate and return a sorted array of inventory filters. - * @returns {Array} An array of filter objects, sorted by name within each group. - */ - static get cardsFilters() { - const { OPERATORS } = foundry.applications.ux.SearchFilter; + /** + * Groups an array of items by their `group`. + * @param {Array} items - The array of items to group. Each item is expected to have an optional `group` property. + * @returns {Object>} An object where keys are group names and values are arrays of items belonging to each group. + */ + #groupItems(items) { + return items.reduce((groups, item) => { + const group = item.group ?? '_none'; + groups[group] = groups[group] || []; + groups[group].push(item); + return groups; + }, {}); + } - const typesFilters = Object.values(CONFIG.daggerheart.DOMAIN.cardTypes) - .map(({ id, label }) => ({ - group: game.i18n.localize("Type"), - name: game.i18n.localize(label), - filter: { - field: "system.type", - operator: OPERATORS.EQUALS, - value: id - } - })); + /** + * Creates a DOM section element for a group of items with corresponding filter buttons. + * @param {string} groupName - The name of the group, used as the section label. + * @param {Array} items - The items to create buttons for. Each item should have: + * @returns {HTMLDivElement} The section DOM element containing the label and buttons. + */ + #createSection(groupName, items) { + const section = document.createElement('fieldset'); + section.className = 'filter-section'; - const domainFilter = Object.values(CONFIG.daggerheart.DOMAIN.domains).map(({id, label}) => ({ - group: game.i18n.localize("DAGGERHEART.Sheets.DomainCard.Domain"), - name: game.i18n.localize(label), - filter: { - field: "system.domain", - operator: OPERATORS.EQUALS, - value: id - } - })) + const header = document.createElement('legend'); + header.textContent = groupName; + section.appendChild(header); - const sort = (arr) => game.i18n.sortObjects(arr, "name"); + const buttons = document.createElement('div'); + buttons.className = 'filter-buttons'; - return [ - ...sort(typesFilters), - ...sort(domainFilter), - ]; - } + items.forEach(item => { + const button = document.createElement('button'); + button.className = `filter-button ${item.enabled ? 'active' : ''}`; + button.textContent = item.name; + item.element = button; + buttons.appendChild(button); + }); + + section.appendChild(buttons); + return section; + } + + /** + * Get filter data from active filters + * @returns {Array} Array of filter configurations + */ + getActiveFilterData() { + return this.menuItems.filter(item => item.enabled).map(item => item.filter); + } + + /** + * Handles click events on filter buttons. + * Toggles the active state of the clicked button and updates the corresponding item's `enabled` state. + * Then triggers the provided callback with the event, the content element, and the current active filter data. + * @param {PointerEvent} event - The click event triggered by interacting with a filter button. + * @returns {void} + */ + #handleClick(event) { + event.preventDefault(); + event.stopPropagation(); + + const button = event.target.closest('.filter-button'); + if (!button) return; + + const clickedItem = this.menuItems.find(item => item.element === button); + if (!clickedItem) return; + + const isActive = button.classList.toggle('active'); + clickedItem.enabled = isActive; + + const filters = this.getActiveFilterData(); + + if (filters.length > 0) { + this.target.classList.add('fa-beat', 'active'); + } else { + this.target.classList.remove('fa-beat', 'active'); + } + + this.callback(event, this.contentElement, filters); + } + + /** + * Generate and return a sorted array of inventory filters. + * @returns {Array} An array of filter objects, sorted by name within each group. + */ + static get invetoryFilters() { + const { OPERATORS } = foundry.applications.ux.SearchFilter; + + const typesFilters = Object.entries(CONFIG.Item.dataModels) + .filter(([, { metadata }]) => metadata.isInventoryItem) + .map(([type, { metadata }]) => ({ + group: game.i18n.localize('Type'), + name: game.i18n.localize(metadata.label), + filter: { + field: 'type', + operator: OPERATORS.EQUALS, + value: type + } + })); + + const burdenFilter = Object.values(CONFIG.daggerheart.GENERAL.burden).map(({ value, label }) => ({ + group: game.i18n.localize('DAGGERHEART.Sheets.Weapon.Burden'), + name: game.i18n.localize(label), + filter: { + field: 'system.burden', + operator: OPERATORS.EQUALS, + value: value + } + })); + + const damageTypeFilter = Object.values(CONFIG.daggerheart.GENERAL.damageTypes).map(({ id, abbreviation }) => ({ + group: 'Damage Type', //TODO localize + name: game.i18n.localize(abbreviation), + filter: { + field: 'system.damage.type', + operator: OPERATORS.EQUALS, + value: id + } + })); + + return [ + ...game.i18n.sortObjects(typesFilters, 'name'), + ...game.i18n.sortObjects(burdenFilter, 'name'), + ...game.i18n.sortObjects(damageTypeFilter, 'name') + ]; + } + + /** + * Generate and return a sorted array of inventory filters. + * @returns {Array} An array of filter objects, sorted by name within each group. + */ + static get cardsFilters() { + const { OPERATORS } = foundry.applications.ux.SearchFilter; + + const typesFilters = Object.values(CONFIG.daggerheart.DOMAIN.cardTypes).map(({ id, label }) => ({ + group: game.i18n.localize('Type'), + name: game.i18n.localize(label), + filter: { + field: 'system.type', + operator: OPERATORS.EQUALS, + value: id + } + })); + + const domainFilter = Object.values(CONFIG.daggerheart.DOMAIN.domains).map(({ id, label }) => ({ + group: game.i18n.localize('DAGGERHEART.Sheets.DomainCard.Domain'), + name: game.i18n.localize(label), + filter: { + field: 'system.domain', + operator: OPERATORS.EQUALS, + value: id + } + })); + + const sort = arr => game.i18n.sortObjects(arr, 'name'); + + return [...sort(typesFilters), ...sort(domainFilter)]; + } }