@7h3laughingman/foundry-helpers 13.351.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.MD +21 -0
- package/README.md +3 -0
- package/dist/lib-wrapper/index.d.ts +348 -0
- package/dist/utilities/array.d.ts +12 -0
- package/dist/utilities/array.js +40 -0
- package/dist/utilities/index.d.ts +6 -0
- package/dist/utilities/index.js +6 -0
- package/dist/utilities/localize.d.ts +25 -0
- package/dist/utilities/localize.js +56 -0
- package/dist/utilities/module.d.ts +21 -0
- package/dist/utilities/module.js +130 -0
- package/dist/utilities/settings.d.ts +3 -0
- package/dist/utilities/settings.js +10 -0
- package/dist/utilities/time.d.ts +1 -0
- package/dist/utilities/time.js +5 -0
- package/dist/utilities/user.d.ts +6 -0
- package/dist/utilities/user.js +25 -0
- package/package.json +48 -0
package/LICENSE.MD
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 7H3LaughingMan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
[](https://github.com/7H3LaughingMan/foundry-helpers/blob/v13/LICENSE.MD)
|
|
2
|
+
[](https://www.npmjs.com/package/@7h3laughingman/foundry-helpers)
|
|
3
|
+
[](https://www.npmjs.com/package/@7h3laughingman/foundry-helpers)
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
|
3
|
+
export {};
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
enum WRAPPER_TYPES {
|
|
7
|
+
"WRAPPER" = 1,
|
|
8
|
+
"MIXED" = 2,
|
|
9
|
+
"OVERRIDE" = 3,
|
|
10
|
+
"LISTENER" = 4
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
enum PERF_MODES {
|
|
14
|
+
"NORMAL" = 1,
|
|
15
|
+
"AUTO" = 2,
|
|
16
|
+
"FAST" = 3
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type WrapperType = "WRAPPER" | "MIXED" | "OVERRIDE" | "LISTENER" | WRAPPER_TYPES;
|
|
20
|
+
type PerfomanceModes = "NORMAL" | "FAST" | "AUTO" | PERF_MODES;
|
|
21
|
+
|
|
22
|
+
export class libWrapper {
|
|
23
|
+
/**
|
|
24
|
+
* Get libWrapper version
|
|
25
|
+
* @returns libWrapper version in string form, i.e. "<MAJOR>.<MINOR>.<PATCH>.<SUFFIX><META>"
|
|
26
|
+
*/
|
|
27
|
+
static get version(): string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get libWrapper version
|
|
31
|
+
* @returns libWrapper version in array form, i.e. [<MAJOR>, <MINOR>, <PATCH>, <SUFFIX>, <META>]
|
|
32
|
+
*/
|
|
33
|
+
static get versions(): [number, number, number, number, string];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the Git version identifier.
|
|
37
|
+
* @returns Git version identifier, usually 'HEAD' or the commit hash.
|
|
38
|
+
*/
|
|
39
|
+
static get git_version(): string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @returns The real libWrapper module will always return false. Fallback implementations (e.g. poly-fill / shim) should return true.
|
|
43
|
+
*/
|
|
44
|
+
static get is_fallback(): boolean;
|
|
45
|
+
|
|
46
|
+
// Errors
|
|
47
|
+
static get LibWrapperError(): Error;
|
|
48
|
+
static get Error(): Error;
|
|
49
|
+
|
|
50
|
+
static get LibWrapperInternalError(): Error;
|
|
51
|
+
static get InternalError(): Error;
|
|
52
|
+
|
|
53
|
+
static get LibWrapperPackageError(): Error;
|
|
54
|
+
static get PackageError(): Error;
|
|
55
|
+
|
|
56
|
+
static get LibWrapperAlreadyOverriddenError(): Error;
|
|
57
|
+
static get AlreadyOverriddenError(): Error;
|
|
58
|
+
|
|
59
|
+
static get LibWrapperInvalidWrapperChainError(): Error;
|
|
60
|
+
static get InvalidWrapperChainError(): Error;
|
|
61
|
+
|
|
62
|
+
// Enums - First introduced in v1.9.0.0
|
|
63
|
+
static get WRAPPER(): WRAPPER_TYPES.WRAPPER;
|
|
64
|
+
static get MIXED(): WRAPPER_TYPES.MIXED;
|
|
65
|
+
static get OVERRIDE(): WRAPPER_TYPES.OVERRIDE;
|
|
66
|
+
static get LISTENER(): WRAPPER_TYPES.LISTENER;
|
|
67
|
+
|
|
68
|
+
static get PERF_NORMAL(): PERF_MODES.NORMAL;
|
|
69
|
+
static get PERF_AUTO(): PERF_MODES.AUTO;
|
|
70
|
+
static get PERF_FAST(): PERF_MODES.FAST;
|
|
71
|
+
|
|
72
|
+
// Methods
|
|
73
|
+
/**
|
|
74
|
+
* Test for a minimum libWrapper version.
|
|
75
|
+
* First introduced in v1.4.0.0.
|
|
76
|
+
*
|
|
77
|
+
* @param major Minimum major version
|
|
78
|
+
* @param minor Minimum minor version. Default is 0.
|
|
79
|
+
* @param patch Minimum patch version. Default is 0.
|
|
80
|
+
* @param suffix Minimum suffix version. Default is 0.
|
|
81
|
+
* @returns Returns true if the libWrapper version is at least the queried version, otherwise false.
|
|
82
|
+
*/
|
|
83
|
+
static version_at_least(major: number, minor?: number, patch?: number, suffix?: number): boolean;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Register a new wrapper.
|
|
87
|
+
* Important: If called before the 'init' hook, this method will fail.
|
|
88
|
+
*
|
|
89
|
+
* In addition to wrapping class methods, there is also support for wrapping methods on specific object instances, as well as class methods inherited from parent classes.
|
|
90
|
+
* However, it is recommended to wrap methods directly in the class that defines them whenever possible, as inheritance/instance wrapping is less thoroughly tested and will incur a performance penalty.
|
|
91
|
+
*
|
|
92
|
+
* Triggers FVTT hook 'libWrapper.Register' when successful.
|
|
93
|
+
*
|
|
94
|
+
* Returns a unique numeric target identifier, which can be used as a replacement for 'target' in future calls to 'libWrapper.register' and 'libWrapper.unregister'.
|
|
95
|
+
*
|
|
96
|
+
* @param package_id The package identifier, i.e. the 'id' field in your module/system/world's manifest.
|
|
97
|
+
*
|
|
98
|
+
* @param target The target identifier, specifying which wrapper should be registered.
|
|
99
|
+
*
|
|
100
|
+
* This can be either:
|
|
101
|
+
* 1. A unique target identifier obtained from a previous 'libWrapper.register' call.
|
|
102
|
+
* 2. A string containing the path to the function you wish to add the wrapper to, starting at global scope, for example 'SightLayer.prototype.updateToken'.
|
|
103
|
+
*
|
|
104
|
+
* Support for the unique target identifiers (option #1) was added in v1.11.0.0, with previous versions only supporting option #2.
|
|
105
|
+
*
|
|
106
|
+
* Since v1.8.0.0, the string path (option #2) can contain string array indexing.
|
|
107
|
+
* For example, 'CONFIG.Actor.sheetClasses.character["dnd5e.ActorSheet5eCharacter"].cls.prototype._onLongRest' is a valid path.
|
|
108
|
+
* It is important to note that indexing in libWrapper does not work exactly like in JavaScript:
|
|
109
|
+
* - The index must be a single string, quoted using the ' or " characters. It does not support e.g. numbers or objects.
|
|
110
|
+
* - A backslash \ can be used to escape another character so that it loses its special meaning, e.g. quotes i.e. ' and " as well as the character \ itself.
|
|
111
|
+
*
|
|
112
|
+
* By default, libWrapper searches for normal methods or property getters only. To wrap a property's setter, append '#set' to the name, for example 'SightLayer.prototype.blurDistance#set'.
|
|
113
|
+
*
|
|
114
|
+
* @param fn Wrapper function. The first argument will be the next function in the chain, except for 'OVERRIDE' wrappers.
|
|
115
|
+
* The remaining arguments will correspond to the parameters passed to the wrapped method.
|
|
116
|
+
*
|
|
117
|
+
* @param type The type of the wrapper. Default is 'MIXED'.
|
|
118
|
+
*
|
|
119
|
+
* The possible types are:
|
|
120
|
+
*
|
|
121
|
+
* 'LISTENER' / libWrapper.LISTENER:
|
|
122
|
+
* Use this to register a listener function. This function will be called immediately before the target is called, but is not part of the call chain.
|
|
123
|
+
* Use when you just need to know a method is being called and the parameters used for the call, without needing to modify the parameters or execute any
|
|
124
|
+
* code after the method finishes execution.
|
|
125
|
+
* Listeners will always be called first, before any other type, and should be used whenever possible as they have a virtually zero chance of conflict.
|
|
126
|
+
* Note that asynchronous listeners are *not* awaited before execution is allowed to proceed.
|
|
127
|
+
* First introduced in v1.13.0.0.
|
|
128
|
+
*
|
|
129
|
+
* 'WRAPPER' / libWrapper.WRAPPER:
|
|
130
|
+
* Use if your wrapper will *always* continue the chain.
|
|
131
|
+
* This type has priority over MIXED and OVERRIDE. It should be preferred over those whenever possible as it massively reduces the likelihood of conflicts.
|
|
132
|
+
* Note that the library will auto-detect if you use this type but do not call the original function, and automatically unregister your wrapper.
|
|
133
|
+
*
|
|
134
|
+
* 'MIXED' / libWrapper.MIXED:
|
|
135
|
+
* Default type. Your wrapper will be allowed to decide whether it continue the chain or not.
|
|
136
|
+
* These will always come after 'WRAPPER'-type wrappers. Order is not guaranteed, but conflicts will be auto-detected.
|
|
137
|
+
*
|
|
138
|
+
* 'OVERRIDE' / libWrapper.OVERRIDE:
|
|
139
|
+
* Use if your wrapper will *never* continue the chain. This type has the lowest priority, and will always be called last.
|
|
140
|
+
* If another package already has an 'OVERRIDE' wrapper registered to the same method, using this type will throw a <libWrapper.ERRORS.package> exception.
|
|
141
|
+
* Catching this exception should allow you to fail gracefully, and for example warn the user of the conflict.
|
|
142
|
+
* Note that if the GM has explicitly given your package priority over the existing one, no exception will be thrown and your wrapper will take over.
|
|
143
|
+
*
|
|
144
|
+
* @param options Additional options to libWrapper.
|
|
145
|
+
*
|
|
146
|
+
* @param options.chain If 'true', the first parameter to 'fn' will be a function object that can be called to continue the chain.
|
|
147
|
+
* This parameter must be 'true' when registering non-OVERRIDE wrappers.
|
|
148
|
+
* Default is 'false' if type=='OVERRIDE', otherwise 'true'.
|
|
149
|
+
* First introduced in v1.3.6.0.
|
|
150
|
+
*
|
|
151
|
+
* @param options.perf_mode Selects the preferred performance mode for this wrapper. Default is 'AUTO'.
|
|
152
|
+
* It will be used if all other wrappers registered on the same target also prefer the same mode, otherwise the default will be used instead.
|
|
153
|
+
* This option should only be specified with good reason. In most cases, using 'AUTO' in order to allow the GM to choose is the best option.
|
|
154
|
+
* First introduced in v1.5.0.0.
|
|
155
|
+
*
|
|
156
|
+
* The possible modes are:
|
|
157
|
+
*
|
|
158
|
+
* 'NORMAL' / libWrapper.PERF_NORMAL:
|
|
159
|
+
* Enables all conflict detection capabilities provided by libWrapper. Slower than 'FAST'.
|
|
160
|
+
* Useful if wrapping a method commonly modified by other packages, to ensure most issues are detected.
|
|
161
|
+
* In most other cases, this mode is not recommended and 'AUTO' should be used instead.
|
|
162
|
+
*
|
|
163
|
+
* 'FAST' / libWrapper.PERF_FAST:
|
|
164
|
+
* Disables some conflict detection capabilities provided by libWrapper, in exchange for performance. Faster than 'NORMAL'.
|
|
165
|
+
* Will guarantee wrapper call order and per-package prioritization, but fewer conflicts will be detectable.
|
|
166
|
+
* This performance mode will result in comparable performance to traditional non-libWrapper wrapping methods.
|
|
167
|
+
* Useful if wrapping a method called repeatedly in a tight loop, for example 'WallsLayer.testWall'.
|
|
168
|
+
* In most other cases, this mode is not recommended and 'AUTO' should be used instead.
|
|
169
|
+
*
|
|
170
|
+
* 'AUTO' / libWrapper.PERF_AUTO:
|
|
171
|
+
* Default performance mode. If unsure, choose this mode.
|
|
172
|
+
* Will allow the GM to choose which performance mode to use.
|
|
173
|
+
* Equivalent to 'FAST' when the libWrapper 'High-Performance Mode' setting is enabled by the GM, otherwise 'NORMAL'.
|
|
174
|
+
*
|
|
175
|
+
* @param options.bind An array of parameters that should be passed to 'fn'.
|
|
176
|
+
*
|
|
177
|
+
* This allows avoiding an extra function call, for instance:
|
|
178
|
+
* libWrapper.register(PACKAGE_ID, "foo", function(wrapped, ...args) { return someFunction.call(this, wrapped, "foo", "bar", ...args) });
|
|
179
|
+
* becomes
|
|
180
|
+
* libWrapper.register(PACKAGE_ID, "foo", someFunction, "WRAPPER", {bind: ["foo", "bar"]});
|
|
181
|
+
*
|
|
182
|
+
* First introduced in v1.12.0.0.
|
|
183
|
+
*
|
|
184
|
+
* @returns Unique numeric 'target' identifier which can be used in future 'libWrapper.register' and 'libWrapper.unregister' calls.
|
|
185
|
+
* Added in v1.11.0.0.
|
|
186
|
+
*/
|
|
187
|
+
static register(
|
|
188
|
+
package_id: string,
|
|
189
|
+
target: number | string,
|
|
190
|
+
fn: Function,
|
|
191
|
+
type?: WRAPPER_TYPES,
|
|
192
|
+
options?: {
|
|
193
|
+
chain?: boolean;
|
|
194
|
+
perf_mode?: PerfomanceModes;
|
|
195
|
+
bind?: unknown[];
|
|
196
|
+
}
|
|
197
|
+
): number;
|
|
198
|
+
static register<T extends ThisType<F>, F extends (...args: any) => any>(
|
|
199
|
+
package_id: string,
|
|
200
|
+
target: number | string,
|
|
201
|
+
fn: (this: T, wrapped: F, ...args: Parameters<F>) => ReturnType<F>,
|
|
202
|
+
type?: WRAPPER_TYPES.WRAPPER | WRAPPER_TYPES.MIXED | "WRAPPER" | "MIXED",
|
|
203
|
+
options?: {
|
|
204
|
+
chain?: boolean;
|
|
205
|
+
perf_mode?: PerfomanceModes;
|
|
206
|
+
bind?: unknown[];
|
|
207
|
+
}
|
|
208
|
+
): number;
|
|
209
|
+
static register<T extends ThisType<F>, F extends (...args: any) => any>(
|
|
210
|
+
package_id: string,
|
|
211
|
+
target: number | string,
|
|
212
|
+
fn: (this: T, ...args: Parameters<F>) => ReturnType<F>,
|
|
213
|
+
type?: WRAPPER_TYPES.OVERRIDE | "OVERRIDE",
|
|
214
|
+
options?: {
|
|
215
|
+
chain?: boolean;
|
|
216
|
+
perf_mode?: PerfomanceModes;
|
|
217
|
+
bind?: unknown[];
|
|
218
|
+
}
|
|
219
|
+
): number;
|
|
220
|
+
static register<T extends ThisType<F>, F extends (...args: any) => any>(
|
|
221
|
+
package_id: string,
|
|
222
|
+
target: number | string,
|
|
223
|
+
fn: (this: T, ...args: Parameters<F>) => void | Promise<void>,
|
|
224
|
+
type?: WRAPPER_TYPES.LISTENER | "LISTENER",
|
|
225
|
+
options?: {
|
|
226
|
+
chain?: boolean;
|
|
227
|
+
perf_mode?: PerfomanceModes;
|
|
228
|
+
bind?: unknown[];
|
|
229
|
+
}
|
|
230
|
+
): number;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Unregister an existing wrapper.
|
|
234
|
+
*
|
|
235
|
+
* Triggers FVTT hook 'libWrapper.Unregister' when successful.
|
|
236
|
+
*
|
|
237
|
+
* @param package_id The package identifier, i.e. the 'id' field in your module/system/world's manifest.
|
|
238
|
+
*
|
|
239
|
+
* @param target The target identifier, specifying which wrapper should be unregistered.
|
|
240
|
+
*
|
|
241
|
+
* This can be either:
|
|
242
|
+
* 1. A unique target identifier obtained from a previous 'libWrapper.register' call. This is the recommended option.
|
|
243
|
+
* 2. A string containing the path to the function you wish to remove the wrapper from, starting at global scope, with the same syntax as the 'target' parameter to 'libWrapper.register'.
|
|
244
|
+
*
|
|
245
|
+
* Support for the unique target identifiers (option #1) was added in v1.11.0.0, with previous versions only supporting option #2.
|
|
246
|
+
* It is recommended to use option #1 if possible, in order to guard against the case where the class or object at the given path is no longer the same as when `libWrapper.register' was called.
|
|
247
|
+
*
|
|
248
|
+
* @param fail If true, this method will throw an exception if it fails to find the method to unwrap. Default is 'true'.
|
|
249
|
+
*/
|
|
250
|
+
static unregister(package_id: string, target: number | string, fail?: boolean): void;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Unregister all wrappers created by a given package.
|
|
254
|
+
*
|
|
255
|
+
* Triggers FVTT hook 'libWrapper.UnregisterAll' when successful.
|
|
256
|
+
*
|
|
257
|
+
* @param package_id The package identifier, i.e. the 'id' field in your module/system/world's manifest.
|
|
258
|
+
*/
|
|
259
|
+
static unregister_all(package_id: string): void;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Ignore conflicts matching specific filters when detected, instead of warning the user.
|
|
263
|
+
*
|
|
264
|
+
* This can be used when there are conflict warnings that are known not to cause any issues, but are unable to be resolved.
|
|
265
|
+
* Conflicts will be ignored if they involve both 'package_id' and one of 'ignore_ids', and relate to one of 'targets'.
|
|
266
|
+
*
|
|
267
|
+
* Note that the user can still see which detected conflicts were ignored, by toggling "Show ignored conflicts" in the "Conflicts" tab in the libWrapper settings.
|
|
268
|
+
*
|
|
269
|
+
* First introduced in v1.7.0.0.
|
|
270
|
+
*
|
|
271
|
+
* @param package_id The package identifier, i.e. the 'id' field in your module/system/world's manifest. This will be the package that owns this ignore entry.
|
|
272
|
+
*
|
|
273
|
+
* @param ignore_ids Other package ID(s) with which conflicts should be ignored.
|
|
274
|
+
*
|
|
275
|
+
* @param targets Target(s) for which conflicts should be ignored, corresponding to the 'target' parameter to 'libWrapper.register'.
|
|
276
|
+
* This method does not accept the unique target identifiers returned by 'libWrapper.register'.
|
|
277
|
+
*
|
|
278
|
+
* @param options Additional options to libWrapper.
|
|
279
|
+
*
|
|
280
|
+
* @param options.ignore_errors If 'true', will also ignore confirmed conflicts (i.e. errors), rather than only potential conflicts (i.e. warnings).
|
|
281
|
+
* Be careful when setting this to 'true', as confirmed conflicts are almost certainly something the user should be made aware of.
|
|
282
|
+
* Defaults to 'false'.
|
|
283
|
+
*/
|
|
284
|
+
static ignore_conflicts(package_id: string, ignore_ids: string | string[], targets: string | string[], options?: { ingore_errors?: boolean }): void;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
declare module "foundry-types/client/helpers/hooks.mjs" {
|
|
289
|
+
namespace Hooks {
|
|
290
|
+
function on(hook: "libWrapper.Ready", callback: () => boolean | void | Promise<boolean | void>): number;
|
|
291
|
+
function on(
|
|
292
|
+
hook: "libWrapper.Register",
|
|
293
|
+
callback: (
|
|
294
|
+
package_id: string,
|
|
295
|
+
target: number | string,
|
|
296
|
+
type: WRAPPER_TYPES,
|
|
297
|
+
options: {
|
|
298
|
+
chain?: boolean;
|
|
299
|
+
perf_mode?: PerfomanceModes;
|
|
300
|
+
bind?: unknown[];
|
|
301
|
+
},
|
|
302
|
+
wrapper_id: number
|
|
303
|
+
) => boolean | void | Promise<boolean | void>
|
|
304
|
+
): number;
|
|
305
|
+
function on(
|
|
306
|
+
hook: "libWrapper.Unregister",
|
|
307
|
+
callback: (package_id: string, target: number | string, wrapper_id: number) => boolean | void | Promise<boolean | void>
|
|
308
|
+
): number;
|
|
309
|
+
function on(hook: "libWrapper.UnregisterAll", callback: (package_id: string) => boolean | void | Promise<boolean | void>): number;
|
|
310
|
+
function on(
|
|
311
|
+
hook: "libWrapper.ConflictDetected",
|
|
312
|
+
callback: (package_id: string, other_info: string, target: string, frozen_names: string[]) => boolean | void | Promise<boolean | void>
|
|
313
|
+
): number;
|
|
314
|
+
function on(
|
|
315
|
+
hook: "libWrapper.OverrideLost",
|
|
316
|
+
callback: (package_id: string, other_info: string, target: string, frozen_names: string[]) => boolean | void | Promise<boolean | void>
|
|
317
|
+
): number;
|
|
318
|
+
|
|
319
|
+
function once(hook: "libWrapper.Ready", callback: () => boolean | void | Promise<boolean | void>): number;
|
|
320
|
+
function once(
|
|
321
|
+
hook: "libWrapper.Register",
|
|
322
|
+
callback: (
|
|
323
|
+
package_id: string,
|
|
324
|
+
target: number | string,
|
|
325
|
+
type: WRAPPER_TYPES,
|
|
326
|
+
options: {
|
|
327
|
+
chain?: boolean;
|
|
328
|
+
perf_mode?: PerfomanceModes;
|
|
329
|
+
bind?: unknown[];
|
|
330
|
+
},
|
|
331
|
+
wrapper_id: number
|
|
332
|
+
) => boolean | void | Promise<boolean | void>
|
|
333
|
+
): number;
|
|
334
|
+
function once(
|
|
335
|
+
hook: "libWrapper.Unregister",
|
|
336
|
+
callback: (package_id: string, target: number | string, wrapper_id: number) => boolean | void | Promise<boolean | void>
|
|
337
|
+
): number;
|
|
338
|
+
function once(hook: "libWrapper.UnregisterAll", callback: (package_id: string) => boolean | void | Promise<boolean | void>): number;
|
|
339
|
+
function once(
|
|
340
|
+
hook: "libWrapper.ConflictDetected",
|
|
341
|
+
callback: (package_id: string, other_info: string, target: string, frozen_names: string[]) => boolean | void | Promise<boolean | void>
|
|
342
|
+
): number;
|
|
343
|
+
function once(
|
|
344
|
+
hook: "libWrapper.OverrideLost",
|
|
345
|
+
callback: (package_id: string, other_info: string, target: string, frozen_names: string[]) => boolean | void | Promise<boolean | void>
|
|
346
|
+
): number;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class CycleArray<T> extends Array<T> {
|
|
2
|
+
#private;
|
|
3
|
+
get index(): number;
|
|
4
|
+
get current(): T;
|
|
5
|
+
get isLast(): boolean;
|
|
6
|
+
get isFirst(): boolean;
|
|
7
|
+
setFromValue(value: T): void;
|
|
8
|
+
increment(): T;
|
|
9
|
+
decrement(): T;
|
|
10
|
+
cycle(direction: number | boolean): T;
|
|
11
|
+
}
|
|
12
|
+
export declare function includesAny(array: unknown[], entries: unknown[]): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class CycleArray extends Array {
|
|
2
|
+
#index = 0;
|
|
3
|
+
get index() {
|
|
4
|
+
if (this.#index > this.length - 1)
|
|
5
|
+
this.#index = this.length - 1;
|
|
6
|
+
return this.#index;
|
|
7
|
+
}
|
|
8
|
+
get current() {
|
|
9
|
+
return this.at(this.index);
|
|
10
|
+
}
|
|
11
|
+
get isLast() {
|
|
12
|
+
return this.index === this.length - 1;
|
|
13
|
+
}
|
|
14
|
+
get isFirst() {
|
|
15
|
+
return this.index === 0;
|
|
16
|
+
}
|
|
17
|
+
setFromValue(value) {
|
|
18
|
+
const index = this.indexOf(value);
|
|
19
|
+
this.#index = index >= 0 ? index : this.#index;
|
|
20
|
+
}
|
|
21
|
+
increment() {
|
|
22
|
+
this.#index = (this.index + 1) % this.length;
|
|
23
|
+
return this.current;
|
|
24
|
+
}
|
|
25
|
+
decrement() {
|
|
26
|
+
const modulo = this.length;
|
|
27
|
+
const value = this.index - 1;
|
|
28
|
+
this.#index = ((value % modulo) + modulo) % modulo;
|
|
29
|
+
return this.current;
|
|
30
|
+
}
|
|
31
|
+
cycle(direction) {
|
|
32
|
+
return Number(direction) > 0 ? this.increment() : this.decrement();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function includesAny(array, entries) {
|
|
36
|
+
for (const entry of entries)
|
|
37
|
+
if (array.includes(entry))
|
|
38
|
+
return true;
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
declare class Localize extends Function {
|
|
2
|
+
subkeys: string[];
|
|
3
|
+
constructor(...subkeys: string[]);
|
|
4
|
+
path(...path: string[]): string;
|
|
5
|
+
getLocalizeData(...args: LocalizeArgs): {
|
|
6
|
+
path: string;
|
|
7
|
+
data?: LocalizeData;
|
|
8
|
+
};
|
|
9
|
+
localizeOrFormat(path: string, data?: LocalizeData): string;
|
|
10
|
+
ifExist(...args: LocalizeArgs): string | undefined;
|
|
11
|
+
notify(type: "info" | "warning" | "error" | "success", ...args: NotificationArgs): foundry.applications.ui.Notification;
|
|
12
|
+
info(...args: NotificationArgs): foundry.applications.ui.Notification;
|
|
13
|
+
warning(...args: NotificationArgs): foundry.applications.ui.Notification;
|
|
14
|
+
error(...args: NotificationArgs): foundry.applications.ui.Notification;
|
|
15
|
+
success(...args: NotificationArgs): foundry.applications.ui.Notification;
|
|
16
|
+
sub(...subkeys: string[]): Localize;
|
|
17
|
+
}
|
|
18
|
+
interface Localize {
|
|
19
|
+
(...args: LocalizeArgs): string;
|
|
20
|
+
}
|
|
21
|
+
export declare const localize: Localize;
|
|
22
|
+
export type LocalizeData = Record<string, Maybe<string | number | boolean>>;
|
|
23
|
+
export type LocalizeArgs = string[] | [...string[], string | LocalizeData];
|
|
24
|
+
export type NotificationArgs = LocalizeArgs | [...LocalizeArgs, string | LocalizeData | boolean];
|
|
25
|
+
export type { Localize };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as R from "remeda";
|
|
2
|
+
import { MODULE } from "./module.js";
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
4
|
+
class Localize extends Function {
|
|
5
|
+
constructor(...subkeys) {
|
|
6
|
+
super();
|
|
7
|
+
this.subkeys = subkeys;
|
|
8
|
+
const self = this;
|
|
9
|
+
function localize(...args) {
|
|
10
|
+
const { data, path } = self.getLocalizeData(...args);
|
|
11
|
+
return self.localizeOrFormat(path, data);
|
|
12
|
+
}
|
|
13
|
+
Object.assign(localize, this);
|
|
14
|
+
Object.setPrototypeOf(localize, Object.getPrototypeOf(this));
|
|
15
|
+
return localize;
|
|
16
|
+
}
|
|
17
|
+
path(...path) {
|
|
18
|
+
return MODULE.path(...this.subkeys, ...path);
|
|
19
|
+
}
|
|
20
|
+
getLocalizeData(...args) {
|
|
21
|
+
const data = R.isObjectType(args.at(-1)) ? args.pop() : undefined;
|
|
22
|
+
const path = this.path(...args);
|
|
23
|
+
return { path, data };
|
|
24
|
+
}
|
|
25
|
+
localizeOrFormat(path, data) {
|
|
26
|
+
return typeof data === "object" ? game.i18n.format(path, data) : game.i18n.localize(path);
|
|
27
|
+
}
|
|
28
|
+
ifExist(...args) {
|
|
29
|
+
const { data, path } = this.getLocalizeData(...args);
|
|
30
|
+
if (game.i18n.has(path, true)) {
|
|
31
|
+
return this.localizeOrFormat(path, data);
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
notify(type, ...args) {
|
|
36
|
+
const permanent = R.isBoolean(args.at(-1)) ? args.pop() : false;
|
|
37
|
+
const message = this(...args);
|
|
38
|
+
return foundry.ui.notifications.notify(message, type, { permanent });
|
|
39
|
+
}
|
|
40
|
+
info(...args) {
|
|
41
|
+
return this.notify("info", ...args);
|
|
42
|
+
}
|
|
43
|
+
warning(...args) {
|
|
44
|
+
return this.notify("warning", ...args);
|
|
45
|
+
}
|
|
46
|
+
error(...args) {
|
|
47
|
+
return this.notify("error", ...args);
|
|
48
|
+
}
|
|
49
|
+
success(...args) {
|
|
50
|
+
return this.notify("success", ...args);
|
|
51
|
+
}
|
|
52
|
+
sub(...subkeys) {
|
|
53
|
+
return new Localize(...this.subkeys, ...subkeys);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export const localize = new Localize();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Module } from "@7h3laughingman/foundry-types/client/packages/_module.mjs";
|
|
2
|
+
import { ImageFilePath } from "@7h3laughingman/foundry-types/common/constants.mjs";
|
|
3
|
+
declare class CustomModule {
|
|
4
|
+
#private;
|
|
5
|
+
get id(): string;
|
|
6
|
+
get current(): Module;
|
|
7
|
+
get name(): string;
|
|
8
|
+
get isDebug(): boolean;
|
|
9
|
+
register(id: string, globalName?: string): void;
|
|
10
|
+
globalPath(...path: string[]): string;
|
|
11
|
+
path(...path: string[]): string;
|
|
12
|
+
relativePath(...path: string[]): string;
|
|
13
|
+
imagePath(...path: [...string[], ImageFilePath]): ImageFilePath;
|
|
14
|
+
templatePath(...path: [...string[], `${string}.hbs`]): string;
|
|
15
|
+
Error(message: string): Error;
|
|
16
|
+
error(input: string, error?: unknown): void;
|
|
17
|
+
apiExpose(key: string, toExpose: Record<string, unknown>): void;
|
|
18
|
+
debugExpose(key: string, toExpose: unknown): void;
|
|
19
|
+
}
|
|
20
|
+
export declare const MODULE: CustomModule;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as R from "remeda";
|
|
2
|
+
import { localize } from "./localize.js";
|
|
3
|
+
import { getSetting } from "./settings.js";
|
|
4
|
+
class CustomModule {
|
|
5
|
+
#api = {};
|
|
6
|
+
#current;
|
|
7
|
+
#debug = {};
|
|
8
|
+
#globalName = "";
|
|
9
|
+
#id;
|
|
10
|
+
get id() {
|
|
11
|
+
if (!this.#id)
|
|
12
|
+
throw new Error("Module needs to be registered.");
|
|
13
|
+
return this.#id;
|
|
14
|
+
}
|
|
15
|
+
get current() {
|
|
16
|
+
return (this.#current ??= game.modules.get(this.id));
|
|
17
|
+
}
|
|
18
|
+
get name() {
|
|
19
|
+
return this.current.title;
|
|
20
|
+
}
|
|
21
|
+
get isDebug() {
|
|
22
|
+
return foundry.utils.getProperty(CONFIG, `debug.${this.id}`) === true;
|
|
23
|
+
}
|
|
24
|
+
register(id, globalName) {
|
|
25
|
+
if (this.#id)
|
|
26
|
+
throw new Error("Module has already been registered.");
|
|
27
|
+
this.#id = id;
|
|
28
|
+
this.#globalName = globalName || id.replace(/-(\w)/g, (_, p1) => p1.toUpperCase());
|
|
29
|
+
Hooks.once("init", () => {
|
|
30
|
+
const self = this;
|
|
31
|
+
const context = {};
|
|
32
|
+
Object.defineProperties(context, {
|
|
33
|
+
active: {
|
|
34
|
+
get() {
|
|
35
|
+
return self.current.active;
|
|
36
|
+
},
|
|
37
|
+
configurable: false,
|
|
38
|
+
enumerable: false
|
|
39
|
+
},
|
|
40
|
+
api: {
|
|
41
|
+
get() {
|
|
42
|
+
return self.#api;
|
|
43
|
+
},
|
|
44
|
+
configurable: false,
|
|
45
|
+
enumerable: false
|
|
46
|
+
},
|
|
47
|
+
debug: {
|
|
48
|
+
get() {
|
|
49
|
+
return self.#debug;
|
|
50
|
+
},
|
|
51
|
+
configurable: false,
|
|
52
|
+
enumerable: false
|
|
53
|
+
},
|
|
54
|
+
getSetting: {
|
|
55
|
+
value: function (setting) {
|
|
56
|
+
return self.current.active ? getSetting(setting) : undefined;
|
|
57
|
+
},
|
|
58
|
+
writable: false,
|
|
59
|
+
configurable: false,
|
|
60
|
+
enumerable: false
|
|
61
|
+
},
|
|
62
|
+
localize: {
|
|
63
|
+
value: localize.sub(id),
|
|
64
|
+
configurable: false,
|
|
65
|
+
enumerable: false,
|
|
66
|
+
writable: false
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
Object.defineProperty(game, this.#globalName, {
|
|
70
|
+
value: context,
|
|
71
|
+
configurable: false,
|
|
72
|
+
enumerable: false,
|
|
73
|
+
writable: false
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
globalPath(...path) {
|
|
78
|
+
return `${this.#globalName}.${R.join(path, ".")}`;
|
|
79
|
+
}
|
|
80
|
+
path(...path) {
|
|
81
|
+
return `${this.id}.${R.join(path, ".")}`;
|
|
82
|
+
}
|
|
83
|
+
relativePath(...path) {
|
|
84
|
+
return `modules/${this.id}/${R.join(path, "/")}`;
|
|
85
|
+
}
|
|
86
|
+
imagePath(...path) {
|
|
87
|
+
return this.relativePath(...path);
|
|
88
|
+
}
|
|
89
|
+
templatePath(...path) {
|
|
90
|
+
return this.relativePath(...path);
|
|
91
|
+
}
|
|
92
|
+
Error(message) {
|
|
93
|
+
return new Error(`\n[${this.name}] ${message}`);
|
|
94
|
+
}
|
|
95
|
+
error(input, error) {
|
|
96
|
+
let message = `[${this.name}] ${input}`;
|
|
97
|
+
if (error instanceof Error) {
|
|
98
|
+
message += `\n${error.message}`;
|
|
99
|
+
}
|
|
100
|
+
else if (typeof error === "string") {
|
|
101
|
+
message += `\n${error}`;
|
|
102
|
+
}
|
|
103
|
+
console.error(message);
|
|
104
|
+
}
|
|
105
|
+
apiExpose(key, toExpose) {
|
|
106
|
+
if (foundry.utils.hasProperty(this.#api, key))
|
|
107
|
+
throw this.Error(`The API key "${key}" has already been defined.`);
|
|
108
|
+
const exposed = foundry.utils.deepClone(toExpose);
|
|
109
|
+
Object.freeze(exposed);
|
|
110
|
+
Object.defineProperty(this.#api, key, {
|
|
111
|
+
value: exposed,
|
|
112
|
+
configurable: false,
|
|
113
|
+
enumerable: false,
|
|
114
|
+
writable: false
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
debugExpose(key, toExpose) {
|
|
118
|
+
if (foundry.utils.hasProperty(this.#debug, key))
|
|
119
|
+
throw this.Error(`The debug key "${key}" has already been defined.`);
|
|
120
|
+
const exposed = foundry.utils.deepClone(toExpose);
|
|
121
|
+
Object.freeze(exposed);
|
|
122
|
+
Object.defineProperty(this.#debug, key, {
|
|
123
|
+
value: exposed,
|
|
124
|
+
configurable: false,
|
|
125
|
+
enumerable: false,
|
|
126
|
+
writable: false
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export const MODULE = new CustomModule();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { MODULE } from "./module.js";
|
|
2
|
+
export function settingPath(...path) {
|
|
3
|
+
return MODULE.path("settings", ...path);
|
|
4
|
+
}
|
|
5
|
+
export function getSetting(key) {
|
|
6
|
+
return game.settings.get(MODULE.id, key);
|
|
7
|
+
}
|
|
8
|
+
export function setSetting(key, value) {
|
|
9
|
+
return game.settings.set(MODULE.id, key, value);
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function waitTimeout(delay?: number): Promise<void>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function getCurrentUser(): User;
|
|
2
|
+
export declare function userIsGM(user?: User): boolean;
|
|
3
|
+
export declare function getPrimaryUpdater(actor: Actor): User | null;
|
|
4
|
+
export declare function isPrimaryUpdater(actor: Actor, user?: User): boolean;
|
|
5
|
+
export declare function primaryPlayerOwner(actor: Actor): User | null;
|
|
6
|
+
export declare function isPrimaryOwner(actor: Actor, user?: User): boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function getCurrentUser() {
|
|
2
|
+
return game.user ?? game.data.users.find((value) => value._id === game.userId);
|
|
3
|
+
}
|
|
4
|
+
export function userIsGM(user = getCurrentUser()) {
|
|
5
|
+
return user && user.role >= CONST.USER_ROLES.ASSISTANT;
|
|
6
|
+
}
|
|
7
|
+
export function getPrimaryUpdater(actor) {
|
|
8
|
+
const { activeGM } = game.users;
|
|
9
|
+
if (activeGM)
|
|
10
|
+
return activeGM;
|
|
11
|
+
const primaryPlayer = actor.isToken ? null : game.users.getDesignatedUser((user) => user.active && user.character === actor);
|
|
12
|
+
if (primaryPlayer)
|
|
13
|
+
return primaryPlayer;
|
|
14
|
+
return game.users.getDesignatedUser((user) => actor.canUserModify(user, "update"));
|
|
15
|
+
}
|
|
16
|
+
export function isPrimaryUpdater(actor, user = getCurrentUser()) {
|
|
17
|
+
return getPrimaryUpdater(actor) === user;
|
|
18
|
+
}
|
|
19
|
+
export function primaryPlayerOwner(actor) {
|
|
20
|
+
const assigned = game.users.getDesignatedUser((user) => user.active && user.character === actor);
|
|
21
|
+
return assigned ?? game.users.getDesignatedUser((user) => user.active && !user.isGM && actor.testUserPermission(user, "OWNER"));
|
|
22
|
+
}
|
|
23
|
+
export function isPrimaryOwner(actor, user = getCurrentUser()) {
|
|
24
|
+
return user.isGM || primaryPlayerOwner(actor) === user;
|
|
25
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@7h3laughingman/foundry-helpers",
|
|
3
|
+
"version": "13.351.0",
|
|
4
|
+
"description": "Basic helpers used for developing modules for Foundry VTT",
|
|
5
|
+
"homepage": "https://github.com/7H3LaughingMan/foundry-helpers#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/7H3LaughingMan/foundry-helpers/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/7H3LaughingMan/foundry-helpers.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "7H3LaughingMan <7H3LaughingMan@proton.me> (https://github.com/7H3LaughingMan)",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
"./lib-wrapper": "./dist/lib-wrapper/index.d.ts",
|
|
18
|
+
"./utilities": "./dist/utilities/index.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"./dist/*"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"tsc": "tsc",
|
|
25
|
+
"copy-dts": "copyfiles -u 1 \"src/**/*.d.ts\" dist",
|
|
26
|
+
"eslint": "eslint src",
|
|
27
|
+
"eslint:fix": "eslint src --fix && prettier src --write"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@7h3laughingman/foundry-types": "~13.351",
|
|
31
|
+
"remeda": "^2.33.6"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@eslint/js": "^9.39.2",
|
|
35
|
+
"@eslint/json": "^0.14.0",
|
|
36
|
+
"copyfiles": "^2.4.1",
|
|
37
|
+
"eslint": "^9.39.2",
|
|
38
|
+
"eslint-config-prettier": "^10.1.8",
|
|
39
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
40
|
+
"prettier-plugin-organize-imports": "^4.3.0",
|
|
41
|
+
"typescript": "5.9.3",
|
|
42
|
+
"typescript-eslint": "^8.55.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=24"
|
|
46
|
+
},
|
|
47
|
+
"sideEffects": false
|
|
48
|
+
}
|