daggerheart/module/applications/ui/effectsDisplay.mjs
WBHarry aa1d117c43
[V14] Effect Stacking (#1667)
* Added the ability for effects to have stacks

* Fixed effect stacking

* Improved token overlay spacing

* Compendium updaetes

* Simplify effect click event (#1748)

* Fixed a bunch of deprecations

* Corrected AgileScout Beastform json data

* Updated TokenHUD to the new v14

* Removed DestroyOnEmpty from consumables

* Fixed so that tooltips don't get stuck (#1745)

* [Feature] TagTeam Partial Rendering (#1735)

* I done did it, I think

* Think I fixed the partial rendering bug for gm->player

* [V14] 1743 - Damage Update Error (#1746)

* Fixed DamageParts causing errors on update

* Fixed ActionBaseConfig error when no damage present on the action

* Fix removal of damage field

* Removed unneccessary default value function for parts

---------

Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>

* Simplify effect click event

---------

Co-authored-by: WBHarry <williambjrklund@gmail.com>
Co-authored-by: WBHarry <89362246+WBHarry@users.noreply.github.com>

* Fixed stacking-value pointer event

* Set the stacking value in EffectsDisplay to be tabular-nums for monospacing

* Made baseEffect.stacking nullable instead of having an enabled property

* .

* Fixed so that actor._onUpdateDescantDocuments re-renders the EffectDisplay if effects were updated

---------

Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com>
2026-03-25 13:54:14 +01:00

128 lines
4 KiB
JavaScript

import { getIconVisibleActiveEffects } from '../../helpers/utils.mjs';
import { RefreshType } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
/**
* A UI element which displays the Active Effects on a selected token.
*
* @extends ApplicationV2
* @mixes HandlebarsApplication
*/
export default class DhEffectsDisplay extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(options = {}) {
super(options);
this.setupHooks();
}
/** @inheritDoc */
static DEFAULT_OPTIONS = {
id: 'effects-display',
tag: 'div',
classes: ['daggerheart', 'dh-style', 'effects-display'],
window: {
frame: false,
positioned: false,
resizable: false,
minimizable: false
},
actions: {}
};
/** @override */
static PARTS = {
resources: {
root: true,
template: 'systems/daggerheart/templates/ui/effects-display.hbs'
}
};
get element() {
return document.body.querySelector('.daggerheart.dh-style.effects-display');
}
get hidden() {
return this.element.classList.contains('hidden');
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
for (const element of this.element?.querySelectorAll('.effect-container a') ?? []) {
element.addEventListener('click', e => this.#onClickEffect(e));
element.addEventListener('contextmenu', e => this.#onClickEffect(e, -1));
}
}
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.effects = DhEffectsDisplay.getTokenEffects();
return context;
}
static getTokenEffects = token => {
const actor = token
? token.actor
: canvas.tokens.controlled.length === 0
? !game.user.isGM
? game.user.character
: null
: canvas.tokens.controlled[0].actor;
return getIconVisibleActiveEffects(actor?.getActiveEffects() ?? []);
};
toggleHidden(token, focused) {
if (!this.element) return;
const effects = DhEffectsDisplay.getTokenEffects(focused ? token : null);
this.element.hidden = effects.length === 0;
Hooks.callAll(CONFIG.DH.HOOKS.effectDisplayToggle, this.element.hidden, token);
this.render();
}
async #onClickEffect(event, delta = 1) {
const element = event.target.closest('.effect-container');
const effects = DhEffectsDisplay.getTokenEffects();
const effect = effects.find(x => x.id === element.dataset.effectId);
if (!effect || (delta >= 0 && !effect.system.stacking)) {
return;
}
const maxValue = effect.system.stacking?.max ?? Infinity;
const newValue = Math.clamp((effect.system.stacking?.value ?? 1) + delta, 0, maxValue);
if (newValue > 0) {
await effect.update({ 'system.stacking.value': newValue });
} else {
await effect.delete();
}
this.render();
}
setupHooks() {
Hooks.on('controlToken', this.toggleHidden.bind(this));
Hooks.on(RefreshType.EffectsDisplay, this.toggleHidden.bind(this));
}
async close(options) {
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
if (options.closeKey) return;
Hooks.off('controlToken', this.toggleHidden);
Hooks.off(RefreshType.EffectsDisplay, this.toggleHidden);
return super.close(options);
}
async _onRender(context, options) {
await super._onRender(context, options);
this.element.hidden = context.effects.length === 0;
if (options?.force) {
document.getElementById('ui-right-column-1')?.appendChild(this.element);
}
}
}