From d46d727a701c9c6101a8df46697462bc73bd29c6 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 29 Sep 2024 00:03:43 -0600 Subject: [PATCH 1/8] Implement the size storable class mixin --- src/sheets/Player/v1.mjs | 4 +- src/sheets/mixins/SizeStorable.mjs | 119 +++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/sheets/mixins/SizeStorable.mjs diff --git a/src/sheets/Player/v1.mjs b/src/sheets/Player/v1.mjs index 162af4d..8e775f1 100644 --- a/src/sheets/Player/v1.mjs +++ b/src/sheets/Player/v1.mjs @@ -1,4 +1,6 @@ -export class PlayerSheetv1 extends ActorSheet { +import { SizeStorable } from "../mixins/SizeStorable.mjs"; + +export class PlayerSheetv1 extends SizeStorable(ActorSheet) { static get defaultOptions() { let opts = foundry.utils.mergeObject( super.defaultOptions, diff --git a/src/sheets/mixins/SizeStorable.mjs b/src/sheets/mixins/SizeStorable.mjs new file mode 100644 index 0000000..776d0d3 --- /dev/null +++ b/src/sheets/mixins/SizeStorable.mjs @@ -0,0 +1,119 @@ +import { DialogManager } from "../../utils/DialogManager.mjs"; + +/** + * This mixin allows making a class so that it can store the width/height data + * to the sheet or localhost in order to make using the text sheets a lil nicer. + * + * @param {ActorSheet|ItemSheet} cls The Sheet class to augment + * @returns The augmented class + */ +export function SizeStorable(cls) { + return class SizeStorableClass extends cls { + constructor(doc, opts) { + + /* + Find the saved size of the sheet, it takes the following order of precedence + from highest to lowest: + - Locally saved + - Default values on actor + - Default values from constructor + */ + /** @type {string|undefined} */ + let size = localStorage.getItem(`${game.system.id}.size:${doc.uuid}`); + size ??= doc.getFlag(game.system.id, `size`); + + // Apply the saved value to the options + if (size) { + const [ width, height ] = size.split(`,`); + opts.width = width; + opts.height = height; + }; + + super(doc, opts); + }; + + get hasLocalSize() { + return localStorage.getItem(`${game.system.id}.size:${this.object.uuid}`) != null; + }; + + get hasGlobalSize() { + return this.object.getFlag(game.system.id, `size`) != null; + }; + + _getHeaderButtons() { + return [ + { + class: `size-save`, + icon: `fa-solid fa-floppy-disk`, + label: `Save Size`, + onclick: () => { + + const buttons = { + saveGlobal: { + label: `Save Global Size`, + callback: () => { + this.object.setFlag( + game.system.id, + `size`, + `${this.position.width},${this.position.height}`, + ); + }, + }, + saveLocal: { + label: `Save For Me Only`, + callback: () => { + localStorage.setItem( + `${game.system.id}.size:${this.object.uuid}`, + `${this.position.width},${this.position.height}`, + ); + }, + }, + }; + + // Add resets if there is a size already + if (this.hasGlobalSize) { + buttons.resetGlobal = { + label: `Reset Global Size`, + callback: () => { + this.object.unsetFlag(game.system.id, `size`); + }, + }; + }; + + if (this.hasLocalSize) { + buttons.resetLocal = { + label: `Reset Size For Me Only`, + callback: () => { + localStorage.removeItem(`${game.system.id}.size:${this.object.uuid}`); + }, + }; + }; + + // When a non-GM is using this system, we only want to save local sizes + if (!game.user.isGM) { + delete buttons.saveGlobal; + delete buttons.resetGlobal; + }; + + DialogManager.createOrFocus( + `${this.object.uuid}:size-save`, + { + title: `Save size of sheet: ${this.title}`, + content: `Saving the size of this sheet will cause it to open at the size it is when you press the save button`, + buttons, + render: (html) => { + const el = html[2]; + el.style = `display: grid; grid-template-columns: 1fr 1fr; gap: 8px;`; + }, + }, + { + jQuery: true, + }, + ); + }, + }, + ...super._getHeaderButtons(), + ]; + }; + }; +}; From 713ab3fa00676d7f39ac17f3ad18e78e700693b8 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 29 Sep 2024 00:04:45 -0600 Subject: [PATCH 2/8] Make dev settings show in the config when in localhost --- src/settings/dev_settings.mjs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/settings/dev_settings.mjs b/src/settings/dev_settings.mjs index 229775c..80235b1 100644 --- a/src/settings/dev_settings.mjs +++ b/src/settings/dev_settings.mjs @@ -1,16 +1,25 @@ export function registerDevSettings() { + const isLocalhost = window.location.hostname === `localhost`; + game.settings.register(game.system.id, `devMode`, { + name: `Dev Mode?`, scope: `client`, type: Boolean, - config: false, + config: isLocalhost, default: false, - requiresReload: true, + requiresReload: false, }); game.settings.register(game.system.id, `defaultTab`, { + name: `Default Sidebar Tab`, scope: `client`, type: String, - config: false, + config: isLocalhost, requiresReload: false, + onChange(value) { + if (!ui.sidebar.tabs[value]) { + ui.notifications.warn(`"${value}" cannot be found in the sidebar tabs, it may not work at reload.`); + } + }, }); }; From e0f6b2a8e195a3978ac6d26166d88011af50e1c7 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 29 Sep 2024 00:05:03 -0600 Subject: [PATCH 3/8] Allow empty catch blocks --- eslint.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index f412e22..462ea73 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -49,6 +49,7 @@ export default [ "func-names": [`warn`, `as-needed`], "grouped-accessor-pairs": `error`, "no-alert": `error`, + "no-empty": [`error`, { allowEmptyCatch: true }], "no-implied-eval": `error`, "no-invalid-this": `error`, "no-lonely-if": `error`, From 4584b1a7a5f8adeceaa0cfc68e689f97b8fa8735 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 29 Sep 2024 00:09:30 -0600 Subject: [PATCH 4/8] Change the way feature flags are working because using settings was a bad idea (and bump version to 2.0.0 since it's an API change) --- eslint.config.mjs | 2 ++ src/consts.mjs | 3 --- src/hooks/renderChatMessage.mjs | 7 ++---- src/settings/world_settings.mjs | 22 ------------------- .../feature_flags/rollModeMessageContent.mjs | 5 +---- src/utils/globalTaf.mjs | 10 ++++----- system.json | 2 +- 7 files changed, 11 insertions(+), 40 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 462ea73..544b98e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -31,6 +31,7 @@ export default [ ActiveEffect: `readonly`, Dialog: `readonly`, renderTemplate: `readonly`, + TextEditor: `readonly`, }, }, }, @@ -42,6 +43,7 @@ export default [ languageOptions: { globals: { Logger: `readonly`, + taf: `readonly`, }, }, rules: { diff --git a/src/consts.mjs b/src/consts.mjs index 0c13482..e69de29 100644 --- a/src/consts.mjs +++ b/src/consts.mjs @@ -1,3 +0,0 @@ -export const FEATURE_FLAGS = Object.freeze({ - ROLLMODECONTENT: `Roll Mode Message Content`, -}); diff --git a/src/hooks/renderChatMessage.mjs b/src/hooks/renderChatMessage.mjs index 15f70a0..496a135 100644 --- a/src/hooks/renderChatMessage.mjs +++ b/src/hooks/renderChatMessage.mjs @@ -1,5 +1,3 @@ -import { FEATURE_FLAGS } from "../consts.mjs"; - Hooks.on(`renderChatMessage`, (msg, html) => { // Short-Circuit when the flag isn't set for the message @@ -7,13 +5,12 @@ Hooks.on(`renderChatMessage`, (msg, html) => { return; } - const featureFlags = game.settings.get(game.system.id, `flags`); - const featureFlagEnabled = featureFlags.includes(FEATURE_FLAGS.ROLLMODECONTENT); + const featureFlagEnabled = taf.FEATURES.ROLL_MODE_CONTENT; const contentElement = html.find(`.message-content`)[0]; let content = contentElement.innerHTML; if (featureFlagEnabled && msg.blind && !game.user.isGM) { - content = content.replace(/-=.*?=-/gm, `???`); + content = content.replace(/-=.*?=-/gm, `??`); } else { content = content.replace(/-=|=-/gm, ``); } diff --git a/src/settings/world_settings.mjs b/src/settings/world_settings.mjs index db4d7ed..eb3aad7 100644 --- a/src/settings/world_settings.mjs +++ b/src/settings/world_settings.mjs @@ -1,24 +1,2 @@ -import { FEATURE_FLAGS } from "../consts.mjs"; - export function registerWorldSettings() { - game.settings.register(game.system.id, `flags`, { - name: `Feature Flags`, - hint: `World-based feature flags that are used to enable/disable specific behaviours`, - scope: `world`, - type: new foundry.data.fields.SetField( - new foundry.data.fields.StringField( - { - empty: false, - trim: true, - options: Object.values(FEATURE_FLAGS), - }, - ), - { - required: false, - initial: new Set(), - }, - ), - config: true, - requiresReload: true, - }); }; diff --git a/src/utils/feature_flags/rollModeMessageContent.mjs b/src/utils/feature_flags/rollModeMessageContent.mjs index 059852a..67ed613 100644 --- a/src/utils/feature_flags/rollModeMessageContent.mjs +++ b/src/utils/feature_flags/rollModeMessageContent.mjs @@ -1,8 +1,5 @@ -import { FEATURE_FLAGS } from "../../consts.mjs"; - export function hideMessageText(content) { - const featureFlags = game.settings.get(game.system.id, `flags`); - const hideContent = featureFlags.includes(FEATURE_FLAGS.ROLLMODECONTENT); + const hideContent = taf.FEATURES.ROLL_MODE_CONTENT; if (hideContent) { return `-=${content}=-`; } diff --git a/src/utils/globalTaf.mjs b/src/utils/globalTaf.mjs index 3ed03fe..e038c4d 100644 --- a/src/utils/globalTaf.mjs +++ b/src/utils/globalTaf.mjs @@ -1,11 +1,11 @@ -import { FEATURE_FLAGS } from "../consts.mjs"; import { hideMessageText } from "./feature_flags/rollModeMessageContent.mjs"; globalThis.taf = Object.freeze({ - utils: { + utils: Object.freeze({ hideMessageText, - }, - const: { - FEATURE_FLAGS, + }), + FEATURES: { + ROLL_MODE_CONTENT: false, + STORABLE_SHEET_SIZE: false, }, }); diff --git a/system.json b/system.json index 712924d..d1561a6 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "taf", "title": "Text-Based Actors", "description": "", - "version": "1.2.0", + "version": "2.0.0", "download": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/dotdungeon.zip", "manifest": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/system.json", "url": "https://github.com/Oliver-Akins/Text-Actors-Foundry", From 4a1469ad70805e26e42db61778683e8ec636cfd8 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 29 Sep 2024 00:18:23 -0600 Subject: [PATCH 5/8] Prevent overwriting the global taf object --- src/utils/globalTaf.mjs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/utils/globalTaf.mjs b/src/utils/globalTaf.mjs index e038c4d..899328e 100644 --- a/src/utils/globalTaf.mjs +++ b/src/utils/globalTaf.mjs @@ -1,11 +1,18 @@ import { hideMessageText } from "./feature_flags/rollModeMessageContent.mjs"; -globalThis.taf = Object.freeze({ - utils: Object.freeze({ - hideMessageText, - }), - FEATURES: { - ROLL_MODE_CONTENT: false, - STORABLE_SHEET_SIZE: false, +Object.defineProperty( + globalThis, + `taf`, + { + value: Object.freeze({ + utils: Object.freeze({ + hideMessageText, + }), + FEATURES: Object.preventExtensions({ + ROLL_MODE_CONTENT: false, + STORABLE_SHEET_SIZE: false, + }), + }), + writable: false, }, -}); +); From a0f20a586f5c421ef126886342766243ab24927d Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 29 Sep 2024 00:18:57 -0600 Subject: [PATCH 6/8] Feature-flag the StorableSize implementation --- src/sheets/mixins/SizeStorable.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sheets/mixins/SizeStorable.mjs b/src/sheets/mixins/SizeStorable.mjs index 776d0d3..38ba887 100644 --- a/src/sheets/mixins/SizeStorable.mjs +++ b/src/sheets/mixins/SizeStorable.mjs @@ -8,6 +8,12 @@ import { DialogManager } from "../../utils/DialogManager.mjs"; * @returns The augmented class */ export function SizeStorable(cls) { + + // Don't augment class when the feature isn't enabled + if (!taf.FEATURES.STORABLE_SHEET_SIZE) { + return cls; + } + return class SizeStorableClass extends cls { constructor(doc, opts) { From 07cd7951e6f3d61ff40fe9a940d7835b8382c0c6 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 29 Sep 2024 00:46:39 -0600 Subject: [PATCH 7/8] Add some info into the README about the featureflags --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a9ac76..f530123 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,25 @@ # Text-Based Actors This is an intentionally bare-bones system that features a text-only character sheet, allowing the playing of games that may not otherwise have a Foundry system -implementation. \ No newline at end of file +implementation. + +## Features +There are not too many features included in this system for things like automation +as it's meant to be used to mostly play rules-light games. However there are some +special features that can be enabled on a per-world basis using a world script +to enable feature flags that you want. + +### List of Feature Flags +| Flag | Description +| - | - +| `ROLL_MODE_CONTENT` | Allows players, GMs, and macros to send "blind" chat messages where only the GM gets to see the content. +| `STORABLE_SHEET_SIZE` | Makes it so that certain sheets are able to have their size saved, so that it resumes that size when opened. + +### Example Feature Flag +In order to change these flags, you must make a world script that has something +like the below code in it: + +```js +// v- this is the name of the flag from the table above +taf.FEATURES.STORABLE_SHEET_SIZE = true; +``` \ No newline at end of file From 478f91877fee727726e3147bae2807a0b1c09752 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 29 Sep 2024 00:47:15 -0600 Subject: [PATCH 8/8] Add a ready log to show the feature flags during dev --- src/main.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main.mjs b/src/main.mjs index 7f4ae1b..0314ffe 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -44,7 +44,7 @@ Hooks.once(`init`, () => { // MARK: ready hook -Hooks.once( `ready`, () => { +Hooks.once(`ready`, () => { Logger.info(`Ready`); let defaultTab = game.settings.get(game.system.id, `defaultTab`); @@ -56,4 +56,9 @@ Hooks.once( `ready`, () => { ui.sidebar.tabs[defaultTab].activate(); }; }; + + if (game.settings.get(game.system.id, `devMode`)) { + console.log(`%cFeature Flags:`, `color: #00aa00; font-style: bold; font-size: 1.5rem;`); + Logger.table(taf.FEATURES); + }; });