diff --git a/lang/en.json b/lang/en.json index fa4dc2db..82372f99 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2313,7 +2313,7 @@ "diceIsRerolled": "The dice has been rerolled (x{times})", "pendingSaves": "Pending Reaction Rolls", "openSheetSettings": "Open Settings", - "compediumBrowser": "Compedium Browser" + "compendiumBrowser": "Compendium Browser" } } } diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 0927a3ba..168625bb 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -25,7 +25,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { // title: 'Item Browser', window: { frame: true, - title: 'Compedium Browser', + title: 'Compendium Browser', icon: 'fa-solid fa-book-atlas', positioned: true, resizable: true @@ -33,7 +33,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { actions: { selectFolder: this.selectFolder, expandContent: this.expandContent, - resetFilters: this.resetFilters + resetFilters: this.resetFilters, + sortList: this.sortList }, position: { width: 1000, @@ -102,7 +103,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { context.menu = this.selectedMenu; context.formatLabel = this.formatLabel; context.formatChoices = this.formatChoices; - context.fieldFilter = this.fieldFilter = this.selectedMenu.data?.filters ? this._createFieldFilter() : []; + context.fieldFilter = this.fieldFilter = this._createFieldFilter(); context.items = this.items; console.log(this.items); return context; @@ -136,7 +137,10 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { this.selectedMenu = { path: folderPath.split('.'), - data: folderData + data: { + ...folderData, + columns: ItemBrowser.getFolderConfig(folderData) + } }; let items = []; @@ -161,7 +165,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { formatLabel(item, field) { const property = foundry.utils.getProperty(item, field.key); - if (typeof field.format !== 'function') return property; + if (typeof field.format !== 'function') return property ?? '-'; return field.format(property); } @@ -177,11 +181,15 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { } _createFieldFilter() { - const filters = []; - this.selectedMenu.data.filters.forEach(f => { + const filters = ItemBrowser.getFolderConfig(this.selectedMenu.data, 'filters'); + filters.forEach(f => { if (typeof f.field === 'string') f.field = foundry.utils.getProperty(game, f.field); - else if (typeof f.choices === 'function') f.choices = f.choices(); - filters.push(f); + else if (typeof f.choices === 'function') { + f.choices = f.choices(); + console.log(f.choices) + } + f.name ??= f.key; + // filters.push(f); }); return filters; } @@ -278,24 +286,22 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { async _onInputFilterBrowser(event) { this.#filteredItems.browser.input.clear(); - console.log(event.target.name); - - this.fieldFilter.find(f => f.key === event.target.name).value = event.target.value; + this.fieldFilter.find(f => f.name === event.target.name).value = event.target.value; // console.log(_event, html, filters) for (const li of event.target.closest('[data-application-part="list"]').querySelectorAll('.item-container')) { const itemUUID = li.dataset.itemUuid, item = this.items.find(i => i.uuid === itemUUID); + + if(!item) continue; const matchesMenu = this.fieldFilter.length === 0 || - this.fieldFilter.every(f => { - return ( - (!f.value && f.value !== false) || - foundry.applications.ux.SearchFilter.evaluateFilter(item, this.createFilterData(f)) - ); - }); + this.fieldFilter.every(f => ( + !f.value && f.value !== false) || + ItemBrowser.evaluateFilter(item, this.createFilterData(f)) + ); if (matchesMenu) this.#filteredItems.browser.input.add(item.id); const { search } = this.#filteredItems.browser; @@ -303,6 +309,32 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { // li.hidden = !(matchesMenu); } } + + /** + * Foundry evaluateFilter doesn't allow you to match if filter values are included into item data + * @param {*} obj + * @param {*} filter + */ + static evaluateFilter(obj, filter) { + const docValue = foundry.utils.getProperty(obj, filter.field); + const filterValue = filter.value; + switch (filter.operator) { + case "contains2": + return Array.isArray(docValue) ? docValue.includes(filterValue) : [docValue].includes(filterValue); + break; + case "contains3": + return docValue.some(f => f.value === filterValue); + break; + default: + return foundry.applications.ux.SearchFilter.evaluateFilter(obj, filter); + break; + } + if(filter.operator === "contains2") { + const docValue = foundry.utils.getProperty(obj, filter.field); + const filterValue = filter.value; + return Array.isArray(docValue) ? docValue.includes(filterValue) : [docValue].includes(filterValue); + } return foundry.applications.ux.SearchFilter.evaluateFilter(obj, filter) + } createFilterData(filter) { return { @@ -321,6 +353,33 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { this.render({ force: true }); } + static getFolderConfig(folder, property = "columns") { + if(!folder) return []; + return folder[property] ?? CONFIG.DH.ITEMBROWSER.typeConfig[folder.listType]?.[property] ?? []; + } + + static sortList(_, target) { + const key = target.dataset.sortKey, + type = !target.dataset.sortType || target.dataset.sortType === "DESC" ? "ASC" : "DESC", + itemListContainer = target.closest(".compendium-results").querySelector(".item-list"), + itemList = itemListContainer.querySelectorAll(".item-container"); + + target.closest(".item-list-header").querySelectorAll('[data-sort-key]').forEach(b => b.dataset.sortType = ""); + target.dataset.sortType = type; + + const newOrder = [...itemList].reverse().sort((a, b) => { + const aProp = a.querySelector(`[data-item-key="${key}"]`), + bProp = b.querySelector(`[data-item-key="${key}"]`) + if(type === "DESC") { + return aProp.innerText < bProp.innerText ? 1 : -1; + } else { + return aProp.innerText > bProp.innerText ? 1 : -1; + } + }); + + itemListContainer.replaceChildren(...newOrder); + } + /** * Serialize salient information about this Document when dragging it. * @returns {object} An object of drag data. diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index 232aa413..f89270cf 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -1,3 +1,300 @@ +export const typeConfig = { + adversaries: { + columns: [ + { + key: "system.tier", + label: "Tier" + }, + { + key: "system.type", + label: "Type" + } + ], + filters: [ + { + key: "system.tier", + label: "Tier", + field: 'system.api.models.actors.DhAdversary.schema.fields.tier' + }, + { + key: "system.type", + label: "Type", + field: 'system.api.models.actors.DhAdversary.schema.fields.type' + }, + { + key: "system.difficulty", + name: "difficulty.min", + label: "Difficulty (Min)", + field: 'system.api.models.actors.DhAdversary.schema.fields.difficulty', + operator: "gte" + }, + { + key: "system.difficulty", + name: "difficulty.max", + label: "Difficulty (Max)", + field: 'system.api.models.actors.DhAdversary.schema.fields.difficulty', + operator: "lte" + }, + { + key: "system.resources.hitPoints.max", + name: "hp.min", + label: "Hit Points (Min)", + field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.hitPoints.fields.max', + operator: "gte" + }, + { + key: "system.resources.hitPoints.max", + name: "hp.max", + label: "Hit Points (Max)", + field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.hitPoints.fields.max', + operator: "lte" + }, + { + key: "system.resources.stress.max", + name: "stress.min", + label: "Stress (Min)", + field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.stress.fields.max', + operator: "gte" + }, + { + key: "system.resources.stress.max", + name: "stress.max", + label: "Stress (Max)", + field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.stress.fields.max', + operator: "lte" + }, + ] + }, + items: { + columns: [ + { + key: "type", + label: "Type" + }, + { + key: "system.secondary", + label: "Subtype", + format: (isSecondary) => isSecondary ? "secondary" : (isSecondary === false ? "primary" : '-') + }, + { + key: "system.tier", + label: "Tier" + } + ], + filters: [ + { + key: "type", + label: "Type", + choices: () => CONFIG.Item.documentClass.TYPES.filter(t => ["armor", "weapon", "consumable", "loot"].includes(t)).map(t => ({ value: t, label: t })) + }, + { + key: "system.secondary", + label: "Subtype", + choices: [ + { value: false, label: "Primary Weapon"}, + { value: true, label: "Secondary Weapon"} + ] + }, + { + key: "system.tier", + label: "Tier", + choices: [{ value: "1", label: "1"}, { value: "2", label: "2"}, { value: "3", label: "3"}, { value: "4", label: "4"}] + }, + { + key: "system.burden", + label: "Burden", + field: 'system.api.models.items.DHWeapon.schema.fields.burden' + }, + { + key: "system.attack.roll.trait", + label: "Trait", + field: 'system.api.models.actions.actionsTypes.attack.schema.fields.roll.fields.trait' + }, + { + key: "system.attack.range", + label: "Range", + field: 'system.api.models.actions.actionsTypes.attack.schema.fields.range' + }, + { + key: "system.baseScore", + name: "armor.min", + label: "Armor Score (Min)", + field: 'system.api.models.items.DHArmor.schema.fields.baseScore', + operator: "gte" + }, + { + key: "system.baseScore", + name: "armor.max", + label: "Armor Score (Max)", + field: 'system.api.models.items.DHArmor.schema.fields.baseScore', + operator: "lte" + }, + { + key: "system.features", + label: "Features", + choices: () => [...Object.entries(CONFIG.DH.ITEM.weaponFeatures), ...Object.entries(CONFIG.DH.ITEM.armorFeatures)].map(([k,v]) => ({ value: k, label: v.label})), + operator: "contains3" + } + ] + }, + features: { + columns: [ + + ], + filters: [ + + ] + }, + cards: { + columns: [ + { + key: "system.type", + label: "Type" + }, + { + key: "system.domain", + label: "Domain" + }, + { + key: "system.level", + label: "Level" + } + ], + filters: [ + { + key: "system.type", + label: "Type", + field: 'system.api.models.items.DHDomainCard.schema.fields.type' + }, + { + key: "system.domain", + label: "Domain", + field: 'system.api.models.items.DHDomainCard.schema.fields.domain' + }, + { + key: "system.level", + name: "level.min", + label: "Level (Min)", + field: 'system.api.models.items.DHDomainCard.schema.fields.level', + operator: "gte" + }, + { + key: "system.level", + name: "level.max", + label: "Level (Max)", + field: 'system.api.models.items.DHDomainCard.schema.fields.level', + operator: "lte" + }, + { + key: "system.recallCost", + name: "recall.min", + label: "Recall Cost (Min)", + field: 'system.api.models.items.DHDomainCard.schema.fields.recallCost', + operator: "gte" + }, + { + key: "system.recallCost", + name: "recall.max", + label: "Recall Cost (Max)", + field: 'system.api.models.items.DHDomainCard.schema.fields.recallCost', + operator: "lte" + } + ] + }, + classes: { + columns: [ + { + key: "system.evasion", + label: "Evasion" + }, + { + key: "system.hitPoints", + label: "Hit Points" + }, + { + key: "system.domains", + label: "Domains" + } + ], + filters: [ + { + key: "system.evasion", + name: "evasion.min", + label: "Evasion (Min)", + field: 'system.api.models.items.DHClass.schema.fields.evasion', + operator: "gte" + }, + { + key: "system.evasion", + name: "evasion.max", + label: "Evasion (Max)", + field: 'system.api.models.items.DHClass.schema.fields.evasion', + operator: "lte" + }, + { + key: "system.hitPoints", + name: "hp.min", + label: "Hit Points (Min)", + field: 'system.api.models.items.DHClass.schema.fields.hitPoints', + operator: "gte" + }, + { + key: "system.hitPoints", + name: "hp.max", + label: "Hit Points (Max)", + field: 'system.api.models.items.DHClass.schema.fields.hitPoints', + operator: "lte" + }, + { + key: "system.domains", + label: "Domains", + choices: () => Object.values(CONFIG.DH.DOMAIN.domains).map(d => ({ value: d.id, label: d.label})), + operator: "contains2" + } + ] + }, + subclasses: { + columns: [ + { + key: "id", + label: "Class", + format: (id) => { + return ""; + } + }, + { + key: "system.spellcastingTrait", + label: "Spellcasting Trait" + } + ], + filters: [] + }, + beastforms: { + columns: [ + { + key: "system.tier", + label: "Tier" + }, + { + key: "system.mainTrait", + label: "Main Trait" + } + ], + filters: [ + { + key: "system.tier", + label: "Tier", + field: 'system.api.models.items.DHBeastform.schema.fields.tier' + }, + { + key: "system.mainTrait", + label: "Main Trait", + field: 'system.api.models.items.DHBeastform.schema.fields.mainTrait' + } + ] + } +} + export const compendiumConfig = { "daggerheart": { id: "daggerheart", @@ -8,64 +305,7 @@ export const compendiumConfig = { keys: ["adversaries"], label: "Adversaries", type: ["adversary"], - columns: [ - { - key: "system.tier", - label: "Tier" - }, - { - key: "system.type", - label: "Type" - } - ], - filters: [ - { - key: "system.tier", - label: "Tier", - field: 'system.api.models.actors.DhAdversary.schema.fields.tier' - }, - { - key: "system.type", - label: "Type", - field: 'system.api.models.actors.DhAdversary.schema.fields.type' - }, - { - key: "system.difficulty", - label: "Difficulty (Min)", - field: 'system.api.models.actors.DhAdversary.schema.fields.difficulty', - operator: "gte" - }, - { - key: "system.difficulty", - label: "Difficulty (Max)", - field: 'system.api.models.actors.DhAdversary.schema.fields.difficulty', - operator: "lte" - }, - { - key: "system.resources.hitPoints.max", - label: "Hit Points (Min)", - field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.hitPoints.fields.max', - operator: "gte" - }, - { - key: "system.resources.hitPoints.max", - label: "Hit Points (Max)", - field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.hitPoints.fields.max', - operator: "lte" - }, - { - key: "system.resources.stress.max", - label: "Stress (Min)", - field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.stress.fields.max', - operator: "gte" - }, - { - key: "system.resources.stress.max", - label: "Stress (Max)", - field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.stress.fields.max', - operator: "lte" - }, - ] + listType: "adversaries" }, "ancestries": { id: "ancestries", @@ -86,71 +326,7 @@ export const compendiumConfig = { keys: ["armors", "weapons", "consumables", "loot"], label: "Equipments", type: ["armor", "weapon", "consumable", "loot"], - columns: [ - { - key: "type", - label: "Type" - }, - { - key: "system.secondary", - label: "Subtype", - format: (isSecondary) => isSecondary ? "secondary" : (isSecondary === false ? "primary" : '-') - }, - { - key: "system.tier", - label: "Tier" - } - ], - filters: [ - { - key: "type", - label: "Type", - // filtered: ["armor", "weapon", "consumable", "loot"], - // field: 'system.api.documents.DHItem.schema.fields.type', - // valueAttr: 'label' - choices: () => CONFIG.Item.documentClass.TYPES.filter(t => ["armor", "weapon", "consumable", "loot"].includes(t)).map(t => ({ value: t, label: t })) - }, - { - key: "system.secondary", - label: "Subtype", - choices: [ - { value: false, label: "Primary Weapon"}, - { value: true, label: "Secondary Weapon"} - ] - }, - { - key: "system.tier", - label: "Tier", - choices: [{ value: "1", label: "1"}, { value: "2", label: "2"}, { value: "3", label: "3"}, { value: "4", label: "4"}] - }, - { - key: "system.burden", - label: "Burden", - field: 'system.api.models.items.DHWeapon.schema.fields.burden' - }, - { - key: "system.attack.roll.trait", - label: "Trait", - field: 'system.api.models.actions.actionsTypes.attack.schema.fields.roll.fields.trait' - }, - { - key: "system.attack.range", - label: "Range", - field: 'system.api.models.actions.actionsTypes.attack.schema.fields.range' - }, - { - key: "system.baseScore", - label: "Armor Score (Min)", - field: 'system.api.models.items.DHArmor.schema.fields.baseScore', - operator: "gte" - }, - { - key: "system.baseScore", - label: "Armor Score (Max)", - field: 'system.api.models.items.DHArmor.schema.fields.baseScore', - operator: "lte" - } - ] + listType: "items" }, "classes": { id: "classes", @@ -168,29 +344,25 @@ export const compendiumConfig = { id: "items", keys: ["classes"], label: "Items", - type: ["armor", "weapon", "consumable", "loot"] + type: ["armor", "weapon", "consumable", "loot"], + listType: "items" } - } + }, + listType: "classes" }, "subclasses": { id: "subclasses", keys: ["subclasses"], label: "Subclasses", type: ["subclass"], - folders: { - "features": { - id: "features", - keys: ["subclasses"], - label: "Features", - type: ["feature"] - } - } + listType: "subclasses" }, "domains": { id: "domains", keys: ["domains"], label: "Domain Cards", - type: ["domainCard"] + type: ["domainCard"], + listType: "cards" }, "communities": { id: "communities", @@ -217,6 +389,7 @@ export const compendiumConfig = { keys: ["beastforms"], label: "Beastforms", type: ["beastform"], + listType: "beastforms", folders: { "features": { id: "features", diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index e8a6e35b..fd280f74 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -139,4 +139,8 @@ export default class DHArmor extends AttachableItem { const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`]; return labels; } + + get features() { + return this.armorFeatures; + } } diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 1f55e878..2e76e09b 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -91,6 +91,10 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { return this.actions; } + get features() { + return []; + } + /** * Obtain a data object used to evaluate any dice rolls associated with this Item Type * @param {object} [options] - Options which modify the getRollData method. diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 5ecd152c..157b83cf 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -20,7 +20,8 @@ export default class DHSubclass extends BaseDataItem { choices: CONFIG.DH.ACTOR.abilities, integer: false, nullable: true, - initial: null + initial: null, + label: "DAGGERHEART.ITEMS.Subclass.spellcastingTrait" }), features: new ItemLinkFields(), featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }), diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index eca5f288..77e96113 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -227,4 +227,8 @@ export default class DHWeapon extends AttachableItem { return labels; } + + get features() { + return this.weaponFeatures; + } } diff --git a/styles/less/ui/item-browser/item-browser.less b/styles/less/ui/item-browser/item-browser.less index 9b84f2ec..f8356502 100644 --- a/styles/less/ui/item-browser/item-browser.less +++ b/styles/less/ui/item-browser/item-browser.less @@ -20,7 +20,7 @@ gap: 10px; } - .compedium-sidebar { + .compendium-sidebar { position: relative; width: 200px; padding: 16px; @@ -70,7 +70,7 @@ } } - .compedium-results { + .compendium-results { padding: 16px; } @@ -219,12 +219,43 @@ scrollbar-color: light-dark(@dark-blue, @golden) transparent; } + .item-list-header, + .item-list [data-action="expandContent"] { + display: flex; + + > * { + flex: 1; + } + .item-list-img { + width: 40px; + flex: unset; + } + .item-list-name { + flex-grow: 3 !important; + } + } + .item-list-header { background-color: light-dark(transparent, #18162e); color: light-dark(#18162e, #f3c267); border: 1px solid light-dark(#18162e, #f3c267); border-radius: 6px; min-height: 30px; + + div[data-sort-key] { + &:after { + font-family: "Font Awesome 6 Pro"; + margin-left: 5px; + } + + &[data-sort-type="ASC"]:after { + content : "\f0d7"; + } + + &[data-sort-type="DESC"]:after { + content : "\f0d8"; + } + } } .item-list { @@ -240,6 +271,7 @@ .item-desc .wrapper { padding: 0 10px; + display: flex; } img { diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs index 9550bb81..a7dff753 100644 --- a/templates/sheets/actors/character/inventory.hbs +++ b/templates/sheets/actors/character/inventory.hbs @@ -10,7 +10,7 @@ - + diff --git a/templates/sheets/items/subclass/settings.hbs b/templates/sheets/items/subclass/settings.hbs index 237ba9db..d6f7db53 100644 --- a/templates/sheets/items/subclass/settings.hbs +++ b/templates/sheets/items/subclass/settings.hbs @@ -7,6 +7,6 @@
\ No newline at end of file diff --git a/templates/ui/itemBrowser/itemBrowser.hbs b/templates/ui/itemBrowser/itemBrowser.hbs index af1824ca..019a4f6e 100644 --- a/templates/ui/itemBrowser/itemBrowser.hbs +++ b/templates/ui/itemBrowser/itemBrowser.hbs @@ -1,15 +1,14 @@ -