Merged with main

This commit is contained in:
WBHarry 2025-08-10 01:31:05 +02:00
commit 130b2bf100
65 changed files with 1085 additions and 860 deletions

View file

@ -6,6 +6,6 @@ export { default as DeathMove } from './deathMove.mjs';
export { default as Downtime } from './downtime.mjs';
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
export { default as OwnershipSelection } from './ownershipSelection.mjs';
export { default as RerollDialog } from './rerollDialog.mjs';
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';

View file

@ -45,6 +45,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
return this.config.title;
}
get actor() {
return this.config?.data?.parent;
}
/** @override */
static PARTS = {
header: {
@ -69,9 +73,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
icon
}));
this.config.costs ??= [];
if (this.config.costs?.length) {
const updatedCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call(
this.action,
this.action ?? { actor: this.actor },
this.config.costs
);
context.costs = updatedCosts.map(x => ({
@ -80,7 +85,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
? this.action.parent.parent.name
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
}));
context.canRoll = game.system.api.fields.ActionFields.CostField.hasCost.call(this.action, updatedCosts);
context.canRoll = game.system.api.fields.ActionFields.CostField.hasCost.call(
this.action ?? { actor: this.actor },
updatedCosts
);
this.config.data.scale = this.config.costs[0].total;
}
if (this.config.uses?.max) {
@ -143,6 +151,20 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.config.experiences.indexOf(button.dataset.key) > -1
? this.config.experiences.filter(x => x !== button.dataset.key)
: [...this.config.experiences, button.dataset.key];
if (this.config?.data?.parent?.type === 'character' || this.config?.data?.parent?.type === 'companion') {
this.config.costs =
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
? this.config.costs.filter(x => x.extKey !== button.dataset.key)
: [
...this.config.costs,
{
extKey: button.dataset.key,
key: 'hope',
value: 1,
name: this.config.data?.experiences?.[button.dataset.key]?.name
}
];
}
this.render();
}
@ -152,8 +174,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.config.roll.type = this.reactionOverride
? CONFIG.DH.ITEM.actionTypes.reaction.id
: this.config.roll.type === CONFIG.DH.ITEM.actionTypes.reaction.id
? null
: this.config.roll.type;
? null
: this.config.roll.type;
this.render();
}
}

View file

@ -56,12 +56,14 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
label,
icon
}));
context.modifiers = this.config.modifiers;
return context;
}
static updateRollConfiguration(_event, _, formData) {
const { ...rest } = foundry.utils.expandObject(formData.object);
foundry.utils.mergeObject(this.config.roll, rest.roll);
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
this.config.selectedRollMode = rest.selectedRollMode;
this.render();

View file

@ -0,0 +1,279 @@
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(message, options = {}) {
super(options);
this.message = message;
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
const type = message.system.damage[typeKey];
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
const part = type.parts[partKey];
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
const dice = part.dice[diceKey];
const activeResults = dice.results.filter(x => x.active);
acc[diceKey] = {
dice: dice.dice,
selectedResults: activeResults.length,
maxSelected: activeResults.length,
results: activeResults.map(x => ({ ...x, selected: true }))
};
return acc;
}, {});
return acc;
}, {});
return acc;
}, {});
}
static DEFAULT_OPTIONS = {
id: 'reroll-dialog',
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
window: {
icon: 'fa-solid fa-dice'
},
actions: {
toggleResult: RerollDamageDialog.#toggleResult,
selectRoll: RerollDamageDialog.#selectRoll,
doReroll: RerollDamageDialog.#doReroll,
save: RerollDamageDialog.#save
}
};
/** @override */
static PARTS = {
main: {
id: 'main',
template: 'systems/daggerheart/templates/dialogs/rerollDialog/damage/main.hbs'
},
footer: {
id: 'footer',
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
}
};
get title() {
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.damageTitle');
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
element.addEventListener('change', this.toggleDice.bind(this));
});
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.damage = this.damage;
context.disabledReroll = !this.getRerollDice().length;
context.saveDisabled = !this.isSelectionDone();
return context;
}
static async #save() {
const update = {
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
const type = this.damage[typeKey];
let typeTotal = 0;
const messageType = this.message.system.damage[typeKey];
const parts = Object.keys(type).map(partKey => {
const part = type[partKey];
const messagePart = messageType.parts[partKey];
let partTotal = messagePart.modifierTotal;
const dice = Object.keys(part).map(diceKey => {
const dice = part[diceKey];
const total = dice.results.reduce((acc, result) => {
if (result.active) acc += result.result;
return acc;
}, 0);
partTotal += total;
const messageDice = messagePart.dice[diceKey];
return {
...messageDice,
total: total,
results: dice.results.map(x => ({
...x,
hasRerolls: dice.results.length > 1
}))
};
});
typeTotal += partTotal;
return {
...messagePart,
total: partTotal,
dice: dice
};
});
acc[typeKey] = {
...messageType,
total: typeTotal,
parts: parts
};
return acc;
}, {})
};
await this.message.update(update);
await this.close();
}
getRerollDice() {
const rerollDice = [];
Object.keys(this.damage).forEach(typeKey => {
const type = this.damage[typeKey];
Object.keys(type).forEach(partKey => {
const part = type[partKey];
Object.keys(part).forEach(diceKey => {
const dice = part[diceKey];
Object.keys(dice.results).forEach(resultKey => {
const result = dice.results[resultKey];
if (result.toReroll) {
rerollDice.push({
...result,
dice: dice.dice,
type: typeKey,
part: partKey,
dice: diceKey,
result: resultKey
});
}
});
});
});
});
return rerollDice;
}
isSelectionDone() {
const diceFinishedData = [];
Object.keys(this.damage).forEach(typeKey => {
const type = this.damage[typeKey];
Object.keys(type).forEach(partKey => {
const part = type[partKey];
Object.keys(part).forEach(diceKey => {
const dice = part[diceKey];
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
diceFinishedData.push(selected === dice.maxSelected);
});
});
});
return diceFinishedData.every(x => x);
}
toggleDice(event) {
const target = event.target;
const { type, part, dice } = target.dataset;
const toggleDice = this.damage[type][part][dice];
const existingDiceRerolls = this.getRerollDice().filter(
x => x.type === type && x.part === part && x.dice === dice
);
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
toggleDice.toReroll = !allRerolled;
toggleDice.results.forEach(result => {
if (result.active) {
result.toReroll = !allRerolled;
}
});
this.render();
}
static #toggleResult(event) {
event.stopPropagation();
const target = event.target.closest('.to-reroll-result');
const { type, part, dice, result } = target.dataset;
const toggleDice = this.damage[type][part][dice];
const toggleResult = toggleDice.results[result];
toggleResult.toReroll = !toggleResult.toReroll;
const existingDiceRerolls = this.getRerollDice().filter(
x => x.type === type && x.part === part && x.dice === dice
);
const allToReroll = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
toggleDice.toReroll = allToReroll;
this.render();
}
static async #selectRoll(_, button) {
const { type, part, dice, result } = button.dataset;
const diceVal = this.damage[type][part][dice];
const diceResult = diceVal.results[result];
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
return ui.notifications.warn(
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
);
}
if (diceResult.active) {
diceVal.toReroll = false;
diceResult.toReroll = false;
}
diceVal.selectedResults += diceResult.active ? -1 : 1;
diceResult.active = !diceResult.active;
this.render();
}
static async #doReroll() {
const toReroll = this.getRerollDice().map(x => {
const { type, part, dice, result } = x;
const diceData = this.damage[type][part][dice].results[result];
return {
...diceData,
dice: this.damage[type][part][dice].dice,
typeKey: type,
partKey: part,
diceKey: dice,
resultsIndex: result
};
});
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
if (game.modules.get('dice-so-nice')?.active) {
const diceSoNiceRoll = {
_evaluated: true,
dice: roll.dice,
options: { appearance: {} }
};
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
}
toReroll.forEach((data, index) => {
const { typeKey, partKey, diceKey, resultsIndex } = data;
const rerolledDice = roll.dice[index];
const dice = this.damage[typeKey][partKey][diceKey];
dice.toReroll = false;
dice.results[resultsIndex].active = false;
dice.results[resultsIndex].discarded = true;
dice.results[resultsIndex].toReroll = false;
dice.results.splice(dice.results.length, 0, {
...rerolledDice.results[0],
toReroll: false,
selected: true
});
});
this.render();
}
}

View file

@ -1,4 +1,5 @@
import DhAppearance from '../../data/settings/Appearance.mjs';
import { getDiceSoNicePreset } from '../../config/generalConfig.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -25,7 +26,8 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
},
actions: {
reset: this.reset,
save: this.save
save: this.save,
preview: this.preview
},
form: { handler: this.updateData, submitOnChange: true }
};
@ -89,6 +91,22 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
this.render();
}
static async preview() {
const source = this.settings._source.diceSoNice[this.tabGroups.diceSoNice];
let faces = 'd12';
switch (this.tabGroups.diceSoNice) {
case 'advantage':
case 'disadvantage':
faces = 'd6';
}
const preset = await getDiceSoNicePreset(source, faces);
const diceSoNiceRoll = await new Roll(`1${faces}`).evaluate();
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, false);
}
static async reset() {
this.settings = new DhAppearance();
this.render();

View file

@ -631,13 +631,33 @@ export default class CharacterSheet extends DHBaseActorSheet {
},
hasRoll: true
};
this.document.diceRoll({
const result = await this.document.diceRoll({
...config,
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: abilityLabel
})
});
setTimeout(() => {
this.consumeResource(result?.costs);
}, 50);
}
async consumeResource(costs) {
if (!costs?.length) return;
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
const resource = usefulResources[c.key];
return {
key: c.key,
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
target: resource.target,
keyIsID: resource.keyIsID
};
});
await this.actor.modifyResource(resources);
}
//TODO: redo toggleEquipItem method

View file

@ -101,8 +101,7 @@ export default class DhpEnvironment extends DHBaseActorSheet {
const item = event.currentTarget.closest('.inventory-item');
if (item) {
const adversary = game.actors.find(x => x.type === 'adversary' && x.id === item.dataset.itemId);
const adversaryData = { type: 'Actor', uuid: adversary.uuid };
const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid };
event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData));
event.dataTransfer.setDragImage(item, 60, 0);
}

View file

@ -51,9 +51,8 @@ import { ItemBrowser } from '../../ui/itemBrowser.mjs';
*/
/**
* @template {Constructor<foundry.applications.api.DocumentSheet>} BaseDocumentSheet
* @param {BaseDocumentSheet} Base - The base class to extend.
* @returns {BaseDocumentSheet}
* @template {new (...args: any[]) => {}} T
* @arg Base {T}
*/
export default function DHApplicationMixin(Base) {
class DHSheetV2 extends HandlebarsApplicationMixin(Base) {
@ -123,12 +122,13 @@ export default function DHApplicationMixin(Base) {
super._attachPartListeners(partId, htmlElement, options);
this._dragDrop.forEach(d => d.bind(htmlElement));
}
/**@inheritdoc */
async _onFirstRender(context, options) {
await super._onFirstRender(context, options);
const docs = [];
for (var docData of this.relatedDocs) {
for (const docData of this.relatedDocs) {
const doc = await foundry.utils.fromUuid(docData.uuid);
docs.push(doc);
}
@ -247,6 +247,9 @@ export default function DHApplicationMixin(Base) {
/* Context Menu */
/* -------------------------------------------- */
/**
* Create all configured context menus for this application ins tance.
*/
_createContextMenus() {
for (const config of this.options.contextMenus) {
const { handler, selector, options } = config;
@ -257,9 +260,9 @@ export default function DHApplicationMixin(Base) {
/* -------------------------------------------- */
/**
* Get the set of ContextMenu options for DomainCards.
* Get the set of ContextMenu options for ActiveEffects.
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
* @this {CharacterSheet}
* @this {DHSheetV2}
* @protected
*/
static #getEffectContextOptions() {
@ -305,8 +308,13 @@ export default function DHApplicationMixin(Base) {
}
/**
* Get the set of ContextMenu options.
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
* Get the common ContextMenu options for an element.
* @param {Object} options
* @param {boolean} [options.usable=false] - Whether to include an option to use the item or apply damage.
* @param {boolean} [options.toChat=false] - Whether to include an option to send the item to chat.
* @param {boolean} [options.deletable=true] - Whether to include an option to delete the item.
*
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]}
*/
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
const options = [
@ -325,7 +333,7 @@ export default function DHApplicationMixin(Base) {
}
];
if (usable)
if (usable) {
options.unshift({
name: 'DAGGERHEART.GENERAL.damage',
icon: 'fa-solid fa-explosion',
@ -340,15 +348,16 @@ export default function DHApplicationMixin(Base) {
}
});
options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
condition: target => {
const doc = getDocFromElementSync(target);
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
},
callback: async (target, event) => (await getDocFromElement(target)).use(event)
});
options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
condition: target => {
const doc = getDocFromElementSync(target);
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
},
callback: async (target, event) => (await getDocFromElement(target)).use(event)
});
}
if (toChat)
options.push({

View file

@ -7,8 +7,6 @@ const { ActorSheetV2 } = foundry.applications.sheets;
/**
* A base actor sheet extending {@link ActorSheetV2} via {@link DHApplicationMixin}
* @extends ActorSheetV2
* @mixes DHSheetV2
*/
export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
@ -106,7 +104,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
/**
* Get the set of ContextMenu options for Features.
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
* @this {DHSheetV2}
* @this {DHBaseActorSheet}
* @protected
*/
static #getFeatureContextOptions() {

View file

@ -7,8 +7,6 @@ const { ItemSheetV2 } = foundry.applications.sheets;
/**
* A base item sheet extending {@link ItemSheetV2} via {@link DHApplicationMixin}
* @extends ItemSheetV2
* @mixes DHSheetV2
*/
export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
/** @inheritDoc */
@ -108,7 +106,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
/**
* Get the set of ContextMenu options for Features.
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
* @this {DHSheetV2}
* @this {DHBaseItemSheet}
* @protected
*/
static #getFeatureContextOptions() {

View file

@ -23,8 +23,21 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
_getEntryContextOptions() {
return [
...super._getEntryContextOptions(),
// {
// name: 'Reroll',
// icon: '<i class="fa-solid fa-dice"></i>',
// condition: li => {
// const message = game.messages.get(li.dataset.messageId);
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
// },
// callback: li => {
// const message = game.messages.get(li.dataset.messageId);
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
// }
// },
{
name: 'Reroll',
name: 'Reroll Damage',
icon: '<i class="fa-solid fa-dice"></i>',
condition: li => {
const message = game.messages.get(li.dataset.messageId);
@ -35,7 +48,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
},
callback: li => {
const message = game.messages.get(li.dataset.messageId);
new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
new game.system.api.applications.dialogs.RerollDamageDialog(message).render({ force: true });
}
}
];
@ -54,14 +67,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
html.querySelectorAll('.simple-roll-button').forEach(element =>
element.addEventListener('click', event => this.onRollSimple(event, data.message))
);
html.querySelectorAll('.target-container').forEach(element => {
element.addEventListener('mouseenter', this.hoverTarget);
element.addEventListener('mouseleave', this.unhoverTarget);
element.addEventListener('click', this.clickTarget);
});
html.querySelectorAll('.button-target-selection').forEach(element => {
element.addEventListener('click', event => this.onTargetSelection(event, data.message));
});
html.querySelectorAll('.healing-button').forEach(element =>
element.addEventListener('click', event => this.onHealing(event, data.message))
);
@ -159,33 +164,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
});
}
onTargetSelection(event, message) {
event.stopPropagation();
const msg = ui.chat.collection.get(message._id);
msg.system.targetMode = Boolean(event.target.dataset.targetHit);
}
hoverTarget(event) {
event.stopPropagation();
const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (!token?.controlled) token._onHoverIn(event, { hoverOutOthers: true });
}
unhoverTarget(event) {
const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (!token?.controlled) token._onHoverOut(event);
}
clickTarget(event) {
event.stopPropagation();
const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (!token) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist'));
return;
}
game.canvas.pan(token);
}
async onRollSimple(event, message) {
const buttonType = event.target.dataset.type ?? 'damage',
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),

View file

@ -17,7 +17,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
this.presets = options.presets;
if(this.presets?.compendium && this.presets?.folder)
if (this.presets?.compendium && this.presets?.folder)
ItemBrowser.selectFolder.call(this, null, null, this.presets.compendium, this.presets.folder);
}
@ -26,7 +26,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
id: 'itemBrowser',
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser'],
tag: 'div',
// title: 'Item Browser',
window: {
frame: true,
title: 'Compendium Browser',
@ -41,9 +40,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
sortList: this.sortList
},
position: {
top: 330,
left: 120,
width: 800,
left: 100,
width: 850,
height: 600
}
};
@ -88,16 +86,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
/** @inheritDoc */
async _preFirstRender(context, options) {
if(context.presets?.render?.noFolder || context.presets?.render?.lite)
options.position.width = 600;
if (context.presets?.render?.noFolder || context.presets?.render?.lite) options.position.width = 600;
await super._preFirstRender(context, options);
}
/** @inheritDoc */
async _preRender(context, options) {
if(context.presets?.render?.noFolder || context.presets?.render?.lite)
if (context.presets?.render?.noFolder || context.presets?.render?.lite)
options.parts.splice(options.parts.indexOf('sidebar'), 1);
await super._preRender(context, options);
@ -110,18 +106,17 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this._createSearchFilter();
this._createFilterInputs();
this._createDragProcess();
if(context.presets?.render?.lite)
this.element.classList.add('lite');
if(context.presets?.render?.noFolder)
this.element.classList.add('no-folder');
if(context.presets?.render?.noFilter)
this.element.classList.add('no-filter');
if(this.presets?.filter) {
Object.entries(this.presets.filter).forEach(([k,v]) => this.fieldFilter.find(c => c.name === k).value = v.value);
if (context.presets?.render?.lite) this.element.classList.add('lite');
if (context.presets?.render?.noFolder) this.element.classList.add('no-folder');
if (context.presets?.render?.noFilter) this.element.classList.add('no-filter');
if (this.presets?.filter) {
Object.entries(this.presets.filter).forEach(
([k, v]) => (this.fieldFilter.find(c => c.name === k).value = v.value)
);
await this._onInputFilterBrowser();
}
}
@ -198,6 +193,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
formatLabel(item, field) {
const property = foundry.utils.getProperty(item, field.key);
if (Array.isArray(property)) property.join(', ');
if (typeof field.format !== 'function') return property ?? '-';
return field.format(property);
}
@ -315,19 +311,18 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
async _onInputFilterBrowser(event) {
this.#filteredItems.browser.input.clear();
if(event) this.fieldFilter.find(f => f.name === event.target.name).value = event.target.value;
if (event) this.fieldFilter.find(f => f.name === event.target.name).value = event.target.value;
for (const li of this.element.querySelectorAll('.item-container')) {
const itemUUID = li.dataset.itemUuid,
item = this.items.find(i => i.uuid === itemUUID);
if(!item) continue;
if (!item) continue;
const matchesMenu =
this.fieldFilter.length === 0 ||
this.fieldFilter.every(f => (
!f.value && f.value !== false) ||
ItemBrowser.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);
@ -335,21 +330,21 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
li.hidden = !(search.has(item.id) && matchesMenu);
}
}
/**
* Foundry evaluateFilter doesn't allow you to match if filter values are included into item data
* @param {*} obj
* @param {*} filter
* @param {*} obj
* @param {*} filter
*/
static evaluateFilter(obj, filter) {
let docValue = foundry.utils.getProperty(obj, filter.field);
let filterValue = filter.value;
switch (filter.operator) {
case "contains2":
case 'contains2':
filterValue = Array.isArray(filterValue) ? filterValue : [filterValue];
docValue = Array.isArray(docValue) ? docValue : [docValue];
return docValue.some(dv => filterValue.includes(dv));
case "contains3":
case 'contains3':
return docValue.some(f => f.value === filterValue);
default:
return foundry.applications.ux.SearchFilter.evaluateFilter(obj, filter);
@ -373,30 +368,33 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this.render({ force: true });
}
static getFolderConfig(folder, property = "columns") {
if(!folder) return [];
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");
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
.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") {
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);
}