@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/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: { ms: MultiplayerStateManager<S, M> }) => void;
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
@@ -1,3 +1,5 @@
1
1
  export * from "@noya-app/state-manager";
2
+ export * from "./avatarStyle";
3
+ export * from "./components/UserPointersOverlay";
2
4
  export * from "./hooks";
3
5
  export * from "./inspector/StateInspector";
@@ -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
+ }