tgui-core 1.1.7 → 1.1.9

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.
Files changed (291) hide show
  1. package/lib/common/assets.ts +38 -0
  2. package/lib/common/collections.ts +27 -0
  3. package/lib/common/color.ts +88 -0
  4. package/lib/common/constants.ts +349 -0
  5. package/lib/common/events.ts +262 -0
  6. package/{dist/common/exhaustive.d.ts → lib/common/exhaustive.ts} +3 -1
  7. package/lib/common/format.ts +167 -0
  8. package/{dist/common/fp.d.ts → lib/common/fp.ts} +16 -2
  9. package/lib/common/hotkeys.ts +207 -0
  10. package/lib/common/http.ts +16 -0
  11. package/lib/common/keycodes.ts +86 -0
  12. package/{dist/common/keys.d.ts → lib/common/keys.ts} +24 -21
  13. package/lib/common/math.ts +76 -0
  14. package/lib/common/perf.ts +72 -0
  15. package/lib/common/random.ts +32 -0
  16. package/lib/common/react.ts +59 -0
  17. package/lib/common/redux.ts +187 -0
  18. package/lib/common/storage.ts +207 -0
  19. package/lib/common/string.ts +169 -0
  20. package/lib/common/timer.ts +63 -0
  21. package/lib/common/type-utils.ts +41 -0
  22. package/lib/common/types.d.ts +12 -0
  23. package/lib/common/uuid.ts +18 -0
  24. package/lib/components/AnimatedNumber.tsx +180 -0
  25. package/lib/components/Autofocus.tsx +23 -0
  26. package/lib/components/Blink.tsx +91 -0
  27. package/lib/components/BlockQuote.tsx +9 -0
  28. package/lib/components/BodyZoneSelector.tsx +149 -0
  29. package/lib/components/Box.tsx +252 -0
  30. package/lib/components/Button.tsx +425 -0
  31. package/lib/components/ByondUi.jsx +110 -0
  32. package/lib/components/Chart.tsx +155 -0
  33. package/lib/components/Collapsible.tsx +43 -0
  34. package/lib/components/ColorBox.tsx +29 -0
  35. package/lib/components/Dialog.tsx +81 -0
  36. package/lib/components/Dimmer.tsx +13 -0
  37. package/lib/components/Divider.tsx +20 -0
  38. package/lib/components/DmIcon.tsx +86 -0
  39. package/lib/components/DraggableControl.jsx +276 -0
  40. package/lib/components/Dropdown.tsx +246 -0
  41. package/lib/components/FakeTerminal.jsx +52 -0
  42. package/lib/components/FitText.tsx +99 -0
  43. package/lib/components/Flex.tsx +159 -0
  44. package/lib/components/Icon.tsx +95 -0
  45. package/lib/components/Image.tsx +54 -0
  46. package/lib/components/InfinitePlane.jsx +192 -0
  47. package/lib/components/Input.tsx +176 -0
  48. package/lib/components/KeyListener.tsx +40 -0
  49. package/lib/components/Knob.tsx +178 -0
  50. package/lib/components/LabeledControls.tsx +44 -0
  51. package/lib/components/LabeledList.tsx +154 -0
  52. package/lib/components/MenuBar.tsx +228 -0
  53. package/lib/components/Modal.tsx +23 -0
  54. package/lib/components/NoticeBox.tsx +45 -0
  55. package/lib/components/NumberInput.tsx +328 -0
  56. package/lib/components/Popper.tsx +100 -0
  57. package/lib/components/ProgressBar.tsx +105 -0
  58. package/lib/components/RestrictedInput.jsx +301 -0
  59. package/lib/components/RoundGauge.tsx +180 -0
  60. package/lib/components/Section.tsx +120 -0
  61. package/lib/components/Slider.tsx +169 -0
  62. package/lib/components/Stack.tsx +96 -0
  63. package/lib/components/StyleableSection.tsx +33 -0
  64. package/lib/components/Table.tsx +84 -0
  65. package/lib/components/Tabs.tsx +89 -0
  66. package/lib/components/TextArea.tsx +182 -0
  67. package/lib/components/TimeDisplay.jsx +64 -0
  68. package/lib/components/Tooltip.tsx +152 -0
  69. package/lib/components/TrackOutsideClicks.tsx +35 -0
  70. package/lib/components/VirtualList.tsx +69 -0
  71. package/lib/styles/atomic/candystripe.scss +8 -0
  72. package/lib/styles/atomic/centered-image.scss +7 -0
  73. package/lib/styles/atomic/color.scss +21 -0
  74. package/lib/styles/atomic/debug-layout.scss +17 -0
  75. package/lib/styles/atomic/fit-text.scss +14 -0
  76. package/lib/styles/atomic/links.scss +12 -0
  77. package/lib/styles/atomic/outline.scss +47 -0
  78. package/lib/styles/atomic/text.scss +44 -0
  79. package/lib/styles/base.scss +32 -0
  80. package/lib/styles/colors.scss +92 -0
  81. package/lib/styles/components/BlockQuote.module.scss +20 -0
  82. package/lib/styles/components/BlockQuote.module.scss.d.ts +4 -0
  83. package/lib/styles/components/Button.module.scss +157 -0
  84. package/lib/styles/components/Button.module.scss.d.ts +46 -0
  85. package/lib/styles/components/ColorBox.module.scss +12 -0
  86. package/lib/styles/components/ColorBox.module.scss.d.ts +4 -0
  87. package/lib/styles/components/Dialog.module.scss +60 -0
  88. package/lib/styles/components/Dialog.module.scss.d.ts +10 -0
  89. package/lib/styles/components/Dimmer.module.scss +22 -0
  90. package/lib/styles/components/Dimmer.module.scss.d.ts +4 -0
  91. package/lib/styles/components/Divider.module.scss +27 -0
  92. package/lib/styles/components/Divider.module.scss.d.ts +6 -0
  93. package/lib/styles/components/Dropdown.scss +72 -0
  94. package/lib/styles/components/Flex.module.scss +13 -0
  95. package/lib/styles/components/Flex.module.scss.d.ts +5 -0
  96. package/lib/styles/components/Icon.module.scss +25 -0
  97. package/lib/styles/components/Icon.module.scss.d.ts +5 -0
  98. package/lib/styles/components/Input.module.scss +64 -0
  99. package/lib/styles/components/Input.module.scss.d.ts +8 -0
  100. package/lib/styles/components/Knob.module.scss +131 -0
  101. package/lib/styles/components/Knob.module.scss.d.ts +33 -0
  102. package/lib/styles/components/LabeledList.module.scss +49 -0
  103. package/lib/styles/components/LabeledList.module.scss.d.ts +8 -0
  104. package/lib/styles/components/MenuBar.module.scss +75 -0
  105. package/lib/styles/components/MenuBar.module.scss.d.ts +14 -0
  106. package/lib/styles/components/Modal.module.scss +14 -0
  107. package/lib/styles/components/Modal.module.scss.d.ts +4 -0
  108. package/lib/styles/components/NoticeBox.module.scss +65 -0
  109. package/lib/styles/components/NoticeBox.module.scss.d.ts +27 -0
  110. package/lib/styles/components/NumberInput.module.scss +71 -0
  111. package/lib/styles/components/NumberInput.module.scss.d.ts +9 -0
  112. package/lib/styles/components/ProgressBar.module.scss +63 -0
  113. package/lib/styles/components/ProgressBar.module.scss.d.ts +27 -0
  114. package/lib/styles/components/RoundGauge.module.scss +85 -0
  115. package/lib/styles/components/RoundGauge.module.scss.d.ts +49 -0
  116. package/lib/styles/components/Section.module.scss +130 -0
  117. package/lib/styles/components/Section.module.scss.d.ts +13 -0
  118. package/lib/styles/components/Slider.module.scss +54 -0
  119. package/lib/styles/components/Slider.module.scss.d.ts +8 -0
  120. package/lib/styles/components/Stack.module.scss +60 -0
  121. package/lib/styles/components/Stack.module.scss.d.ts +12 -0
  122. package/lib/styles/components/Table.module.scss +44 -0
  123. package/lib/styles/components/Table.module.scss.d.ts +10 -0
  124. package/lib/styles/components/Tabs.module.scss +144 -0
  125. package/lib/styles/components/Tabs.module.scss.d.ts +35 -0
  126. package/lib/styles/components/TextArea.module.scss +86 -0
  127. package/lib/styles/components/TextArea.module.scss.d.ts +11 -0
  128. package/lib/styles/components/Tooltip.module.scss +24 -0
  129. package/lib/styles/components/Tooltip.module.scss.d.ts +4 -0
  130. package/lib/styles/functions.scss +79 -0
  131. package/lib/styles/input.scss +9 -0
  132. package/lib/styles/main.scss +20 -0
  133. package/lib/styles/reset.scss +68 -0
  134. package/package.json +6 -6
  135. package/dist/ProgressBar.module-BkAFfFy0.js +0 -29
  136. package/dist/Section.module-CLVHJ4yA.js +0 -15
  137. package/dist/assets/BlockQuote.css +0 -1
  138. package/dist/assets/Button.css +0 -1
  139. package/dist/assets/ColorBox.css +0 -1
  140. package/dist/assets/Dialog.css +0 -1
  141. package/dist/assets/Dimmer.css +0 -1
  142. package/dist/assets/Divider.css +0 -1
  143. package/dist/assets/Flex.css +0 -1
  144. package/dist/assets/Icon.css +0 -6
  145. package/dist/assets/Input.css +0 -1
  146. package/dist/assets/Knob.css +0 -1
  147. package/dist/assets/LabeledList.css +0 -1
  148. package/dist/assets/MenuBar.css +0 -1
  149. package/dist/assets/Modal.css +0 -1
  150. package/dist/assets/NoticeBox.css +0 -1
  151. package/dist/assets/NumberInput.css +0 -1
  152. package/dist/assets/ProgressBar.css +0 -1
  153. package/dist/assets/RoundGauge.css +0 -1
  154. package/dist/assets/Section.css +0 -1
  155. package/dist/assets/Slider.css +0 -1
  156. package/dist/assets/Stack.css +0 -1
  157. package/dist/assets/Table.css +0 -1
  158. package/dist/assets/Tabs.css +0 -1
  159. package/dist/assets/TextArea.css +0 -1
  160. package/dist/assets/Tooltip.css +0 -1
  161. package/dist/common/assets.d.ts +0 -4
  162. package/dist/common/assets.js +0 -21
  163. package/dist/common/collections.d.ts +0 -10
  164. package/dist/common/collections.js +0 -15
  165. package/dist/common/color.d.ts +0 -25
  166. package/dist/common/color.js +0 -69
  167. package/dist/common/constants.d.ts +0 -102
  168. package/dist/common/constants.js +0 -312
  169. package/dist/common/events.d.ts +0 -33
  170. package/dist/common/events.js +0 -147
  171. package/dist/common/exhaustive.js +0 -6
  172. package/dist/common/format.d.ts +0 -11
  173. package/dist/common/format.js +0 -114
  174. package/dist/common/fp.js +0 -9
  175. package/dist/common/hotkeys.d.ts +0 -25
  176. package/dist/common/hotkeys.js +0 -112
  177. package/dist/common/http.d.ts +0 -4
  178. package/dist/common/http.js +0 -10
  179. package/dist/common/keycodes.d.ts +0 -85
  180. package/dist/common/keycodes.js +0 -88
  181. package/dist/common/keys.js +0 -8
  182. package/dist/common/math.d.ts +0 -39
  183. package/dist/common/math.js +0 -41
  184. package/dist/common/perf.d.ts +0 -24
  185. package/dist/common/perf.js +0 -33
  186. package/dist/common/random.d.ts +0 -16
  187. package/dist/common/random.js +0 -18
  188. package/dist/common/react.d.ts +0 -23
  189. package/dist/common/react.js +0 -30
  190. package/dist/common/redux.d.ts +0 -64
  191. package/dist/common/redux.js +0 -72
  192. package/dist/common/storage.js +0 -124
  193. package/dist/common/string.d.ts +0 -65
  194. package/dist/common/string.js +0 -83
  195. package/dist/common/timer.d.ts +0 -18
  196. package/dist/common/timer.js +0 -28
  197. package/dist/common/type-utils.d.ts +0 -9
  198. package/dist/common/type-utils.js +0 -25
  199. package/dist/common/uuid.d.ts +0 -9
  200. package/dist/common/uuid.js +0 -10
  201. package/dist/components/AnimatedNumber.d.ts +0 -60
  202. package/dist/components/AnimatedNumber.js +0 -76
  203. package/dist/components/Autofocus.d.ts +0 -4
  204. package/dist/components/Autofocus.js +0 -17
  205. package/dist/components/Blink.d.ts +0 -26
  206. package/dist/components/Blink.js +0 -56
  207. package/dist/components/BlockQuote.d.ts +0 -3
  208. package/dist/components/BlockQuote.js +0 -13
  209. package/dist/components/BodyZoneSelector.d.ts +0 -28
  210. package/dist/components/BodyZoneSelector.js +0 -115
  211. package/dist/components/Box.d.ts +0 -91
  212. package/dist/components/Box.js +0 -133
  213. package/dist/components/Button.d.ts +0 -93
  214. package/dist/components/Button.js +0 -298
  215. package/dist/components/ByondUi.js +0 -73
  216. package/dist/components/Chart.d.ts +0 -28
  217. package/dist/components/Chart.js +0 -95
  218. package/dist/components/Collapsible.d.ts +0 -15
  219. package/dist/components/Collapsible.js +0 -27
  220. package/dist/components/ColorBox.d.ts +0 -8
  221. package/dist/components/ColorBox.js +0 -24
  222. package/dist/components/Dialog.d.ts +0 -24
  223. package/dist/components/Dialog.js +0 -67
  224. package/dist/components/Dimmer.d.ts +0 -3
  225. package/dist/components/Dimmer.js +0 -13
  226. package/dist/components/Divider.d.ts +0 -6
  227. package/dist/components/Divider.js +0 -22
  228. package/dist/components/DmIcon.d.ts +0 -31
  229. package/dist/components/DmIcon.js +0 -31
  230. package/dist/components/DraggableControl.js +0 -176
  231. package/dist/components/Dropdown.d.ts +0 -48
  232. package/dist/components/Dropdown.js +0 -152
  233. package/dist/components/FakeTerminal.js +0 -38
  234. package/dist/components/FitText.d.ts +0 -22
  235. package/dist/components/FitText.js +0 -63
  236. package/dist/components/Flex.d.ts +0 -93
  237. package/dist/components/Flex.js +0 -72
  238. package/dist/components/Icon.d.ts +0 -30
  239. package/dist/components/Icon.js +0 -51
  240. package/dist/components/Image.d.ts +0 -14
  241. package/dist/components/Image.js +0 -35
  242. package/dist/components/InfinitePlane.js +0 -139
  243. package/dist/components/Input.d.ts +0 -61
  244. package/dist/components/Input.js +0 -89
  245. package/dist/components/KeyListener.d.ts +0 -15
  246. package/dist/components/KeyListener.js +0 -23
  247. package/dist/components/Knob.d.ts +0 -49
  248. package/dist/components/Knob.js +0 -162
  249. package/dist/components/LabeledControls.d.ts +0 -11
  250. package/dist/components/LabeledControls.js +0 -39
  251. package/dist/components/LabeledList.d.ts +0 -57
  252. package/dist/components/LabeledList.js +0 -94
  253. package/dist/components/MenuBar.d.ts +0 -28
  254. package/dist/components/MenuBar.js +0 -174
  255. package/dist/components/Modal.d.ts +0 -3
  256. package/dist/components/Modal.js +0 -25
  257. package/dist/components/NoticeBox.d.ts +0 -20
  258. package/dist/components/NoticeBox.js +0 -49
  259. package/dist/components/NumberInput.d.ts +0 -45
  260. package/dist/components/NumberInput.js +0 -221
  261. package/dist/components/Popper.d.ts +0 -27
  262. package/dist/components/Popper.js +0 -177
  263. package/dist/components/ProgressBar.d.ts +0 -46
  264. package/dist/components/ProgressBar.js +0 -37
  265. package/dist/components/RestrictedInput.js +0 -155
  266. package/dist/components/RoundGauge.d.ts +0 -53
  267. package/dist/components/RoundGauge.js +0 -147
  268. package/dist/components/Section.d.ts +0 -63
  269. package/dist/components/Section.js +0 -62
  270. package/dist/components/Slider.d.ts +0 -46
  271. package/dist/components/Slider.js +0 -124
  272. package/dist/components/Stack.d.ts +0 -27
  273. package/dist/components/Stack.js +0 -67
  274. package/dist/components/StyleableSection.d.ts +0 -11
  275. package/dist/components/StyleableSection.js +0 -16
  276. package/dist/components/Table.d.ts +0 -29
  277. package/dist/components/Table.js +0 -67
  278. package/dist/components/Tabs.d.ts +0 -23
  279. package/dist/components/Tabs.js +0 -89
  280. package/dist/components/TextArea.d.ts +0 -39
  281. package/dist/components/TextArea.js +0 -118
  282. package/dist/components/TimeDisplay.js +0 -34
  283. package/dist/components/Tooltip.d.ts +0 -29
  284. package/dist/components/Tooltip.js +0 -83
  285. package/dist/components/TrackOutsideClicks.d.ts +0 -13
  286. package/dist/components/TrackOutsideClicks.js +0 -24
  287. package/dist/components/VirtualList.d.ts +0 -8
  288. package/dist/components/VirtualList.js +0 -34
  289. package/dist/components/index.js +0 -92
  290. package/dist/popper-CiqSDJTE.js +0 -906
  291. /package/{dist/components/index.d.ts → lib/components/index.ts} +0 -0
@@ -0,0 +1,262 @@
1
+ import { KEY_ALT, KEY_CTRL, KEY_F1, KEY_F12, KEY_SHIFT } from './keycodes';
2
+
3
+ type Fn = (...args: any[]) => void;
4
+
5
+ export class EventEmitter {
6
+ private listeners: Record<string, Fn[]>;
7
+
8
+ constructor() {
9
+ this.listeners = {};
10
+ }
11
+
12
+ on(name: string, listener: Fn): void {
13
+ this.listeners[name] = this.listeners[name] || [];
14
+ this.listeners[name].push(listener);
15
+ }
16
+
17
+ off(name: string, listener: Fn): void {
18
+ const listeners = this.listeners[name];
19
+ if (!listeners) {
20
+ throw new Error(`There is no listeners for "${name}"`);
21
+ }
22
+ this.listeners[name] = listeners.filter((existingListener) => {
23
+ return existingListener !== listener;
24
+ });
25
+ }
26
+
27
+ emit(name: string, ...params: any[]): void {
28
+ const listeners = this.listeners[name];
29
+ if (!listeners) {
30
+ return;
31
+ }
32
+ for (let i = 0, len = listeners.length; i < len; i += 1) {
33
+ const listener = listeners[i];
34
+ listener(...params);
35
+ }
36
+ }
37
+
38
+ clear(): void {
39
+ this.listeners = {};
40
+ }
41
+ }
42
+
43
+ export const globalEvents = new EventEmitter();
44
+ let ignoreWindowFocus = false;
45
+
46
+ export const setupGlobalEvents = (
47
+ options: { ignoreWindowFocus?: boolean } = {},
48
+ ): void => {
49
+ ignoreWindowFocus = !!options.ignoreWindowFocus;
50
+ };
51
+
52
+ // Window focus
53
+ // --------------------------------------------------------
54
+
55
+ let windowFocusTimeout: ReturnType<typeof setTimeout> | null;
56
+ let windowFocused = true;
57
+
58
+ // Pretend to always be in focus.
59
+ function setWindowFocus(value: boolean, delayed?: boolean) {
60
+ if (ignoreWindowFocus) {
61
+ windowFocused = true;
62
+ return;
63
+ }
64
+ if (windowFocusTimeout) {
65
+ clearTimeout(windowFocusTimeout);
66
+ windowFocusTimeout = null;
67
+ }
68
+ if (delayed) {
69
+ windowFocusTimeout = setTimeout(() => setWindowFocus(value));
70
+ return;
71
+ }
72
+ if (windowFocused !== value) {
73
+ windowFocused = value;
74
+ globalEvents.emit(value ? 'window-focus' : 'window-blur');
75
+ globalEvents.emit('window-focus-change', value);
76
+ }
77
+ }
78
+
79
+ // Focus stealing
80
+ // --------------------------------------------------------
81
+
82
+ let focusStolenBy: HTMLElement | null = null;
83
+
84
+ export function canStealFocus(node: HTMLElement) {
85
+ const tag = String(node.tagName).toLowerCase();
86
+ return tag === 'input' || tag === 'textarea';
87
+ }
88
+
89
+ function stealFocus(node: HTMLElement) {
90
+ releaseStolenFocus();
91
+ focusStolenBy = node;
92
+ focusStolenBy.addEventListener('blur', releaseStolenFocus);
93
+ }
94
+
95
+ function releaseStolenFocus() {
96
+ if (focusStolenBy) {
97
+ focusStolenBy.removeEventListener('blur', releaseStolenFocus);
98
+ focusStolenBy = null;
99
+ }
100
+ }
101
+
102
+ // Focus follows the mouse
103
+ // --------------------------------------------------------
104
+
105
+ let focusedNode: HTMLElement | null = null;
106
+ let lastVisitedNode: HTMLElement | null = null;
107
+ const trackedNodes: HTMLElement[] = [];
108
+
109
+ export function addScrollableNode(node: HTMLElement) {
110
+ trackedNodes.push(node);
111
+ }
112
+
113
+ export function removeScrollableNode(node: HTMLElement) {
114
+ const index = trackedNodes.indexOf(node);
115
+ if (index >= 0) {
116
+ trackedNodes.splice(index, 1);
117
+ }
118
+ }
119
+
120
+ function focusNearestTrackedParent(node: HTMLElement | null) {
121
+ if (focusStolenBy || !windowFocused) {
122
+ return;
123
+ }
124
+ const body = document.body;
125
+ while (node && node !== body) {
126
+ if (trackedNodes.includes(node)) {
127
+ // NOTE: Contains is a DOM4 method
128
+ if (node.contains(focusedNode)) {
129
+ return;
130
+ }
131
+ focusedNode = node;
132
+ node.focus();
133
+ return;
134
+ }
135
+ node = node.parentElement;
136
+ }
137
+ }
138
+
139
+ window.addEventListener('mousemove', (e) => {
140
+ const node = e.target as HTMLElement;
141
+ if (node !== lastVisitedNode) {
142
+ lastVisitedNode = node;
143
+ focusNearestTrackedParent(node);
144
+ }
145
+ });
146
+
147
+ // Focus event hooks
148
+ // --------------------------------------------------------
149
+
150
+ window.addEventListener('focusin', (e) => {
151
+ lastVisitedNode = null;
152
+ focusedNode = e.target as HTMLElement;
153
+ setWindowFocus(true);
154
+ if (canStealFocus(e.target as HTMLElement)) {
155
+ stealFocus(e.target as HTMLElement);
156
+ }
157
+ });
158
+
159
+ window.addEventListener('focusout', () => {
160
+ lastVisitedNode = null;
161
+ setWindowFocus(false, true);
162
+ });
163
+
164
+ window.addEventListener('blur', () => {
165
+ lastVisitedNode = null;
166
+ setWindowFocus(false, true);
167
+ });
168
+
169
+ window.addEventListener('beforeunload', () => {
170
+ setWindowFocus(false);
171
+ });
172
+
173
+ // Key events
174
+ // --------------------------------------------------------
175
+
176
+ const keyHeldByCode: Record<number, boolean> = {};
177
+
178
+ export class KeyEvent {
179
+ event: KeyboardEvent;
180
+ type: 'keydown' | 'keyup';
181
+ code: number;
182
+ ctrl: boolean;
183
+ shift: boolean;
184
+ alt: boolean;
185
+ repeat: boolean;
186
+ _str?: string;
187
+
188
+ constructor(e: KeyboardEvent, type: 'keydown' | 'keyup', repeat?: boolean) {
189
+ this.event = e;
190
+ this.type = type;
191
+ this.code = e.keyCode;
192
+ this.ctrl = e.ctrlKey;
193
+ this.shift = e.shiftKey;
194
+ this.alt = e.altKey;
195
+ this.repeat = !!repeat;
196
+ }
197
+
198
+ hasModifierKeys() {
199
+ return this.ctrl || this.alt || this.shift;
200
+ }
201
+
202
+ isModifierKey() {
203
+ return (
204
+ this.code === KEY_CTRL || this.code === KEY_SHIFT || this.code === KEY_ALT
205
+ );
206
+ }
207
+
208
+ isDown() {
209
+ return this.type === 'keydown';
210
+ }
211
+
212
+ isUp() {
213
+ return this.type === 'keyup';
214
+ }
215
+
216
+ toString() {
217
+ if (this._str) {
218
+ return this._str;
219
+ }
220
+ this._str = '';
221
+ if (this.ctrl) {
222
+ this._str += 'Ctrl+';
223
+ }
224
+ if (this.alt) {
225
+ this._str += 'Alt+';
226
+ }
227
+ if (this.shift) {
228
+ this._str += 'Shift+';
229
+ }
230
+ if (this.code >= 48 && this.code <= 90) {
231
+ this._str += String.fromCharCode(this.code);
232
+ } else if (this.code >= KEY_F1 && this.code <= KEY_F12) {
233
+ this._str += 'F' + (this.code - 111);
234
+ } else {
235
+ this._str += '[' + this.code + ']';
236
+ }
237
+ return this._str;
238
+ }
239
+ }
240
+
241
+ // IE8: Keydown event is only available on document.
242
+ document.addEventListener('keydown', (e) => {
243
+ if (canStealFocus(e.target as HTMLElement)) {
244
+ return;
245
+ }
246
+ const code = e.keyCode;
247
+ const key = new KeyEvent(e, 'keydown', keyHeldByCode[code]);
248
+ globalEvents.emit('keydown', key);
249
+ globalEvents.emit('key', key);
250
+ keyHeldByCode[code] = true;
251
+ });
252
+
253
+ document.addEventListener('keyup', (e) => {
254
+ if (canStealFocus(e.target as HTMLElement)) {
255
+ return;
256
+ }
257
+ const code = e.keyCode;
258
+ const key = new KeyEvent(e, 'keyup');
259
+ globalEvents.emit('keyup', key);
260
+ globalEvents.emit('key', key);
261
+ keyHeldByCode[code] = false;
262
+ });
@@ -14,4 +14,6 @@
14
14
  * exhaustiveCheck(color);
15
15
  * }
16
16
  */
17
- export declare function exhaustiveCheck(input: never): void;
17
+ export function exhaustiveCheck(input: never) {
18
+ throw new Error(`Unhandled case: ${input}`);
19
+ }
@@ -0,0 +1,167 @@
1
+ const SI_SYMBOLS = [
2
+ 'f', // femto
3
+ 'p', // pico
4
+ 'n', // nano
5
+ 'μ', // micro
6
+ 'm', // milli
7
+ // NOTE: This is a space for a reason. When we right align si numbers,
8
+ // in monospace mode, we want to units and numbers stay in their respective
9
+ // columns. If rendering in HTML mode, this space will collapse into
10
+ // a single space anyway.
11
+ ' ', // base
12
+ 'k', // kilo
13
+ 'M', // mega
14
+ 'G', // giga
15
+ 'T', // tera
16
+ 'P', // peta
17
+ 'E', // exa
18
+ 'Z', // zetta
19
+ 'Y', // yotta
20
+ 'R', // ronna
21
+ 'Q', // quecca
22
+ 'F',
23
+ 'N',
24
+ 'H',
25
+ ] as const;
26
+
27
+ const SI_BASE_INDEX = SI_SYMBOLS.indexOf(' ');
28
+
29
+ // Formats a number to a human readable form, with a custom unit
30
+ export const formatSiUnit = (
31
+ value: number,
32
+ minBase1000 = -SI_BASE_INDEX,
33
+ unit = '',
34
+ ): string => {
35
+ if (!isFinite(value)) {
36
+ return value.toString();
37
+ }
38
+
39
+ const realBase10 = Math.floor(Math.log10(Math.abs(value)));
40
+ const base10 = Math.max(minBase1000 * 3, realBase10);
41
+ const base1000 = Math.floor(base10 / 3);
42
+ const symbol =
43
+ SI_SYMBOLS[Math.min(base1000 + SI_BASE_INDEX, SI_SYMBOLS.length - 1)];
44
+
45
+ const scaledValue = value / Math.pow(1000, base1000);
46
+
47
+ let formattedValue = scaledValue.toFixed(2);
48
+ if (formattedValue.endsWith('.00')) {
49
+ formattedValue = formattedValue.slice(0, -3);
50
+ } else if (formattedValue.endsWith('.0')) {
51
+ formattedValue = formattedValue.slice(0, -2);
52
+ }
53
+
54
+ return `${formattedValue} ${symbol.trim()}${unit}`.trim();
55
+ };
56
+
57
+ // Formats a number to a human readable form, with power (W) as the unit
58
+ export function formatPower(value: number, minBase1000 = 0) {
59
+ return formatSiUnit(value, minBase1000, 'W');
60
+ }
61
+
62
+ export function formatEnergy(value: number, minBase1000 = 0) {
63
+ return formatSiUnit(value, minBase1000, 'J');
64
+ }
65
+
66
+ // Formats a number as a currency string
67
+ export function formatMoney(value: number, precision = 0) {
68
+ if (!Number.isFinite(value)) {
69
+ return String(value);
70
+ }
71
+
72
+ // Round the number and make it fixed precision
73
+ const roundedValue = Number(value.toFixed(precision));
74
+
75
+ // Handle the negative sign
76
+ const isNegative = roundedValue < 0;
77
+ const absoluteValue = Math.abs(roundedValue);
78
+
79
+ // Convert to string and place thousand separators
80
+ const parts = absoluteValue.toString().split('.');
81
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, '\u2009'); // Thin space
82
+
83
+ const formattedValue = parts.join('.');
84
+
85
+ return isNegative ? `-${formattedValue}` : formattedValue;
86
+ }
87
+
88
+ // Formats a floating point number as a number on the decibel scale
89
+ export function formatDb(value: number) {
90
+ const db = 20 * Math.log10(value);
91
+ const sign = db >= 0 ? '+' : '-';
92
+ let formatted: string | number = Math.abs(db);
93
+
94
+ if (formatted === Infinity) {
95
+ formatted = 'Inf';
96
+ } else {
97
+ formatted = formatted.toFixed(2);
98
+ }
99
+
100
+ return `${sign}${formatted} dB`;
101
+ }
102
+
103
+ const SI_BASE_TEN_UNITS = [
104
+ '',
105
+ '· 10³', // kilo
106
+ '· 10⁶', // mega
107
+ '· 10⁹', // giga
108
+ '· 10¹²', // tera
109
+ '· 10¹⁵', // peta
110
+ '· 10¹⁸', // exa
111
+ '· 10²¹', // zetta
112
+ '· 10²⁴', // yotta
113
+ '· 10²⁷', // ronna
114
+ '· 10³⁰', // quecca
115
+ '· 10³³',
116
+ '· 10³⁶',
117
+ '· 10³⁹',
118
+ ] as const;
119
+
120
+ // Converts a number to a string with SI base 10 units
121
+ export const formatSiBaseTenUnit = (
122
+ value: number,
123
+ minBase1000 = 0,
124
+ unit = '',
125
+ ): string => {
126
+ if (!isFinite(value)) {
127
+ return 'NaN';
128
+ }
129
+
130
+ const realBase10 = Math.floor(Math.log10(value));
131
+ const base10 = Math.max(minBase1000 * 3, realBase10);
132
+ const base1000 = Math.floor(base10 / 3);
133
+ const symbol = SI_BASE_TEN_UNITS[base1000];
134
+
135
+ const scaledValue = value / Math.pow(1000, base1000);
136
+ const precision = Math.max(0, 2 - (base10 % 3));
137
+ const formattedValue = scaledValue.toFixed(precision);
138
+
139
+ return `${formattedValue} ${symbol} ${unit}`.trim();
140
+ };
141
+
142
+ /**
143
+ * Formats decisecond count into HH:MM:SS display by default
144
+ * "short" format does not pad and adds hms suffixes
145
+ */
146
+ export const formatTime = (
147
+ val: number,
148
+ formatType: 'short' | 'default' = 'default',
149
+ ): string => {
150
+ const totalSeconds = Math.floor(val / 10);
151
+ const hours = Math.floor(totalSeconds / 3600);
152
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
153
+ const seconds = totalSeconds % 60;
154
+
155
+ if (formatType === 'short') {
156
+ const hoursFormatted = hours > 0 ? `${hours}h` : '';
157
+ const minutesFormatted = minutes > 0 ? `${minutes}m` : '';
158
+ const secondsFormatted = seconds > 0 ? `${seconds}s` : '';
159
+ return `${hoursFormatted}${minutesFormatted}${secondsFormatted}`;
160
+ }
161
+
162
+ const hoursPadded = String(hours).padStart(2, '0');
163
+ const minutesPadded = String(minutes).padStart(2, '0');
164
+ const secondsPadded = String(seconds).padStart(2, '0');
165
+
166
+ return `${hoursPadded}:${minutesPadded}:${secondsPadded}`;
167
+ };
@@ -1,4 +1,5 @@
1
1
  type Func = (...args: any[]) => any;
2
+
2
3
  /**
3
4
  * Creates a function that returns the result of invoking the given
4
5
  * functions, where each successive invocation is supplied the return
@@ -14,5 +15,18 @@ type Func = (...args: any[]) => any;
14
15
  * const composedFunction2 = flow([add2, multiplyBy3], subtract5); // ((4 + 2) * 3) - 5 = 13
15
16
  *
16
17
  */
17
- export declare const flow: (...funcs: Array<Func | Func[]>) => (input: any, ...rest: any[]) => any;
18
- export {};
18
+ export const flow =
19
+ (...funcs: Array<Func | Func[]>) =>
20
+ (input: any, ...rest: any[]): any => {
21
+ let output = input;
22
+
23
+ for (const func of funcs) {
24
+ // Recurse into the array of functions
25
+ if (Array.isArray(func)) {
26
+ output = flow(...func)(output, ...rest);
27
+ } else if (func) {
28
+ output = func(output, ...rest);
29
+ }
30
+ }
31
+ return output;
32
+ };
@@ -0,0 +1,207 @@
1
+ import { globalEvents, KeyEvent } from './events';
2
+ import * as keycodes from './keycodes';
3
+
4
+ // BYOND macros, in `key: command` format.
5
+ const byondMacros: Record<string, string> = {};
6
+
7
+ // Default set of acquired keys, which will not be sent to BYOND.
8
+ const hotKeysAcquired = [
9
+ keycodes.KEY_ESCAPE,
10
+ keycodes.KEY_ENTER,
11
+ keycodes.KEY_SPACE,
12
+ keycodes.KEY_TAB,
13
+ keycodes.KEY_CTRL,
14
+ keycodes.KEY_SHIFT,
15
+ keycodes.KEY_UP,
16
+ keycodes.KEY_DOWN,
17
+ keycodes.KEY_LEFT,
18
+ keycodes.KEY_RIGHT,
19
+ keycodes.KEY_F5,
20
+ ];
21
+
22
+ // State of passed-through keys.
23
+ const keyState: Record<string, boolean> = {};
24
+
25
+ // Custom listeners for key events
26
+ const keyListeners: ((key: KeyEvent) => void)[] = [];
27
+
28
+ /**
29
+ * Converts a browser keycode to BYOND keycode.
30
+ */
31
+ function keyCodeToByond(keyCode: number) {
32
+ if (keyCode === 16) return 'Shift';
33
+ if (keyCode === 17) return 'Ctrl';
34
+ if (keyCode === 18) return 'Alt';
35
+ if (keyCode === 33) return 'Northeast';
36
+ if (keyCode === 34) return 'Southeast';
37
+ if (keyCode === 35) return 'Southwest';
38
+ if (keyCode === 36) return 'Northwest';
39
+ if (keyCode === 37) return 'West';
40
+ if (keyCode === 38) return 'North';
41
+ if (keyCode === 39) return 'East';
42
+ if (keyCode === 40) return 'South';
43
+ if (keyCode === 45) return 'Insert';
44
+ if (keyCode === 46) return 'Delete';
45
+
46
+ if ((keyCode >= 48 && keyCode <= 57) || (keyCode >= 65 && keyCode <= 90)) {
47
+ return String.fromCharCode(keyCode);
48
+ }
49
+ if (keyCode >= 96 && keyCode <= 105) {
50
+ return 'Numpad' + (keyCode - 96);
51
+ }
52
+ if (keyCode >= 112 && keyCode <= 123) {
53
+ return 'F' + (keyCode - 111);
54
+ }
55
+ if (keyCode === 188) return ',';
56
+ if (keyCode === 189) return '-';
57
+ if (keyCode === 190) return '.';
58
+ }
59
+
60
+ /**
61
+ * Keyboard passthrough logic. This allows you to keep doing things
62
+ * in game while the browser window is focused.
63
+ */
64
+ function handlePassthrough(key: KeyEvent) {
65
+ const keyString = String(key);
66
+ // In addition to F5, support reloading with Ctrl+R and Ctrl+F5
67
+ if (keyString === 'Ctrl+F5' || keyString === 'Ctrl+R') {
68
+ location.reload();
69
+ return;
70
+ }
71
+ // Prevent passthrough on Ctrl+F
72
+ if (keyString === 'Ctrl+F') {
73
+ return;
74
+ }
75
+ // NOTE: Alt modifier is pretty bad and sticky in IE11.
76
+
77
+ if (
78
+ key.event.defaultPrevented ||
79
+ key.isModifierKey() ||
80
+ hotKeysAcquired.includes(key.code)
81
+ ) {
82
+ return;
83
+ }
84
+ const byondKeyCode = keyCodeToByond(key.code);
85
+ if (!byondKeyCode) {
86
+ return;
87
+ }
88
+ // Macro
89
+ const macro = byondMacros[byondKeyCode];
90
+ if (macro) {
91
+ return Byond.command(macro);
92
+ }
93
+ // KeyDown
94
+ if (key.isDown() && !keyState[byondKeyCode]) {
95
+ keyState[byondKeyCode] = true;
96
+ const command = `KeyDown "${byondKeyCode}"`;
97
+ return Byond.command(command);
98
+ }
99
+ // KeyUp
100
+ if (key.isUp() && keyState[byondKeyCode]) {
101
+ keyState[byondKeyCode] = false;
102
+ const command = `KeyUp "${byondKeyCode}"`;
103
+ return Byond.command(command);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Acquires a lock on the hotkey, which prevents it from being
109
+ * passed through to BYOND.
110
+ */
111
+ export function acquireHotKey(keyCode: number) {
112
+ hotKeysAcquired.push(keyCode);
113
+ }
114
+
115
+ /**
116
+ * Makes the hotkey available to BYOND again.
117
+ */
118
+ export function releaseHotKey(keyCode: number) {
119
+ const index = hotKeysAcquired.indexOf(keyCode);
120
+ if (index >= 0) {
121
+ hotKeysAcquired.splice(index, 1);
122
+ }
123
+ }
124
+
125
+ export function releaseHeldKeys() {
126
+ for (const byondKeyCode of Object.keys(keyState)) {
127
+ if (keyState[byondKeyCode]) {
128
+ keyState[byondKeyCode] = false;
129
+ Byond.command(`KeyUp "${byondKeyCode}"`);
130
+ }
131
+ }
132
+ }
133
+
134
+ type ByondSkinMacro = {
135
+ command: string;
136
+ name: string;
137
+ };
138
+
139
+ export function setupHotKeys() {
140
+ // Read macros
141
+ Byond.winget('default.*').then((data: Record<string, string>) => {
142
+ // Group each macro by ref
143
+ const groupedByRef: Record<string, ByondSkinMacro> = {};
144
+ for (const key of Object.keys(data)) {
145
+ const keyPath = key.split('.');
146
+ const ref = keyPath[1];
147
+ const prop = keyPath[2];
148
+ if (ref && prop) {
149
+ // This piece of code imperatively adds each property to a
150
+ // ByondSkinMacro object in the order we meet it, which is hard
151
+ // to express safely in typescript.
152
+ if (!groupedByRef[ref]) {
153
+ groupedByRef[ref] = {} as any;
154
+ }
155
+ groupedByRef[ref][prop] = data[key];
156
+ }
157
+ }
158
+ // Insert macros
159
+ const escapedQuotRegex = /\\"/g;
160
+
161
+ function unescape(str: string) {
162
+ return str.substring(1, str.length - 1).replace(escapedQuotRegex, '"');
163
+ }
164
+
165
+ for (const ref of Object.keys(groupedByRef)) {
166
+ const macro = groupedByRef[ref];
167
+ const byondKeyName = unescape(macro.name);
168
+ byondMacros[byondKeyName] = unescape(macro.command);
169
+ }
170
+ });
171
+ // Setup event handlers
172
+ globalEvents.on('window-blur', () => {
173
+ releaseHeldKeys();
174
+ });
175
+ globalEvents.on('key', (key: KeyEvent) => {
176
+ for (const keyListener of keyListeners) {
177
+ keyListener(key);
178
+ }
179
+ handlePassthrough(key);
180
+ });
181
+ }
182
+
183
+ /**
184
+ * Registers for any key events, such as key down or key up.
185
+ * This should be preferred over directly connecting to keydown/keyup
186
+ * as it lets tgui prevent the key from reaching BYOND.
187
+ *
188
+ * If using in a component, prefer KeyListener, which automatically handles
189
+ * stopping listening when unmounting.
190
+ *
191
+ * @param callback The function to call whenever a key event occurs
192
+ * @returns A callback to stop listening
193
+ */
194
+ export function listenForKeyEvents(callback: (key: KeyEvent) => void) {
195
+ keyListeners.push(callback);
196
+
197
+ let removed = false;
198
+
199
+ return () => {
200
+ if (removed) {
201
+ return;
202
+ }
203
+
204
+ removed = true;
205
+ keyListeners.splice(keyListeners.indexOf(callback), 1);
206
+ };
207
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * An equivalent to `fetch`, except will automatically retry.
3
+ */
4
+ export function fetchRetry(
5
+ url: string,
6
+ options?: RequestInit,
7
+ retryTimer: number = 1000,
8
+ ): Promise<Response> {
9
+ return fetch(url, options).catch(() => {
10
+ return new Promise((resolve) => {
11
+ setTimeout(() => {
12
+ fetchRetry(url, options, retryTimer).then(resolve);
13
+ }, retryTimer);
14
+ });
15
+ });
16
+ }