Merge branch 'main' into bug/103-enrich-htmlfield-content-before-its-used-in-applications

This commit is contained in:
Joaquin Pereyra 2025-07-14 15:49:20 -03:00
commit d26ed22e74
69 changed files with 1781 additions and 446 deletions

View file

@ -4,7 +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";
import { getDocFromElement, itemAbleRollParse } from "../../../helpers/utils.mjs";
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@ -23,6 +23,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
makeDeathMove: CharacterSheet.#makeDeathMove,
levelManagement: CharacterSheet.#levelManagement,
toggleEquipItem: CharacterSheet.#toggleEquipItem,
useItem: this.useItem, //TODO Fix this
useAction: this.useAction,
toggleResourceDice: this.toggleResourceDice,
handleResourceDice: this.handleResourceDice,
toChat: this.toChat
},
window: {
resizable: true
@ -102,6 +107,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelectorAll('.inventory-item-resource').forEach(element => {
element.addEventListener('change', this.updateItemResource.bind(this));
});
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
element.addEventListener('change', this.updateItemQuantity.bind(this));
});
}
/** @inheritDoc */
async _onRender(context, options) {
await super._onRender(context, options);
@ -486,6 +502,27 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
}
/* -------------------------------------------- */
/* Application Listener Actions */
/* -------------------------------------------- */
async updateItemResource(event) {
const item = this.getItem(event.currentTarget);
if (!item) return;
const max = item.system.resource.max ? itemAbleRollParse(item.system.resource.max, this.document, item) : null;
const value = max ? Math.min(Number(event.currentTarget.value), max) : event.currentTarget.value;
await item.update({ 'system.resource.value': value });
this.render();
}
async updateItemQuantity(event) {
const item = this.getItem(event.currentTarget);
if (!item) return;
await item.update({ 'system.quantity': event.currentTarget.value });
this.render();
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
@ -639,16 +676,81 @@ export default class CharacterSheet extends DHBaseActorSheet {
action.use(event);
}
/**
* Toggle the used state of a resource dice.
* @type {ApplicationClickAction}
*/
static async toggleResourceDice(event) {
const target = event.target.closest('.item-resource');
const item = this.getItem(event);
if (!item) return;
const diceState = item.system.resource.diceStates[target.dataset.dice];
await item.update({
[`system.resource.diceStates.${target.dataset.dice}.used`]: diceState?.used ? !diceState.used : true
});
}
/**
* Handle the roll values of resource dice.
* @type {ApplicationClickAction}
*/
static async handleResourceDice(event) {
const item = this.getItem(event);
if (!item) return;
const rollValues = await game.system.api.applications.dialogs.ResourceDiceDialog.create(item, this.document);
if (!rollValues) return;
await item.update({
'system.resource.diceStates': rollValues.reduce((acc, state, index) => {
acc[index] = { value: state.value, used: state.used };
return acc;
}, {})
});
this.render();
}
/**
* 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.value.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);
if (!item) return;
item.toChat(this.document.id);
}
}
async _onDragStart(event) {
const item = this.getItem(event);
const dragData = {
type: item.documentName,
uuid: item.uuid
};
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
super._onDragStart(event);
}
@ -656,7 +758,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
// Prevent event bubbling to avoid duplicate handling
event.preventDefault();
event.stopPropagation();
super._onDrop(event);
this._onDropItem(event, TextEditor.getDragEventData(event));
}

View file

@ -21,6 +21,11 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
},
actions: {
removeAction: DHBaseItemSheet.#removeAction,
addFeature: DHBaseItemSheet.#addFeature,
editFeature: DHBaseItemSheet.#editFeature,
removeFeature: DHBaseItemSheet.#removeFeature,
addResource: DHBaseItemSheet.#addResource,
removeResource: DHBaseItemSheet.#removeResource
addFeature: DHBaseItemSheet.#addFeature
},
dragDrop: [
@ -179,6 +184,71 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
'system.features': [...this.document.system.features, feature]
});
}
/**
* Edit an existing feature on the item
* @type {ApplicationClickAction}
*/
static async #editFeature(_event, button) {
const target = button.closest('.feature-item');
const feature = this.document.system.features.find(x => x?.id === target.id);
if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}
feature.sheet.render(true);
}
/**
* Remove a feature from the item.
* @type {ApplicationClickAction}
*/
static async #removeFeature(event, button) {
event.stopPropagation();
const target = button.closest('.feature-item');
const feature = this.document.system.features.find(x => x && x.id === target.id);
if (feature) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
type: game.i18n.localize(`TYPES.Item.feature`),
name: feature.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
});
if (!confirmed) return;
}
await this.document.update({
'system.features': this.document.system.features
.filter(feature => feature && feature.id !== target.id)
.map(x => x.uuid)
});
}
/**
* Add a resource to the item.
* @type {ApplicationClickAction}
*/
static async #addResource() {
await this.document.update({
'system.resource': { type: 'simple', value: 0 }
});
}
/**
* Remove the resource from the item.
* @type {ApplicationClickAction}
*/
static async #removeResource() {
await this.document.update({
'system.resource': null
});
}
/* -------------------------------------------- */
/* Application Drag/Drop */
/* -------------------------------------------- */
@ -193,7 +263,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
if (featureItem) {
const feature = this.document.system.features.find(x => x?.id === featureItem.id);
if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}

View file

@ -10,10 +10,6 @@ export default class DHHeritageSheet extends DHBaseItemSheet {
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
feature: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
scrollable: ['.feature']
},
effects: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']

View file

@ -1,4 +1,3 @@
export default function ItemAttachmentSheet(Base) {
return class extends Base {
static DEFAULT_OPTIONS = {
@ -25,10 +24,7 @@ export default function ItemAttachmentSheet(Base) {
...super.TABS,
primary: {
...super.TABS?.primary,
tabs: [
...(super.TABS?.primary?.tabs || []),
{ id: 'attachments' }
],
tabs: [...(super.TABS?.primary?.tabs || []), { id: 'attachments' }],
initial: super.TABS?.primary?.initial || 'description',
labelPrefix: super.TABS?.primary?.labelPrefix || 'DAGGERHEART.GENERAL.Tabs'
}
@ -46,29 +42,28 @@ export default function ItemAttachmentSheet(Base) {
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
const attachmentsSection = event.target.closest('.attachments-section');
if (!attachmentsSection) return super._onDrop(event);
event.preventDefault();
event.stopPropagation();
const item = await Item.implementation.fromDropData(data);
if (!item) return;
// Call the data model's public method
await this.document.system.addAttachment(item);
}
static async #removeAttachment(event, target) {
// Call the data model's public method
await this.document.system.removeAttachment(target.dataset.uuid);
}
}
async _preparePartContext(partId, context) {
await super._preparePartContext(partId, context);
if (partId === 'attachments') {
// Keep this simple UI preparation in the mixin
const attachedUUIDs = this.document.system.attached;
@ -83,8 +78,8 @@ export default function ItemAttachmentSheet(Base) {
})
);
}
return context;
}
};
}
}

View file

@ -3,12 +3,109 @@ import DHHeritageSheet from '../api/heritage-sheet.mjs';
export default class AncestrySheet extends DHHeritageSheet {
/**@inheritdoc */
static DEFAULT_OPTIONS = {
classes: ['ancestry']
classes: ['ancestry'],
actions: {
addFeature: AncestrySheet.#addFeature,
editFeature: AncestrySheet.#editFeature,
removeFeature: AncestrySheet.#removeFeature
},
dragDrop: [{ dragSelector: null, dropSelector: '.tab.features .drop-section' }]
};
/**@inheritdoc */
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' },
...super.PARTS
...super.PARTS,
features: { template: 'systems/daggerheart/templates/sheets/items/ancestry/features.hbs' }
};
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
/**
* Add a new feature to the item, prompting the user for its type.
* @type {ApplicationClickAction}
*/
static async #addFeature(_event, button) {
const feature = await game.items.documentClass.create({
type: 'feature',
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }),
system: {
subType: button.dataset.type
}
});
await this.document.update({
'system.features': [...this.document.system.features.map(x => x.uuid), feature.uuid]
});
}
/**
* Edit an existing feature on the item
* @type {ApplicationClickAction}
*/
static async #editFeature(_event, button) {
const target = button.closest('.feature-item');
const feature = this.document.system[`${target.dataset.type}Feature`];
if (!feature || Object.keys(feature).length === 0) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}
feature.sheet.render(true);
}
/**
* Remove a feature from the item.
* @type {ApplicationClickAction}
*/
static async #removeFeature(event, button) {
event.stopPropagation();
const target = button.closest('.feature-item');
const feature = this.document.system[`${target.dataset.type}Feature`];
const featureExists = feature && Object.keys(feature).length > 0;
if (featureExists) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
type: game.i18n.localize(`TYPES.Item.feature`),
name: feature.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
});
if (!confirmed) return;
}
if (featureExists && target.dataset.type === 'primary') await feature.update({ 'system.primary': null });
await this.document.update({
'system.features': this.document.system.features.filter(x => x && x.uuid !== feature.uuid).map(x => x.uuid)
});
}
/* -------------------------------------------- */
/* Application Drag/Drop */
/* -------------------------------------------- */
/**
* On drop on the item.
* @param {DragEvent} event - The drag event
*/
async _onDrop(event) {
event.stopPropagation();
event.preventDefault();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (item?.type === 'feature') {
const subType = event.target.closest('.primary-feature') ? 'primary' : 'secondary';
await item.update({ 'system.subType': subType });
await this.document.update({
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
});
}
}
}

View file

@ -87,6 +87,16 @@ export default class ClassSheet extends DHBaseItemSheet {
await this.document.update({
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
});
} else if (item.type === 'feature') {
if (target.classList.contains('hope-feature')) {
await this.document.update({
'system.hopeFeatures': [...this.document.system.hopeFeatures.map(x => x.uuid), item.uuid]
});
} else if (target.classList.contains('class-feature')) {
await this.document.update({
'system.classFeatures': [...this.document.system.classFeatures.map(x => x.uuid), item.uuid]
});
}
} else if (item.type === 'weapon') {
if (target.classList.contains('primary-weapon-section')) {
if (!this.document.system.characterGuide.suggestedPrimaryWeapon && !item.system.secondary)
@ -146,7 +156,7 @@ export default class ClassSheet extends DHBaseItemSheet {
static async #removeItemFromCollection(_event, element) {
const { uuid, target } = element.dataset;
const prop = foundry.utils.getProperty(this.document.system, target);
await this.document.update({ [target]: prop.filter(i => i.uuid !== uuid) });
await this.document.update({ [`system.${target}`]: prop.filter(i => i.uuid !== uuid) });
}
/**

View file

@ -9,6 +9,10 @@ export default class CommunitySheet extends DHHeritageSheet {
/**@inheritdoc */
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' },
...super.PARTS
...super.PARTS,
feature: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
scrollable: ['.feature']
}
};
}

View file

@ -13,6 +13,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
header: { template: 'systems/daggerheart/templates/sheets/items/feature/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
settings: { template: 'systems/daggerheart/templates/sheets/items/feature/settings.hbs' },
actions: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
scrollable: ['.actions']
@ -26,7 +27,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
/**@override */
static TABS = {
primary: {
tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'effects' }],
tabs: [{ id: 'description' }, { id: 'settings' }, { id: 'actions' }, { id: 'effects' }],
initial: 'description',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}

View file

@ -64,7 +64,7 @@ export default class SubclassSheet extends DHBaseItemSheet {
if (featureItem) {
const feature = this.document.system[featureItem.dataset.type];
if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}