Merge pull request #4 from Oliver-Akins/feature/sheet-size-saving

Sheet Size Saving + Feature Flag Rework
This commit is contained in:
Oliver 2024-09-29 00:52:26 -06:00 committed by GitHub
commit 9ea74b12c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 190 additions and 49 deletions

View file

@ -2,3 +2,24 @@
This is an intentionally bare-bones system that features a text-only character 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 sheet, allowing the playing of games that may not otherwise have a Foundry system
implementation. 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;
```

View file

@ -31,6 +31,7 @@ export default [
ActiveEffect: `readonly`, ActiveEffect: `readonly`,
Dialog: `readonly`, Dialog: `readonly`,
renderTemplate: `readonly`, renderTemplate: `readonly`,
TextEditor: `readonly`,
}, },
}, },
}, },
@ -42,6 +43,7 @@ export default [
languageOptions: { languageOptions: {
globals: { globals: {
Logger: `readonly`, Logger: `readonly`,
taf: `readonly`,
}, },
}, },
rules: { rules: {
@ -49,6 +51,7 @@ export default [
"func-names": [`warn`, `as-needed`], "func-names": [`warn`, `as-needed`],
"grouped-accessor-pairs": `error`, "grouped-accessor-pairs": `error`,
"no-alert": `error`, "no-alert": `error`,
"no-empty": [`error`, { allowEmptyCatch: true }],
"no-implied-eval": `error`, "no-implied-eval": `error`,
"no-invalid-this": `error`, "no-invalid-this": `error`,
"no-lonely-if": `error`, "no-lonely-if": `error`,

View file

@ -1,3 +0,0 @@
export const FEATURE_FLAGS = Object.freeze({
ROLLMODECONTENT: `Roll Mode Message Content`,
});

View file

@ -1,5 +1,3 @@
import { FEATURE_FLAGS } from "../consts.mjs";
Hooks.on(`renderChatMessage`, (msg, html) => { Hooks.on(`renderChatMessage`, (msg, html) => {
// Short-Circuit when the flag isn't set for the message // Short-Circuit when the flag isn't set for the message
@ -7,13 +5,12 @@ Hooks.on(`renderChatMessage`, (msg, html) => {
return; return;
} }
const featureFlags = game.settings.get(game.system.id, `flags`); const featureFlagEnabled = taf.FEATURES.ROLL_MODE_CONTENT;
const featureFlagEnabled = featureFlags.includes(FEATURE_FLAGS.ROLLMODECONTENT);
const contentElement = html.find(`.message-content`)[0]; const contentElement = html.find(`.message-content`)[0];
let content = contentElement.innerHTML; let content = contentElement.innerHTML;
if (featureFlagEnabled && msg.blind && !game.user.isGM) { if (featureFlagEnabled && msg.blind && !game.user.isGM) {
content = content.replace(/-=.*?=-/gm, `???`); content = content.replace(/-=.*?=-/gm, `??`);
} else { } else {
content = content.replace(/-=|=-/gm, ``); content = content.replace(/-=|=-/gm, ``);
} }

View file

@ -44,7 +44,7 @@ Hooks.once(`init`, () => {
// MARK: ready hook // MARK: ready hook
Hooks.once( `ready`, () => { Hooks.once(`ready`, () => {
Logger.info(`Ready`); Logger.info(`Ready`);
let defaultTab = game.settings.get(game.system.id, `defaultTab`); let defaultTab = game.settings.get(game.system.id, `defaultTab`);
@ -56,4 +56,9 @@ Hooks.once( `ready`, () => {
ui.sidebar.tabs[defaultTab].activate(); 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);
};
}); });

View file

@ -1,16 +1,25 @@
export function registerDevSettings() { export function registerDevSettings() {
const isLocalhost = window.location.hostname === `localhost`;
game.settings.register(game.system.id, `devMode`, { game.settings.register(game.system.id, `devMode`, {
name: `Dev Mode?`,
scope: `client`, scope: `client`,
type: Boolean, type: Boolean,
config: false, config: isLocalhost,
default: false, default: false,
requiresReload: true, requiresReload: false,
}); });
game.settings.register(game.system.id, `defaultTab`, { game.settings.register(game.system.id, `defaultTab`, {
name: `Default Sidebar Tab`,
scope: `client`, scope: `client`,
type: String, type: String,
config: false, config: isLocalhost,
requiresReload: false, 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.`);
}
},
}); });
}; };

View file

@ -1,24 +1,2 @@
import { FEATURE_FLAGS } from "../consts.mjs";
export function registerWorldSettings() { 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,
});
}; };

View file

@ -1,4 +1,6 @@
export class PlayerSheetv1 extends ActorSheet { import { SizeStorable } from "../mixins/SizeStorable.mjs";
export class PlayerSheetv1 extends SizeStorable(ActorSheet) {
static get defaultOptions() { static get defaultOptions() {
let opts = foundry.utils.mergeObject( let opts = foundry.utils.mergeObject(
super.defaultOptions, super.defaultOptions,

View file

@ -0,0 +1,125 @@
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) {
// 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) {
/*
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(),
];
};
};
};

View file

@ -1,8 +1,5 @@
import { FEATURE_FLAGS } from "../../consts.mjs";
export function hideMessageText(content) { export function hideMessageText(content) {
const featureFlags = game.settings.get(game.system.id, `flags`); const hideContent = taf.FEATURES.ROLL_MODE_CONTENT;
const hideContent = featureFlags.includes(FEATURE_FLAGS.ROLLMODECONTENT);
if (hideContent) { if (hideContent) {
return `-=${content}=-`; return `-=${content}=-`;
} }

View file

@ -1,11 +1,18 @@
import { FEATURE_FLAGS } from "../consts.mjs";
import { hideMessageText } from "./feature_flags/rollModeMessageContent.mjs"; import { hideMessageText } from "./feature_flags/rollModeMessageContent.mjs";
globalThis.taf = Object.freeze({ Object.defineProperty(
utils: { globalThis,
hideMessageText, `taf`,
{
value: Object.freeze({
utils: Object.freeze({
hideMessageText,
}),
FEATURES: Object.preventExtensions({
ROLL_MODE_CONTENT: false,
STORABLE_SHEET_SIZE: false,
}),
}),
writable: false,
}, },
const: { );
FEATURE_FLAGS,
},
});

View file

@ -2,7 +2,7 @@
"id": "taf", "id": "taf",
"title": "Text-Based Actors", "title": "Text-Based Actors",
"description": "", "description": "",
"version": "1.2.0", "version": "2.0.0",
"download": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/dotdungeon.zip", "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", "manifest": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/system.json",
"url": "https://github.com/Oliver-Akins/Text-Actors-Foundry", "url": "https://github.com/Oliver-Akins/Text-Actors-Foundry",