tgui-core 1.0.2 → 1.0.4
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/{src/components → components}/AnimatedNumber.tsx +185 -185
- package/{src/components → components}/BlockQuote.tsx +15 -15
- package/{src/components → components}/BodyZoneSelector.tsx +149 -149
- package/{src/components → components}/Box.tsx +255 -255
- package/{src/components → components}/Button.tsx +415 -415
- package/{src/components → components}/ByondUi.jsx +121 -121
- package/{src/components → components}/Chart.tsx +160 -160
- package/{src/components → components}/ColorBox.tsx +30 -30
- package/{src/components → components}/Dimmer.tsx +19 -19
- package/{src/components → components}/Divider.tsx +26 -26
- package/{src/components → components}/DmIcon.tsx +72 -72
- package/{src/components → components}/DraggableControl.jsx +282 -282
- package/{src/components → components}/Dropdown.tsx +246 -246
- package/{src/components → components}/Flex.tsx +105 -105
- package/{src/components → components}/Icon.tsx +91 -91
- package/{src/components → components}/Input.tsx +181 -181
- package/{src/components → components}/KeyListener.tsx +40 -40
- package/{src/components → components}/Knob.tsx +185 -185
- package/{src/components → components}/LabeledList.tsx +130 -130
- package/{src/components → components}/MenuBar.tsx +233 -238
- package/{src/components → components}/Modal.tsx +25 -25
- package/{src/components → components}/NoticeBox.tsx +48 -48
- package/{src/components → components}/NumberInput.tsx +328 -328
- package/{src/components → components}/ProgressBar.tsx +79 -79
- package/{src/components → components}/RestrictedInput.jsx +301 -301
- package/{src/components → components}/RoundGauge.tsx +189 -189
- package/{src/components → components}/Section.tsx +125 -125
- package/{src/components → components}/Slider.tsx +173 -173
- package/{src/components → components}/Stack.tsx +101 -101
- package/{src/components → components}/Table.tsx +90 -90
- package/{src/components → components}/Tabs.tsx +90 -90
- package/{src/components → components}/TextArea.tsx +198 -198
- package/{src/components → components}/TimeDisplay.jsx +64 -64
- package/components/index.ts +51 -0
- package/{src/debug/KitchenSink.jsx → debug/KitchenSink.tsx} +56 -56
- package/{src/debug/actions.js → debug/actions.ts} +11 -11
- package/{src/debug/hooks.js → debug/hooks.ts} +10 -10
- package/{src/debug/middleware.js → debug/middleware.ts} +67 -86
- package/{src/debug/reducer.js → debug/reducer.ts} +27 -22
- package/{src/debug/selectors.js → debug/selectors.ts} +7 -7
- package/{src/layouts → layouts}/Layout.tsx +75 -75
- package/{src/layouts → layouts}/NtosWindow.tsx +162 -162
- package/{src/layouts → layouts}/Pane.tsx +56 -56
- package/{src/layouts → layouts}/Window.tsx +227 -227
- package/layouts/index.ts +10 -0
- package/package.json +3 -2
- package/src/assets.ts +43 -43
- package/src/backend.ts +368 -369
- package/src/drag.ts +280 -280
- package/src/events.ts +237 -237
- package/src/hotkeys.ts +212 -212
- package/src/renderer.ts +50 -50
- package/stories/Blink.stories.tsx +20 -0
- package/stories/BlockQuote.stories.tsx +23 -0
- package/stories/Box.stories.tsx +27 -0
- package/stories/Button.stories.tsx +68 -0
- package/stories/ByondUi.stories.tsx +45 -0
- package/stories/Collapsible.stories.tsx +23 -0
- package/stories/Flex.stories.tsx +68 -0
- package/stories/Input.stories.tsx +124 -0
- package/stories/LabeledList.stories.tsx +73 -0
- package/stories/Popper.stories.tsx +58 -0
- package/stories/ProgressBar.stories.tsx +58 -0
- package/stories/Stack.stories.tsx +55 -0
- package/stories/Storage.stories.tsx +46 -0
- package/stories/Themes.stories.tsx +30 -0
- package/stories/Tooltip.stories.tsx +48 -0
- package/stories/common.tsx +19 -0
- package/tsconfig.json +0 -21
- package/src/components/Grid.tsx +0 -44
- /package/{src/common → common}/collections.ts +0 -0
- /package/{src/common → common}/color.ts +0 -0
- /package/{src/common → common}/events.ts +0 -0
- /package/{src/common → common}/exhaustive.ts +0 -0
- /package/{src/common → common}/fp.ts +0 -0
- /package/{src/common → common}/keycodes.ts +0 -0
- /package/{src/common → common}/keys.ts +0 -0
- /package/{src/common → common}/math.ts +0 -0
- /package/{src/common → common}/perf.ts +0 -0
- /package/{src/common → common}/random.ts +0 -0
- /package/{src/common → common}/react.ts +0 -0
- /package/{src/common → common}/redux.ts +0 -0
- /package/{src/common → common}/storage.js +0 -0
- /package/{src/common → common}/string.ts +0 -0
- /package/{src/common → common}/timer.ts +0 -0
- /package/{src/common → common}/type-utils.ts +0 -0
- /package/{src/common → common}/types.ts +0 -0
- /package/{src/common → common}/uuid.ts +0 -0
- /package/{src/common → common}/vector.ts +0 -0
- /package/{src/components → components}/Autofocus.tsx +0 -0
- /package/{src/components → components}/Blink.jsx +0 -0
- /package/{src/components → components}/Collapsible.tsx +0 -0
- /package/{src/components → components}/Dialog.tsx +0 -0
- /package/{src/components → components}/FakeTerminal.jsx +0 -0
- /package/{src/components → components}/FitText.tsx +0 -0
- /package/{src/components → components}/Image.tsx +0 -0
- /package/{src/components → components}/InfinitePlane.jsx +0 -0
- /package/{src/components → components}/LabeledControls.tsx +0 -0
- /package/{src/components → components}/Popper.tsx +0 -0
- /package/{src/components → components}/StyleableSection.tsx +0 -0
- /package/{src/components → components}/Tooltip.tsx +0 -0
- /package/{src/components → components}/TrackOutsideClicks.tsx +0 -0
- /package/{src/components → components}/VirtualList.tsx +0 -0
- /package/{src/debug → debug}/index.ts +0 -0
- /package/{src/styles → styles}/base.scss +0 -0
- /package/{src/styles → styles}/colors.scss +0 -0
- /package/{src/styles → styles}/components/BlockQuote.scss +0 -0
- /package/{src/styles → styles}/components/Button.scss +0 -0
- /package/{src/styles → styles}/components/ColorBox.scss +0 -0
- /package/{src/styles → styles}/components/Dialog.scss +0 -0
- /package/{src/styles → styles}/components/Dimmer.scss +0 -0
- /package/{src/styles → styles}/components/Divider.scss +0 -0
- /package/{src/styles → styles}/components/Dropdown.scss +0 -0
- /package/{src/styles → styles}/components/Flex.scss +0 -0
- /package/{src/styles → styles}/components/Icon.scss +0 -0
- /package/{src/styles → styles}/components/Input.scss +0 -0
- /package/{src/styles → styles}/components/Knob.scss +0 -0
- /package/{src/styles → styles}/components/LabeledList.scss +0 -0
- /package/{src/styles → styles}/components/MenuBar.scss +0 -0
- /package/{src/styles → styles}/components/Modal.scss +0 -0
- /package/{src/styles → styles}/components/NoticeBox.scss +0 -0
- /package/{src/styles → styles}/components/NumberInput.scss +0 -0
- /package/{src/styles → styles}/components/ProgressBar.scss +0 -0
- /package/{src/styles → styles}/components/RoundGauge.scss +0 -0
- /package/{src/styles → styles}/components/Section.scss +0 -0
- /package/{src/styles → styles}/components/Slider.scss +0 -0
- /package/{src/styles → styles}/components/Stack.scss +0 -0
- /package/{src/styles → styles}/components/Table.scss +0 -0
- /package/{src/styles → styles}/components/Tabs.scss +0 -0
- /package/{src/styles → styles}/components/TextArea.scss +0 -0
- /package/{src/styles → styles}/components/Tooltip.scss +0 -0
- /package/{src/styles → styles}/functions.scss +0 -0
- /package/{src/styles → styles}/layouts/Layout.scss +0 -0
- /package/{src/styles → styles}/layouts/NtosHeader.scss +0 -0
- /package/{src/styles → styles}/layouts/NtosWindow.scss +0 -0
- /package/{src/styles → styles}/layouts/TitleBar.scss +0 -0
- /package/{src/styles → styles}/layouts/Window.scss +0 -0
- /package/{src/styles → styles}/main.scss +0 -0
- /package/{src/styles → styles}/reset.scss +0 -0
- /package/{src/styles → styles}/themes/abductor.scss +0 -0
- /package/{src/styles → styles}/themes/admin.scss +0 -0
- /package/{src/styles → styles}/themes/cardtable.scss +0 -0
- /package/{src/styles → styles}/themes/hackerman.scss +0 -0
- /package/{src/styles → styles}/themes/malfunction.scss +0 -0
- /package/{src/styles → styles}/themes/neutral.scss +0 -0
- /package/{src/styles → styles}/themes/ntOS95.scss +0 -0
- /package/{src/styles → styles}/themes/ntos.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_cat.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_darkmode.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_lightmode.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_spooky.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_synth.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_terminal.scss +0 -0
- /package/{src/styles → styles}/themes/paper.scss +0 -0
- /package/{src/styles → styles}/themes/retro.scss +0 -0
- /package/{src/styles → styles}/themes/spookyconsole.scss +0 -0
- /package/{src/styles → styles}/themes/syndicate.scss +0 -0
- /package/{src/styles → styles}/themes/wizard.scss +0 -0
package/src/hotkeys.ts
CHANGED
|
@@ -1,212 +1,212 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file
|
|
3
|
-
* @copyright 2020 Aleksej Komarov
|
|
4
|
-
* @license MIT
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as keycodes from '
|
|
8
|
-
|
|
9
|
-
import { globalEvents, KeyEvent } from './events';
|
|
10
|
-
|
|
11
|
-
// BYOND macros, in `key: command` format.
|
|
12
|
-
const byondMacros: Record<string, string> = {};
|
|
13
|
-
|
|
14
|
-
// Default set of acquired keys, which will not be sent to BYOND.
|
|
15
|
-
const hotKeysAcquired = [
|
|
16
|
-
keycodes.KEY_ESCAPE,
|
|
17
|
-
keycodes.KEY_ENTER,
|
|
18
|
-
keycodes.KEY_SPACE,
|
|
19
|
-
keycodes.KEY_TAB,
|
|
20
|
-
keycodes.KEY_CTRL,
|
|
21
|
-
keycodes.KEY_SHIFT,
|
|
22
|
-
keycodes.KEY_UP,
|
|
23
|
-
keycodes.KEY_DOWN,
|
|
24
|
-
keycodes.KEY_LEFT,
|
|
25
|
-
keycodes.KEY_RIGHT,
|
|
26
|
-
keycodes.KEY_F5,
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
// State of passed-through keys.
|
|
30
|
-
const keyState: Record<string, boolean> = {};
|
|
31
|
-
|
|
32
|
-
// Custom listeners for key events
|
|
33
|
-
const keyListeners: ((key: KeyEvent) => void)[] = [];
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Converts a browser keycode to BYOND keycode.
|
|
37
|
-
*/
|
|
38
|
-
const keyCodeToByond = (keyCode: number) => {
|
|
39
|
-
if (keyCode === 16) return 'Shift';
|
|
40
|
-
if (keyCode === 17) return 'Ctrl';
|
|
41
|
-
if (keyCode === 18) return 'Alt';
|
|
42
|
-
if (keyCode === 33) return 'Northeast';
|
|
43
|
-
if (keyCode === 34) return 'Southeast';
|
|
44
|
-
if (keyCode === 35) return 'Southwest';
|
|
45
|
-
if (keyCode === 36) return 'Northwest';
|
|
46
|
-
if (keyCode === 37) return 'West';
|
|
47
|
-
if (keyCode === 38) return 'North';
|
|
48
|
-
if (keyCode === 39) return 'East';
|
|
49
|
-
if (keyCode === 40) return 'South';
|
|
50
|
-
if (keyCode === 45) return 'Insert';
|
|
51
|
-
if (keyCode === 46) return 'Delete';
|
|
52
|
-
|
|
53
|
-
if ((keyCode >= 48 && keyCode <= 57) || (keyCode >= 65 && keyCode <= 90)) {
|
|
54
|
-
return String.fromCharCode(keyCode);
|
|
55
|
-
}
|
|
56
|
-
if (keyCode >= 96 && keyCode <= 105) {
|
|
57
|
-
return 'Numpad' + (keyCode - 96);
|
|
58
|
-
}
|
|
59
|
-
if (keyCode >= 112 && keyCode <= 123) {
|
|
60
|
-
return 'F' + (keyCode - 111);
|
|
61
|
-
}
|
|
62
|
-
if (keyCode === 188) return ',';
|
|
63
|
-
if (keyCode === 189) return '-';
|
|
64
|
-
if (keyCode === 190) return '.';
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Keyboard passthrough logic. This allows you to keep doing things
|
|
69
|
-
* in game while the browser window is focused.
|
|
70
|
-
*/
|
|
71
|
-
const handlePassthrough = (key: KeyEvent) => {
|
|
72
|
-
const keyString = String(key);
|
|
73
|
-
// In addition to F5, support reloading with Ctrl+R and Ctrl+F5
|
|
74
|
-
if (keyString === 'Ctrl+F5' || keyString === 'Ctrl+R') {
|
|
75
|
-
location.reload();
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
// Prevent passthrough on Ctrl+F
|
|
79
|
-
if (keyString === 'Ctrl+F') {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
// NOTE: Alt modifier is pretty bad and sticky in IE11.
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
key.event.defaultPrevented ||
|
|
86
|
-
key.isModifierKey() ||
|
|
87
|
-
hotKeysAcquired.includes(key.code)
|
|
88
|
-
) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const byondKeyCode = keyCodeToByond(key.code);
|
|
92
|
-
if (!byondKeyCode) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
// Macro
|
|
96
|
-
const macro = byondMacros[byondKeyCode];
|
|
97
|
-
if (macro) {
|
|
98
|
-
return Byond.command(macro);
|
|
99
|
-
}
|
|
100
|
-
// KeyDown
|
|
101
|
-
if (key.isDown() && !keyState[byondKeyCode]) {
|
|
102
|
-
keyState[byondKeyCode] = true;
|
|
103
|
-
const command = `KeyDown "${byondKeyCode}"`;
|
|
104
|
-
return Byond.command(command);
|
|
105
|
-
}
|
|
106
|
-
// KeyUp
|
|
107
|
-
if (key.isUp() && keyState[byondKeyCode]) {
|
|
108
|
-
keyState[byondKeyCode] = false;
|
|
109
|
-
const command = `KeyUp "${byondKeyCode}"`;
|
|
110
|
-
return Byond.command(command);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Acquires a lock on the hotkey, which prevents it from being
|
|
116
|
-
* passed through to BYOND.
|
|
117
|
-
*/
|
|
118
|
-
export const acquireHotKey = (keyCode: number) => {
|
|
119
|
-
hotKeysAcquired.push(keyCode);
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Makes the hotkey available to BYOND again.
|
|
124
|
-
*/
|
|
125
|
-
export const releaseHotKey = (keyCode: number) => {
|
|
126
|
-
const index = hotKeysAcquired.indexOf(keyCode);
|
|
127
|
-
if (index >= 0) {
|
|
128
|
-
hotKeysAcquired.splice(index, 1);
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
export const releaseHeldKeys = () => {
|
|
133
|
-
for (let byondKeyCode of Object.keys(keyState)) {
|
|
134
|
-
if (keyState[byondKeyCode]) {
|
|
135
|
-
keyState[byondKeyCode] = false;
|
|
136
|
-
Byond.command(`KeyUp "${byondKeyCode}"`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
type ByondSkinMacro = {
|
|
142
|
-
command: string;
|
|
143
|
-
name: string;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
export const setupHotKeys = () => {
|
|
147
|
-
// Read macros
|
|
148
|
-
Byond.winget('default.*').then((data: Record<string, string>) => {
|
|
149
|
-
// Group each macro by ref
|
|
150
|
-
const groupedByRef: Record<string, ByondSkinMacro> = {};
|
|
151
|
-
for (let key of Object.keys(data)) {
|
|
152
|
-
const keyPath = key.split('.');
|
|
153
|
-
const ref = keyPath[1];
|
|
154
|
-
const prop = keyPath[2];
|
|
155
|
-
if (ref && prop) {
|
|
156
|
-
// This piece of code imperatively adds each property to a
|
|
157
|
-
// ByondSkinMacro object in the order we meet it, which is hard
|
|
158
|
-
// to express safely in typescript.
|
|
159
|
-
if (!groupedByRef[ref]) {
|
|
160
|
-
groupedByRef[ref] = {} as any;
|
|
161
|
-
}
|
|
162
|
-
groupedByRef[ref][prop] = data[key];
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
// Insert macros
|
|
166
|
-
const escapedQuotRegex = /\\"/g;
|
|
167
|
-
|
|
168
|
-
const unescape = (str: string) =>
|
|
169
|
-
str.substring(1, str.length - 1).replace(escapedQuotRegex, '"');
|
|
170
|
-
for (let ref of Object.keys(groupedByRef)) {
|
|
171
|
-
const macro = groupedByRef[ref];
|
|
172
|
-
const byondKeyName = unescape(macro.name);
|
|
173
|
-
byondMacros[byondKeyName] = unescape(macro.command);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
// Setup event handlers
|
|
177
|
-
globalEvents.on('window-blur', () => {
|
|
178
|
-
releaseHeldKeys();
|
|
179
|
-
});
|
|
180
|
-
globalEvents.on('key', (key: KeyEvent) => {
|
|
181
|
-
for (const keyListener of keyListeners) {
|
|
182
|
-
keyListener(key);
|
|
183
|
-
}
|
|
184
|
-
handlePassthrough(key);
|
|
185
|
-
});
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Registers for any key events, such as key down or key up.
|
|
190
|
-
* This should be preferred over directly connecting to keydown/keyup
|
|
191
|
-
* as it lets tgui prevent the key from reaching BYOND.
|
|
192
|
-
*
|
|
193
|
-
* If using in a component, prefer KeyListener, which automatically handles
|
|
194
|
-
* stopping listening when unmounting.
|
|
195
|
-
*
|
|
196
|
-
* @param callback The function to call whenever a key event occurs
|
|
197
|
-
* @returns A callback to stop listening
|
|
198
|
-
*/
|
|
199
|
-
export const listenForKeyEvents = (callback: (key: KeyEvent) => void) => {
|
|
200
|
-
keyListeners.push(callback);
|
|
201
|
-
|
|
202
|
-
let removed = false;
|
|
203
|
-
|
|
204
|
-
return () => {
|
|
205
|
-
if (removed) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
removed = true;
|
|
210
|
-
keyListeners.splice(keyListeners.indexOf(callback), 1);
|
|
211
|
-
};
|
|
212
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as keycodes from '../common/keycodes';
|
|
8
|
+
|
|
9
|
+
import { globalEvents, KeyEvent } from './events';
|
|
10
|
+
|
|
11
|
+
// BYOND macros, in `key: command` format.
|
|
12
|
+
const byondMacros: Record<string, string> = {};
|
|
13
|
+
|
|
14
|
+
// Default set of acquired keys, which will not be sent to BYOND.
|
|
15
|
+
const hotKeysAcquired = [
|
|
16
|
+
keycodes.KEY_ESCAPE,
|
|
17
|
+
keycodes.KEY_ENTER,
|
|
18
|
+
keycodes.KEY_SPACE,
|
|
19
|
+
keycodes.KEY_TAB,
|
|
20
|
+
keycodes.KEY_CTRL,
|
|
21
|
+
keycodes.KEY_SHIFT,
|
|
22
|
+
keycodes.KEY_UP,
|
|
23
|
+
keycodes.KEY_DOWN,
|
|
24
|
+
keycodes.KEY_LEFT,
|
|
25
|
+
keycodes.KEY_RIGHT,
|
|
26
|
+
keycodes.KEY_F5,
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// State of passed-through keys.
|
|
30
|
+
const keyState: Record<string, boolean> = {};
|
|
31
|
+
|
|
32
|
+
// Custom listeners for key events
|
|
33
|
+
const keyListeners: ((key: KeyEvent) => void)[] = [];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Converts a browser keycode to BYOND keycode.
|
|
37
|
+
*/
|
|
38
|
+
const keyCodeToByond = (keyCode: number) => {
|
|
39
|
+
if (keyCode === 16) return 'Shift';
|
|
40
|
+
if (keyCode === 17) return 'Ctrl';
|
|
41
|
+
if (keyCode === 18) return 'Alt';
|
|
42
|
+
if (keyCode === 33) return 'Northeast';
|
|
43
|
+
if (keyCode === 34) return 'Southeast';
|
|
44
|
+
if (keyCode === 35) return 'Southwest';
|
|
45
|
+
if (keyCode === 36) return 'Northwest';
|
|
46
|
+
if (keyCode === 37) return 'West';
|
|
47
|
+
if (keyCode === 38) return 'North';
|
|
48
|
+
if (keyCode === 39) return 'East';
|
|
49
|
+
if (keyCode === 40) return 'South';
|
|
50
|
+
if (keyCode === 45) return 'Insert';
|
|
51
|
+
if (keyCode === 46) return 'Delete';
|
|
52
|
+
|
|
53
|
+
if ((keyCode >= 48 && keyCode <= 57) || (keyCode >= 65 && keyCode <= 90)) {
|
|
54
|
+
return String.fromCharCode(keyCode);
|
|
55
|
+
}
|
|
56
|
+
if (keyCode >= 96 && keyCode <= 105) {
|
|
57
|
+
return 'Numpad' + (keyCode - 96);
|
|
58
|
+
}
|
|
59
|
+
if (keyCode >= 112 && keyCode <= 123) {
|
|
60
|
+
return 'F' + (keyCode - 111);
|
|
61
|
+
}
|
|
62
|
+
if (keyCode === 188) return ',';
|
|
63
|
+
if (keyCode === 189) return '-';
|
|
64
|
+
if (keyCode === 190) return '.';
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Keyboard passthrough logic. This allows you to keep doing things
|
|
69
|
+
* in game while the browser window is focused.
|
|
70
|
+
*/
|
|
71
|
+
const handlePassthrough = (key: KeyEvent) => {
|
|
72
|
+
const keyString = String(key);
|
|
73
|
+
// In addition to F5, support reloading with Ctrl+R and Ctrl+F5
|
|
74
|
+
if (keyString === 'Ctrl+F5' || keyString === 'Ctrl+R') {
|
|
75
|
+
location.reload();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Prevent passthrough on Ctrl+F
|
|
79
|
+
if (keyString === 'Ctrl+F') {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// NOTE: Alt modifier is pretty bad and sticky in IE11.
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
key.event.defaultPrevented ||
|
|
86
|
+
key.isModifierKey() ||
|
|
87
|
+
hotKeysAcquired.includes(key.code)
|
|
88
|
+
) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const byondKeyCode = keyCodeToByond(key.code);
|
|
92
|
+
if (!byondKeyCode) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Macro
|
|
96
|
+
const macro = byondMacros[byondKeyCode];
|
|
97
|
+
if (macro) {
|
|
98
|
+
return Byond.command(macro);
|
|
99
|
+
}
|
|
100
|
+
// KeyDown
|
|
101
|
+
if (key.isDown() && !keyState[byondKeyCode]) {
|
|
102
|
+
keyState[byondKeyCode] = true;
|
|
103
|
+
const command = `KeyDown "${byondKeyCode}"`;
|
|
104
|
+
return Byond.command(command);
|
|
105
|
+
}
|
|
106
|
+
// KeyUp
|
|
107
|
+
if (key.isUp() && keyState[byondKeyCode]) {
|
|
108
|
+
keyState[byondKeyCode] = false;
|
|
109
|
+
const command = `KeyUp "${byondKeyCode}"`;
|
|
110
|
+
return Byond.command(command);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Acquires a lock on the hotkey, which prevents it from being
|
|
116
|
+
* passed through to BYOND.
|
|
117
|
+
*/
|
|
118
|
+
export const acquireHotKey = (keyCode: number) => {
|
|
119
|
+
hotKeysAcquired.push(keyCode);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Makes the hotkey available to BYOND again.
|
|
124
|
+
*/
|
|
125
|
+
export const releaseHotKey = (keyCode: number) => {
|
|
126
|
+
const index = hotKeysAcquired.indexOf(keyCode);
|
|
127
|
+
if (index >= 0) {
|
|
128
|
+
hotKeysAcquired.splice(index, 1);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const releaseHeldKeys = () => {
|
|
133
|
+
for (let byondKeyCode of Object.keys(keyState)) {
|
|
134
|
+
if (keyState[byondKeyCode]) {
|
|
135
|
+
keyState[byondKeyCode] = false;
|
|
136
|
+
Byond.command(`KeyUp "${byondKeyCode}"`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
type ByondSkinMacro = {
|
|
142
|
+
command: string;
|
|
143
|
+
name: string;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const setupHotKeys = () => {
|
|
147
|
+
// Read macros
|
|
148
|
+
Byond.winget('default.*').then((data: Record<string, string>) => {
|
|
149
|
+
// Group each macro by ref
|
|
150
|
+
const groupedByRef: Record<string, ByondSkinMacro> = {};
|
|
151
|
+
for (let key of Object.keys(data)) {
|
|
152
|
+
const keyPath = key.split('.');
|
|
153
|
+
const ref = keyPath[1];
|
|
154
|
+
const prop = keyPath[2];
|
|
155
|
+
if (ref && prop) {
|
|
156
|
+
// This piece of code imperatively adds each property to a
|
|
157
|
+
// ByondSkinMacro object in the order we meet it, which is hard
|
|
158
|
+
// to express safely in typescript.
|
|
159
|
+
if (!groupedByRef[ref]) {
|
|
160
|
+
groupedByRef[ref] = {} as any;
|
|
161
|
+
}
|
|
162
|
+
groupedByRef[ref][prop] = data[key];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Insert macros
|
|
166
|
+
const escapedQuotRegex = /\\"/g;
|
|
167
|
+
|
|
168
|
+
const unescape = (str: string) =>
|
|
169
|
+
str.substring(1, str.length - 1).replace(escapedQuotRegex, '"');
|
|
170
|
+
for (let ref of Object.keys(groupedByRef)) {
|
|
171
|
+
const macro = groupedByRef[ref];
|
|
172
|
+
const byondKeyName = unescape(macro.name);
|
|
173
|
+
byondMacros[byondKeyName] = unescape(macro.command);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
// Setup event handlers
|
|
177
|
+
globalEvents.on('window-blur', () => {
|
|
178
|
+
releaseHeldKeys();
|
|
179
|
+
});
|
|
180
|
+
globalEvents.on('key', (key: KeyEvent) => {
|
|
181
|
+
for (const keyListener of keyListeners) {
|
|
182
|
+
keyListener(key);
|
|
183
|
+
}
|
|
184
|
+
handlePassthrough(key);
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Registers for any key events, such as key down or key up.
|
|
190
|
+
* This should be preferred over directly connecting to keydown/keyup
|
|
191
|
+
* as it lets tgui prevent the key from reaching BYOND.
|
|
192
|
+
*
|
|
193
|
+
* If using in a component, prefer KeyListener, which automatically handles
|
|
194
|
+
* stopping listening when unmounting.
|
|
195
|
+
*
|
|
196
|
+
* @param callback The function to call whenever a key event occurs
|
|
197
|
+
* @returns A callback to stop listening
|
|
198
|
+
*/
|
|
199
|
+
export const listenForKeyEvents = (callback: (key: KeyEvent) => void) => {
|
|
200
|
+
keyListeners.push(callback);
|
|
201
|
+
|
|
202
|
+
let removed = false;
|
|
203
|
+
|
|
204
|
+
return () => {
|
|
205
|
+
if (removed) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
removed = true;
|
|
210
|
+
keyListeners.splice(keyListeners.indexOf(callback), 1);
|
|
211
|
+
};
|
|
212
|
+
};
|
package/src/renderer.ts
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { perf } from '
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
import { createRoot, Root } from 'react-dom/client';
|
|
4
|
-
|
|
5
|
-
let reactRoot: Root;
|
|
6
|
-
let initialRender: string | boolean = true;
|
|
7
|
-
let suspended = false;
|
|
8
|
-
|
|
9
|
-
// These functions are used purely for profiling.
|
|
10
|
-
export const resumeRenderer = () => {
|
|
11
|
-
initialRender = initialRender || 'resumed';
|
|
12
|
-
suspended = false;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const suspendRenderer = () => {
|
|
16
|
-
suspended = true;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type CreateRenderer = <T extends unknown[] = [unknown]>(
|
|
20
|
-
getVNode?: (...args: T) => ReactNode
|
|
21
|
-
) => (...args: T) => void;
|
|
22
|
-
|
|
23
|
-
enum Render {
|
|
24
|
-
Start = 'render/start',
|
|
25
|
-
Finish = 'render/finish',
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
};
|
|
1
|
+
import { perf } from '../common/perf';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { createRoot, Root } from 'react-dom/client';
|
|
4
|
+
|
|
5
|
+
let reactRoot: Root;
|
|
6
|
+
let initialRender: string | boolean = true;
|
|
7
|
+
let suspended = false;
|
|
8
|
+
|
|
9
|
+
// These functions are used purely for profiling.
|
|
10
|
+
export const resumeRenderer = () => {
|
|
11
|
+
initialRender = initialRender || 'resumed';
|
|
12
|
+
suspended = false;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const suspendRenderer = () => {
|
|
16
|
+
suspended = true;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type CreateRenderer = <T extends unknown[] = [unknown]>(
|
|
20
|
+
getVNode?: (...args: T) => ReactNode
|
|
21
|
+
) => (...args: T) => void;
|
|
22
|
+
|
|
23
|
+
enum Render {
|
|
24
|
+
Start = 'render/start',
|
|
25
|
+
Finish = 'render/finish',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const createRenderer: CreateRenderer =
|
|
29
|
+
(getVNode) =>
|
|
30
|
+
(...args) => {
|
|
31
|
+
perf.mark(Render.Start);
|
|
32
|
+
// Start rendering
|
|
33
|
+
if (!reactRoot) {
|
|
34
|
+
const element = document.getElementById('react-root');
|
|
35
|
+
reactRoot = createRoot(element!);
|
|
36
|
+
}
|
|
37
|
+
if (getVNode) {
|
|
38
|
+
reactRoot.render(getVNode(...args));
|
|
39
|
+
} else {
|
|
40
|
+
reactRoot.render(args[0] as any);
|
|
41
|
+
}
|
|
42
|
+
perf.mark(Render.Finish);
|
|
43
|
+
if (suspended) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (initialRender) {
|
|
48
|
+
initialRender = false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2021 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Blink, Section } from '../components';
|
|
8
|
+
|
|
9
|
+
export const meta = {
|
|
10
|
+
title: 'Blink',
|
|
11
|
+
render: () => <Story />,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const Story = (props) => {
|
|
15
|
+
return (
|
|
16
|
+
<Section>
|
|
17
|
+
<Blink>Blink</Blink>
|
|
18
|
+
</Section>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2021 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BlockQuote, Section } from '../components';
|
|
8
|
+
import { BoxWithSampleText } from './common';
|
|
9
|
+
|
|
10
|
+
export const meta = {
|
|
11
|
+
title: 'BlockQuote',
|
|
12
|
+
render: () => <Story />,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const Story = (props) => {
|
|
16
|
+
return (
|
|
17
|
+
<Section>
|
|
18
|
+
<BlockQuote>
|
|
19
|
+
<BoxWithSampleText />
|
|
20
|
+
</BlockQuote>
|
|
21
|
+
</Section>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2021 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Box, Section } from '../components';
|
|
8
|
+
|
|
9
|
+
export const meta = {
|
|
10
|
+
title: 'Box',
|
|
11
|
+
render: () => <Story />,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const Story = (props) => {
|
|
15
|
+
return (
|
|
16
|
+
<Section>
|
|
17
|
+
<Box bold>bold</Box>
|
|
18
|
+
<Box italic>italic</Box>
|
|
19
|
+
<Box opacity={0.5}>opacity 0.5</Box>
|
|
20
|
+
<Box opacity={0.25}>opacity 0.25</Box>
|
|
21
|
+
<Box m={2}>m: 2</Box>
|
|
22
|
+
<Box textAlign="left">left</Box>
|
|
23
|
+
<Box textAlign="center">center</Box>
|
|
24
|
+
<Box textAlign="right">right</Box>
|
|
25
|
+
</Section>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2021 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Box, Button, Section } from '../components';
|
|
8
|
+
|
|
9
|
+
export const meta = {
|
|
10
|
+
title: 'Button',
|
|
11
|
+
render: () => <Story />,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const COLORS_SPECTRUM = [
|
|
15
|
+
'red',
|
|
16
|
+
'orange',
|
|
17
|
+
'yellow',
|
|
18
|
+
'olive',
|
|
19
|
+
'green',
|
|
20
|
+
'teal',
|
|
21
|
+
'blue',
|
|
22
|
+
'violet',
|
|
23
|
+
'purple',
|
|
24
|
+
'pink',
|
|
25
|
+
'brown',
|
|
26
|
+
'grey',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const COLORS_STATES = ['good', 'average', 'bad', 'black', 'white'];
|
|
30
|
+
|
|
31
|
+
const Story = (props) => {
|
|
32
|
+
return (
|
|
33
|
+
<Section>
|
|
34
|
+
<Box mb={1}>
|
|
35
|
+
<Button content="Simple" />
|
|
36
|
+
<Button selected content="Selected" />
|
|
37
|
+
<Button content="Alt Selected" />
|
|
38
|
+
<Button disabled content="Disabled" />
|
|
39
|
+
<Button color="transparent" content="Transparent" />
|
|
40
|
+
<Button icon="cog" content="Icon" />
|
|
41
|
+
<Button icon="power-off" />
|
|
42
|
+
<Button fluid content="Fluid" />
|
|
43
|
+
<Button
|
|
44
|
+
my={1}
|
|
45
|
+
lineHeight={2}
|
|
46
|
+
minWidth={15}
|
|
47
|
+
textAlign="center"
|
|
48
|
+
content="With Box props"
|
|
49
|
+
/>
|
|
50
|
+
</Box>
|
|
51
|
+
<Box mb={1}>
|
|
52
|
+
{COLORS_STATES.map((color) => (
|
|
53
|
+
<Button key={color} color={color} content={color} />
|
|
54
|
+
))}
|
|
55
|
+
<br />
|
|
56
|
+
{COLORS_SPECTRUM.map((color) => (
|
|
57
|
+
<Button key={color} color={color} content={color} />
|
|
58
|
+
))}
|
|
59
|
+
<br />
|
|
60
|
+
{COLORS_SPECTRUM.map((color) => (
|
|
61
|
+
<Box inline mx="7px" key={color} color={color}>
|
|
62
|
+
{color}
|
|
63
|
+
</Box>
|
|
64
|
+
))}
|
|
65
|
+
</Box>
|
|
66
|
+
</Section>
|
|
67
|
+
);
|
|
68
|
+
};
|