This commit is contained in:
Dapoolp 2025-08-05 21:14:42 +02:00
parent 5cd1956ec2
commit 6fcdca20b1
12 changed files with 456 additions and 180 deletions

View file

@ -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"
}
}
}

View file

@ -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,9 +286,7 @@ 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)
@ -288,14 +294,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
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;
@ -304,6 +310,32 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
}
}
/**
* 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 {
field: filter.key,
@ -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.

View file

@ -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",

View file

@ -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;
}
}

View file

@ -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.

View file

@ -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 }),

View file

@ -227,4 +227,8 @@ export default class DHWeapon extends AttachableItem {
return labels;
}
get features() {
return this.weaponFeatures;
}
}

View file

@ -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 {

View file

@ -10,7 +10,7 @@
<a class="filter-button">
<i class="fa-solid fa-filter"></i>
</a>
<a data-tooltip="{{localize 'DAGGERHEART.UI.Tooltip.compediumBrowser'}}" data-action="tempBrowser">
<a data-tooltip="{{localize 'DAGGERHEART.UI.Tooltip.compendiumBrowser'}}" data-action="tempBrowser">
<i class="fa-solid fa-book-atlas"></i>
</a>
</div>

View file

@ -7,6 +7,6 @@
<fieldset class="two-columns">
<legend>{{localize tabs.settings.label}}</legend>
<span>{{localize "DAGGERHEART.ITEMS.Subclass.spellcastingTrait"}}</span>
{{formField systemFields.spellcastingTrait value=source.system.spellcastingTrait localize=true}}
{{formInput systemFields.spellcastingTrait value=source.system.spellcastingTrait localize=true}}
</fieldset>
</section>

View file

@ -1,15 +1,14 @@
<div class="compedium-results">
<div class="compendium-results">
{{#if menu.data }}
<div class="menu-path">
{{#each menu.path}}
{{#unless (eq this "folders")}}
{{#if (eq this "folders")}}
<span class="path-link">
/
</span>
{{else}}
<span class="item-path">{{this}}</span>
{{#unless @last}}
<span class="path-link">
/
</span>
{{/unless}}
{{/unless}}
{{/if}}
{{/each}}
</div>
<div class="item-filter">
@ -32,19 +31,19 @@
<div class="form-group">
<label>{{localize label}}</label>
<div class="form-fields">
<select name="{{key}}">
{{selectOptions choices valueAttr="value" blank="" }}
<select data-key="{{key}}" name={{name}}>
{{selectOptions choices valueAttr="value" blank="" localize=true}}
</select>
</div>
</div>
{{else}}
{{#if filtered }}
{{formField field localize=true blank="" choices=(@root.formatChoices this) valueAttr="value" name=key}}
{{formField field localize=true blank="" name=name choices=(@root.formatChoices this) valueAttr="value" dataset=(object key=key)}}
{{else}}
{{#if field.label}}
{{formField field localize=true blank="" name=key}}
{{formField field localize=true blank="" name=name dataset=(object key=key) value=""}}
{{else}}
{{formField field localize=true blank="" name=key label=label}}
{{formField field localize=true blank="" name=name dataset=(object key=key) label=label value=""}}
{{/if}}
{{/if}}
{{/if}}
@ -55,10 +54,10 @@
{{!-- <div class="item-list-container"> --}}
{{#if menu.data.columns.length}}
<div class="item-list-header">
<div></div>
<div>Name</div>
<div class="item-list-img"></div>
<div class="item-list-name" data-sort-key="name" data-sort-type="ASC" data-action="sortList">Name</div>
{{#each menu.data.columns}}
<div>{{label}}</div>
<div data-sort-key="{{key}}" data-sort-type="" data-action="sortList">{{label}}</div>
{{/each}}
</div>
{{/if}}
@ -67,10 +66,10 @@
<div class="item-container" data-item-uuid="{{uuid}}" draggable="true">
<div class="item-header">
<div data-action="expandContent">
<img src="{{img}}" data-item-key="img">
<div data-item-key="name">{{name}}</div>
<img src="{{img}}" data-item-key="img" class="item-list-img">
<div data-item-key="name" class="item-list-name">{{name}}</div>
{{#each ../menu.data.columns}}
<div data-item-key="{{@key}}">{{#with (@root.formatLabel ../this this) as | label |}}{{label}}{{/with}}</div>
<div data-item-key="{{key}}">{{#with (@root.formatLabel ../this this) as | label |}}{{{label}}}{{/with}}</div>
{{/each}}
</div>
</div>
@ -83,8 +82,8 @@
{{!-- </div> --}}
{{else}}
<div class="welcome-message">
<h2 class="title">Daggerheart Compedium Browser</h2>
<span class="hint"><i>Select a Folder in sidebar to start browsing trought the compedium</i></span>
<h2 class="title">Daggerheart Compendium Browser</h2>
<span class="hint"><i>Select a Folder in sidebar to start browsing trought the compendium</i></span>
</div>
{{/if}}
</div>

View file

@ -1,4 +1,4 @@
<div class="compedium-sidebar">
<div class="compendium-sidebar">
{{#each compendiums}}
<details class="compendium-container" data-compendium-id="{{id}}" open>
<summary>