Compendium browser per type (#1103)

* Compendium Browser per type

* Sort number column

* Re-add subclass config

* Sidebar buttons

* Add Characters folder

* Css

* Done
This commit is contained in:
Dapoulp 2025-08-28 03:29:40 +02:00 committed by GitHub
parent 1eb3ff11c0
commit 9dd773001d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 542 additions and 300 deletions

View file

@ -162,6 +162,9 @@ Hooks.on('ready', async () => {
if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).displayFear !== 'hide') if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).displayFear !== 'hide')
ui.resources.render({ force: true }); ui.resources.render({ force: true });
if(!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
ui.compendiumBrowser = new applications.ui.ItemBrowser();
registerCountdownHooks(); registerCountdownHooks();
socketRegistration.registerSocketHooks(); socketRegistration.registerSocketHooks();
registerRollDiceHooks(); registerRollDiceHooks();
@ -305,3 +308,6 @@ Hooks.on('moveToken', async (movedToken, data) => {
await effect.value.update({ disabled: effect.disabled }); 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));

View file

@ -2423,6 +2423,7 @@
"evasionMax": "Evasion (Max)", "evasionMax": "Evasion (Max)",
"subtype": "Subtype", "subtype": "Subtype",
"folders": { "folders": {
"characters": "Characters",
"adversaries": "Adversaries", "adversaries": "Adversaries",
"ancestries": "Ancestries", "ancestries": "Ancestries",
"equipment": "Equipment", "equipment": "Equipment",
@ -2433,7 +2434,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

@ -1,6 +1,5 @@
import { abilities } from '../../config/actorConfig.mjs'; import { abilities } from '../../config/actorConfig.mjs';
import { burden } from '../../config/generalConfig.mjs'; import { burden } from '../../config/generalConfig.mjs';
import { ItemBrowser } from '../ui/itemBrowser.mjs';
import { createEmbeddedItemsWithEffects, createEmbeddedItemWithEffects } from '../../helpers/utils.mjs'; import { createEmbeddedItemsWithEffects, createEmbeddedItemWithEffects } from '../../helpers/utils.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -46,8 +45,6 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
}; };
this._dragDrop = this._createDragDropHandlers(); this._dragDrop = this._createDragDropHandlers();
this.itemBrowser = null;
} }
get title() { get title() {
@ -425,8 +422,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
} }
@ -449,7 +445,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
'type': { key: 'type', value: type } 'type': { key: 'type', value: type }
}; };
return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true })); ui.compendiumBrowser.open(presets);
} }
static async viewItem(_, target) { static async viewItem(_, target) {
@ -567,7 +563,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
{ overwrite: true } { overwrite: true }
); );
if (this.itemBrowser) this.itemBrowser.close(); if (ui.compendiumBrowser) ui.compendiumBrowser.close();
this.close(); this.close();
} }

View file

@ -1,6 +1,5 @@
import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs'; import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs';
import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs'; import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs';
import { ItemBrowser } from '../ui/itemBrowser.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -12,8 +11,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
this._dragDrop = this._createDragDropHandlers(); this._dragDrop = this._createDragDropHandlers();
this.tabGroups.primary = 'advancements'; this.tabGroups.primary = 'advancements';
this.itemBrowser = null;
} }
get title() { get title() {
@ -540,7 +537,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
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
@ -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) { static async selectPreview(_, button) {
@ -662,7 +658,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
}, {}); }, {});
await this.actor.levelUp(levelupData); await this.actor.levelUp(levelupData);
if (this.itemBrowser) this.itemBrowser.close();
if (ui.compendiumBrowser) ui.compendiumBrowser.close();
this.close(); this.close();
} }
} }

View file

@ -5,7 +5,6 @@ import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
import FilterMenu from '../../ux/filter-menu.mjs'; import FilterMenu from '../../ux/filter-menu.mjs';
import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs'; import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@ -29,8 +28,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
toggleEquipItem: CharacterSheet.#toggleEquipItem, toggleEquipItem: CharacterSheet.#toggleEquipItem,
toggleResourceDice: CharacterSheet.#toggleResourceDice, toggleResourceDice: CharacterSheet.#toggleResourceDice,
handleResourceDice: CharacterSheet.#handleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice,
useDowntime: this.useDowntime, useDowntime: this.useDowntime
tempBrowser: CharacterSheet.#tempBrowser
}, },
window: { window: {
resizable: true, resizable: true,
@ -635,7 +633,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
const { key } = button.dataset; const { key } = button.dataset;
const presets = { const presets = {
compendium: 'daggerheart',
folder: key, folder: key,
filter: filter:
key === 'subclasses' 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. * Handle the roll values of resource dice.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}

View file

@ -1,6 +1,5 @@
const { HandlebarsApplicationMixin } = foundry.applications.api; const { HandlebarsApplicationMixin } = foundry.applications.api;
import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs'; import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs';
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
const typeSettingsMap = { const typeSettingsMap = {
character: 'extendCharacterDescriptions', character: 'extendCharacterDescriptions',
@ -589,28 +588,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 }
@ -620,7 +618,7 @@ export default function DHApplicationMixin(Base) {
return; return;
} }
return new ItemBrowser({ presets }).render({ force: true }); ui.compendiumBrowser.open(presets);
} }
/** /**

View file

@ -3,3 +3,4 @@ export { default as DhCombatTracker } from './combatTracker.mjs';
export * as DhCountdowns from './countdowns.mjs'; export * as DhCountdowns from './countdowns.mjs';
export { default as DhFearTracker } from './fearTracker.mjs'; export { default as DhFearTracker } from './fearTracker.mjs';
export { default as DhHotbar } from './hotbar.mjs'; export { default as DhHotbar } from './hotbar.mjs';
export { ItemBrowser } from './itemBrowser.mjs';

View file

@ -15,16 +15,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this.fieldFilter = []; this.fieldFilter = [];
this.selectedMenu = { path: [], data: null }; this.selectedMenu = { path: [], data: null };
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig; this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
this.presets = options.presets; this.presets = {};
if (this.presets?.compendium && this.presets?.folder)
ItemBrowser.selectFolder.call(this, null, null, this.presets.compendium, this.presets.folder);
} }
/** @inheritDoc */ /** @inheritDoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
id: 'itemBrowser', id: 'itemBrowser',
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser'], classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'loader'],
tag: 'div', tag: 'div',
window: { window: {
frame: true, 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 */ /** @inheritDoc */
async _preRender(context, options) { async _preRender(context, options) {
if (context.presets?.render?.noFolder || context.presets?.render?.lite) this.presets = options.presets ?? {};
options.parts.splice(options.parts.indexOf('sidebar'), 1);
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); await super._preRender(context, options);
} }
@ -103,22 +98,18 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
async _onRender(context, options) { async _onRender(context, options) {
await super._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._createSearchFilter();
this._createFilterInputs();
this._createDragProcess();
if (context.presets?.render?.lite) this.element.classList.add('lite'); this.element.classList.toggle('lite', this.presets?.render?.lite === true);
this.element.classList.toggle('no-folder', this.presets?.render?.noFolder === true);
if (context.presets?.render?.noFolder) this.element.classList.add('no-folder'); this.element.classList.toggle('no-filter', this.presets?.render?.noFilter === true);
this.element.querySelectorAll('.folder-list > [data-action="selectFolder"]').forEach(element => {
if (context.presets?.render?.noFilter) this.element.classList.add('no-filter'); element.hidden = this.presets.render?.folders?.length && !this.presets.render.folders.includes(element.dataset.folderId);
});
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();
}
} }
_attachPartListeners(partId, htmlElement, options) { _attachPartListeners(partId, htmlElement, options) {
@ -139,19 +130,23 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
async _prepareContext(options) { async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
context.compendiums = this.getCompendiumFolders(foundry.utils.deepClone(this.config)); context.compendiums = this.getCompendiumFolders(foundry.utils.deepClone(this.config));
// context.pathTitle = this.pathTile;
context.menu = this.selectedMenu; context.menu = this.selectedMenu;
context.formatLabel = this.formatLabel; context.formatLabel = this.formatLabel;
context.formatChoices = this.formatChoices; context.formatChoices = this.formatChoices;
context.fieldFilter = this.fieldFilter = this._createFieldFilter();
context.items = this.items; context.items = this.items;
context.presets = this.presets; context.presets = this.presets;
return context; return context;
} }
open(presets = {}) {
this.presets = presets;
ItemBrowser.selectFolder.call(this);
}
getCompendiumFolders(config, parent = null, depth = 0) { getCompendiumFolders(config, parent = null, depth = 0) {
let folders = []; let folders = [];
Object.values(config).forEach(c => { Object.values(config).forEach(c => {
// if(this.presets.render?.folders?.length && !this.presets.render.folders.includes(c.id)) return;
const folder = { const folder = {
id: c.id, id: c.id,
label: game.i18n.localize(c.label), label: game.i18n.localize(c.label),
@ -162,16 +157,14 @@ 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) {
const config = foundry.utils.deepClone(this.config), const folderId = target?.dataset?.folderId ?? this.presets.folder,
compendium = compend ?? target.closest('[data-compendium-id]').dataset.compendiumId, folderData = foundry.utils.getProperty(this.config, folderId) ?? {};
folderId = folder ?? target.dataset.folderId,
folderPath = `${compendium}.folders.${folderId}`,
folderData = foundry.utils.getProperty(config, folderPath);
const columns = ItemBrowser.getFolderConfig(folderData).map(col => ({ const columns = ItemBrowser.getFolderConfig(folderData).map(col => ({
...col, ...col,
@ -179,31 +172,17 @@ 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 = []; await this.render({ force: true, presets: this.presets });
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(this.selectedMenu?.data?.type?.length)
this.loadItems();
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 });
} }
_replaceHTML(result, content, options) { _replaceHTML(result, content, options) {
@ -211,6 +190,75 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
super._replaceHTML(result, content, options); 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) { static expandContent(_, target) {
const parent = target.parentElement; const parent = target.parentElement;
parent.classList.toggle('expanded'); parent.classList.toggle('expanded');
@ -328,6 +376,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
for (const li of html.querySelectorAll('.item-container')) { for (const li of html.querySelectorAll('.item-container')) {
const itemUUID = li.dataset.itemUuid, const itemUUID = li.dataset.itemUuid,
item = this.items.find(i => i.uuid === itemUUID); item = this.items.find(i => i.uuid === itemUUID);
if(!item) continue;
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name); const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
if (matchesSearch) this.#filteredItems.browser.search.add(item.id); if (matchesSearch) this.#filteredItems.browser.search.add(item.id);
const { input } = this.#filteredItems.browser; const { input } = this.#filteredItems.browser;
@ -419,11 +468,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
const newOrder = [...itemList].reverse().sort((a, b) => { const newOrder = [...itemList].reverse().sort((a, b) => {
const aProp = a.querySelector(`[data-item-key="${key}"]`), 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') { if (type === 'DESC') {
return aProp.innerText < bProp.innerText ? 1 : -1; return aValue < bValue ? 1 : -1;
} else { } else {
return aProp.innerText > bProp.innerText ? 1 : -1; return aValue > bValue ? 1 : -1;
} }
}); });
@ -452,4 +503,41 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
_canDragStart() { _canDragStart() {
return true; 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 = `
<i class="fa-solid fa-book-atlas"></i>
${game.i18n.localize("DAGGERHEART.UI.Tooltip.compendiumBrowser")}
`;
button.addEventListener("click", event => {
ui.compendiumBrowser.open(menus[sectionId]);
});
headerActions.append(button);
}
}
} }

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: []
@ -257,7 +355,7 @@ export const typeConfig = {
{ {
key: 'system.domains', key: 'system.domains',
label: 'DAGGERHEART.GENERAL.Domain.plural', 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' operator: 'contains2'
} }
] ]
@ -265,18 +363,28 @@ export const typeConfig = {
subclasses: { subclasses: {
columns: [ columns: [
{ {
key: 'id', key: 'system.linkedClass',
label: 'TYPES.Item.class', label: 'Class',
format: id => { format: linkedClass => linkedClass.name
return '';
}
}, },
{ {
key: 'system.spellcastingTrait', key: 'system.spellcastingTrait',
label: 'DAGGERHEART.ITEMS.Subclass.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: { beastforms: {
columns: [ columns: [
@ -305,10 +413,13 @@ export const typeConfig = {
}; };
export const compendiumConfig = { export const compendiumConfig = {
daggerheart: { characters: {
id: 'daggerheart', id: 'characters',
label: 'DAGGERHEART', keys: ['characters'],
folders: { label: 'DAGGERHEART.UI.ItemBrowser.folders.characters',
type: ['character'],
// listType: 'characters'
},
adversaries: { adversaries: {
id: 'adversaries', id: 'adversaries',
keys: ['adversaries'], keys: ['adversaries'],
@ -321,28 +432,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 +495,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 +517,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 +538,19 @@ 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

@ -30,12 +30,13 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/dialogs/downtime/activities.hbs', 'systems/daggerheart/templates/dialogs/downtime/activities.hbs',
'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs', 'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs',
'systems/daggerheart/templates/ui/chat/parts/roll-part.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/damage-part.hbs',
'systems/daggerheart/templates/ui/chat/parts/target-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/target-part.hbs',
'systems/daggerheart/templates/ui/chat/parts/button-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', 'systems/daggerheart/templates/scene/dh-config.hbs',
]); ]);

View file

@ -23,4 +23,29 @@
color: var(--color-form-hint-hover); 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); }
}
} }

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;
@ -286,6 +292,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; gap: 5px;
flex: 1;
.item-container { .item-container {
&:hover { &:hover {
@ -385,8 +392,12 @@
margin: 0; margin: 0;
.title { .title {
margin: 0;
text-align: center; text-align: center;
font-weight: bold;
}
.hint {
flex: unset;
} }
} }
@ -398,7 +409,7 @@
&.lite, &.lite,
&.no-folder { &.no-folder {
.menu-path { .compendium-sidebar, .menu-path {
display: none; display: none;
} }
} }

View file

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

View file

@ -0,0 +1,22 @@
{{#each fieldFilter}}
{{#if choices }}
<div class="form-group"{{#with (lookup @root.presets.filter key)}}{{#if forced}} disabled{{/if}}{{/with}}>
<label>{{localize label}}</label>
<div class="form-fields">
<select data-key="{{key}}" name={{name}}>
{{selectOptions choices valueAttr="value" blank="" localize=true selected=value}}
</select>
</div>
</div>
{{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}}

View file

@ -1,5 +1,5 @@
<div class="compendium-results"> <div class="compendium-results">
{{#if menu.data }} {{#if menu.path.length }}
<div class="menu-path"> <div class="menu-path">
{{#each menu.path}} {{#each menu.path}}
{{#if (eq this "folders")}} {{#if (eq this "folders")}}
@ -19,67 +19,23 @@
</div> </div>
<input type="search" name="search" class="search-input" placeholder="{{localize 'DAGGERHEART.UI.ItemBrowser.searchPlaceholder'}}"> <input type="search" name="search" class="search-input" placeholder="{{localize 'DAGGERHEART.UI.ItemBrowser.searchPlaceholder'}}">
</div> </div>
{{#if fieldFilter.length}} <a data-tooltip="{{localize 'DAGGERHEART.UI.ItemBrowser.tooltipFilters'}}" data-action="expandContent" disabled><i class="fa-solid fa-filter"></i></a>
<a data-tooltip="{{localize 'DAGGERHEART.UI.ItemBrowser.tooltipFilters'}}" data-action="expandContent"><i class="fa-solid fa-filter"></i></a>
{{/if}}
<a data-tooltip="{{localize 'DAGGERHEART.UI.ItemBrowser.tooltipErase'}}" data-action="resetFilters"><i class="fa-solid fa-eraser"></i></a> <a data-tooltip="{{localize 'DAGGERHEART.UI.ItemBrowser.tooltipErase'}}" data-action="resetFilters"><i class="fa-solid fa-eraser"></i></a>
</div> </div>
<div class="filter-content extensible"> <div class="filter-content extensible">
<div class="wrapper"> <div class="wrapper"></div>
{{#each fieldFilter}}
{{#if choices }}
<div class="form-group"{{#with (lookup @root.presets.filter key)}}{{#if forced}} disabled{{/if}}{{/with}}>
<label>{{localize label}}</label>
<div class="form-fields">
<select data-key="{{key}}" name={{name}}>
{{selectOptions choices valueAttr="value" blank="" localize=true selected=value}}
</select>
</div> </div>
</div> </div>
{{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}}
</div>
</div>
</div>
{{!-- <div class="item-list-container"> --}}
{{#if menu.data.columns.length}} {{#if menu.data.columns.length}}
<div class="item-list-header"> <div class="item-list-header">
<div class="item-list-img"></div> <div class="item-list-img"></div>
<div class="item-list-name" data-sort-key="name" data-sort-type="ASC" data-action="sortList">{{localize 'DAGGERHEART.UI.ItemBrowser.columnName'}}</div> <div class="item-list-name" data-sort-key="name" data-sort-type="ASC" data-action="sortList">{{localize 'DAGGERHEART.UI.ItemBrowser.columnName'}}</div>
{{#each menu.data.columns}} {{#each menu.data.columns}}
<span data-sort-key="{{key}}" data-sort-type="" data-action="sortList">{{localize label}}</span> <div data-sort-key="{{key}}" data-sort-type="" data-action="sortList">{{localize label}}</div>
{{/each}} {{/each}}
</div> </div>
{{/if}} {{/if}}
<div class="item-list"> <div class="item-list"></div>
{{#each items}}
<div class="item-container" data-item-uuid="{{uuid}}" draggable="true">
<div class="item-header">
<div class="item-info" data-action="expandContent">
<img src="{{img}}" data-item-key="img" class="item-list-img">
<span data-item-key="name" class="item-list-name">{{name}}</span>
{{#each ../menu.data.columns}}
<span data-item-key="{{key}}">{{#with (@root.formatLabel ../this this) as | label |}}{{{label}}}{{/with}}</span>
{{/each}}
</div>
</div>
<div class="item-desc extensible">
<span class="wrapper">{{{system.description}}}</span>
</div>
</div>
{{/each}}
</div>
{{!-- </div> --}}
{{else}} {{else}}
<div class="welcome-message"> <div class="welcome-message">
<h2 class="title">{{localize "DAGGERHEART.UI.ItemBrowser.title"}}</h2> <h2 class="title">{{localize "DAGGERHEART.UI.ItemBrowser.title"}}</h2>

View file

@ -0,0 +1,16 @@
{{#each items}}
<div class="item-container" data-item-uuid="{{uuid}}" draggable="true">
<div class="item-header">
<div class="item-info" data-action="expandContent">
<img src="{{img}}" data-item-key="img" class="item-list-img">
<span data-item-key="name" class="item-list-name">{{name}}</span>
{{#each ../menu.data.columns}}
<span data-item-key="{{key}}">{{#with (@root.formatLabel ../this this) as | label |}}{{{label}}}{{/with}}</span>
{{/each}}
</div>
</div>
<div class="item-desc extensible">
<span class="wrapper">{{{system.description}}}</span>
</div>
</div>
{{/each}}

View file

@ -1,14 +1,7 @@
<div class="compendium-sidebar"> <div class="compendium-sidebar">
{{#each compendiums}}
<details class="compendium-container" data-compendium-id="{{id}}" open>
<summary>
{{label}}
<line-div></line-div>
</summary>
<div class="folder-list"> <div class="folder-list">
{{#each folders}} {{#each compendiums}}
<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> <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>
{{!-- <div data-action="selectFolder" data-folder-id="{{id}}">{{label}}</div> --}}
{{#if folders.length}} {{#if folders.length}}
<div class="subfolder-list"> <div class="subfolder-list">
<div class="wrapper"> <div class="wrapper">
@ -26,7 +19,4 @@
{{/if}} {{/if}}
{{/each}} {{/each}}
</div> </div>
<line-div></line-div>
</details>
{{/each}}
</div> </div>