tgui-core 1.0.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/.editorconfig +10 -0
- package/.eslintrc.cjs +78 -0
- package/.gitattributes +4 -0
- package/.prettierrc.yml +1 -0
- package/.vscode/extensions.json +6 -0
- package/.vscode/settings.json +5 -0
- package/README.md +1 -0
- package/global.d.ts +173 -0
- package/package.json +25 -0
- package/src/assets.ts +43 -0
- package/src/backend.ts +369 -0
- package/src/common/collections.ts +349 -0
- package/src/common/color.ts +94 -0
- package/src/common/events.ts +45 -0
- package/src/common/exhaustive.ts +19 -0
- package/src/common/fp.ts +38 -0
- package/src/common/keycodes.ts +86 -0
- package/src/common/keys.ts +39 -0
- package/src/common/math.ts +98 -0
- package/src/common/perf.ts +72 -0
- package/src/common/random.ts +32 -0
- package/src/common/react.ts +65 -0
- package/src/common/redux.ts +196 -0
- package/src/common/storage.js +196 -0
- package/src/common/string.ts +173 -0
- package/src/common/timer.ts +68 -0
- package/src/common/type-utils.ts +41 -0
- package/src/common/types.ts +9 -0
- package/src/common/uuid.ts +24 -0
- package/src/common/vector.ts +51 -0
- package/src/components/AnimatedNumber.tsx +185 -0
- package/src/components/Autofocus.tsx +23 -0
- package/src/components/Blink.jsx +69 -0
- package/src/components/BlockQuote.tsx +15 -0
- package/src/components/BodyZoneSelector.tsx +149 -0
- package/src/components/Box.tsx +255 -0
- package/src/components/Button.tsx +415 -0
- package/src/components/ByondUi.jsx +121 -0
- package/src/components/Chart.tsx +160 -0
- package/src/components/Collapsible.tsx +45 -0
- package/src/components/ColorBox.tsx +30 -0
- package/src/components/Dialog.tsx +85 -0
- package/src/components/Dimmer.tsx +19 -0
- package/src/components/Divider.tsx +26 -0
- package/src/components/DmIcon.tsx +72 -0
- package/src/components/DraggableControl.jsx +282 -0
- package/src/components/Dropdown.tsx +246 -0
- package/src/components/FakeTerminal.jsx +52 -0
- package/src/components/FitText.tsx +99 -0
- package/src/components/Flex.tsx +105 -0
- package/src/components/Grid.tsx +44 -0
- package/src/components/Icon.tsx +91 -0
- package/src/components/Image.tsx +63 -0
- package/src/components/InfinitePlane.jsx +192 -0
- package/src/components/Input.tsx +181 -0
- package/src/components/KeyListener.tsx +40 -0
- package/src/components/Knob.tsx +185 -0
- package/src/components/LabeledControls.tsx +50 -0
- package/src/components/LabeledList.tsx +130 -0
- package/src/components/MenuBar.tsx +238 -0
- package/src/components/Modal.tsx +25 -0
- package/src/components/NoticeBox.tsx +48 -0
- package/src/components/NumberInput.tsx +328 -0
- package/src/components/Popper.tsx +100 -0
- package/src/components/ProgressBar.tsx +79 -0
- package/src/components/RestrictedInput.jsx +301 -0
- package/src/components/RoundGauge.tsx +189 -0
- package/src/components/Section.tsx +125 -0
- package/src/components/Slider.tsx +173 -0
- package/src/components/Stack.tsx +101 -0
- package/src/components/StyleableSection.tsx +30 -0
- package/src/components/Table.tsx +90 -0
- package/src/components/Tabs.tsx +90 -0
- package/src/components/TextArea.tsx +198 -0
- package/src/components/TimeDisplay.jsx +64 -0
- package/src/components/Tooltip.tsx +147 -0
- package/src/components/TrackOutsideClicks.tsx +35 -0
- package/src/components/VirtualList.tsx +69 -0
- package/src/constants.ts +355 -0
- package/src/debug/KitchenSink.jsx +56 -0
- package/src/debug/actions.js +11 -0
- package/src/debug/hooks.js +10 -0
- package/src/debug/index.ts +10 -0
- package/src/debug/middleware.js +86 -0
- package/src/debug/reducer.js +22 -0
- package/src/debug/selectors.js +7 -0
- package/src/drag.ts +280 -0
- package/src/events.ts +237 -0
- package/src/focus.ts +25 -0
- package/src/format.ts +173 -0
- package/src/hotkeys.ts +212 -0
- package/src/http.ts +16 -0
- package/src/layouts/Layout.tsx +75 -0
- package/src/layouts/NtosWindow.tsx +162 -0
- package/src/layouts/Pane.tsx +56 -0
- package/src/layouts/Window.tsx +227 -0
- package/src/renderer.ts +50 -0
- package/styles/base.scss +32 -0
- package/styles/colors.scss +92 -0
- package/styles/components/BlockQuote.scss +20 -0
- package/styles/components/Button.scss +175 -0
- package/styles/components/ColorBox.scss +12 -0
- package/styles/components/Dialog.scss +105 -0
- package/styles/components/Dimmer.scss +22 -0
- package/styles/components/Divider.scss +27 -0
- package/styles/components/Dropdown.scss +72 -0
- package/styles/components/Flex.scss +31 -0
- package/styles/components/Icon.scss +25 -0
- package/styles/components/Input.scss +68 -0
- package/styles/components/Knob.scss +131 -0
- package/styles/components/LabeledList.scss +49 -0
- package/styles/components/MenuBar.scss +75 -0
- package/styles/components/Modal.scss +14 -0
- package/styles/components/NoticeBox.scss +65 -0
- package/styles/components/NumberInput.scss +76 -0
- package/styles/components/ProgressBar.scss +63 -0
- package/styles/components/RoundGauge.scss +88 -0
- package/styles/components/Section.scss +143 -0
- package/styles/components/Slider.scss +54 -0
- package/styles/components/Stack.scss +59 -0
- package/styles/components/Table.scss +44 -0
- package/styles/components/Tabs.scss +144 -0
- package/styles/components/TextArea.scss +84 -0
- package/styles/components/Tooltip.scss +24 -0
- package/styles/functions.scss +79 -0
- package/styles/layouts/Layout.scss +57 -0
- package/styles/layouts/NtosHeader.scss +20 -0
- package/styles/layouts/NtosWindow.scss +26 -0
- package/styles/layouts/TitleBar.scss +111 -0
- package/styles/layouts/Window.scss +103 -0
- package/styles/main.scss +97 -0
- package/styles/reset.scss +68 -0
- package/styles/themes/abductor.scss +68 -0
- package/styles/themes/admin.scss +38 -0
- package/styles/themes/cardtable.scss +57 -0
- package/styles/themes/hackerman.scss +70 -0
- package/styles/themes/malfunction.scss +67 -0
- package/styles/themes/neutral.scss +50 -0
- package/styles/themes/ntOS95.scss +166 -0
- package/styles/themes/ntos.scss +44 -0
- package/styles/themes/ntos_cat.scss +148 -0
- package/styles/themes/ntos_darkmode.scss +44 -0
- package/styles/themes/ntos_lightmode.scss +67 -0
- package/styles/themes/ntos_spooky.scss +69 -0
- package/styles/themes/ntos_synth.scss +99 -0
- package/styles/themes/ntos_terminal.scss +112 -0
- package/styles/themes/paper.scss +184 -0
- package/styles/themes/retro.scss +72 -0
- package/styles/themes/spookyconsole.scss +73 -0
- package/styles/themes/syndicate.scss +67 -0
- package/styles/themes/wizard.scss +68 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const EPSILON = 0.0001;
|
|
8
|
+
|
|
9
|
+
export class Color {
|
|
10
|
+
r: number;
|
|
11
|
+
g: number;
|
|
12
|
+
b: number;
|
|
13
|
+
a: number;
|
|
14
|
+
|
|
15
|
+
constructor(r = 0, g = 0, b = 0, a = 1) {
|
|
16
|
+
this.r = r;
|
|
17
|
+
this.g = g;
|
|
18
|
+
this.b = b;
|
|
19
|
+
this.a = a;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toString(): string {
|
|
23
|
+
// Alpha component needs to permit fractional values, so cannot use |
|
|
24
|
+
let alpha = this.a;
|
|
25
|
+
if (typeof alpha === 'string') {
|
|
26
|
+
alpha = parseFloat(this.a as any);
|
|
27
|
+
}
|
|
28
|
+
if (isNaN(alpha)) {
|
|
29
|
+
alpha = 1;
|
|
30
|
+
}
|
|
31
|
+
return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${alpha})`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Darkens a color by a given percent. Returns a color, which can have toString called to get it's rgba() css value. */
|
|
35
|
+
darken(percent: number): Color {
|
|
36
|
+
percent /= 100;
|
|
37
|
+
return new Color(
|
|
38
|
+
this.r - this.r * percent,
|
|
39
|
+
this.g - this.g * percent,
|
|
40
|
+
this.b - this.b * percent,
|
|
41
|
+
this.a,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Brightens a color by a given percent. Returns a color, which can have toString called to get it's rgba() css value. */
|
|
46
|
+
lighten(percent: number): Color {
|
|
47
|
+
// No point in rewriting code we already have.
|
|
48
|
+
return this.darken(-percent);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a color from the CSS hex color notation.
|
|
53
|
+
*/
|
|
54
|
+
static fromHex(hex: string): Color {
|
|
55
|
+
return new Color(
|
|
56
|
+
parseInt(hex.slice(1, 3), 16),
|
|
57
|
+
parseInt(hex.slice(3, 5), 16),
|
|
58
|
+
parseInt(hex.slice(5, 7), 16),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Linear interpolation of two colors.
|
|
64
|
+
*/
|
|
65
|
+
static lerp(c1: Color, c2: Color, n: number): Color {
|
|
66
|
+
return new Color(
|
|
67
|
+
(c2.r - c1.r) * n + c1.r,
|
|
68
|
+
(c2.g - c1.g) * n + c1.g,
|
|
69
|
+
(c2.b - c1.b) * n + c1.b,
|
|
70
|
+
(c2.a - c1.a) * n + c1.a,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Loops up the color in the provided list of colors
|
|
76
|
+
* with linear interpolation.
|
|
77
|
+
*/
|
|
78
|
+
static lookup(value: number, colors: Color[]): Color {
|
|
79
|
+
const len = colors.length;
|
|
80
|
+
if (len < 2) {
|
|
81
|
+
throw new Error('Needs at least two colors!');
|
|
82
|
+
}
|
|
83
|
+
const scaled = value * (len - 1);
|
|
84
|
+
if (value < EPSILON) {
|
|
85
|
+
return colors[0];
|
|
86
|
+
}
|
|
87
|
+
if (value >= 1 - EPSILON) {
|
|
88
|
+
return colors[len - 1];
|
|
89
|
+
}
|
|
90
|
+
const ratio = scaled % 1;
|
|
91
|
+
const index = scaled | 0;
|
|
92
|
+
return this.lerp(colors[index], colors[index + 1], ratio);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
type Fn = (...args: any[]) => void;
|
|
8
|
+
|
|
9
|
+
export class EventEmitter {
|
|
10
|
+
private listeners: Record<string, Fn[]>;
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
this.listeners = {};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
on(name: string, listener: Fn): void {
|
|
17
|
+
this.listeners[name] = this.listeners[name] || [];
|
|
18
|
+
this.listeners[name].push(listener);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
off(name: string, listener: Fn): void {
|
|
22
|
+
const listeners = this.listeners[name];
|
|
23
|
+
if (!listeners) {
|
|
24
|
+
throw new Error(`There is no listeners for "${name}"`);
|
|
25
|
+
}
|
|
26
|
+
this.listeners[name] = listeners.filter((existingListener) => {
|
|
27
|
+
return existingListener !== listener;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
emit(name: string, ...params: any[]): void {
|
|
32
|
+
const listeners = this.listeners[name];
|
|
33
|
+
if (!listeners) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
for (let i = 0, len = listeners.length; i < len; i += 1) {
|
|
37
|
+
const listener = listeners[i];
|
|
38
|
+
listener(...params);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
clear(): void {
|
|
43
|
+
this.listeners = {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throws an error such that a non-exhaustive check will error at compile time
|
|
3
|
+
* when using TypeScript, rather than at runtime.
|
|
4
|
+
*
|
|
5
|
+
* For example:
|
|
6
|
+
* enum Color { Red, Green, Blue }
|
|
7
|
+
* switch (color) {
|
|
8
|
+
* case Color.Red:
|
|
9
|
+
* return "red";
|
|
10
|
+
* case Color.Green:
|
|
11
|
+
* return "green";
|
|
12
|
+
* default:
|
|
13
|
+
* // This will error at compile time that we forgot blue.
|
|
14
|
+
* exhaustiveCheck(color);
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
export const exhaustiveCheck = (input: never) => {
|
|
18
|
+
throw new Error(`Unhandled case: ${input}`);
|
|
19
|
+
};
|
package/src/common/fp.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
type Func = (...args: any[]) => any;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a function that returns the result of invoking the given
|
|
11
|
+
* functions, where each successive invocation is supplied the return
|
|
12
|
+
* value of the previous.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const add2 = (x) => x + 2;
|
|
17
|
+
* const multiplyBy3 = (x) => x * 3;
|
|
18
|
+
* const subtract5 = (x) => x - 5;
|
|
19
|
+
*
|
|
20
|
+
* const composedFunction = flow(add2, multiplyBy3, subtract5); // ((4 + 2) * 3) - 5 = 13
|
|
21
|
+
* const composedFunction2 = flow([add2, multiplyBy3], subtract5); // ((4 + 2) * 3) - 5 = 13
|
|
22
|
+
*
|
|
23
|
+
*/
|
|
24
|
+
export const flow =
|
|
25
|
+
(...funcs: Array<Func | Func[]>) =>
|
|
26
|
+
(input: any, ...rest: any[]): any => {
|
|
27
|
+
let output = input;
|
|
28
|
+
|
|
29
|
+
for (let func of funcs) {
|
|
30
|
+
// Recurse into the array of functions
|
|
31
|
+
if (Array.isArray(func)) {
|
|
32
|
+
output = flow(...func)(output, ...rest);
|
|
33
|
+
} else if (func) {
|
|
34
|
+
output = func(output, ...rest);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return output;
|
|
38
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All possible browser keycodes, in one file.
|
|
3
|
+
*
|
|
4
|
+
* @file
|
|
5
|
+
* @copyright 2020 Aleksej Komarov
|
|
6
|
+
* @license MIT
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const KEY_BACKSPACE = 8;
|
|
10
|
+
export const KEY_TAB = 9;
|
|
11
|
+
export const KEY_ENTER = 13;
|
|
12
|
+
export const KEY_SHIFT = 16;
|
|
13
|
+
export const KEY_CTRL = 17;
|
|
14
|
+
export const KEY_ALT = 18;
|
|
15
|
+
export const KEY_PAUSE = 19;
|
|
16
|
+
export const KEY_CAPSLOCK = 20;
|
|
17
|
+
export const KEY_ESCAPE = 27;
|
|
18
|
+
export const KEY_SPACE = 32;
|
|
19
|
+
export const KEY_PAGEUP = 33;
|
|
20
|
+
export const KEY_PAGEDOWN = 34;
|
|
21
|
+
export const KEY_END = 35;
|
|
22
|
+
export const KEY_HOME = 36;
|
|
23
|
+
export const KEY_LEFT = 37;
|
|
24
|
+
export const KEY_UP = 38;
|
|
25
|
+
export const KEY_RIGHT = 39;
|
|
26
|
+
export const KEY_DOWN = 40;
|
|
27
|
+
export const KEY_INSERT = 45;
|
|
28
|
+
export const KEY_DELETE = 46;
|
|
29
|
+
export const KEY_0 = 48;
|
|
30
|
+
export const KEY_1 = 49;
|
|
31
|
+
export const KEY_2 = 50;
|
|
32
|
+
export const KEY_3 = 51;
|
|
33
|
+
export const KEY_4 = 52;
|
|
34
|
+
export const KEY_5 = 53;
|
|
35
|
+
export const KEY_6 = 54;
|
|
36
|
+
export const KEY_7 = 55;
|
|
37
|
+
export const KEY_8 = 56;
|
|
38
|
+
export const KEY_9 = 57;
|
|
39
|
+
export const KEY_A = 65;
|
|
40
|
+
export const KEY_B = 66;
|
|
41
|
+
export const KEY_C = 67;
|
|
42
|
+
export const KEY_D = 68;
|
|
43
|
+
export const KEY_E = 69;
|
|
44
|
+
export const KEY_F = 70;
|
|
45
|
+
export const KEY_G = 71;
|
|
46
|
+
export const KEY_H = 72;
|
|
47
|
+
export const KEY_I = 73;
|
|
48
|
+
export const KEY_J = 74;
|
|
49
|
+
export const KEY_K = 75;
|
|
50
|
+
export const KEY_L = 76;
|
|
51
|
+
export const KEY_M = 77;
|
|
52
|
+
export const KEY_N = 78;
|
|
53
|
+
export const KEY_O = 79;
|
|
54
|
+
export const KEY_P = 80;
|
|
55
|
+
export const KEY_Q = 81;
|
|
56
|
+
export const KEY_R = 82;
|
|
57
|
+
export const KEY_S = 83;
|
|
58
|
+
export const KEY_T = 84;
|
|
59
|
+
export const KEY_U = 85;
|
|
60
|
+
export const KEY_V = 86;
|
|
61
|
+
export const KEY_W = 87;
|
|
62
|
+
export const KEY_X = 88;
|
|
63
|
+
export const KEY_Y = 89;
|
|
64
|
+
export const KEY_Z = 90;
|
|
65
|
+
export const KEY_F1 = 112;
|
|
66
|
+
export const KEY_F2 = 113;
|
|
67
|
+
export const KEY_F3 = 114;
|
|
68
|
+
export const KEY_F4 = 115;
|
|
69
|
+
export const KEY_F5 = 116;
|
|
70
|
+
export const KEY_F6 = 117;
|
|
71
|
+
export const KEY_F7 = 118;
|
|
72
|
+
export const KEY_F8 = 119;
|
|
73
|
+
export const KEY_F9 = 120;
|
|
74
|
+
export const KEY_F10 = 121;
|
|
75
|
+
export const KEY_F11 = 122;
|
|
76
|
+
export const KEY_F12 = 123;
|
|
77
|
+
export const KEY_SEMICOLON = 186;
|
|
78
|
+
export const KEY_EQUAL = 187;
|
|
79
|
+
export const KEY_COMMA = 188;
|
|
80
|
+
export const KEY_MINUS = 189;
|
|
81
|
+
export const KEY_PERIOD = 190;
|
|
82
|
+
export const KEY_SLASH = 191;
|
|
83
|
+
export const KEY_LEFT_BRACKET = 219;
|
|
84
|
+
export const KEY_BACKSLASH = 220;
|
|
85
|
+
export const KEY_RIGHT_BRACKET = 221;
|
|
86
|
+
export const KEY_QUOTE = 222;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ### Key codes.
|
|
3
|
+
* event.keyCode is deprecated, use this reference instead.
|
|
4
|
+
*
|
|
5
|
+
* Handles modifier keys (Shift, Alt, Control) and arrow keys.
|
|
6
|
+
*
|
|
7
|
+
* For alphabetical keys, use the actual character (e.g. 'a') instead of the key code.
|
|
8
|
+
*
|
|
9
|
+
* Something isn't here that you want? Just add it:
|
|
10
|
+
* @url https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
|
11
|
+
* @usage
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { KEY } from 'tgui/common/keys';
|
|
14
|
+
*
|
|
15
|
+
* if (event.key === KEY.Enter) {
|
|
16
|
+
* // do something
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export enum KEY {
|
|
21
|
+
Alt = 'Alt',
|
|
22
|
+
Backspace = 'Backspace',
|
|
23
|
+
Control = 'Control',
|
|
24
|
+
Delete = 'Delete',
|
|
25
|
+
Down = 'ArrowDown',
|
|
26
|
+
End = 'End',
|
|
27
|
+
Enter = 'Enter',
|
|
28
|
+
Escape = 'Escape',
|
|
29
|
+
Home = 'Home',
|
|
30
|
+
Insert = 'Insert',
|
|
31
|
+
Left = 'ArrowLeft',
|
|
32
|
+
PageDown = 'PageDown',
|
|
33
|
+
PageUp = 'PageUp',
|
|
34
|
+
Right = 'ArrowRight',
|
|
35
|
+
Shift = 'Shift',
|
|
36
|
+
Space = ' ',
|
|
37
|
+
Tab = 'Tab',
|
|
38
|
+
Up = 'ArrowUp',
|
|
39
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Limits a number to the range between 'min' and 'max'.
|
|
9
|
+
*/
|
|
10
|
+
export const clamp = (value, min, max) => {
|
|
11
|
+
return value < min ? min : value > max ? max : value;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Limits a number between 0 and 1.
|
|
16
|
+
*/
|
|
17
|
+
export const clamp01 = (value) => {
|
|
18
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Scales a number to fit into the range between min and max.
|
|
23
|
+
*/
|
|
24
|
+
export const scale = (value, min, max) => {
|
|
25
|
+
return (value - min) / (max - min);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Robust number rounding.
|
|
30
|
+
*
|
|
31
|
+
* Adapted from Locutus, see: http://locutus.io/php/math/round/
|
|
32
|
+
*
|
|
33
|
+
* @param {number} value
|
|
34
|
+
* @param {number} precision
|
|
35
|
+
* @return {number}
|
|
36
|
+
*/
|
|
37
|
+
export const round = (value, precision) => {
|
|
38
|
+
if (!value || isNaN(value)) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
// helper variables
|
|
42
|
+
let m, f, isHalf, sgn;
|
|
43
|
+
// making sure precision is integer
|
|
44
|
+
precision |= 0;
|
|
45
|
+
m = Math.pow(10, precision);
|
|
46
|
+
value *= m;
|
|
47
|
+
// sign of the number
|
|
48
|
+
sgn = +(value > 0) | -(value < 0);
|
|
49
|
+
// isHalf = value % 1 === 0.5 * sgn;
|
|
50
|
+
isHalf = Math.abs(value % 1) >= 0.4999999999854481;
|
|
51
|
+
f = Math.floor(value);
|
|
52
|
+
if (isHalf) {
|
|
53
|
+
// rounds .5 away from zero
|
|
54
|
+
value = f + (sgn > 0);
|
|
55
|
+
}
|
|
56
|
+
return (isHalf ? value : Math.round(value)) / m;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns a string representing a number in fixed point notation.
|
|
61
|
+
*/
|
|
62
|
+
export const toFixed = (value, fractionDigits = 0) => {
|
|
63
|
+
return Number(value).toFixed(Math.max(fractionDigits, 0));
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Checks whether a value is within the provided range.
|
|
68
|
+
*
|
|
69
|
+
* Range is an array of two numbers, for example: [0, 15].
|
|
70
|
+
*/
|
|
71
|
+
export const inRange = (value, range) => {
|
|
72
|
+
return range && value >= range[0] && value <= range[1];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Walks over the object with ranges, comparing value against every range,
|
|
77
|
+
* and returns the key of the first matching range.
|
|
78
|
+
*
|
|
79
|
+
* Range is an array of two numbers, for example: [0, 15].
|
|
80
|
+
*/
|
|
81
|
+
export const keyOfMatchingRange = (value, ranges) => {
|
|
82
|
+
for (let rangeName of Object.keys(ranges)) {
|
|
83
|
+
const range = ranges[rangeName];
|
|
84
|
+
if (inRange(value, range)) {
|
|
85
|
+
return rangeName;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get number of digits following the decimal point in a number
|
|
92
|
+
*/
|
|
93
|
+
export const numberOfDecimalDigits = (value) => {
|
|
94
|
+
if (Math.floor(value) !== value) {
|
|
95
|
+
return value.toString().split('.')[1].length || 0;
|
|
96
|
+
}
|
|
97
|
+
return 0;
|
|
98
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ghetto performance measurement tools.
|
|
3
|
+
*
|
|
4
|
+
* Uses NODE_ENV to remove itself from production builds.
|
|
5
|
+
*
|
|
6
|
+
* @file
|
|
7
|
+
* @copyright 2020 Aleksej Komarov
|
|
8
|
+
* @license MIT
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const FPS = 60;
|
|
12
|
+
const FRAME_DURATION = 1000 / FPS;
|
|
13
|
+
|
|
14
|
+
// True if Performance API is supported
|
|
15
|
+
const supportsPerf = !!window.performance?.now;
|
|
16
|
+
// High precision markers
|
|
17
|
+
let hpMarkersByName: Record<string, number> = {};
|
|
18
|
+
// Low precision markers
|
|
19
|
+
let lpMarkersByName: Record<string, number> = {};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Marks a certain spot in the code for later measurements.
|
|
23
|
+
*/
|
|
24
|
+
function mark(name: string, timestamp?: number): void {
|
|
25
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
26
|
+
if (supportsPerf && !timestamp) {
|
|
27
|
+
hpMarkersByName[name] = performance.now();
|
|
28
|
+
}
|
|
29
|
+
lpMarkersByName[name] = timestamp || Date.now();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Calculates and returns the difference between two markers as a string.
|
|
35
|
+
*
|
|
36
|
+
* Use logger.log() to print the measurement.
|
|
37
|
+
*/
|
|
38
|
+
function measure(markerNameA: string, markerNameB: string): string | undefined {
|
|
39
|
+
if (process.env.NODE_ENV === 'production') return;
|
|
40
|
+
|
|
41
|
+
let markerA = hpMarkersByName[markerNameA];
|
|
42
|
+
let markerB = hpMarkersByName[markerNameB];
|
|
43
|
+
|
|
44
|
+
if (!markerA || !markerB) {
|
|
45
|
+
markerA = lpMarkersByName[markerNameA];
|
|
46
|
+
markerB = lpMarkersByName[markerNameB];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const duration = Math.abs(markerB - markerA);
|
|
50
|
+
|
|
51
|
+
return formatDuration(duration);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Formats a duration in milliseconds and frames.
|
|
56
|
+
*/
|
|
57
|
+
function formatDuration(duration: number): string {
|
|
58
|
+
const durationInFrames = duration / FRAME_DURATION;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
duration.toFixed(duration < 10 ? 1 : 0) +
|
|
62
|
+
'ms ' +
|
|
63
|
+
'(' +
|
|
64
|
+
durationInFrames.toFixed(2) +
|
|
65
|
+
' frames)'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const perf = {
|
|
70
|
+
mark,
|
|
71
|
+
measure,
|
|
72
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { clamp } from './math';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns random number between lowerBound exclusive and upperBound inclusive
|
|
5
|
+
*/
|
|
6
|
+
export const randomNumber = (lowerBound: number, upperBound: number) => {
|
|
7
|
+
return Math.random() * (upperBound - lowerBound) + lowerBound;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns random integer between lowerBound exclusive and upperBound inclusive
|
|
12
|
+
*/
|
|
13
|
+
export const randomInteger = (lowerBound: number, upperBound: number) => {
|
|
14
|
+
lowerBound = Math.ceil(lowerBound);
|
|
15
|
+
upperBound = Math.floor(upperBound);
|
|
16
|
+
return Math.floor(Math.random() * (upperBound - lowerBound) + lowerBound);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns random array element
|
|
21
|
+
*/
|
|
22
|
+
export const randomPick = <T>(array: T[]) => {
|
|
23
|
+
return array[Math.floor(Math.random() * array.length)];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Return 1 with probability P percent; otherwise 0
|
|
28
|
+
*/
|
|
29
|
+
export const randomProb = (probability: number) => {
|
|
30
|
+
const normalized = clamp(probability, 0, 100) / 100;
|
|
31
|
+
return Math.random() <= normalized;
|
|
32
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Helper for conditionally adding/removing classes in React
|
|
9
|
+
*/
|
|
10
|
+
export const classes = (classNames: (string | BooleanLike)[]) => {
|
|
11
|
+
let className = '';
|
|
12
|
+
for (let i = 0; i < classNames.length; i++) {
|
|
13
|
+
const part = classNames[i];
|
|
14
|
+
if (typeof part === 'string') {
|
|
15
|
+
className += part + ' ';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return className;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Normalizes children prop, so that it is always an array of VDom
|
|
23
|
+
* elements.
|
|
24
|
+
*/
|
|
25
|
+
export const normalizeChildren = <T>(children: T | T[]) => {
|
|
26
|
+
if (Array.isArray(children)) {
|
|
27
|
+
return children.flat().filter((value) => value) as T[];
|
|
28
|
+
}
|
|
29
|
+
if (typeof children === 'object') {
|
|
30
|
+
return [children];
|
|
31
|
+
}
|
|
32
|
+
return [];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Shallowly checks if two objects are different.
|
|
37
|
+
* Credit: https://github.com/developit/preact-compat
|
|
38
|
+
*/
|
|
39
|
+
export const shallowDiffers = (a: object, b: object) => {
|
|
40
|
+
let i;
|
|
41
|
+
for (i in a) {
|
|
42
|
+
if (!(i in b)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (i in b) {
|
|
47
|
+
if (a[i] !== b[i]) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A common case in tgui, when you pass a value conditionally, these are
|
|
56
|
+
* the types that can fall through the condition.
|
|
57
|
+
*/
|
|
58
|
+
export type BooleanLike = number | boolean | null | undefined;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A helper to determine whether the object is renderable by React.
|
|
62
|
+
*/
|
|
63
|
+
export const canRender = (value: unknown) => {
|
|
64
|
+
return value !== undefined && value !== null && typeof value !== 'boolean';
|
|
65
|
+
};
|