From 9dd773001daf96db95a7f28a252fa671ec3ae199 Mon Sep 17 00:00:00 2001 From: Dapoulp <74197441+Dapoulp@users.noreply.github.com> Date: Thu, 28 Aug 2025 03:29:40 +0200 Subject: [PATCH] Compendium browser per type (#1103) * Compendium Browser per type * Sort number column * Re-add subclass config * Sidebar buttons * Add Characters folder * Css * Done --- daggerheart.mjs | 6 + lang/en.json | 7 +- .../characterCreation/characterCreation.mjs | 10 +- module/applications/levelup/levelup.mjs | 11 +- .../applications/sheets/actors/character.mjs | 14 +- .../sheets/api/application-mixin.mjs | 28 +- module/applications/ui/_module.mjs | 1 + module/applications/ui/itemBrowser.mjs | 206 +++++++--- module/config/itemBrowserConfig.mjs | 353 ++++++++++++------ module/systemRegistration/handlebars.mjs | 3 +- styles/less/global/global.less | 25 ++ styles/less/ui/item-browser/item-browser.less | 19 +- .../sheets/actors/character/inventory.hbs | 3 - templates/ui/itemBrowser/filterContainer.hbs | 22 ++ templates/ui/itemBrowser/itemBrowser.hbs | 70 +--- templates/ui/itemBrowser/itemContainer.hbs | 16 + templates/ui/itemBrowser/sidebar.hbs | 48 +-- 17 files changed, 542 insertions(+), 300 deletions(-) create mode 100644 templates/ui/itemBrowser/filterContainer.hbs create mode 100644 templates/ui/itemBrowser/itemContainer.hbs diff --git a/daggerheart.mjs b/daggerheart.mjs index c9a6b54e..1c4c2a85 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -162,6 +162,9 @@ Hooks.on('ready', async () => { if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).displayFear !== 'hide') ui.resources.render({ force: true }); + if(!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser)) + ui.compendiumBrowser = new applications.ui.ItemBrowser(); + registerCountdownHooks(); socketRegistration.registerSocketHooks(); registerRollDiceHooks(); @@ -305,3 +308,6 @@ Hooks.on('moveToken', async (movedToken, data) => { await effect.value.update({ disabled: effect.disabled }); } }); + +Hooks.on("renderCompendiumDirectory", (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); +Hooks.on("renderDocumentDirectory", (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); diff --git a/lang/en.json b/lang/en.json index d3d707d6..01221e9a 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2423,6 +2423,7 @@ "evasionMax": "Evasion (Max)", "subtype": "Subtype", "folders": { + "characters": "Characters", "adversaries": "Adversaries", "ancestries": "Ancestries", "equipment": "Equipment", @@ -2433,7 +2434,11 @@ "environments": "Environments", "beastforms": "Beastforms", "features": "Features", - "items": "Items" + "items": "Items", + "weapons": "Weapons", + "armors": "Armors", + "consumables": "Consumables", + "loots": "Loots" } }, "Notifications": { diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index ba98ef61..490294cd 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -1,6 +1,5 @@ import { abilities } from '../../config/actorConfig.mjs'; import { burden } from '../../config/generalConfig.mjs'; -import { ItemBrowser } from '../ui/itemBrowser.mjs'; import { createEmbeddedItemsWithEffects, createEmbeddedItemWithEffects } from '../../helpers/utils.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -46,8 +45,6 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl }; this._dragDrop = this._createDragDropHandlers(); - - this.itemBrowser = null; } get title() { @@ -425,8 +422,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl equipment = ['armor', 'weapon']; const presets = { - compendium: 'daggerheart', - folder: equipment.includes(type) ? 'equipments' : type, + folder: equipment.includes(type) ? `equipments.folders.${type}s` : type, render: { noFolder: true } @@ -449,7 +445,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl 'type': { key: 'type', value: type } }; - return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true })); + ui.compendiumBrowser.open(presets); } static async viewItem(_, target) { @@ -567,7 +563,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl { overwrite: true } ); - if (this.itemBrowser) this.itemBrowser.close(); + if (ui.compendiumBrowser) ui.compendiumBrowser.close(); this.close(); } diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs index 0b3f8970..99cc53f6 100644 --- a/module/applications/levelup/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -1,6 +1,5 @@ import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs'; import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs'; -import { ItemBrowser } from '../ui/itemBrowser.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -12,8 +11,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) this._dragDrop = this._createDragDropHandlers(); this.tabGroups.primary = 'advancements'; - - this.itemBrowser = null; } get title() { @@ -539,8 +536,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) static async viewCompendium(event, target) { const type = target.dataset.compendium ?? target.dataset.type; - const presets = { - compendium: 'daggerheart', + const presets = { folder: type, render: { noFolder: true @@ -559,7 +555,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) }; } - return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true })); + ui.compendiumBrowser.open(presets); } static async selectPreview(_, button) { @@ -662,7 +658,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) }, {}); await this.actor.levelUp(levelupData); - if (this.itemBrowser) this.itemBrowser.close(); + + if (ui.compendiumBrowser) ui.compendiumBrowser.close(); this.close(); } } diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 308faee7..500141c1 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -5,7 +5,6 @@ import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs'; -import { ItemBrowser } from '../../ui/itemBrowser.mjs'; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ @@ -29,8 +28,7 @@ export default class CharacterSheet extends DHBaseActorSheet { toggleEquipItem: CharacterSheet.#toggleEquipItem, toggleResourceDice: CharacterSheet.#toggleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice, - useDowntime: this.useDowntime, - tempBrowser: CharacterSheet.#tempBrowser + useDowntime: this.useDowntime }, window: { resizable: true, @@ -635,7 +633,6 @@ export default class CharacterSheet extends DHBaseActorSheet { const { key } = button.dataset; const presets = { - compendium: 'daggerheart', folder: key, filter: key === 'subclasses' @@ -651,7 +648,7 @@ export default class CharacterSheet extends DHBaseActorSheet { } }; - return new ItemBrowser({ presets }).render({ force: true }); + ui.compendiumBrowser.open(presets); } /** @@ -768,13 +765,6 @@ export default class CharacterSheet extends DHBaseActorSheet { }); } - /** - * Temp - */ - static async #tempBrowser(_, target) { - new ItemBrowser().render({ force: true }); - } - /** * Handle the roll values of resource dice. * @type {ApplicationClickAction} diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 7f338ac1..2158e48b 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -1,6 +1,5 @@ const { HandlebarsApplicationMixin } = foundry.applications.api; import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs'; -import { ItemBrowser } from '../../ui/itemBrowser.mjs'; const typeSettingsMap = { character: 'extendCharacterDescriptions', @@ -589,28 +588,27 @@ export default function DHApplicationMixin(Base) { static async #browseItem(event, target) { const type = target.dataset.compendium ?? target.dataset.type; - const presets = {}; + const presets = { + render: { + noFolder: true + } + }; switch (type) { case 'loot': + presets.folder = 'equipments.folders.loots'; + break; case 'consumable': + presets.folder = 'equipments.folders.consumables'; + break; case 'armor': + presets.folder = 'equipments.folders.armors'; + break; case 'weapon': - presets.compendium = 'daggerheart'; - presets.folder = 'equipments'; - presets.render = { - noFolder: true - }; - presets.filter = { - type: { key: 'type', value: type, forced: true } - }; + presets.folder = 'equipments.folders.weapons'; break; case 'domainCard': - presets.compendium = 'daggerheart'; presets.folder = 'domains'; - presets.render = { - noFolder: true - }; presets.filter = { 'level.max': { key: 'level.max', value: this.document.system.levelData.level.current }, 'system.domain': { key: 'system.domain', value: this.document.system.domains } @@ -620,7 +618,7 @@ export default function DHApplicationMixin(Base) { return; } - return new ItemBrowser({ presets }).render({ force: true }); + ui.compendiumBrowser.open(presets); } /** diff --git a/module/applications/ui/_module.mjs b/module/applications/ui/_module.mjs index 6a17a61e..815fc4e7 100644 --- a/module/applications/ui/_module.mjs +++ b/module/applications/ui/_module.mjs @@ -3,3 +3,4 @@ export { default as DhCombatTracker } from './combatTracker.mjs'; export * as DhCountdowns from './countdowns.mjs'; export { default as DhFearTracker } from './fearTracker.mjs'; export { default as DhHotbar } from './hotbar.mjs'; +export { ItemBrowser } from './itemBrowser.mjs'; diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 8eefd9cd..40884817 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -15,16 +15,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { this.fieldFilter = []; this.selectedMenu = { path: [], data: null }; this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig; - this.presets = options.presets; - - if (this.presets?.compendium && this.presets?.folder) - ItemBrowser.selectFolder.call(this, null, null, this.presets.compendium, this.presets.folder); + this.presets = {}; } /** @inheritDoc */ static DEFAULT_OPTIONS = { id: 'itemBrowser', - classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser'], + classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'loader'], tag: 'div', window: { frame: true, @@ -84,17 +81,15 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { } }; - /** @inheritDoc */ - async _preFirstRender(context, options) { - if (context.presets?.render?.noFolder || context.presets?.render?.lite) options.position.width = 600; - - await super._preFirstRender(context, options); - } - /** @inheritDoc */ async _preRender(context, options) { - if (context.presets?.render?.noFolder || context.presets?.render?.lite) - options.parts.splice(options.parts.indexOf('sidebar'), 1); + this.presets = options.presets ?? {}; + + const width = this.presets?.render?.noFolder === true || this.presets?.render?.lite === true ? 600 : 850; + if(this.rendered) + this.setPosition({ width }); + else + options.position.width = width; await super._preRender(context, options); } @@ -103,22 +98,18 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { async _onRender(context, options) { await super._onRender(context, options); + this.element + .querySelectorAll('[data-action="selectFolder"]') + .forEach(element => element.classList.toggle('is-selected', element.dataset.folderId === this.selectedMenu.path.join('.'))); + this._createSearchFilter(); - this._createFilterInputs(); - this._createDragProcess(); - - if (context.presets?.render?.lite) this.element.classList.add('lite'); - - if (context.presets?.render?.noFolder) this.element.classList.add('no-folder'); - - if (context.presets?.render?.noFilter) this.element.classList.add('no-filter'); - - if (this.presets?.filter) { - Object.entries(this.presets.filter).forEach( - ([k, v]) => (this.fieldFilter.find(c => c.name === k).value = v.value) - ); - await this._onInputFilterBrowser(); - } + + this.element.classList.toggle('lite', this.presets?.render?.lite === true); + this.element.classList.toggle('no-folder', this.presets?.render?.noFolder === true); + this.element.classList.toggle('no-filter', this.presets?.render?.noFilter === true); + this.element.querySelectorAll('.folder-list > [data-action="selectFolder"]').forEach(element => { + element.hidden = this.presets.render?.folders?.length && !this.presets.render.folders.includes(element.dataset.folderId); + }); } _attachPartListeners(partId, htmlElement, options) { @@ -139,19 +130,23 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { async _prepareContext(options) { const context = await super._prepareContext(options); context.compendiums = this.getCompendiumFolders(foundry.utils.deepClone(this.config)); - // context.pathTitle = this.pathTile; context.menu = this.selectedMenu; context.formatLabel = this.formatLabel; context.formatChoices = this.formatChoices; - context.fieldFilter = this.fieldFilter = this._createFieldFilter(); context.items = this.items; context.presets = this.presets; return context; } + open(presets = {}) { + this.presets = presets; + ItemBrowser.selectFolder.call(this); + } + getCompendiumFolders(config, parent = null, depth = 0) { let folders = []; Object.values(config).forEach(c => { + // if(this.presets.render?.folders?.length && !this.presets.render.folders.includes(c.id)) return; const folder = { id: c.id, label: game.i18n.localize(c.label), @@ -162,16 +157,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { : []; folders.push(folder); }); + folders.sort((a, b) => a.label.localeCompare(b.label)) return folders; } - static async selectFolder(_, target, compend, folder) { - const config = foundry.utils.deepClone(this.config), - compendium = compend ?? target.closest('[data-compendium-id]').dataset.compendiumId, - folderId = folder ?? target.dataset.folderId, - folderPath = `${compendium}.folders.${folderId}`, - folderData = foundry.utils.getProperty(config, folderPath); + static async selectFolder(_, target) { + const folderId = target?.dataset?.folderId ?? this.presets.folder, + folderData = foundry.utils.getProperty(this.config, folderId) ?? {}; const columns = ItemBrowser.getFolderConfig(folderData).map(col => ({ ...col, @@ -179,31 +172,17 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { })); this.selectedMenu = { - path: folderPath.split('.'), + path: folderId?.split('.') ?? [], data: { ...folderData, columns: columns } }; - let items = []; - for (const key of folderData.keys) { - const comp = game.packs.get(`${compendium}.${key}`); - if (!comp) return; - items = items.concat(await comp.getDocuments({ type__in: folderData.type })); - } + await this.render({ force: true, presets: this.presets }); - this.items = ItemBrowser.sortBy(items, 'name'); - - if (target) { - target - .closest('.compendium-sidebar') - .querySelectorAll('[data-action="selectFolder"]') - .forEach(element => element.classList.remove('is-selected')); - target.classList.add('is-selected'); - } - - this.render({ force: true }); + if(this.selectedMenu?.data?.type?.length) + this.loadItems(); } _replaceHTML(result, content, options) { @@ -211,6 +190,75 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { super._replaceHTML(result, content, options); } + loadItems() { + let loadTimeout = this.toggleLoader(true); + + const promises = []; + + game.packs.forEach(pack => { + promises.push( + new Promise(async resolve => { + const items = await pack.getDocuments({ type__in: this.selectedMenu?.data?.type }); + resolve(items); + }) + ) + }); + + Promise.all(promises).then(async result => { + this.items = ItemBrowser.sortBy(result.flatMap(r => r), 'name'); + this.fieldFilter = this._createFieldFilter(); + + if (this.presets?.filter) { + Object.entries(this.presets.filter).forEach( + ([k, v]) => { + const filter = this.fieldFilter.find(c => c.name === k) + if(filter) filter.value = v.value; + } + ); + // await this._onInputFilterBrowser(); + } + + const filterList = await foundry.applications.handlebars.renderTemplate('systems/daggerheart/templates/ui/itemBrowser/filterContainer.hbs', + { + fieldFilter: this.fieldFilter, + presets: this.presets, + formatChoices: this.formatChoices + } + ); + + this.element.querySelector('.filter-content .wrapper').innerHTML = filterList; + const filterContainer = this.element.querySelector('.filter-header > [data-action="expandContent"]'); + if(this.fieldFilter.length === 0) + filterContainer.setAttribute('disabled', ''); + else + filterContainer.removeAttribute('disabled'); + + const itemList = await foundry.applications.handlebars.renderTemplate('systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', + { + items: this.items, + menu: this.selectedMenu, + formatLabel: this.formatLabel + } + ); + + this.element.querySelector('.item-list').innerHTML = itemList; + + this._createFilterInputs(); + await this._onInputFilterBrowser(); + this._createDragProcess(); + + clearTimeout(loadTimeout); + this.toggleLoader(false); + }); + } + + toggleLoader(state) { + const container = this.element.querySelector('.item-list'); + return setTimeout(() => { + container.classList.toggle("loader", state); + }, 100); + } + static expandContent(_, target) { const parent = target.parentElement; parent.classList.toggle('expanded'); @@ -328,6 +376,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { for (const li of html.querySelectorAll('.item-container')) { const itemUUID = li.dataset.itemUuid, item = this.items.find(i => i.uuid === itemUUID); + if(!item) continue; const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name); if (matchesSearch) this.#filteredItems.browser.search.add(item.id); const { input } = this.#filteredItems.browser; @@ -350,7 +399,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { item = this.items.find(i => i.uuid === itemUUID); if (!item) continue; - + const matchesMenu = this.fieldFilter.length === 0 || this.fieldFilter.every( @@ -419,11 +468,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { const newOrder = [...itemList].reverse().sort((a, b) => { const aProp = a.querySelector(`[data-item-key="${key}"]`), - bProp = b.querySelector(`[data-item-key="${key}"]`); + bProp = b.querySelector(`[data-item-key="${key}"]`), + aValue = isNaN(aProp.innerText) ? aProp.innerText : Number(aProp.innerText), + bValue = isNaN(bProp.innerText) ? bProp.innerText : Number(bProp.innerText); if (type === 'DESC') { - return aProp.innerText < bProp.innerText ? 1 : -1; + return aValue < bValue ? 1 : -1; } else { - return aProp.innerText > bProp.innerText ? 1 : -1; + return aValue > bValue ? 1 : -1; } }); @@ -452,4 +503,41 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { _canDragStart() { return true; } + + static injectSidebarButton(html) { + if(!game.user.isGM) return; + const sectionId = html.dataset.tab, + menus = { + actors: { + folder: "adversaries", + render: { + folders: ["adversaries", "characters", "environments"] + } + }, + items: { + folder: "equipments", + render: { + noFolder: true + } + }, + compendium: {} + }; + + if(Object.keys(menus).includes(sectionId)) { + const headerActions = html.querySelector(".header-actions"); + + const button = document.createElement("button"); + button.type = "button"; + button.classList.add("open-compendium-browser"); + button.innerHTML = ` + + ${game.i18n.localize("DAGGERHEART.UI.Tooltip.compendiumBrowser")} + `; + button.addEventListener("click", event => { + ui.compendiumBrowser.open(menus[sectionId]); + }); + + headerActions.append(button); + } + } } diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index d5afe72e..046efd8b 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -149,6 +149,104 @@ export const typeConfig = { } ] }, + weapons: { + columns: [ + { + key: 'system.secondary', + label: 'DAGGERHEART.UI.ItemBrowser.subtype', + format: isSecondary => (isSecondary ? 'secondary' : isSecondary === false ? 'primary' : '-') + }, + { + key: 'system.tier', + label: 'DAGGERHEART.GENERAL.Tiers.singular' + } + ], + filters: [ + { + key: 'system.secondary', + label: 'DAGGERHEART.UI.ItemBrowser.subtype', + choices: [ + { value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' }, + { value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' } + ] + }, + { + key: 'system.tier', + label: 'DAGGERHEART.GENERAL.Tiers.singular', + choices: [ + { value: '1', label: '1' }, + { value: '2', label: '2' }, + { value: '3', label: '3' }, + { value: '4', label: '4' } + ] + }, + { + key: 'system.burden', + label: 'DAGGERHEART.GENERAL.burden', + field: 'system.api.models.items.DHWeapon.schema.fields.burden' + }, + { + key: 'system.attack.roll.trait', + label: 'DAGGERHEART.GENERAL.Trait.single', + field: 'system.api.models.actions.actionsTypes.attack.schema.fields.roll.fields.trait' + }, + { + key: 'system.attack.range', + label: 'DAGGERHEART.GENERAL.range', + field: 'system.api.models.actions.actionsTypes.attack.schema.fields.range' + }, + { + key: 'system.itemFeatures', + label: 'DAGGERHEART.GENERAL.features', + choices: () => + Object.entries(CONFIG.DH.ITEM.weaponFeatures) + .map(([k, v]) => ({ value: k, label: v.label })), + operator: 'contains3' + } + ] + }, + armors: { + columns: [ + { + key: 'system.tier', + label: 'DAGGERHEART.GENERAL.Tiers.singular' + } + ], + filters: [ + { + key: 'system.tier', + label: 'DAGGERHEART.GENERAL.Tiers.singular', + choices: [ + { value: '1', label: '1' }, + { value: '2', label: '2' }, + { value: '3', label: '3' }, + { value: '4', label: '4' } + ] + }, + { + key: 'system.baseScore', + name: 'armor.min', + label: 'DAGGERHEART.UI.ItemBrowser.armorScoreMin', + field: 'system.api.models.items.DHArmor.schema.fields.baseScore', + operator: 'gte' + }, + { + key: 'system.baseScore', + name: 'armor.max', + label: 'DAGGERHEART.UI.ItemBrowser.armorScoreMax', + field: 'system.api.models.items.DHArmor.schema.fields.baseScore', + operator: 'lte' + }, + { + key: 'system.itemFeatures', + label: 'DAGGERHEART.GENERAL.features', + choices: () => + Object.entries(CONFIG.DH.ITEM.armorFeatures) + .map(([k, v]) => ({ value: k, label: v.label })), + operator: 'contains3' + } + ] + }, features: { columns: [], filters: [] @@ -257,7 +355,7 @@ export const typeConfig = { { key: 'system.domains', label: 'DAGGERHEART.GENERAL.Domain.plural', - choices: () => Object.values(CONFIG.DH.DOMAIN.domains).map(d => ({ value: d.id, label: d.label })), + choices: () => Object.values(CONFIG.DH.DOMAIN.allDomains()).map(d => ({ value: d.id, label: d.label })), operator: 'contains2' } ] @@ -265,18 +363,28 @@ export const typeConfig = { subclasses: { columns: [ { - key: 'id', - label: 'TYPES.Item.class', - format: id => { - return ''; - } + key: 'system.linkedClass', + label: 'Class', + format: linkedClass => linkedClass.name }, { key: 'system.spellcastingTrait', label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait' } ], - filters: [] + filters: [ + { + key: 'system.linkedClass.uuid', + label: 'Class', + choices: (items) => { + const list = items.map(item => ({ value: item.system.linkedClass.uuid, label: item.system.linkedClass.name })); + return list.reduce((a,c) => { + if(!(a.find(i => i.value === c.value))) a.push(c); + return a; + }, []); + } + } + ] }, beastforms: { columns: [ @@ -305,109 +413,144 @@ export const typeConfig = { }; export const compendiumConfig = { - daggerheart: { - id: 'daggerheart', - label: 'DAGGERHEART', - folders: { - adversaries: { - id: 'adversaries', - keys: ['adversaries'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.adversaries', - type: ['adversary'], - listType: 'adversaries' - }, - ancestries: { - id: 'ancestries', + characters: { + id: 'characters', + keys: ['characters'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.characters', + type: ['character'], + // listType: 'characters' + }, + adversaries: { + id: 'adversaries', + keys: ['adversaries'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.adversaries', + type: ['adversary'], + listType: 'adversaries' + }, + ancestries: { + id: 'ancestries', + keys: ['ancestries'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.ancestries', + type: ['ancestry'], + /* folders: { + features: { + id: 'features', keys: ['ancestries'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.ancestries', - type: ['ancestry'], - folders: { - features: { - id: 'features', - keys: ['ancestries'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.features', - type: ['feature'] - } - } + label: 'DAGGERHEART.UI.ItemBrowser.folders.features', + type: ['feature'] + } + } */ + }, + equipments: { + id: 'equipments', + keys: ['armors', 'weapons', 'consumables', 'loot'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.equipment', + type: ['armor', 'weapon', 'consumable', 'loot'], + listType: 'items', + folders: { + weapons: { + id: 'weapons', + keys: ['weapons'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.weapons', + type: ['weapon'], + listType: 'weapons' }, - equipments: { - id: 'equipments', - keys: ['armors', 'weapons', 'consumables', 'loot'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.equipment', - type: ['armor', 'weapon', 'consumable', 'loot'], - listType: 'items' + armors: { + id: 'armors', + keys: ['armors'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.armors', + type: ['armor'], + listType: 'armors' }, - classes: { - id: 'classes', - keys: ['classes'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.classes', - type: ['class'], - folders: { - features: { - id: 'features', - keys: ['classes'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.features', - type: ['feature'] - }, - items: { - id: 'items', - keys: ['classes'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.items', - type: ['armor', 'weapon', 'consumable', 'loot'], - listType: 'items' - } - }, - listType: 'classes' + consumables: { + id: 'consumables', + keys: ['consumables'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.consumables', + type: ['consumable'] }, - subclasses: { - id: 'subclasses', - keys: ['subclasses'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.subclasses', - type: ['subclass'], - listType: 'subclasses' - }, - domains: { - id: 'domains', - keys: ['domains'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.domainCards', - type: ['domainCard'], - listType: 'cards' - }, - communities: { - id: 'communities', - keys: ['communities'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.communities', - type: ['community'], - folders: { - features: { - id: 'features', - keys: ['communities'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.features', - type: ['feature'] - } - } - }, - environments: { - id: 'environments', - keys: ['environments'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.environments', - type: ['environment'] - }, - beastforms: { - id: 'beastforms', - keys: ['beastforms'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.beastforms', - type: ['beastform'], - listType: 'beastforms', - folders: { - features: { - id: 'features', - keys: ['beastforms'], - label: 'DAGGERHEART.UI.ItemBrowser.folders.features', - type: ['feature'] - } - } + loots: { + id: 'loots', + keys: ['loots'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.loots', + type: ['loot'] } } + }, + classes: { + id: 'classes', + keys: ['classes'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.classes', + type: ['class'], + /* folders: { + features: { + id: 'features', + keys: ['classes'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.features', + type: ['feature'] + }, + items: { + id: 'items', + keys: ['classes'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.items', + type: ['armor', 'weapon', 'consumable', 'loot'], + listType: 'items' + } + }, */ + listType: 'classes' + }, + subclasses: { + id: 'subclasses', + keys: ['subclasses'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.subclasses', + type: ['subclass'], + listType: 'subclasses' + }, + domains: { + id: 'domains', + keys: ['domains'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.domainCards', + type: ['domainCard'], + listType: 'cards' + }, + communities: { + id: 'communities', + keys: ['communities'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.communities', + type: ['community'], + /* folders: { + features: { + id: 'features', + keys: ['communities'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.features', + type: ['feature'] + } + } */ + }, + environments: { + id: 'environments', + keys: ['environments'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.environments', + type: ['environment'] + }, + beastforms: { + id: 'beastforms', + keys: ['beastforms'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.beastforms', + type: ['beastform'], + listType: 'beastforms', + /* folders: { + features: { + id: 'features', + keys: ['beastforms'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.features', + type: ['feature'] + } + } */ + }, + features: { + id: 'features', + keys: ['features'], + label: 'DAGGERHEART.UI.ItemBrowser.folders.features', + type: ['feature'] } }; diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index cb7be42a..fd569499 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -30,12 +30,13 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/dialogs/downtime/activities.hbs', 'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs', - 'systems/daggerheart/templates/ui/chat/parts/roll-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/damage-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/target-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs', + 'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', + 'systems/daggerheart/templates/scene/dh-config.hbs', ]); diff --git a/styles/less/global/global.less b/styles/less/global/global.less index e135846f..4c06d42b 100644 --- a/styles/less/global/global.less +++ b/styles/less/global/global.less @@ -23,4 +23,29 @@ color: var(--color-form-hint-hover); } } + + .loader { + position: relative; + overflow: hidden !important; + + div { + opacity: .5; + } + + &:before { + font-family: "Font Awesome 6 Pro"; + content: '\f110'; + position: absolute; + height: 100%; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + animation: spinner 1.5s linear infinite; + } + } + + @keyframes spinner { + to { transform: rotate(360deg); } + } } \ No newline at end of file diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index ab9db27c..70a64d89 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -71,6 +71,7 @@ } .compendium-results { + position: relative; padding: 16px; } @@ -101,10 +102,14 @@ .folder-list, .item-list-header, .item-header > div { - gap: 10px; cursor: pointer; } + .item-list-header, + .item-header > div { + gap: 10px; + } + .item-filter { display: flex; align-items: center; @@ -228,7 +233,8 @@ } .item-list-header, - .item-list { + .item-list, + .compendium-sidebar > .folder-list { overflow-y: auto; scrollbar-gutter: stable; scrollbar-width: thin; @@ -286,6 +292,7 @@ display: flex; flex-direction: column; gap: 5px; + flex: 1; .item-container { &:hover { @@ -385,8 +392,12 @@ margin: 0; .title { - margin: 0; text-align: center; + font-weight: bold; + } + + .hint { + flex: unset; } } @@ -398,7 +409,7 @@ &.lite, &.no-folder { - .menu-path { + .compendium-sidebar, .menu-path { display: none; } } diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs index ee5b6034..017d37d9 100644 --- a/templates/sheets/actors/character/inventory.hbs +++ b/templates/sheets/actors/character/inventory.hbs @@ -10,9 +10,6 @@ - - -
diff --git a/templates/ui/itemBrowser/filterContainer.hbs b/templates/ui/itemBrowser/filterContainer.hbs new file mode 100644 index 00000000..da8e8cfe --- /dev/null +++ b/templates/ui/itemBrowser/filterContainer.hbs @@ -0,0 +1,22 @@ +{{#each fieldFilter}} + {{#if choices }} +
+ +
+ +
+
+ {{else}} + {{#if filtered }} + {{formField field localize=true blank="" name=name choices=(@root.formatChoices this) valueAttr="value" dataset=(object key=key) value=value}} + {{else}} + {{#if field.label}} + {{formField field localize=true blank="" name=name dataset=(object key=key) value=value}} + {{else}} + {{formField field localize=true blank="" name=name dataset=(object key=key) label=label value=value}} + {{/if}} + {{/if}} + {{/if}} +{{/each}} \ No newline at end of file diff --git a/templates/ui/itemBrowser/itemBrowser.hbs b/templates/ui/itemBrowser/itemBrowser.hbs index ca0def19..137693fc 100644 --- a/templates/ui/itemBrowser/itemBrowser.hbs +++ b/templates/ui/itemBrowser/itemBrowser.hbs @@ -1,5 +1,5 @@
- {{#if menu.data }} + {{#if menu.path.length }}
- {{#if fieldFilter.length}} - - {{/if}} +
-
- {{#each fieldFilter}} - {{#if choices }} -
- -
- -
-
- {{else}} - {{#if filtered }} - {{formField field localize=true blank="" name=name choices=(@root.formatChoices this) valueAttr="value" dataset=(object key=key) value=value}} - {{else}} - {{#if field.label}} - {{formField field localize=true blank="" name=name dataset=(object key=key) value=value}} - {{else}} - {{formField field localize=true blank="" name=name dataset=(object key=key) label=label value=value}} - {{/if}} - {{/if}} - {{/if}} - {{/each}} -
+
- {{!--
--}} - {{#if menu.data.columns.length}} -
-
-
{{localize 'DAGGERHEART.UI.ItemBrowser.columnName'}}
- {{#each menu.data.columns}} - {{localize label}} - {{/each}} -
- {{/if}} -
- {{#each items}} -
-
-
- - {{name}} - {{#each ../menu.data.columns}} - {{#with (@root.formatLabel ../this this) as | label |}}{{{label}}}{{/with}} - {{/each}} -
-
-
- {{{system.description}}} -
-
- {{/each}} -
- {{!--
--}} + {{#if menu.data.columns.length}} +
+
+
{{localize 'DAGGERHEART.UI.ItemBrowser.columnName'}}
+ {{#each menu.data.columns}} +
{{localize label}}
+ {{/each}} +
+ {{/if}} +
{{else}}

{{localize "DAGGERHEART.UI.ItemBrowser.title"}}

diff --git a/templates/ui/itemBrowser/itemContainer.hbs b/templates/ui/itemBrowser/itemContainer.hbs new file mode 100644 index 00000000..f6aefa6b --- /dev/null +++ b/templates/ui/itemBrowser/itemContainer.hbs @@ -0,0 +1,16 @@ +{{#each items}} +
+
+
+ + {{name}} + {{#each ../menu.data.columns}} + {{#with (@root.formatLabel ../this this) as | label |}}{{{label}}}{{/with}} + {{/each}} +
+
+
+ {{{system.description}}} +
+
+{{/each}} \ No newline at end of file diff --git a/templates/ui/itemBrowser/sidebar.hbs b/templates/ui/itemBrowser/sidebar.hbs index 8df0aed3..28a34a22 100644 --- a/templates/ui/itemBrowser/sidebar.hbs +++ b/templates/ui/itemBrowser/sidebar.hbs @@ -1,32 +1,22 @@
- {{#each compendiums}} -
- - {{label}} - - -
- {{#each folders}} -
{{label}}
- {{!--
{{label}}
--}} - {{#if folders.length}} -
-
- {{#each folders}} -
- • {{label}} -
- {{/each}} -
+
+ {{#each compendiums}} +
{{label}}
+ {{#if folders.length}} +
+
+ {{#each folders}} +
+ • {{label}} +
+ {{/each}}
- {{/if}} - {{/each}} -
- -
- {{/each}} +
+ {{/if}} + {{/each}} +