#Sure
1 messages · Page 1 of 1 (latest)
I'm developing it for off Foundry for any web development purpose as well as having Foundry support. I'm angling to monetize certain aspects / adds ons for the off Foundry effort. Right now I'm incubating the development kind of in stealth only on the Foundry platform. There is no Svelte UI framework for an app / desktop like UI framework.
For the Foundry version there are also lots of reactive integrations for Foundry like reactive documents and reactive document embedded collections. Everything that can be made reactive is made so out of the box.
Most of Wasp's modules use TRL like Item Piles. Module Compatible Checker, Automatic Animations. Also a really interesting and deep module called Alpha Suit https://foundryvtt.com/packages/alpha-suit which I haven't used, but looks deep.
Hm, is this required due to how settings are declared? https://github.com/averrin/alpha-suit/blob/fa9eda94af208e93ca757beec0b0c323b18da1de/src/modules/settings.js#L27
Oh, nevermind. That seems to be an old version. Odd that it turned up in my search.
Looks like they are not using the reactive settings functionality of TRL.
Here is a link to a module I'm working on for settings setup. allSettings gets added at the end this.registerAll: https://github.com/typhonjs-fvtt/mce-everywhere/blob/main/src/model/mceGameSettings.js
Here is the Svelte component that defines the settings app which uses the automatic game settings component TJSSettingsEdit: https://github.com/typhonjs-fvtt/mce-everywhere/blob/main/src/view/ConfigSettingAppShell.svelte
Similar pattern to Automatic Animations: https://github.com/otigon/automated-jb2a-animations/blob/main/src/gameSettings.js
Hm, but looking at Automatic Animations and MCE Everywhere, it seems that if one wants to access settings outside of a UI, one must still use 'game.settings.get', right?
No you get a store from TJSGameSettings. In the case of MCE Everywhere the reactive settings are used in the settings app / TJSSettingsEdit component. You can of course use game.settings.get which in the case of MCE Everywhere I'm doing particular things in the control flow of Foundry, so not using them reactively.
You get a store / reactive setting from the TJSGameSettings instance via gameSettings.getStore(<key>). You can use that from JS or of course hook it up in Svelte.
Automatic Animations likely uses them, but it's a bit of a behemoth. One thing to take note that you have to be generally aware that a lot of folks that might be using a library will have various prior dev experience, so you don't always find best practices just like any other API usage including Foundry core / App v1 API, etc.
So can you then do something like stateStore.someSetting from JS?
// From JS side of things...
const mySetting = gameSettings.getStore('mySetting');
// Call unsubscribe function when you unsubscribe / you are managing this.
const unsubscribeFn = mySetting.subscribe((data) => {
// Do something w/ reactive data which is the deserialized / already parsed setting data.
});
// From Svelte side of things...
const mySetting = gameSettings.getStore('mySetting');
// This is a reactive statement (`$:`) it logs the setting any time it changes.
// `$mySetting` -> the leading $ in Svelte signals that it is a store being dereferenced.
// Svelte will automatically subscribe / unsubscribe for you.
$: console.log('mySetting: ', $mySetting);
What if you want to get a setting's value from within a hook?
If the hook is defined in a Svelte component then all you have to do is $mySetting. From the JS side of things you'd still use game.settings.get.
While there is a get function defined in the svelte/store library which is used like get(mySetting) from the JS side of things that is inefficient as it temporarily subscribes / gets the value / then unsubscribes.
It very much is like the standard observer pattern / event listener when using a store from JS.
@worldly wharf Sounds like our goals are slightly different then. This is the gist of what I'm trying to achieve: https://gist.github.com/Varriount/161beabe461936fb580e10d14c2b3e64
No.. You didn't quite grasp the power and flexibility of the API at hand. I just fully implemented what I think you are looking for here... See webm.
You could build an "live settings" JS object like what you described in your initial brainstorm post from this API in ~64 lines of code including comments.
/**
* Provides an accessible JS object that is updated reactively from a TJSGameSettings instance.
*/
export class JSReactiveSettings
{
/**
* Stores registered settings keys and unsubscribe functions.
*
* @type {Map<string, Function>}
*/
#unsubscribeMap = new Map();
subscribe(gameSettings)
{
for (const setting of gameSettings)
{
const key = setting.key;
if (typeof this[key] === 'function')
{
console.log(`JSReactiveSettings warning: key (${key}) shadows a function. Skipping key.`);
}
// Update this instance storing setting data by key.
this.#unsubscribeMap.set(key, gameSettings.getStore(key).subscribe((data) =>
{
this[key] = data;
// Notify any child instance that a particular key has updated.
this._update(key);
}));
}
}
/**
* If you made a child class of JSReactiveSettings you can get an update when a key changes by overriding.
*
* @param {string} key - The setting / local key that updated.
*
* @protected
*/
_update(key)
{
// TODO Remove this! Just here for demonstration purposes.
console.log(`!! JSReactiveSettings - _update - key: ${key}`);
}
/**
* Unsubscribe and clear instance data.
*/
unsubscribe()
{
for (const key of this.#unsubscribeMap.keys())
{
const unsubFn = this.#unsubscribeMap.get(key);
if (typeof unsubFn === 'function') { unsubFn(); }
delete this[key];
}
this.#unsubscribeMap.clear();
}
}
// Use the above like this:
const settings = new JSReactiveSettings();
// Pass it an instance of TJSGameSettings
settings.subscribe(gameSettings);
// settings is now "live" and you should be able to reference `settings` like
`settings.mySetting` and it is the latest value.
You can use the above in pure JS without using Svelte. It would be easiest / actually necessary to consume TRL though by using a bundler as it is distributed as a NPM package.
I might even add the above to TRL as JSLiveSettings as it actually works well.
It works too even if you use the Foundry API to change a setting via game.settings.set anywhere in your code.
Yeah. The only functional difference I can see is that the JSLiveSettings class doesn't update a setting when it's mutated on the class. But that's a minor (and possibly unwanted) piece of behavior.
You can make it a Proxy.... heh.... more implementation, but easy. Give me 10 minutes or less.. ;P
Nah, I think a setter would suffice (via Object.defineProperty)
Complicates things / no need at all...
Sure, which is why I said that piece of behavior is minor. I'd been debating myself whether to include it.
I think you could even make it play nicely with TypeScript introspection by taking a class:
// Assuming `Setting` has a signature of `function <T>(...) -> T`
class MySettings {
foo = Setting<String>(...)
bar = Setting<Number>(...)
}
// Assuming `fromClass` has a signature of `function <T>(t: T) -> T
mySettings = JSReactiveSettings.fromClass(MySettings)
(there might be a better way to do this in TypeScript, I'm not super-familiar with its type system)
I assume you could use an type for TS.
// Define this somewhere accessible
type MySettings = {
a: number
b: string
};
/** @type {JSLiveSettings<MySettings>} */
const settings = new JSLiveSettings();
// or TS:
const settings = new JSLiveSettings<MySettings>();
You can template in JSDoc and TRL generates TS types for everything from ESM source.
@worldly wharf I do wonder how the performance will work out with that strategy though. One of the reasons I was using the Function constructor along with generated code was to take advantage of how Javascript interpeters optimize property access (https://draft.li/blog/2016/12/22/javascript-engines-hidden-classes/).
For game settings? It's not like one is rapidly changing game settings in a usual case. Even if you are it's fine. "Premature optimization is the root of all evil"
Changing (writing), sure. But accessing (reading) is a different matter. That can happen quite often.
You are directly accessing settings.mySetting at runtime.
So yeah.. This was a fun little experiment... I have committed TJSLiveGameSettings to TRL. It essentially allows you to collate all game settings into a single reactive instance w/ a JS API and also usable in Svelte. I did use Object.defineProperty w/ accessors, but seal the object and start things up in the constructor. So you have accessors from the pure JS side, but I also made it a "readable store"; more or less a customizable "derived store". This is accessible from the JS side of things and also usable in Svelte. There are options to pass in exclude / include key Sets to limit the settings. Overall pretty neat though however specialized, but I can see use cases for it.
Code in a branch currently: https://github.com/typhonjs-fvtt-lib/svelte-standard/blob/v0.0.18-dev/src/store/settings/TJSLiveGameSettings.js
Next up complete TJSLiveStorage for the session and local Web Storage reactive store functionality that also is a part of TRL.
I do hope you get a chance to check out TRL one day, but no worries. Have fun creating your solution. Alas no way to make things work w/ typings as what I've come up with is dynamic, but it's short and sweet and maintainable.
Edit: It turns out that you can type the live game settings store w/ JSDoc and a typedef that describes the dynamic accessor setting keys. Something like the following:
/**
* @typedef {TJSLiveGameSettings} MyLiveGameSettings - Extend TJSLiveGameSettings and name this anything.
*
* @property {boolean} myBooleanSetting - Add property / type entries for setting keys associated w/ accessors.
*/
/** @type {MyLiveGameSettings} */
const liveGameSettings = new TJSLiveGameSettings(gameSettings);
liveGameSettings.myBooleanSetting // is now typed as a boolean.
One could use this in a stock core Foundry App v1 Application.
Demo video shows the final effort; buttons across the top are for testing which you can see in the Svelte template to the right:
@worldly wharf One thing I might recommend (and perhaps this is already done), would be to make things modular, especially with regards to integrating with things like Svelte. Many modules don't really have any UI components.
Why do you think it's not modular? This is all JS / nothing that requires Svelte compiler / build step and all of the TRL packages are modular using sub-package exports via NPM and ESM. Technically true that you'd have to add svelte NPM module as a dependency as a few JS resources like writable from svelte/store are required, but it's all JS code / nothing that requires the Svelte compiler. Svelte is modular just like TRL is modular NPM package and just what you use is pulled into your code.
If you just wanted TJSGameSettings / TJSLiveGameSettings from TRL you'd do this and only pull in the code you need:
import {
TJSLiveGameSettings
TJSGameSettings } from '@typhonjs-fvtt/svelte-standard/store';
Sure, but that's still pulling in logic meant for Svelte, even if indirectly.