Compendium Browser per type

This commit is contained in:
Dapoolp 2025-08-27 00:49:58 +02:00
parent aaf6c689fc
commit 0fff4b8f79
9 changed files with 282 additions and 85 deletions

View file

@ -2399,7 +2399,11 @@
"environments": "Environments", "environments": "Environments",
"beastforms": "Beastforms", "beastforms": "Beastforms",
"features": "Features", "features": "Features",
"items": "Items" "items": "Items",
"weapons": "Weapons",
"armors": "Armors",
"consumables": "Consumables",
"loots": "Loots"
} }
}, },
"Notifications": { "Notifications": {

View file

@ -425,8 +425,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
equipment = ['armor', 'weapon']; equipment = ['armor', 'weapon'];
const presets = { const presets = {
compendium: 'daggerheart', folder: equipment.includes(type) ? `equipments.folders.${type}s` : type,
folder: equipment.includes(type) ? 'equipments' : type,
render: { render: {
noFolder: true noFolder: true
} }

View file

@ -539,8 +539,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
static async viewCompendium(event, target) { static async viewCompendium(event, target) {
const type = target.dataset.compendium ?? target.dataset.type; const type = target.dataset.compendium ?? target.dataset.type;
const presets = { const presets = {
compendium: 'daggerheart',
folder: type, folder: type,
render: { render: {
noFolder: true noFolder: true

View file

@ -577,28 +577,27 @@ export default function DHApplicationMixin(Base) {
static async #browseItem(event, target) { static async #browseItem(event, target) {
const type = target.dataset.compendium ?? target.dataset.type; const type = target.dataset.compendium ?? target.dataset.type;
const presets = {}; const presets = {
render: {
noFolder: true
}
};
switch (type) { switch (type) {
case 'loot': case 'loot':
presets.folder = 'equipments.folders.loots';
break;
case 'consumable': case 'consumable':
presets.folder = 'equipments.folders.consumables';
break;
case 'armor': case 'armor':
presets.folder = 'equipments.folders.armors';
break;
case 'weapon': case 'weapon':
presets.compendium = 'daggerheart'; presets.folder = 'equipments.folders.weapons';
presets.folder = 'equipments';
presets.render = {
noFolder: true
};
presets.filter = {
type: { key: 'type', value: type, forced: true }
};
break; break;
case 'domainCard': case 'domainCard':
presets.compendium = 'daggerheart';
presets.folder = 'domains'; presets.folder = 'domains';
presets.render = {
noFolder: true
};
presets.filter = { presets.filter = {
'level.max': { key: 'level.max', value: this.document.system.levelData.level.current }, 'level.max': { key: 'level.max', value: this.document.system.levelData.level.current },
'system.domain': { key: 'system.domain', value: this.document.system.domains } 'system.domain': { key: 'system.domain', value: this.document.system.domains }

View file

@ -17,8 +17,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig; this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
this.presets = options.presets; this.presets = options.presets;
if (this.presets?.compendium && this.presets?.folder) if (this.presets?.folder)
ItemBrowser.selectFolder.call(this, null, null, this.presets.compendium, this.presets.folder); ItemBrowser.selectFolder.call(this, null, null, this.presets.folder);
} }
/** @inheritDoc */ /** @inheritDoc */
@ -115,7 +115,10 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
if (this.presets?.filter) { if (this.presets?.filter) {
Object.entries(this.presets.filter).forEach( Object.entries(this.presets.filter).forEach(
([k, v]) => (this.fieldFilter.find(c => c.name === k).value = v.value) ([k, v]) => {
const filter = this.fieldFilter.find(c => c.name === k)
if(filter) filter.value = v.value;
}
); );
await this._onInputFilterBrowser(); await this._onInputFilterBrowser();
} }
@ -162,16 +165,34 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
: []; : [];
folders.push(folder); folders.push(folder);
}); });
folders.sort((a, b) => a.label.localeCompare(b.label))
return folders; return folders;
} }
static async selectFolder(_, target, compend, folder) { static async selectFolder(_, target, folder) {
const config = foundry.utils.deepClone(this.config),
compendium = compend ?? target.closest('[data-compendium-id]').dataset.compendiumId, let loadTimeout = ItemBrowser.toggleLoader(target, true);
folderId = folder ?? target.dataset.folderId,
folderPath = `${compendium}.folders.${folderId}`, const folderId = folder ?? target.dataset.folderId,
folderData = foundry.utils.getProperty(config, folderPath); folderData = foundry.utils.getProperty(this.config, folderId) ?? {},
promises = [];
game.packs.forEach(pack => {
promises.push(
new Promise(async resolve => {
const items = await pack.getDocuments({ type__in: folderData?.type });
resolve(items);
})
)
});
Promise.all(promises).then(result => {
this.items = ItemBrowser.sortBy(result.flatMap(r => r), 'name');
clearTimeout(loadTimeout);
ItemBrowser.toggleLoader(target, false);
this.render({ force: true });
});
const columns = ItemBrowser.getFolderConfig(folderData).map(col => ({ const columns = ItemBrowser.getFolderConfig(folderData).map(col => ({
...col, ...col,
@ -179,22 +200,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
})); }));
this.selectedMenu = { this.selectedMenu = {
path: folderPath.split('.'), path: folderId.split('.'),
data: { data: {
...folderData, ...folderData,
columns: columns 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 }));
}
this.items = ItemBrowser.sortBy(items, 'name');
if (target) { if (target) {
target target
.closest('.compendium-sidebar') .closest('.compendium-sidebar')
@ -202,8 +214,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
.forEach(element => element.classList.remove('is-selected')); .forEach(element => element.classList.remove('is-selected'));
target.classList.add('is-selected'); target.classList.add('is-selected');
} }
this.render({ force: true });
} }
_replaceHTML(result, content, options) { _replaceHTML(result, content, options) {
@ -211,6 +221,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
super._replaceHTML(result, content, options); super._replaceHTML(result, content, options);
} }
static toggleLoader(target, state) {
if(!target) return;
const container = target.closest(".window-content");
return setTimeout(() => {
container.classList.toggle("loader", state);
}, 100);
}
static expandContent(_, target) { static expandContent(_, target) {
const parent = target.parentElement; const parent = target.parentElement;
parent.classList.toggle('expanded'); parent.classList.toggle('expanded');

View file

@ -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: { features: {
columns: [], columns: [],
filters: [] filters: []
@ -305,10 +403,10 @@ export const typeConfig = {
}; };
export const compendiumConfig = { export const compendiumConfig = {
daggerheart: { // daggerheart: {
id: 'daggerheart', // id: 'daggerheart',
label: 'DAGGERHEART', // label: 'DAGGERHEART',
folders: { // folders: {
adversaries: { adversaries: {
id: 'adversaries', id: 'adversaries',
keys: ['adversaries'], keys: ['adversaries'],
@ -321,28 +419,56 @@ export const compendiumConfig = {
keys: ['ancestries'], keys: ['ancestries'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.ancestries', label: 'DAGGERHEART.UI.ItemBrowser.folders.ancestries',
type: ['ancestry'], type: ['ancestry'],
folders: { /* folders: {
features: { features: {
id: 'features', id: 'features',
keys: ['ancestries'], keys: ['ancestries'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.features', label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
type: ['feature'] type: ['feature']
} }
} } */
}, },
equipments: { equipments: {
id: 'equipments', id: 'equipments',
keys: ['armors', 'weapons', 'consumables', 'loot'], keys: ['armors', 'weapons', 'consumables', 'loot'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.equipment', label: 'DAGGERHEART.UI.ItemBrowser.folders.equipment',
type: ['armor', 'weapon', 'consumable', 'loot'], type: ['armor', 'weapon', 'consumable', 'loot'],
listType: 'items' listType: 'items',
folders: {
weapons: {
id: 'weapons',
keys: ['weapons'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.weapons',
type: ['weapon'],
listType: 'weapons'
},
armors: {
id: 'armors',
keys: ['armors'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.armors',
type: ['armor'],
listType: 'armors'
},
consumables: {
id: 'consumables',
keys: ['consumables'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.consumables',
type: ['consumable']
},
loots: {
id: 'loots',
keys: ['loots'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.loots',
type: ['loot']
}
}
}, },
classes: { classes: {
id: 'classes', id: 'classes',
keys: ['classes'], keys: ['classes'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.classes', label: 'DAGGERHEART.UI.ItemBrowser.folders.classes',
type: ['class'], type: ['class'],
folders: { /* folders: {
features: { features: {
id: 'features', id: 'features',
keys: ['classes'], keys: ['classes'],
@ -356,7 +482,7 @@ export const compendiumConfig = {
type: ['armor', 'weapon', 'consumable', 'loot'], type: ['armor', 'weapon', 'consumable', 'loot'],
listType: 'items' listType: 'items'
} }
}, }, */
listType: 'classes' listType: 'classes'
}, },
subclasses: { subclasses: {
@ -378,14 +504,14 @@ export const compendiumConfig = {
keys: ['communities'], keys: ['communities'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.communities', label: 'DAGGERHEART.UI.ItemBrowser.folders.communities',
type: ['community'], type: ['community'],
folders: { /* folders: {
features: { features: {
id: 'features', id: 'features',
keys: ['communities'], keys: ['communities'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.features', label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
type: ['feature'] type: ['feature']
} }
} } */
}, },
environments: { environments: {
id: 'environments', id: 'environments',
@ -399,15 +525,21 @@ export const compendiumConfig = {
label: 'DAGGERHEART.UI.ItemBrowser.folders.beastforms', label: 'DAGGERHEART.UI.ItemBrowser.folders.beastforms',
type: ['beastform'], type: ['beastform'],
listType: 'beastforms', listType: 'beastforms',
folders: { /* folders: {
features: { features: {
id: 'features', id: 'features',
keys: ['beastforms'], keys: ['beastforms'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.features', label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
type: ['feature'] type: ['feature']
} }
} } */
},
features: {
id: 'features',
keys: ['features'],
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
type: ['feature']
} }
} // }
} // }
}; };

View file

@ -23,4 +23,27 @@
color: var(--color-form-hint-hover); color: var(--color-form-hint-hover);
} }
} }
.loader {
div {
opacity: .5;
// transition: opacity .3s ease-in-out;
}
&: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); }
}
} }

View file

@ -71,6 +71,7 @@
} }
.compendium-results { .compendium-results {
position: relative;
padding: 16px; padding: 16px;
} }
@ -101,10 +102,14 @@
.folder-list, .folder-list,
.item-list-header, .item-list-header,
.item-header > div { .item-header > div {
gap: 10px;
cursor: pointer; cursor: pointer;
} }
.item-list-header,
.item-header > div {
gap: 10px;
}
.item-filter { .item-filter {
display: flex; display: flex;
align-items: center; align-items: center;
@ -228,7 +233,8 @@
} }
.item-list-header, .item-list-header,
.item-list { .item-list,
.compendium-sidebar > .folder-list {
overflow-y: auto; overflow-y: auto;
scrollbar-gutter: stable; scrollbar-gutter: stable;
scrollbar-width: thin; scrollbar-width: thin;

View file

@ -1,32 +1,49 @@
<div class="compendium-sidebar"> <div class="compendium-sidebar">
{{#each compendiums}} <div class="folder-list">
<details class="compendium-container" data-compendium-id="{{id}}" open> {{#each compendiums}}
<summary> <div class="{{#if selected}} is-selected{{/if}}" data-action="selectFolder" data-folder-id="{{id}}" {{#if folders.length}}data-tooltip="DAGGERHEART.UI.Tooltip.rightClickExtand" data-tooltip-direction="RIGHT"{{/if}}>{{label}}</div>
{{label}} {{#if folders.length}}
<line-div></line-div> <div class="subfolder-list">
</summary> <div class="wrapper">
<div class="folder-list"> {{#each folders}}
{{#each folders}} <div
<div class="{{#if selected}} is-selected{{/if}}" data-action="selectFolder" data-folder-id="{{id}}" {{#if folders.length}}data-tooltip="DAGGERHEART.UI.Tooltip.rightClickExtand" data-tooltip-direction="RIGHT"{{/if}}>{{label}}</div> class="subfolder-item {{#if selected}} is-selected{{/if}}"
{{!-- <div data-action="selectFolder" data-folder-id="{{id}}">{{label}}</div> --}} data-action="selectFolder"
{{#if folders.length}} data-folder-id="{{../id}}.folders.{{id}}"
<div class="subfolder-list"> >
<div class="wrapper"> <span>• {{label}}</span>
{{#each folders}} </div>
<div {{/each}}
class="subfolder-item {{#if selected}} is-selected{{/if}}" </div>
data-action="selectFolder"
data-folder-id="{{../id}}.folders.{{id}}"
>
<span>• {{label}}</span>
</div>
{{/each}}
</div> </div>
</div> {{/if}}
{{/if}} {{!-- <details class="compendium-container" data-compendium-id="{{id}}" open>
{{/each}} <summary>
</div> {{label}}
<line-div></line-div> <line-div></line-div>
</details> </summary>
{{/each}} <div class="folder-list">
{{#each folders}}
<div class="{{#if selected}} is-selected{{/if}}" data-action="selectFolder" data-folder-id="{{id}}" {{#if folders.length}}data-tooltip="DAGGERHEART.UI.Tooltip.rightClickExtand" data-tooltip-direction="RIGHT"{{/if}}>{{label}}</div>
{{#if folders.length}}
<div class="subfolder-list">
<div class="wrapper">
{{#each folders}}
<div
class="subfolder-item {{#if selected}} is-selected{{/if}}"
data-action="selectFolder"
data-folder-id="{{../id}}.folders.{{id}}"
>
<span>• {{label}}</span>
</div>
{{/each}}
</div>
</div>
{{/if}}
{{/each}}
</div>
<line-div></line-div>
</details> --}}
{{/each}}
</div>
</div> </div>