@diffenderfer-games/hub 2.0.0-next.0 → 2.0.0-next.1
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/CHANGELOG.md +9 -0
- package/LICENSE +21 -21
- package/README.md +191 -139
- package/dist/doctor/cli.mjs +49 -19
- package/dist/doctor/cli.mjs.map +1 -1
- package/dist/shared/client-version.gen.js +1 -1
- package/dist/shared/daily.js +1 -1
- package/dist/shared/globals.js +8 -3
- package/dist/shared/globals.js.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/package.json +89 -89
- package/src/auto.ts +39 -39
- package/src/core/connection.ts +299 -299
- package/src/core/errors.ts +147 -147
- package/src/core/game-data.ts +38 -38
- package/src/core/hub-economy.ts +179 -179
- package/src/core/hub-progress.ts +205 -205
- package/src/core/hub.ts +131 -131
- package/src/core/menu-control.ts +61 -61
- package/src/core/offline/events.ts +50 -50
- package/src/core/offline/favorites.ts +21 -21
- package/src/core/offline/local-store.ts +261 -261
- package/src/core/offline/queue.ts +171 -171
- package/src/core/page.ts +74 -74
- package/src/core/rest/endpoint-types.ts +22 -22
- package/src/core/rest/rest-client.ts +152 -152
- package/src/core/storage/json-guards.ts +74 -74
- package/src/core/storage/key-value.ts +66 -66
- package/src/core/ticket.ts +43 -43
- package/src/core/types.ts +134 -134
- package/src/core/version/client-version.gen.ts +1 -1
- package/src/core/version/hub-version.ts +62 -62
- package/src/core/version/semver.ts +97 -97
- package/src/daily/daily-system.ts +161 -161
- package/src/daily/page-daily.ts +36 -36
- package/src/daily/types.ts +67 -67
- package/src/daily.ts +10 -10
- package/src/doctor/checks.ts +78 -76
- package/src/doctor/cli.ts +98 -106
- package/src/doctor/hub-version-fetch.ts +43 -0
- package/src/doctor/tsconfig.json +12 -12
- package/src/globals.ts +115 -110
- package/src/index.ts +50 -50
- package/src/input/device-state.ts +146 -146
- package/src/input/edges.ts +55 -55
- package/src/input/gamepad.ts +61 -61
- package/src/input/input-events.ts +32 -32
- package/src/input/input-registry.ts +164 -164
- package/src/input/input-system.ts +393 -393
- package/src/input/legacy-controls.ts +64 -64
- package/src/input/nav-state.ts +191 -191
- package/src/input/navigator.ts +171 -171
- package/src/input/on-screen-controls.ts +101 -101
- package/src/input/page-control.ts +43 -43
- package/src/input/page-input.ts +193 -193
- package/src/input/player-remaps.ts +77 -77
- package/src/input/source-values.ts +116 -116
- package/src/input/spatial.ts +47 -47
- package/src/input/types.ts +331 -331
- package/src/input/zero-config.ts +177 -177
- package/src/input.ts +22 -22
- package/src/mp.ts +10 -10
- package/src/overlay/controls-panel.ts +163 -163
- package/src/overlay/dom.ts +46 -46
- package/src/overlay/layout-clusters.ts +162 -162
- package/src/overlay/layout-geometry.ts +136 -136
- package/src/overlay/layout.ts +203 -203
- package/src/overlay/listener-scope.ts +23 -23
- package/src/overlay/on-screen-keyboard.ts +137 -137
- package/src/overlay/overlay.ts +208 -208
- package/src/overlay/styles.ts +9 -9
- package/src/overlay/virtual-controls.ts +262 -262
- package/src/race.ts +10 -10
- package/src/realtime/bridge.ts +121 -121
- package/src/realtime/cross-bundle.ts +29 -29
- package/src/realtime/fallback-notice.ts +48 -48
- package/src/realtime/hub-link.ts +65 -65
- package/src/realtime/latest-state.ts +33 -33
- package/src/realtime/multiplayer-forwarder.ts +28 -28
- package/src/realtime/multiplayer-types.ts +52 -52
- package/src/realtime/page-runtime.ts +60 -60
- package/src/realtime/race-forwarder.ts +59 -59
- package/src/realtime/race-types.ts +132 -132
- package/src/realtime/room-types.ts +125 -125
- package/src/realtime/rooms-forwarder.ts +79 -79
- package/src/realtime/runtime-api.ts +49 -49
- package/src/realtime/runtime-opener.ts +61 -61
- package/src/realtime/social-forwarder.ts +72 -72
- package/src/realtime/social-types.ts +78 -78
- package/src/realtime/types.ts +53 -53
- package/src/realtime/wire-types.ts +72 -72
- package/src/rooms.ts +10 -10
- package/src/social.ts +18 -18
- package/src/types.ts +13 -13
- package/src/uistack/page-stack.ts +45 -45
- package/src/uistack/play-suspension.ts +82 -82
- package/src/uistack/types.ts +76 -76
- package/src/uistack/ui-stack.ts +144 -144
|
@@ -1,137 +1,137 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The on-screen keyboard for text fields, usable by touch, mouse or gamepad
|
|
3
|
-
* (the stick or d-pad moves between keys, A presses, B or Start closes).
|
|
4
|
-
*/
|
|
5
|
-
import { RepeatingEdges, RisingEdges } from '../input/edges';
|
|
6
|
-
import { isAnyPadButtonPressed, PAD_BUTTON, readGamepads } from '../input/gamepad';
|
|
7
|
-
import type { Direction } from '../input/gamepad';
|
|
8
|
-
import { findNearestInDirection, rectOf } from '../input/spatial';
|
|
9
|
-
import { findElements } from './dom';
|
|
10
|
-
|
|
11
|
-
const LETTER_ROWS = ['1234567890', 'qwertyuiop', 'asdfghjkl', 'zxcvbnm'];
|
|
12
|
-
const STICK_THRESHOLD = 0.5;
|
|
13
|
-
|
|
14
|
-
/** One key's markup. */
|
|
15
|
-
function keyHtml(label: string, key: string, extraClass = ''): string {
|
|
16
|
-
return `<button class="k ${extraClass}" data-k="${key}">${label}</button>`;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const KEYBOARD_HTML = `<div class="kb-rows">${LETTER_ROWS.map((row) => `<div class="kb-row">${Array.from(row).map((character) => keyHtml(character, character)).join('')}</div>`).join('')}`
|
|
20
|
-
+ `<div class="kb-row">${keyHtml('⇧', 'shift', 'wide')}${keyHtml('space', ' ', 'space')}${keyHtml('⌫', 'back', 'wide')}${keyHtml('↵', 'enter', 'wide')}${keyHtml('✕', 'close', 'wide')}</div></div>`;
|
|
21
|
-
|
|
22
|
-
/** The direction the pads point, from the summed stick and d-pad. */
|
|
23
|
-
function heldDirection(pads: readonly Gamepad[]): Direction | null {
|
|
24
|
-
let horizontal = 0;
|
|
25
|
-
let vertical = 0;
|
|
26
|
-
for (const pad of pads) {
|
|
27
|
-
horizontal += (pad.axes[0] ?? 0) - Number(pad.buttons[PAD_BUTTON.LEFT]?.pressed === true) + Number(pad.buttons[PAD_BUTTON.RIGHT]?.pressed === true);
|
|
28
|
-
vertical += (pad.axes[1] ?? 0) - Number(pad.buttons[PAD_BUTTON.UP]?.pressed === true) + Number(pad.buttons[PAD_BUTTON.DOWN]?.pressed === true);
|
|
29
|
-
}
|
|
30
|
-
if (Math.abs(horizontal) <= STICK_THRESHOLD && Math.abs(vertical) <= STICK_THRESHOLD) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
if (Math.abs(horizontal) > Math.abs(vertical)) {
|
|
34
|
-
return horizontal > 0 ? 'right' : 'left';
|
|
35
|
-
}
|
|
36
|
-
return vertical > 0 ? 'down' : 'up';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The on-screen keyboard (one at a time, inside the overlay's shadow root). */
|
|
40
|
-
export class OnScreenKeyboard {
|
|
41
|
-
private readonly root: ShadowRoot;
|
|
42
|
-
private panel: HTMLDivElement | null = null;
|
|
43
|
-
private target: HTMLInputElement | HTMLTextAreaElement | null = null;
|
|
44
|
-
private keys: HTMLElement[] = [];
|
|
45
|
-
private focusedIndex = 0;
|
|
46
|
-
private isShifted = false;
|
|
47
|
-
private readonly repeats = new RepeatingEdges();
|
|
48
|
-
private readonly presses = new RisingEdges();
|
|
49
|
-
|
|
50
|
-
constructor(root: ShadowRoot) {
|
|
51
|
-
this.root = root;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Whether the keyboard is showing. */
|
|
55
|
-
get isOpen(): boolean {
|
|
56
|
-
return this.panel !== null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Open it for `target` (replacing any open keyboard). */
|
|
60
|
-
open(target: HTMLInputElement | HTMLTextAreaElement | null): void {
|
|
61
|
-
this.close();
|
|
62
|
-
this.target = target;
|
|
63
|
-
this.isShifted = false;
|
|
64
|
-
this.focusedIndex = 0;
|
|
65
|
-
const panel = document.createElement('div');
|
|
66
|
-
panel.className = 'kb';
|
|
67
|
-
panel.innerHTML = KEYBOARD_HTML;
|
|
68
|
-
this.root.appendChild(panel);
|
|
69
|
-
this.panel = panel;
|
|
70
|
-
this.keys = findElements(panel, '.k', HTMLElement);
|
|
71
|
-
for (const key of this.keys) {
|
|
72
|
-
const press = (event: Event): void => {
|
|
73
|
-
event.preventDefault();
|
|
74
|
-
this.press(key.dataset['k'] ?? '');
|
|
75
|
-
};
|
|
76
|
-
key.addEventListener('mousedown', press);
|
|
77
|
-
key.addEventListener('touchstart', press, { passive: false });
|
|
78
|
-
}
|
|
79
|
-
this.highlightFocusedKey();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Close it. */
|
|
83
|
-
close(): void {
|
|
84
|
-
this.panel?.remove();
|
|
85
|
-
this.panel = null;
|
|
86
|
-
this.target = null;
|
|
87
|
-
this.keys = [];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Called every frame while open: gamepad navigation. */
|
|
91
|
-
pollGamepads(): void {
|
|
92
|
-
if (this.panel === null) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const pads = readGamepads();
|
|
96
|
-
const direction = heldDirection(pads);
|
|
97
|
-
if (this.repeats.fires('move', direction !== null) && direction !== null) {
|
|
98
|
-
this.moveFocus(direction);
|
|
99
|
-
}
|
|
100
|
-
if (this.presses.rose('act', isAnyPadButtonPressed(pads, PAD_BUTTON.A))) {
|
|
101
|
-
this.press(this.keys[this.focusedIndex]?.dataset['k'] ?? '');
|
|
102
|
-
}
|
|
103
|
-
const isClosing = isAnyPadButtonPressed(pads, PAD_BUTTON.B) || isAnyPadButtonPressed(pads, PAD_BUTTON.START);
|
|
104
|
-
if (this.presses.rose('close', isClosing)) {
|
|
105
|
-
this.close();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private press(key: string): void {
|
|
110
|
-
if (key === 'close' || key === 'enter') {
|
|
111
|
-
this.close();
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (key === 'shift') {
|
|
115
|
-
this.isShifted = !this.isShifted;
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const target = this.target;
|
|
119
|
-
if (target === null || key === '') {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
target.value = key === 'back' ? target.value.slice(0, -1) : target.value + (this.isShifted ? key.toUpperCase() : key);
|
|
123
|
-
target.dispatchEvent(new Event('input', { bubbles: true }));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private moveFocus(direction: Direction): void {
|
|
127
|
-
const next = findNearestInDirection(this.keys.map(rectOf), this.focusedIndex, direction);
|
|
128
|
-
if (next >= 0) {
|
|
129
|
-
this.focusedIndex = next;
|
|
130
|
-
this.highlightFocusedKey();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private highlightFocusedKey(): void {
|
|
135
|
-
this.keys.forEach((key, index) => key.classList.toggle('kfocus', index === this.focusedIndex));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The on-screen keyboard for text fields, usable by touch, mouse or gamepad
|
|
3
|
+
* (the stick or d-pad moves between keys, A presses, B or Start closes).
|
|
4
|
+
*/
|
|
5
|
+
import { RepeatingEdges, RisingEdges } from '../input/edges';
|
|
6
|
+
import { isAnyPadButtonPressed, PAD_BUTTON, readGamepads } from '../input/gamepad';
|
|
7
|
+
import type { Direction } from '../input/gamepad';
|
|
8
|
+
import { findNearestInDirection, rectOf } from '../input/spatial';
|
|
9
|
+
import { findElements } from './dom';
|
|
10
|
+
|
|
11
|
+
const LETTER_ROWS = ['1234567890', 'qwertyuiop', 'asdfghjkl', 'zxcvbnm'];
|
|
12
|
+
const STICK_THRESHOLD = 0.5;
|
|
13
|
+
|
|
14
|
+
/** One key's markup. */
|
|
15
|
+
function keyHtml(label: string, key: string, extraClass = ''): string {
|
|
16
|
+
return `<button class="k ${extraClass}" data-k="${key}">${label}</button>`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const KEYBOARD_HTML = `<div class="kb-rows">${LETTER_ROWS.map((row) => `<div class="kb-row">${Array.from(row).map((character) => keyHtml(character, character)).join('')}</div>`).join('')}`
|
|
20
|
+
+ `<div class="kb-row">${keyHtml('⇧', 'shift', 'wide')}${keyHtml('space', ' ', 'space')}${keyHtml('⌫', 'back', 'wide')}${keyHtml('↵', 'enter', 'wide')}${keyHtml('✕', 'close', 'wide')}</div></div>`;
|
|
21
|
+
|
|
22
|
+
/** The direction the pads point, from the summed stick and d-pad. */
|
|
23
|
+
function heldDirection(pads: readonly Gamepad[]): Direction | null {
|
|
24
|
+
let horizontal = 0;
|
|
25
|
+
let vertical = 0;
|
|
26
|
+
for (const pad of pads) {
|
|
27
|
+
horizontal += (pad.axes[0] ?? 0) - Number(pad.buttons[PAD_BUTTON.LEFT]?.pressed === true) + Number(pad.buttons[PAD_BUTTON.RIGHT]?.pressed === true);
|
|
28
|
+
vertical += (pad.axes[1] ?? 0) - Number(pad.buttons[PAD_BUTTON.UP]?.pressed === true) + Number(pad.buttons[PAD_BUTTON.DOWN]?.pressed === true);
|
|
29
|
+
}
|
|
30
|
+
if (Math.abs(horizontal) <= STICK_THRESHOLD && Math.abs(vertical) <= STICK_THRESHOLD) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (Math.abs(horizontal) > Math.abs(vertical)) {
|
|
34
|
+
return horizontal > 0 ? 'right' : 'left';
|
|
35
|
+
}
|
|
36
|
+
return vertical > 0 ? 'down' : 'up';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The on-screen keyboard (one at a time, inside the overlay's shadow root). */
|
|
40
|
+
export class OnScreenKeyboard {
|
|
41
|
+
private readonly root: ShadowRoot;
|
|
42
|
+
private panel: HTMLDivElement | null = null;
|
|
43
|
+
private target: HTMLInputElement | HTMLTextAreaElement | null = null;
|
|
44
|
+
private keys: HTMLElement[] = [];
|
|
45
|
+
private focusedIndex = 0;
|
|
46
|
+
private isShifted = false;
|
|
47
|
+
private readonly repeats = new RepeatingEdges();
|
|
48
|
+
private readonly presses = new RisingEdges();
|
|
49
|
+
|
|
50
|
+
constructor(root: ShadowRoot) {
|
|
51
|
+
this.root = root;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Whether the keyboard is showing. */
|
|
55
|
+
get isOpen(): boolean {
|
|
56
|
+
return this.panel !== null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Open it for `target` (replacing any open keyboard). */
|
|
60
|
+
open(target: HTMLInputElement | HTMLTextAreaElement | null): void {
|
|
61
|
+
this.close();
|
|
62
|
+
this.target = target;
|
|
63
|
+
this.isShifted = false;
|
|
64
|
+
this.focusedIndex = 0;
|
|
65
|
+
const panel = document.createElement('div');
|
|
66
|
+
panel.className = 'kb';
|
|
67
|
+
panel.innerHTML = KEYBOARD_HTML;
|
|
68
|
+
this.root.appendChild(panel);
|
|
69
|
+
this.panel = panel;
|
|
70
|
+
this.keys = findElements(panel, '.k', HTMLElement);
|
|
71
|
+
for (const key of this.keys) {
|
|
72
|
+
const press = (event: Event): void => {
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
this.press(key.dataset['k'] ?? '');
|
|
75
|
+
};
|
|
76
|
+
key.addEventListener('mousedown', press);
|
|
77
|
+
key.addEventListener('touchstart', press, { passive: false });
|
|
78
|
+
}
|
|
79
|
+
this.highlightFocusedKey();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Close it. */
|
|
83
|
+
close(): void {
|
|
84
|
+
this.panel?.remove();
|
|
85
|
+
this.panel = null;
|
|
86
|
+
this.target = null;
|
|
87
|
+
this.keys = [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Called every frame while open: gamepad navigation. */
|
|
91
|
+
pollGamepads(): void {
|
|
92
|
+
if (this.panel === null) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const pads = readGamepads();
|
|
96
|
+
const direction = heldDirection(pads);
|
|
97
|
+
if (this.repeats.fires('move', direction !== null) && direction !== null) {
|
|
98
|
+
this.moveFocus(direction);
|
|
99
|
+
}
|
|
100
|
+
if (this.presses.rose('act', isAnyPadButtonPressed(pads, PAD_BUTTON.A))) {
|
|
101
|
+
this.press(this.keys[this.focusedIndex]?.dataset['k'] ?? '');
|
|
102
|
+
}
|
|
103
|
+
const isClosing = isAnyPadButtonPressed(pads, PAD_BUTTON.B) || isAnyPadButtonPressed(pads, PAD_BUTTON.START);
|
|
104
|
+
if (this.presses.rose('close', isClosing)) {
|
|
105
|
+
this.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private press(key: string): void {
|
|
110
|
+
if (key === 'close' || key === 'enter') {
|
|
111
|
+
this.close();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (key === 'shift') {
|
|
115
|
+
this.isShifted = !this.isShifted;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const target = this.target;
|
|
119
|
+
if (target === null || key === '') {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
target.value = key === 'back' ? target.value.slice(0, -1) : target.value + (this.isShifted ? key.toUpperCase() : key);
|
|
123
|
+
target.dispatchEvent(new Event('input', { bubbles: true }));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private moveFocus(direction: Direction): void {
|
|
127
|
+
const next = findNearestInDirection(this.keys.map(rectOf), this.focusedIndex, direction);
|
|
128
|
+
if (next >= 0) {
|
|
129
|
+
this.focusedIndex = next;
|
|
130
|
+
this.highlightFocusedKey();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private highlightFocusedKey(): void {
|
|
135
|
+
this.keys.forEach((key, index) => key.classList.toggle('kfocus', index === this.focusedIndex));
|
|
136
|
+
}
|
|
137
|
+
}
|