Finished animation framework

This commit is contained in:
WBHarry 2026-06-10 14:51:44 +02:00
parent 208e8192b4
commit 0961ad8d73
5 changed files with 129 additions and 97 deletions

View file

@ -12,6 +12,7 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) { export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) {
previusCountdownData = null; previusCountdownData = null;
changedCountdownsForAnimation = new Set();
countdownChangeAnimationTimeout = null; countdownChangeAnimationTimeout = null;
constructor(options = {}) { constructor(options = {}) {
@ -58,7 +59,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
} }
}; };
/**@inheritdoc */
async _renderFrame(options) { async _renderFrame(options) {
const frame = await super._renderFrame(options); const frame = await super._renderFrame(options);
@ -71,6 +71,52 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
return frame; return frame;
} }
async _onRender(context, options) {
await super._onRender(context, options);
/**/
this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0;
if (options?.force) {
document.getElementById('ui-right-column-1')?.appendChild(this.element);
}
/**/
if (this.changedCountdownsForAnimation.size) {
if (this.countdownChangeAnimationTimeout)
clearTimeout(this.countdownChangeAnimationTimeout);
for (const countdownKey of this.changedCountdownsForAnimation) {
this.element.querySelector(`.countdown-container[data-countdown="${countdownKey}"]`)
.classList.add('change-glow');
/* If the countdown is not currently visible, add a glow to the CountdownType pill */
const visibleTypes = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownTypeModes) ?? []
const countdown = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)
.countdowns[countdownKey];
if (!visibleTypes.includes(countdown?.type)) {
this.element.querySelector(`.header-type-toggles .header-type[data-type="${countdown.type}"]`)
.classList.add('change-glow');
}
}
this.countdownChangeAnimationTimeout = setTimeout(() => {
this.changedCountdownsForAnimation.clear();
for (const element of this.element.querySelectorAll('.countdown-container')) {
element.classList.remove('change-glow');
}
for (const element of this.element.querySelectorAll('.header-type-toggles .header-type')) {
element.classList.remove('change-glow');
}
}, 3000);
}
this.previusCountdownData =
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).countdowns;
}
/** Returns countdown data filtered by ownership */ /** Returns countdown data filtered by ownership */
#getCountdowns() { #getCountdowns() {
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
@ -84,12 +130,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
_getCountdownData() { _getCountdownData() {
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
const typeModes = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownTypeModes) ?? [];
return this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => { return this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
if (!typeModes.includes(countdown.type))
return acc;
const playersWithAccess = game.users.reduce((acc, user) => { const playersWithAccess = game.users.reduce((acc, user) => {
const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown); const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown);
if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) { if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
@ -112,7 +154,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
!countdownEditable || !countdownEditable ||
(isLooping && (countdown.progress.current > 0 || countdown.progress.start === '0')); (isLooping && (countdown.progress.current > 0 || countdown.progress.start === '0'));
acc[key] = { acc[countdown.type][key] = {
...countdown, ...countdown,
editable: countdownEditable, editable: countdownEditable,
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0, noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0,
@ -121,25 +163,27 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
loopTooltip: isLooping && game.i18n.localize(loopTooltip) loopTooltip: isLooping && game.i18n.localize(loopTooltip)
}; };
return acc; return acc;
}, {}); }, Object.keys(CONFIG.DH.GENERAL.countdownType).reduce((acc, key) => {
acc[key] = {};
return acc;
}, {}));
} }
/** @override */ /** @override */
async _prepareContext(options) { async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
context.isGM = game.user.isGM; context.isGM = game.user.isGM;
this.previusCountdownData =
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).countdowns;
context.iconOnly = context.iconOnly =
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) === game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode)
CONFIG.DH.GENERAL.countdownAppMode.iconOnly; === CONFIG.DH.GENERAL.countdownAppMode.iconOnly;
const userCountdownTypes = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownTypeModes) ?? []; context.userCountdownTypes =
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownTypeModes) ?? [];
context.typeToggles = Object.values(CONFIG.DH.GENERAL.countdownType).map(type => ({ context.typeToggles = Object.values(CONFIG.DH.GENERAL.countdownType).map(type => ({
type: type.id, type: type.id,
label: game.i18n.localize(type.label), label: game.i18n.localize(type.label),
active: userCountdownTypes.includes(type.id) active: context.userCountdownTypes.includes(type.id)
})); }));
context.countdowns = this._getCountdownData(); context.countdowns = this._getCountdownData();
@ -191,26 +235,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
currentTypes.includes(type) ? currentTypes.filter(x => x !== type) : [...currentTypes, type]; currentTypes.includes(type) ? currentTypes.filter(x => x !== type) : [...currentTypes, type];
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownTypeModes, newTypes); await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownTypeModes, newTypes);
for (const type of Object.keys(CONFIG.DH.GENERAL.countdownType)) { this.render();
const toggleElement = this.element.querySelector(`.header-type-toggles .header-type[data-type="${type}"]`);
if (newTypes.includes(type))
toggleElement.classList.remove('inactive');
else
toggleElement.classList.add('inactive');
}
const updatedCountdownsElement = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/countdowns/parts/countdowns.hbs',
{
countdowns: this._getCountdownData(),
iconOnly: game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) ===
CONFIG.DH.GENERAL.countdownAppMode.iconOnly,
isGM: game.user.isGM
}
);
this.element.querySelector('#countdowns .countdowns-container').innerHTML = updatedCountdownsElement;
} }
static async #onEditCountdowns() { static async #onEditCountdowns() {
@ -267,10 +292,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
} }
static async gmSetSetting(data) { static async gmSetSetting(data) {
// if(Object.keys(data).some(x => x.includes('countdowns'))) {
// this.previusCountdownData = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).countdowns;
// }
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data); await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data);
game.socket.emit(`system.${CONFIG.DH.id}`, { game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh, action: socketEvent.Refresh,
@ -328,26 +349,4 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
refreshType: RefreshType.Countdown refreshType: RefreshType.Countdown
}); });
} }
/**
*
* @param {*} context
* @param {*} options
*/
async performChangeAnimations(changedCountdowns) {
if (this.countdownChangeAnimationTimeout)
clearTimeout(this.countdownChangeAnimationTimeout);
this.countdownChangeAnimationTimeout = setTimeout(() => {
}, 3000);
}
async _onRender(context, options) {
await super._onRender(context, options);
this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0;
if (options?.force) {
document.getElementById('ui-right-column-1')?.appendChild(this.element);
}
}
} }

View file

@ -16,16 +16,16 @@ export default class DhCountdowns extends foundry.abstract.DataModel {
handleChange() { handleChange() {
const previousCountdowns = foundry.ui.countdowns.previusCountdownData; const previousCountdowns = foundry.ui.countdowns.previusCountdownData;
const changedCountdowns = Object.values(this.countdowns).reduce((acc, [key, countdown]) => { const changedCountdowns = Object.entries(this.countdowns).reduce((acc, [key, countdown]) => {
const previousCountdown = previousCountdowns[key]; const previousCountdown = previousCountdowns[key];
if (!previousCountdown || (previousCountdown.current !== countdown.current)) { if (!previousCountdown || (previousCountdown.progress.current !== countdown.progress.current)) {
acc.push(key); acc.push(key);
} }
return acc; return acc;
}, []); }, []);
foundry.ui.performChangeAnimations(changedCountdowns); foundry.ui.countdowns.changedCountdownsForAnimation.add(...changedCountdowns);
} }
migrateData(source) { migrateData(source) {

View file

@ -16,7 +16,8 @@ export default class RegisterHandlebarsHelpers {
empty: this.empty, empty: this.empty,
pluralize: this.pluralize, pluralize: this.pluralize,
positive: this.positive, positive: this.positive,
isNullish: this.isNullish isNullish: this.isNullish,
debug: this.debug
}); });
} }
static add(a, b) { static add(a, b) {
@ -92,4 +93,8 @@ export default class RegisterHandlebarsHelpers {
static isNullish(a) { static isNullish(a) {
return a === null || a === undefined; return a === null || a === undefined;
} }
static debug(a) {
return a;
}
} }

View file

@ -89,6 +89,10 @@
&.inactive { &.inactive {
opacity: 0.4; opacity: 0.4;
} }
&.change-glow {
animation: change-glow 1s ease-in-out infinite;
}
} }
} }
} }
@ -101,10 +105,20 @@
overflow: auto; overflow: auto;
max-height: 312px; max-height: 312px;
.countdown-category-container {
&.hidden {
display: none;
}
}
.countdown-container { .countdown-container {
display: flex; display: flex;
width: 100%; width: 100%;
&.change-glow {
animation: change-glow 1s ease-in-out infinite;
}
&.icon-only { &.icon-only {
gap: 8px; gap: 8px;
@ -203,4 +217,14 @@
// } // }
} }
} }
@keyframes change-glow {
0% {
box-shadow: 0 0 1px 1px @golden;
}
100% {
box-shadow: 0 0 2px 2px @golden;
}
}
} }

View file

@ -1,40 +1,44 @@
{{#each countdowns as | countdown id |}} {{#each countdowns as | category key |}}
<div class="countdown-container {{#if ../iconOnly}}icon-only{{/if}}" data-countdown="{{id}}"> <div class="countdown-category-container {{#unless (includes @root.userCountdownTypes key)}}hidden{{/unless}}">
<div class="countdown-main-container"> {{#each category as |countdown id|}}
<img src="{{countdown.img}}" {{#if ../iconOnly}}data-tooltip="{{countdown.name}}"{{/if}}/> <div class="countdown-container {{#if ../iconOnly}}icon-only{{/if}}" data-countdown="{{id}}">
<div class="countdown-content"> <div class="countdown-main-container">
{{#unless ../iconOnly}} <img src="{{countdown.img}}" {{#if ../iconOnly}}data-tooltip="{{countdown.name}}"{{/if}}/>
<header>{{countdown.name}}</header> <div class="countdown-content">
{{/unless}} {{#unless ../iconOnly}}
<div class="countdown-tools"> <header>{{countdown.name}}</header>
<div class="countdown-tool-controls"> {{/unless}}
{{#if countdown.editable}}<a data-action="decreaseCountdown"><i class="fa-solid fa-minus"></i></a>{{/if}} <div class="countdown-tools">
<div class="progress-tag"> <div class="countdown-tool-controls">
{{countdown.progress.current}}/{{countdown.progress.start}} {{#if countdown.editable}}<a data-action="decreaseCountdown"><i class="fa-solid fa-minus"></i></a>{{/if}}
<div class="progress-tag">
{{countdown.progress.current}}/{{countdown.progress.start}}
</div>
{{#if countdown.editable}}<a data-action="increaseCountdown"><i class="fa-solid fa-plus"></i></a>{{/if}}
</div>
<div class="countdown-tool-icons">
{{#if (not ../iconOnly)}}
{{#if (and countdown.noPlayerAccess @root.isGM)}}
<i class="fa-solid fa-eye-slash" data-tooltip="{{localize "DAGGERHEART.UI.Countdowns.noPlayerAccess"}}"></i>
{{/if}}
{{#unless (eq countdown.progress.looping "noLooping")}}
<span data-tooltip="{{countdown.loopTooltip}}">
<a class="looping-container {{#if countdown.shouldLoop}}should-loop{{/if}}" {{#if countdown.loopDisabled}}disabled{{/if}} data-action="loopCountdown">
<i class="loop-marker fa-solid fa-repeat"></i>
{{#if (eq countdown.progress.looping "increasing")}}
<i class="direction-marker fa-solid fa-angles-up" data-tooltip="{{localize "DAGGERHEART.UI.Countdowns.increasingLoop"}}"></i>
{{else if (eq countdown.progress.looping "decreasing")}}
<i class="direction-marker fa-solid fa-angles-down" data-tooltip="{{localize "DAGGERHEART.UI.Countdowns.decreasingLoop"}}"></i>
{{/if}}
</a>
</span>
{{/unless}}
{{/if}}
</div>
</div> </div>
{{#if countdown.editable}}<a data-action="increaseCountdown"><i class="fa-solid fa-plus"></i></a>{{/if}}
</div>
<div class="countdown-tool-icons">
{{#if (not ../iconOnly)}}
{{#if (and countdown.noPlayerAccess @root.isGM)}}
<i class="fa-solid fa-eye-slash" data-tooltip="{{localize "DAGGERHEART.UI.Countdowns.noPlayerAccess"}}"></i>
{{/if}}
{{#unless (eq countdown.progress.looping "noLooping")}}
<span data-tooltip="{{countdown.loopTooltip}}">
<a class="looping-container {{#if countdown.shouldLoop}}should-loop{{/if}}" {{#if countdown.loopDisabled}}disabled{{/if}} data-action="loopCountdown">
<i class="loop-marker fa-solid fa-repeat"></i>
{{#if (eq countdown.progress.looping "increasing")}}
<i class="direction-marker fa-solid fa-angles-up" data-tooltip="{{localize "DAGGERHEART.UI.Countdowns.increasingLoop"}}"></i>
{{else if (eq countdown.progress.looping "decreasing")}}
<i class="direction-marker fa-solid fa-angles-down" data-tooltip="{{localize "DAGGERHEART.UI.Countdowns.decreasingLoop"}}"></i>
{{/if}}
</a>
</span>
{{/unless}}
{{/if}}
</div> </div>
</div> </div>
</div> </div>
</div> {{/each}}
</div> </div>
{{/each}} {{/each}}