From 96eccc62f2a4e1d02ab5b66cad42a1c4d5885ef6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Mar 2026 22:06:30 -0600 Subject: [PATCH] Add partial rerendering to document sheets via a custom mixin --- module/apps/GenericItemSheet.mjs | 18 ++++++- module/apps/PlayerSheet.mjs | 53 +++++++++----------- module/apps/mixins/TAFDocumentSheetMixin.mjs | 36 +++++++++++++ 3 files changed, 77 insertions(+), 30 deletions(-) create mode 100644 module/apps/mixins/TAFDocumentSheetMixin.mjs diff --git a/module/apps/GenericItemSheet.mjs b/module/apps/GenericItemSheet.mjs index f392a85..560fc12 100644 --- a/module/apps/GenericItemSheet.mjs +++ b/module/apps/GenericItemSheet.mjs @@ -1,10 +1,15 @@ import { __ID__, filePath } from "../consts.mjs"; +import { TAFDocumentSheetMixin } from "./mixins/TAFDocumentSheetMixin.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; const { ItemSheetV2 } = foundry.applications.sheets; const { setProperty } = foundry.utils; -export class GenericItemSheet extends HandlebarsApplicationMixin(ItemSheetV2) { +export class GenericItemSheet extends + TAFDocumentSheetMixin( + HandlebarsApplicationMixin( + ItemSheetV2, +)) { // #region Options static DEFAULT_OPTIONS = { classes: [ @@ -29,6 +34,17 @@ export class GenericItemSheet extends HandlebarsApplicationMixin(ItemSheetV2) { header: { template: filePath(`templates/GenericItemSheet/header.hbs`) }, content: { template: filePath(`templates/GenericItemSheet/content.hbs`) }, }; + + /** + * This tells the Application's TAFDocumentSheetMixin how to rerender this app + * when specific properties get changed on the actor, so that it doesn't need + * to full-app rendering if we can do a partial rerender instead. + */ + static PROPERTY_TO_PARTIAL = { + "name": [`header`], + "img": [`header`], + "system": [`content`], + }; // #endregion Options // #region Instance Data diff --git a/module/apps/PlayerSheet.mjs b/module/apps/PlayerSheet.mjs index 5d65dd2..056e6eb 100644 --- a/module/apps/PlayerSheet.mjs +++ b/module/apps/PlayerSheet.mjs @@ -2,24 +2,19 @@ import { __ID__, filePath } from "../consts.mjs"; import { AttributeManager } from "./AttributeManager.mjs"; import { attributeSorter } from "../utils/attributeSort.mjs"; import { TAFDocumentSheetConfig } from "./TAFDocumentSheetConfig.mjs"; +import { TAFDocumentSheetMixin } from "./mixins/TAFDocumentSheetMixin.mjs"; import { toPrecision } from "../utils/roundToPrecision.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; const { ActorSheetV2 } = foundry.applications.sheets; -const { getProperty, hasProperty } = foundry.utils; +const { getProperty } = foundry.utils; const { TextEditor } = foundry.applications.ux; -const propertyToParts = { - "name": [`header`], - "img": [`header`], - "system.attr": [`attributes`], - "system.attr.value": [`attributes`, `content`], - "system.attr.max": [`attributes`, `content`], - "system.content": [`content`], - "system.carryCapacity": [`items`], -}; - -export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) { +export class PlayerSheet extends + TAFDocumentSheetMixin( + HandlebarsApplicationMixin( + ActorSheetV2, +)) { // #region Options static DEFAULT_OPTIONS = { @@ -59,6 +54,21 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) { }, }; + /** + * This tells the Application's TAFDocumentSheetMixin how to rerender this app + * when specific properties get changed on the actor, so that it doesn't need + * to full-app rendering if we can do a partial rerender instead. + */ + static PROPERTY_TO_PARTIAL = { + "name": [`header`], + "img": [`header`], + "system.attr": [`attributes`], + "system.attr.value": [`attributes`, `content`], + "system.attr.max": [`attributes`, `content`], + "system.content": [`content`], + "system.carryCapacity": [`items`], + }; + static TABS = { primary: { initial: `content`, @@ -67,7 +77,7 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) { { id: `content` }, { id: `items` }, ], - } + }, }; // #endregion Options @@ -136,21 +146,6 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) { return controls; }; - _configureRenderOptions(options) { - // Only rerender the parts of the app that got changed - if (options.renderContext === `updateActor`) { - const parts = new Set(); - for (const property in propertyToParts) { - if (hasProperty(options.renderData, property)) { - propertyToParts[property].forEach(partID => parts.add(partID)); - }; - }; - options.parts = options.parts?.filter(part => !parts.has(part)) ?? Array.from(parts); - }; - - super._configureRenderOptions(options); - }; - async close() { this.#attributeManager?.close(); this.#attributeManager = null; @@ -250,7 +245,7 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) { }; async _prepareItem(item) { - const weightUnit = game.settings.get(__ID__, `weightUnit`) + const weightUnit = game.settings.get(__ID__, `weightUnit`); const ctx = { uuid: item.uuid, img: item.img, diff --git a/module/apps/mixins/TAFDocumentSheetMixin.mjs b/module/apps/mixins/TAFDocumentSheetMixin.mjs new file mode 100644 index 0000000..1715a6d --- /dev/null +++ b/module/apps/mixins/TAFDocumentSheetMixin.mjs @@ -0,0 +1,36 @@ +const { hasProperty } = foundry.utils; + +export function TAFDocumentSheetMixin(HandlebarsApplication) { + class TAFDocumentSheet extends HandlebarsApplication { + /** @type {Record | null} */ + static PROPERTY_TO_PARTIAL = null; + + /** + * This override is used by the mixin in order to allow for partial + * re-rendering of applications based on what properties changed. + * It requires that a static PROPERTY_TO_PARTIAL to be defined as + * an object of path keys to arrays of part IDs in order to work. + * This will not interfere with renders that are not started as + * part of the actor update lifecycle. + */ + _configureRenderOptions(options) { + + if (options.renderContext === `updateActor`) { + const propertyToParts = this.constructor.PROPERTY_TO_PARTIAL; + if (propertyToParts) { + const parts = new Set(); + for (const property in propertyToParts) { + if (hasProperty(options.renderData, property)) { + propertyToParts[property].forEach(partID => parts.add(partID)); + }; + }; + options.parts = options.parts?.filter(part => !parts.has(part)) ?? Array.from(parts); + } + }; + + super._configureRenderOptions(options); + }; + }; + + return TAFDocumentSheet; +};