@noya-app/noya-multiplayer-react 0.1.17 → 0.1.19
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +20 -0
- package/dist/index.d.mts +55 -4
- package/dist/index.d.ts +55 -4
- package/dist/index.js +353 -132
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +321 -107
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/src/avatarStyle.ts +82 -0
- package/src/components/UserPointersOverlay.tsx +182 -0
- package/src/globals.ts +1 -39
- package/src/hooks.ts +28 -10
- package/src/index.ts +2 -0
- package/src/inspector/StateInspector.tsx +29 -1
- package/src/inspector/useStateInspector.tsx +6 -1
- package/src/useObservable.ts +25 -0
package/src/hooks.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
ActionHandler,
|
|
5
5
|
ConnectionEvent,
|
|
6
|
+
EphemeralUserData,
|
|
6
7
|
Get,
|
|
7
8
|
MultiplayerStateManager,
|
|
8
9
|
MultiplayerStateManagerOptions,
|
|
@@ -23,13 +24,10 @@ import {
|
|
|
23
24
|
useState,
|
|
24
25
|
useSyncExternalStore,
|
|
25
26
|
} from "react";
|
|
26
|
-
import {
|
|
27
|
-
shouldTrackEvents$,
|
|
28
|
-
shouldTrackTasks$,
|
|
29
|
-
useObservable,
|
|
30
|
-
} from "./globals";
|
|
27
|
+
import { shouldTrackEvents$, shouldTrackTasks$ } from "./globals";
|
|
31
28
|
import { StateInspector } from "./inspector/StateInspector";
|
|
32
29
|
import { useStateInspector } from "./inspector/useStateInspector";
|
|
30
|
+
import { useObservable } from "./useObservable";
|
|
33
31
|
|
|
34
32
|
export function useSyncStateManager<S extends object, M>(
|
|
35
33
|
stateManager: StateManager<S, M>
|
|
@@ -137,12 +135,25 @@ export function useSyncMultiplayerStateManager<S extends object, M = void>(
|
|
|
137
135
|
);
|
|
138
136
|
}
|
|
139
137
|
|
|
138
|
+
export function useEphemeralUserData<E>(
|
|
139
|
+
ephemeralUserData: EphemeralUserData<E>
|
|
140
|
+
) {
|
|
141
|
+
return useSyncExternalStore(
|
|
142
|
+
ephemeralUserData.addListener,
|
|
143
|
+
ephemeralUserData.getSnapshot,
|
|
144
|
+
ephemeralUserData.getSnapshot
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
140
148
|
const defaultStubSync = stubSync();
|
|
141
149
|
|
|
142
|
-
export function useMultiplayerState<S extends object, M = void>(
|
|
150
|
+
export function useMultiplayerState<S extends object, M = void, E = void>(
|
|
143
151
|
initialState: S | (() => S),
|
|
144
152
|
options?: MultiplayerStateManagerOptions<S, M> & {
|
|
145
|
-
sync?: (options: {
|
|
153
|
+
sync?: (options: {
|
|
154
|
+
ms: MultiplayerStateManager<S, M>;
|
|
155
|
+
ephemeralUserData: EphemeralUserData<E>;
|
|
156
|
+
}) => void;
|
|
146
157
|
trackConnectionEvents?: boolean;
|
|
147
158
|
trackTasks?: boolean;
|
|
148
159
|
inspector?:
|
|
@@ -150,7 +161,7 @@ export function useMultiplayerState<S extends object, M = void>(
|
|
|
150
161
|
| Pick<ComponentProps<typeof StateInspector>, "colorScheme" | "anchor">;
|
|
151
162
|
}
|
|
152
163
|
) {
|
|
153
|
-
const { sync = defaultStubSync as unknown as SyncAdapter<S, M>, ...rest } =
|
|
164
|
+
const { sync = defaultStubSync as unknown as SyncAdapter<S, M, E>, ...rest } =
|
|
154
165
|
options ?? {};
|
|
155
166
|
|
|
156
167
|
const [multiplayerStateManager] = useState(
|
|
@@ -163,14 +174,19 @@ export function useMultiplayerState<S extends object, M = void>(
|
|
|
163
174
|
>();
|
|
164
175
|
|
|
165
176
|
const [connectedUsers, setConnectedUsers] = useState<MultiplayerUser[]>([]);
|
|
177
|
+
const [ephemeralUserData] = useState<EphemeralUserData<E>>(
|
|
178
|
+
() => new EphemeralUserData()
|
|
179
|
+
);
|
|
166
180
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
167
181
|
const [userId, setUserId] = useState<string | undefined>();
|
|
168
182
|
|
|
169
183
|
const extras = useMemo(() => {
|
|
170
184
|
return {
|
|
171
185
|
tasks,
|
|
186
|
+
userId,
|
|
172
187
|
connectionEvents,
|
|
173
188
|
connectedUsers,
|
|
189
|
+
ephemeralUserData,
|
|
174
190
|
// evaluate: async (code: string) => {
|
|
175
191
|
// const payload = await rpcManager.request({ type: "eval", code });
|
|
176
192
|
|
|
@@ -179,7 +195,7 @@ export function useMultiplayerState<S extends object, M = void>(
|
|
|
179
195
|
// }
|
|
180
196
|
// },
|
|
181
197
|
};
|
|
182
|
-
}, [tasks, connectionEvents, connectedUsers]);
|
|
198
|
+
}, [tasks, connectionEvents, connectedUsers, ephemeralUserData, userId]);
|
|
183
199
|
|
|
184
200
|
const globalTrackEvents = useObservable(shouldTrackEvents$);
|
|
185
201
|
const globalTrackTasks = useObservable(shouldTrackTasks$);
|
|
@@ -231,13 +247,14 @@ export function useMultiplayerState<S extends object, M = void>(
|
|
|
231
247
|
if (syncRef.current) {
|
|
232
248
|
return syncRef.current({
|
|
233
249
|
ms: multiplayerStateManager,
|
|
250
|
+
ephemeralUserData,
|
|
234
251
|
onConnectionEvent: (e) => trackEventsCallbackRef.current?.(e),
|
|
235
252
|
onChangeConnectedUsers: (users, userId) =>
|
|
236
253
|
trackConnectedUsersCallbackRef.current?.(users, userId),
|
|
237
254
|
onChangeTasks: (tasks) => trackTasksCallbackRef.current?.(tasks),
|
|
238
255
|
});
|
|
239
256
|
}
|
|
240
|
-
}, [multiplayerStateManager]);
|
|
257
|
+
}, [ephemeralUserData, multiplayerStateManager]);
|
|
241
258
|
|
|
242
259
|
const state = useSyncMultiplayerStateManager(multiplayerStateManager);
|
|
243
260
|
|
|
@@ -250,6 +267,7 @@ export function useMultiplayerState<S extends object, M = void>(
|
|
|
250
267
|
}, [initialState, multiplayerStateManager, options?.schema]);
|
|
251
268
|
|
|
252
269
|
useStateInspector({
|
|
270
|
+
ephemeralUserData,
|
|
253
271
|
multiplayerStateManager,
|
|
254
272
|
state,
|
|
255
273
|
connectionEvents,
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
ConnectionEvent,
|
|
5
|
+
EphemeralUserData,
|
|
5
6
|
ExtendedPathKey,
|
|
6
7
|
MultiplayerStateManager,
|
|
7
8
|
MultiplayerUser,
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
chromeLight,
|
|
25
26
|
} from "react-inspector";
|
|
26
27
|
import { shouldTrackEvents$, shouldTrackTasks$ } from "../globals";
|
|
27
|
-
import { useManagedHistory } from "../hooks";
|
|
28
|
+
import { useEphemeralUserData, useManagedHistory } from "../hooks";
|
|
28
29
|
import { useLocalStorageState } from "./useLocalStorageState";
|
|
29
30
|
|
|
30
31
|
const lightTheme = {
|
|
@@ -242,6 +243,7 @@ type Anchor = "left" | "right" | "bottom left" | "bottom right";
|
|
|
242
243
|
export const StateInspector = memo(function StateInspector<
|
|
243
244
|
S extends object,
|
|
244
245
|
M,
|
|
246
|
+
E,
|
|
245
247
|
>({
|
|
246
248
|
state,
|
|
247
249
|
connectionEvents,
|
|
@@ -251,6 +253,7 @@ export const StateInspector = memo(function StateInspector<
|
|
|
251
253
|
unstyled,
|
|
252
254
|
colorScheme = "light",
|
|
253
255
|
multiplayerStateManager,
|
|
256
|
+
ephemeralUserData,
|
|
254
257
|
anchor = "right",
|
|
255
258
|
forceInit,
|
|
256
259
|
...props
|
|
@@ -263,6 +266,7 @@ export const StateInspector = memo(function StateInspector<
|
|
|
263
266
|
unstyled?: boolean;
|
|
264
267
|
colorScheme?: "light" | "dark";
|
|
265
268
|
multiplayerStateManager: MultiplayerStateManager<S, M>;
|
|
269
|
+
ephemeralUserData: EphemeralUserData<E>;
|
|
266
270
|
anchor?: Anchor;
|
|
267
271
|
forceInit: () => void;
|
|
268
272
|
} & ComponentPropsWithoutRef<"div">) {
|
|
@@ -299,6 +303,10 @@ export const StateInspector = memo(function StateInspector<
|
|
|
299
303
|
"noya-multiplayer-react-show-tasks",
|
|
300
304
|
false
|
|
301
305
|
);
|
|
306
|
+
const [showEphemeral, setShowEphemeral] = useLocalStorageState(
|
|
307
|
+
"noya-multiplayer-react-show-ephemeral",
|
|
308
|
+
false
|
|
309
|
+
);
|
|
302
310
|
|
|
303
311
|
useEffect(() => {
|
|
304
312
|
shouldTrackEvents$.set(showEvents);
|
|
@@ -315,6 +323,7 @@ export const StateInspector = memo(function StateInspector<
|
|
|
315
323
|
}
|
|
316
324
|
}, [connectionEvents]);
|
|
317
325
|
|
|
326
|
+
const ephemeral = useEphemeralUserData(ephemeralUserData);
|
|
318
327
|
const historySnapshot = useManagedHistory(multiplayerStateManager.sm);
|
|
319
328
|
|
|
320
329
|
useEffect(() => {
|
|
@@ -668,6 +677,25 @@ export const StateInspector = memo(function StateInspector<
|
|
|
668
677
|
))}
|
|
669
678
|
</div>
|
|
670
679
|
</DisclosureSection>
|
|
680
|
+
<DisclosureSection
|
|
681
|
+
open={showEphemeral}
|
|
682
|
+
setOpen={setShowEphemeral}
|
|
683
|
+
title="Ephemeral User Data"
|
|
684
|
+
colorScheme={colorScheme}
|
|
685
|
+
>
|
|
686
|
+
<div style={styles.sectionInner}>
|
|
687
|
+
{Object.entries(ephemeral).map(([key, value]) => (
|
|
688
|
+
<InspectorRow key={key} colorScheme={colorScheme}>
|
|
689
|
+
<ObjectInspector
|
|
690
|
+
name={key}
|
|
691
|
+
data={value}
|
|
692
|
+
theme={theme}
|
|
693
|
+
expandLevel={10}
|
|
694
|
+
/>
|
|
695
|
+
</InspectorRow>
|
|
696
|
+
))}
|
|
697
|
+
</div>
|
|
698
|
+
</DisclosureSection>
|
|
671
699
|
<DisclosureSection
|
|
672
700
|
open={showEvents}
|
|
673
701
|
setOpen={setShowEvents}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
ConnectionEvent,
|
|
5
|
+
EphemeralUserData,
|
|
5
6
|
MultiplayerStateManager,
|
|
6
7
|
MultiplayerUser,
|
|
7
8
|
Task,
|
|
@@ -18,7 +19,7 @@ function createPortalElement() {
|
|
|
18
19
|
return el;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function useStateInspector<S extends object, M>({
|
|
22
|
+
export function useStateInspector<S extends object, M, E>({
|
|
22
23
|
state,
|
|
23
24
|
connectionEvents,
|
|
24
25
|
connectedUsers,
|
|
@@ -28,6 +29,7 @@ export function useStateInspector<S extends object, M>({
|
|
|
28
29
|
colorScheme,
|
|
29
30
|
anchor,
|
|
30
31
|
multiplayerStateManager,
|
|
32
|
+
ephemeralUserData,
|
|
31
33
|
forceInit,
|
|
32
34
|
}: {
|
|
33
35
|
state: S;
|
|
@@ -37,6 +39,7 @@ export function useStateInspector<S extends object, M>({
|
|
|
37
39
|
userId?: string;
|
|
38
40
|
disabled: boolean;
|
|
39
41
|
multiplayerStateManager: MultiplayerStateManager<S, M>;
|
|
42
|
+
ephemeralUserData: EphemeralUserData<E>;
|
|
40
43
|
forceInit: () => void;
|
|
41
44
|
} & Pick<ComponentProps<typeof StateInspector>, "colorScheme" | "anchor">) {
|
|
42
45
|
const [root, setRoot] = React.useState<Root | null>(null);
|
|
@@ -73,6 +76,7 @@ export function useStateInspector<S extends object, M>({
|
|
|
73
76
|
multiplayerStateManager={
|
|
74
77
|
multiplayerStateManager as MultiplayerStateManager<any, any>
|
|
75
78
|
}
|
|
79
|
+
ephemeralUserData={ephemeralUserData as EphemeralUserData<any>}
|
|
76
80
|
/>
|
|
77
81
|
);
|
|
78
82
|
}, [
|
|
@@ -86,5 +90,6 @@ export function useStateInspector<S extends object, M>({
|
|
|
86
90
|
userId,
|
|
87
91
|
tasks,
|
|
88
92
|
forceInit,
|
|
93
|
+
ephemeralUserData,
|
|
89
94
|
]);
|
|
90
95
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { GetWithPath, Observable, PathKey } from "@noya-app/observable";
|
|
2
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
3
|
+
|
|
4
|
+
export function useObservable<T>(observable: Observable<T>): T;
|
|
5
|
+
export function useObservable<T, P extends PathKey[]>(
|
|
6
|
+
observable: Observable<T>,
|
|
7
|
+
path: P
|
|
8
|
+
): GetWithPath<T, P>;
|
|
9
|
+
export function useObservable<T, P extends PathKey[]>(
|
|
10
|
+
observable: Observable<T>,
|
|
11
|
+
path?: P
|
|
12
|
+
) {
|
|
13
|
+
const { get, listen } = useMemo(() => {
|
|
14
|
+
const listen = path
|
|
15
|
+
? (callback: (value: GetWithPath<T, P>) => void) =>
|
|
16
|
+
observable.listen(path, callback)
|
|
17
|
+
: observable.listen;
|
|
18
|
+
|
|
19
|
+
const get: () => void = path ? () => observable.get(path) : observable.get;
|
|
20
|
+
|
|
21
|
+
return { get, listen };
|
|
22
|
+
}, [observable, path]);
|
|
23
|
+
|
|
24
|
+
return useSyncExternalStore(listen, get, get);
|
|
25
|
+
}
|