FEAT: add inventory-fieldset-items-V2 and inventory-item-V2 partials for Actors

This commit is contained in:
Joaquin Pereyra 2025-07-11 17:49:31 -03:00
parent 9ccbfe1b01
commit 634bb3d644
28 changed files with 733 additions and 514 deletions

View file

@ -9,8 +9,6 @@ export default class AdversarySheet extends DHBaseActorSheet {
window: { resizable: true },
actions: {
reactionRoll: AdversarySheet.#reactionRoll,
useItem: this.useItem,
toChat: this.toChat
},
window: {
resizable: true
@ -28,7 +26,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
/** @inheritdoc */
static TABS = {
primary: {
tabs: [{ id: 'features' }, { id: 'notes' }, { id: 'effects' }],
tabs: [{ id: 'features' }, { id: 'effects' }, { id: 'notes' }],
initial: 'features',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
@ -80,12 +78,6 @@ export default class AdversarySheet extends DHBaseActorSheet {
}
}
getItem(element) {
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
item = this.document.items.get(itemId);
return item;
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
@ -111,42 +103,4 @@ export default class AdversarySheet extends DHBaseActorSheet {
this.actor.diceRoll(config);
}
/**
*
* @type {ApplicationClickAction}
*/
static async useItem(event) {
const action = this.getItem(event) ?? this.actor.system.attack;
action.use(event);
}
/**
*
* @type {ApplicationClickAction}
*/
static async toChat(event, button) {
if (button?.dataset?.type === 'experience') {
const experience = this.document.system.experiences[button.dataset.uuid];
const cls = getDocumentClass('ChatMessage');
const systemData = {
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
description: `${experience.name} ${experience.total.signedString()}`
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
} else {
const item = this.getItem(event) ?? this.document.system.attack;
item.toChat(this.document.id);
}
}
}

View file

@ -4,6 +4,7 @@ import { abilities } from '../../../config/actorConfig.mjs';
import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs';
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
import FilterMenu from '../../ux/filter-menu.mjs';
import { getDocFromElement } from "../../../helpers/utils.mjs";
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@ -23,8 +24,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
makeDeathMove: CharacterSheet.#makeDeathMove,
levelManagement: CharacterSheet.#levelManagement,
toggleEquipItem: CharacterSheet.#toggleEquipItem,
useItem: this.useItem, //TODO Fix this
toChat: this.toChat
},
window: {
resizable: true
@ -33,7 +32,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
contextMenus: [
{
handler: CharacterSheet._getContextMenuOptions,
selector: '[data-item-id]',
selector: '[data-item-uuid]',
options: {
parentClassHooks: false,
fixed: true
@ -97,20 +96,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
this._createSearchFilter();
}
/* -------------------------------------------- */
getItem(element) {
const listElement = (element.target ?? element).closest('[data-item-id]');
const itemId = listElement.dataset.itemId;
switch (listElement.dataset.type) {
case 'effect':
return this.document.effects.get(itemId);
default:
return this.document.items.get(itemId);
}
}
/* -------------------------------------------- */
/* Prepare Context */
/* -------------------------------------------- */
@ -175,7 +160,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
* @protected
*/
async _prepareLoadoutContext(context, _options) {
context.listView = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList);
context.cardView = !game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList);
}
/**
@ -229,38 +214,21 @@ export default class CharacterSheet extends DHBaseActorSheet {
* @protected
*/
static _getContextMenuOptions() {
/**
* Get the item from the element.
* @param {HTMLElement} el
* @returns {foundry.documents.Item?}
*/
const getItem = element => {
const listElement = (element.target ?? element).closest('[data-item-id]');
const itemId = listElement.dataset.itemId;
switch (listElement.dataset.type) {
case 'effect':
return this.document.effects.get(itemId);
default:
return this.document.items.get(itemId);
}
};
return [
{
name: 'DAGGERHEART.ACTORS.Character.contextMenu.useItem',
icon: '<i class="fa-solid fa-burst"></i>',
condition: el => {
const item = getItem(el);
return !['class', 'subclass'].includes(item.type);
condition: target => {
const doc = getDocFromElement(target);
return typeof doc.use === 'function';
},
callback: (button, event) => CharacterSheet.useItem.call(this, event, button)
callback: (target, event) => getDocFromElement(target).use(event)
},
{
name: 'DAGGERHEART.ACTORS.Character.contextMenu.equip',
icon: '<i class="fa-solid fa-hands"></i>',
condition: el => {
const item = getItem(el);
condition: target => {
const item = getDocFromElement(target);
return ['weapon', 'armor'].includes(item.type) && !item.system.equipped;
},
callback: CharacterSheet.#toggleEquipItem.bind(this)
@ -269,7 +237,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
name: 'DAGGERHEART.ACTORS.Character.contextMenu.unequip',
icon: '<i class="fa-solid fa-hands"></i>',
condition: el => {
const item = getItem(el);
const item = getDocFromElement(el);
return ['weapon', 'armor'].includes(item.type) && item.system.equipped;
},
callback: CharacterSheet.#toggleEquipItem.bind(this)
@ -277,51 +245,38 @@ export default class CharacterSheet extends DHBaseActorSheet {
{
name: 'DAGGERHEART.ACTORS.Character.contextMenu.toLoadout',
icon: '<i class="fa-solid fa-arrow-up"></i>',
condition: el => {
const item = getItem(el);
condition: target => {
const item = getDocFromElement(target);
return ['domainCard'].includes(item.type) && item.system.inVault;
},
callback: target => getItem(target).update({ 'system.inVault': false })
callback: target => getDocFromElement(target).update({ 'system.inVault': false })
},
{
name: 'DAGGERHEART.ACTORS.Character.contextMenu.toVault',
icon: '<i class="fa-solid fa-arrow-down"></i>',
condition: el => {
const item = getItem(el);
condition: target => {
const item = getDocFromElement(target);
return ['domainCard'].includes(item.type) && !item.system.inVault;
},
callback: target => getItem(target).update({ 'system.inVault': true })
callback: target => getDocFromElement(target).update({ 'system.inVault': true })
},
{
name: 'DAGGERHEART.ACTORS.Character.contextMenu.sendToChat',
icon: '<i class="fa-regular fa-message"></i>',
callback: CharacterSheet.toChat.bind(this)
condition: target => {
return typeof getDocFromElement(target).toChat === 'function';
},
callback: (target) => getDocFromElement(target).toChat(this.document.id),
},
{
name: 'CONTROLS.CommonEdit',
icon: '<i class="fa-solid fa-pen-to-square"></i>',
callback: target => getItem(target).sheet.render({ force: true })
callback: target => getDocFromElement(target).sheet.render({ force: true })
},
{
name: 'CONTROLS.CommonDelete',
icon: '<i class="fa-solid fa-trash"></i>',
callback: async el => {
const item = getItem(el);
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
type: game.i18n.localize(`TYPES.${item.documentName}.${item.type}`),
name: item.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', {
name: item.name
})
});
if (!confirmed) return;
item.delete();
}
callback: async el => getDocFromElement(el).deleteDialog(),
}
];
}
@ -417,7 +372,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
this.#filteredItems.inventory.search.clear();
for (const li of html.querySelectorAll('.inventory-item')) {
const item = this.document.items.get(li.dataset.itemId);
const item = getDocFromElement(li);
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
if (matchesSearch) this.#filteredItems.inventory.search.add(item.id);
const { menu } = this.#filteredItems.inventory;
@ -437,7 +392,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
this.#filteredItems.loadout.search.clear();
for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) {
const item = this.document.items.get(li.dataset.itemId);
const item = getDocFromElement(li);
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
if (matchesSearch) this.#filteredItems.loadout.search.add(item.id);
const { menu } = this.#filteredItems.loadout;
@ -488,7 +443,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
this.#filteredItems.inventory.menu.clear();
for (const li of html.querySelectorAll('.inventory-item')) {
const item = this.document.items.get(li.dataset.itemId);
const item = getDocFromElement(li);
const matchesMenu =
filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f));
@ -509,7 +464,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
this.#filteredItems.loadout.menu.clear();
for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) {
const item = this.document.items.get(li.dataset.itemId);
const item = getDocFromElement(li);
const matchesMenu =
filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f));
@ -577,13 +532,15 @@ export default class CharacterSheet extends DHBaseActorSheet {
this.document.diceRoll(config);
}
//TODO: redo toggleEquipItem method
/**
* Toggles the equipped state of an item (armor or weapon).
* @type {ApplicationClickAction}
*/
static async #toggleEquipItem(_event, button) {
//TODO: redo this
const item = this.actor.items.get(button.closest('[data-item-id]')?.dataset.itemId);
const item = getDocFromElement(button);
if (!item) return;
if (item.system.equipped) {
await item.update({ 'system.equipped': false });
@ -631,9 +588,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
* Toggles whether an item is stored in the vault.
* @type {ApplicationClickAction}
*/
static async #toggleVault(event, button) {
const docId = button.closest('[data-item-id]')?.dataset.itemId;
const doc = this.document.items.get(docId);
static async #toggleVault(_event, button) {
const doc = getDocFromElement(button)
await doc?.update({ 'system.inVault': !doc.system.inVault });
}
@ -645,61 +601,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
return CONFIG.ux.ContextMenu.triggerContextMenu(event);
}
/**
* Use a item
* @type {ApplicationClickAction}
*/
static async useItem(event, button) {
const item = this.getItem(button);
if (!item) return;
// Should dandle its actions. Or maybe they'll be separate buttons as per an Issue on the board
if (item.type === 'feature') {
item.use(event);
} else if (item instanceof ActiveEffect) {
item.toChat(this);
} else {
const wasUsed = await item.use(event);
if (wasUsed && item.type === 'weapon') {
Hooks.callAll(CONFIG.DH.HOOKS.characterAttack, {});
}
}
}
/**
* Send item to Chat
* @type {ApplicationClickAction}
*/
static async toChat(event, button) {
if (button?.dataset?.type === 'experience') {
const experience = this.document.system.experiences[button.dataset.uuid];
const cls = getDocumentClass('ChatMessage');
const systemData = {
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}`
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
} else {
const item = this.getItem(event);
if (!item) return;
item.toChat(this.document.id);
}
}
async _onDragStart(_, event) {
super._onDragStart(event);
}
async _onDrop(event) {
super._onDrop(event);
this._onDropItem(event, TextEditor.getDragEventData(event));

View file

@ -6,11 +6,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
static DEFAULT_OPTIONS = {
classes: ['actor', 'companion'],
position: { width: 300 },
actions: {
viewActor: this.viewActor,
useItem: this.useItem,
toChat: this.toChat
}
actions: {}
};
static PARTS = {
@ -29,52 +25,4 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
};
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
static async viewActor(_, button) {
const target = button.closest('[data-item-uuid]');
const actor = await foundry.utils.fromUuid(target.dataset.itemUuid);
if (!actor) return;
actor.sheet.render(true);
}
getAction(element) {
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
item = this.document.system.actions.find(x => x.id === itemId);
return item;
}
static async useItem(event) {
const action = this.getAction(event) ?? this.actor.system.attack;
action.use(event);
}
static async toChat(event, button) {
if (button?.dataset?.type === 'experience') {
const experience = this.document.system.experiences[button.dataset.uuid];
const cls = getDocumentClass('ChatMessage');
const systemData = {
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}`
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
} else {
const item = this.getAction(event) ?? this.document.system.attack;
item.toChat(this.document.id);
}
}
}

View file

@ -9,10 +9,7 @@ export default class DhpEnvironment extends DHBaseActorSheet {
position: {
width: 500
},
actions: {
useItem: this.useItem,
toChat: this.toChat
},
actions: {},
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]
};
@ -76,45 +73,6 @@ export default class DhpEnvironment extends DHBaseActorSheet {
/* -------------------------------------------- */
getItem(element) {
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
item = this.document.items.get(itemId);
return item;
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
/**
*
* @type {ApplicationClickAction}
*/
async viewAdversary(_, button) {
const target = button.closest('[data-item-uuid]');
const adversary = await foundry.utils.fromUuid(target.dataset.itemUuid);
if (!adversary) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.adversaryMissing'));
return;
}
adversary.sheet.render({ force: true });
}
static async useItem(event, button) {
const action = this.getItem(event);
if (!action) {
await this.viewAdversary(event, button);
} else {
action.use(event);
}
}
static async toChat(event) {
const item = this.getItem(event);
item.toChat(this.document.id);
}
async _onDragStart(event) {
const item = event.currentTarget.closest('.inventory-item');