From 6081b8f9e89ad0de218c851e44d1abd42ba70647 Mon Sep 17 00:00:00 2001 From: Eldritch-Oliver Date: Sat, 4 Oct 2025 19:40:37 -0600 Subject: [PATCH] Add a custom DocumentSheetConfig that supports tab-based configuration for my system-specific stuff --- langs/en-ca.json | 18 ++ module/apps/TAFDocumentSheetConfig.mjs | 171 +++++++++++++++++++ module/utils/getSizing.mjs | 32 ++++ styles/Apps/TAFDocumentSheetConfig.css | 15 ++ styles/main.css | 2 +- templates/TAFDocumentSheetConfig/foundry.hbs | 4 + templates/TAFDocumentSheetConfig/system.hbs | 48 ++++++ 7 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 module/apps/TAFDocumentSheetConfig.mjs create mode 100644 module/utils/getSizing.mjs create mode 100644 styles/Apps/TAFDocumentSheetConfig.css create mode 100644 templates/TAFDocumentSheetConfig/foundry.hbs create mode 100644 templates/TAFDocumentSheetConfig/system.hbs diff --git a/langs/en-ca.json b/langs/en-ca.json index 99de0b3..de83d02 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -13,6 +13,24 @@ }, "sheet-names": { "PlayerSheet": "Player Sheet" + }, + "Apps": { + "TAFDocumentSheetConfig": { + "Sizing": "Sizing", + "Width": { + "label": "Width" + }, + "Height": { + "label": "Height" + }, + "Resizable": { + "label": "Resizable" + }, + "tabs": { + "foundry": "Foundry", + "system": "Text-Based Actors" + } + } } } } diff --git a/module/apps/TAFDocumentSheetConfig.mjs b/module/apps/TAFDocumentSheetConfig.mjs new file mode 100644 index 0000000..760e462 --- /dev/null +++ b/module/apps/TAFDocumentSheetConfig.mjs @@ -0,0 +1,171 @@ +import { __ID__, filePath } from "../consts.mjs"; +import { getDefaultSizing } from "../utils/getSizing.mjs"; + +const { diffObject, expandObject, flattenObject } = foundry.utils; +const { DocumentSheetConfig } = foundry.applications.apps; +const { CONST } = foundry; + +export class TAFDocumentSheetConfig extends DocumentSheetConfig { + + // #region Options + static DEFAULT_OPTIONS = { + classes: [`taf`], + form: { + handler: this.#onSubmit, + }, + }; + + static get PARTS() { + const { form, footer } = super.PARTS; + return { + tabs: { template: `templates/generic/tab-navigation.hbs` }, + foundryTab: { + ...form, + template: filePath(`templates/TAFDocumentSheetConfig/foundry.hbs`), + templates: [ `templates/sheets/document-sheet-config.hbs` ], + }, + systemTab: { + template: filePath(`templates/TAFDocumentSheetConfig/system.hbs`), + classes: [`standard-form`], + }, + footer, + }; + }; + + static TABS = { + main: { + initial: `system`, + labelPrefix: `taf.Apps.TAFDocumentSheetConfig.tabs`, + tabs: [ + { id: `system` }, + { id: `foundry` }, + ], + }, + }; + // #endregion Options + + // #region Data Prep + async _preparePartContext(partID, context, options) { + this._prepareTabs(`main`); + + context.meta = { + idp: this.id, + }; + + switch (partID) { + case `foundryTab`: { + await this._prepareFormContext(context, options); + break; + }; + case `systemTab`: { + await this._prepareSystemSettingsContext(context, options); + break; + }; + case `footer`: { + await this._prepareFooterContext(context, options); + break; + }; + }; + return context; + }; + + async _prepareSystemSettingsContext(context, _options) { + // Inherited values for placeholders + const defaults = getDefaultSizing(); + context.placeholders = { + ...defaults, + resizable: defaults.resizable ? `Resizable` : `Not Resizable`, + }; + + // Custom values from document itself + const sheetConfig = this.document.getFlag(__ID__, `PlayerSheet`) ?? {}; + const sizing = sheetConfig.size ?? {}; + context.values = { + width: sizing.width, + height: sizing.height, + resizable: sizing.resizable ?? ``, + }; + + // Static prep + context.resizeOptions = [ + { label: `Default (${context.placeholders.resizable})`, value: `` }, + { label: `Resizable`, value: `true` }, + { label: `No Resizing`, value: `false` }, + ]; + }; + // #endregion Data Prep + + // #region Actions + /** @this {TAFDocumentSheetConfig} */ + static async #onSubmit(event, form, formData) { + const foundryReopen = await TAFDocumentSheetConfig.#submitFoundry.call(this, event, form, formData); + const systemReopen = await TAFDocumentSheetConfig.#submitSystem.call(this, event, form, formData); + if (foundryReopen || systemReopen) { + this.document._onSheetChange({ sheetOpen: true }); + }; + }; + + /** + * This method is mostly the form submission handler that foundry uses in + * DocumentSheetConfig, however because we clobber that in order to save our + * own config stuff as well, we need to duplicate Foundry's handling and tweak + * it a bit to make it work nicely with our custom saving. + * + * @this {TAFDocumentSheetConfig} + */ + static async #submitFoundry(_event, _form, formData) { + const { object } = formData; + const { documentName, type = CONST.BASE_DOCUMENT_TYPE } = this.document; + + // Update themes. + const themes = game.settings.get(`core`, `sheetThemes`); + const defaultTheme = foundry.utils.getProperty(themes, `defaults.${documentName}.${type}`); + const documentTheme = themes.documents?.[this.document.uuid]; + const themeChanged = (object.defaultTheme !== defaultTheme) || (object.theme !== documentTheme); + if (themeChanged) { + foundry.utils.setProperty(themes, `defaults.${documentName}.${type}`, object.defaultTheme); + themes.documents ??= {}; + themes.documents[this.document.uuid] = object.theme; + await game.settings.set(`core`, `sheetThemes`, themes); + } + + // Update sheets. + const { defaultClass } = this.constructor.getSheetClassesForSubType(documentName, type); + const sheetClass = this.document.getFlag(`core`, `sheetClass`) ?? ``; + const defaultSheetChanged = object.defaultClass !== defaultClass; + const documentSheetChanged = object.sheetClass !== sheetClass; + + if (themeChanged || (game.user.isGM && defaultSheetChanged)) { + if (game.user.isGM && defaultSheetChanged) { + const setting = game.settings.get(`core`, `sheetClasses`); + foundry.utils.setProperty(setting, `${documentName}.${type}`, object.defaultClass); + await game.settings.set(`core`, `sheetClasses`, setting); + } + + // This causes us to manually rerender the sheet due to the theme or default + // sheet class changing resulting in no update making it to the client-document's + // _onUpdate handling + if (!documentSheetChanged) { + return true; + } + } + + // Update the document-specific override. + if (documentSheetChanged) { + this.document.setFlag(`core`, `sheetClass`, object.sheetClass); + }; + return false; + }; + + /** @this {TAFDocumentSheetConfig} */ + static async #submitSystem(_event, _form, formData) { + const { FLAGS: flags } = expandObject(formData.object); + const diff = flattenObject(diffObject(this.document.flags, flags)); + const hasChanges = Object.keys(diff).length > 0; + if (hasChanges) { + await this.document.update({ flags }); + }; + return hasChanges; + }; + // #endregion Actions +}; diff --git a/module/utils/getSizing.mjs b/module/utils/getSizing.mjs new file mode 100644 index 0000000..63e6822 --- /dev/null +++ b/module/utils/getSizing.mjs @@ -0,0 +1,32 @@ +import { PlayerSheet } from "../apps/PlayerSheet.mjs"; + +/** + * @typedef SheetSizing + * @property {number} width The initial width of the application + * @property {number} height The initial height of the application + * @property {boolean} resizable Whether or not the application + * is able to be resized with a drag handle. + */ + +/** + * Retrieves the computed default sizing data based on world settings + * and the sheet class' DEFAULT_OPTIONS + * @returns {SheetSizing} + */ +export function getDefaultSizing() { + /** @type {SheetSizing} */ + const sizing = { + width: undefined, + height: undefined, + resizable: undefined, + }; + + // TODO: defaults from world settings + + // Defaults from the sheet class itself + sizing.height ||= PlayerSheet.DEFAULT_OPTIONS.position.height; + sizing.width ||= PlayerSheet.DEFAULT_OPTIONS.position.width; + sizing.resizable ||= PlayerSheet.DEFAULT_OPTIONS.window.resizable; + + return sizing; +}; diff --git a/styles/Apps/TAFDocumentSheetConfig.css b/styles/Apps/TAFDocumentSheetConfig.css new file mode 100644 index 0000000..247c8fd --- /dev/null +++ b/styles/Apps/TAFDocumentSheetConfig.css @@ -0,0 +1,15 @@ +.taf.sheet-config { + + section { + display: flex; + flex-direction: column; + gap: 1rem; + } + + .tab { + display: none; + } + .tab.active { + display: unset; + } +} diff --git a/styles/main.css b/styles/main.css index bc257ab..10ea6ec 100644 --- a/styles/main.css +++ b/styles/main.css @@ -20,4 +20,4 @@ @import url("./Apps/Ask.css") layer(apps); @import url("./Apps/AttributeManager.css") layer(apps); @import url("./Apps/PlayerSheet.css") layer(apps); -@import url("./Apps/ResizeControlManager.css") layer(apps); +@import url("./Apps/TAFDocumentSheetConfig.css") layer(apps); diff --git a/templates/TAFDocumentSheetConfig/foundry.hbs b/templates/TAFDocumentSheetConfig/foundry.hbs new file mode 100644 index 0000000..c23dc90 --- /dev/null +++ b/templates/TAFDocumentSheetConfig/foundry.hbs @@ -0,0 +1,4 @@ +{{log this}} +
+ {{> "templates/sheets/document-sheet-config.hbs" }} +
diff --git a/templates/TAFDocumentSheetConfig/system.hbs b/templates/TAFDocumentSheetConfig/system.hbs new file mode 100644 index 0000000..a39e0c8 --- /dev/null +++ b/templates/TAFDocumentSheetConfig/system.hbs @@ -0,0 +1,48 @@ +
+
+ + {{ localize "taf.Apps.TAFDocumentSheetConfig.Sizing" }} + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+