@noya-app/noya-multiplayer-react 0.1.11 → 0.1.12

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.
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ConnectionEvent,
3
3
  ExtendedPathKey,
4
+ MultiplayerUser,
4
5
  StateManager,
5
6
  } from "@noya-app/state-manager";
6
7
  import React, {
@@ -35,6 +36,16 @@ const darkTheme = {
35
36
  OBJECT_NAME_COLOR: "rgba(255,255,255,0.7)",
36
37
  } as any;
37
38
 
39
+ const styles = {
40
+ sectionInner: {
41
+ flex: "1 1 0",
42
+ overflowY: "auto",
43
+ overflowX: "hidden",
44
+ display: "flex",
45
+ flexDirection: "column",
46
+ },
47
+ } as const;
48
+
38
49
  function ToggleButton({
39
50
  showInspector,
40
51
  setShowInspector,
@@ -61,7 +72,11 @@ function ToggleButton({
61
72
  justifyContent: "center",
62
73
  padding: "2px 0",
63
74
  }}
64
- onClick={() => setShowInspector(!showInspector)}
75
+ onClick={(event) => {
76
+ event.stopPropagation();
77
+
78
+ setShowInspector(!showInspector);
79
+ }}
65
80
  >
66
81
  <span style={{ width: "12px", height: "12px" }}>
67
82
  {showInspector === (anchor === "right") ? (
@@ -108,6 +123,7 @@ function DisclosureSection({
108
123
  children,
109
124
  colorScheme,
110
125
  isFirst,
126
+ style,
111
127
  }: {
112
128
  open: boolean;
113
129
  setOpen?: (value: boolean) => void;
@@ -116,6 +132,7 @@ function DisclosureSection({
116
132
  children: React.ReactNode;
117
133
  colorScheme: "light" | "dark";
118
134
  isFirst?: boolean;
135
+ style?: CSSProperties;
119
136
  }) {
120
137
  const theme = colorScheme === "light" ? lightTheme : darkTheme;
121
138
 
@@ -128,6 +145,7 @@ function DisclosureSection({
128
145
  flex: open ? "1 1 0" : "0",
129
146
  display: "flex",
130
147
  flexDirection: "column",
148
+ ...style,
131
149
  }}
132
150
  >
133
151
  <div
@@ -160,6 +178,60 @@ function DisclosureSection({
160
178
  );
161
179
  }
162
180
 
181
+ function InspectorRow({
182
+ children,
183
+ colorScheme,
184
+ selected,
185
+ style,
186
+ variant,
187
+ }: {
188
+ children: React.ReactNode;
189
+ colorScheme: "light" | "dark";
190
+ selected?: boolean;
191
+ style?: CSSProperties;
192
+ variant?: "up" | "down";
193
+ }) {
194
+ const solidBorderColor =
195
+ colorScheme === "light" ? "rgb(223 223 223)" : "rgb(29 29 29)";
196
+
197
+ return (
198
+ <div
199
+ style={{
200
+ borderBottom: `1px solid ${solidBorderColor}`,
201
+ fontSize: "12px",
202
+ fontFamily: "Menlo, monospace",
203
+ padding: "2px 12px 1px",
204
+ display: "flex",
205
+ alignItems: "center",
206
+ background:
207
+ variant === "up"
208
+ ? "rgba(0,255,0,0.1)"
209
+ : variant === "down"
210
+ ? "rgba(255,0,0,0.1)"
211
+ : selected
212
+ ? "rgb(59 130 246 / 15%)"
213
+ : undefined,
214
+ // background:
215
+ // colorScheme === "light"
216
+ // ? "rgba(0,0,0,0.05)"
217
+ // : "rgba(255,255,255,0.05)",
218
+ ...style,
219
+ }}
220
+ >
221
+ <span
222
+ style={{
223
+ fontFamily: "Menlo, monospace",
224
+ fontSize: "11px",
225
+ borderRadius: 4,
226
+ display: "inline-block",
227
+ }}
228
+ >
229
+ {children}
230
+ </span>
231
+ </div>
232
+ );
233
+ }
234
+
163
235
  const HISTORY_ELEMENT_PREFIX = "noya-multiplayer-history-";
164
236
 
165
237
  type Anchor = "left" | "right";
@@ -170,6 +242,8 @@ export const StateInspector = memo(function StateInspector<
170
242
  >({
171
243
  state,
172
244
  connectionEvents,
245
+ connectedUsers,
246
+ userId,
173
247
  unstyled,
174
248
  colorScheme = "light",
175
249
  stateManager,
@@ -178,6 +252,8 @@ export const StateInspector = memo(function StateInspector<
178
252
  }: {
179
253
  state: S;
180
254
  connectionEvents?: ConnectionEvent<S>[];
255
+ connectedUsers?: MultiplayerUser[];
256
+ userId?: string;
181
257
  unstyled?: boolean;
182
258
  colorScheme?: "light" | "dark";
183
259
  stateManager: StateManager<S, M>;
@@ -204,6 +280,14 @@ export const StateInspector = memo(function StateInspector<
204
280
  "noya-multiplayer-react-show-history",
205
281
  false
206
282
  );
283
+ const [showUsers, setShowUsers] = useLocalStorageState(
284
+ "noya-multiplayer-react-show-users",
285
+ true
286
+ );
287
+ const [showData, setShowData] = useLocalStorageState(
288
+ "noya-multiplayer-react-show-data",
289
+ true
290
+ );
207
291
 
208
292
  useEffect(() => {
209
293
  trackEvents.set(showEvents);
@@ -242,8 +326,8 @@ export const StateInspector = memo(function StateInspector<
242
326
  }, [historySnapshot.historyIndex]);
243
327
 
244
328
  const theme = colorScheme === "light" ? lightTheme : darkTheme;
245
- const borderColor =
246
- colorScheme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)";
329
+ // const borderColor =
330
+ // colorScheme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)";
247
331
  const solidBorderColor =
248
332
  colorScheme === "light" ? "rgb(223 223 223)" : "rgb(29 29 29)";
249
333
 
@@ -300,12 +384,16 @@ export const StateInspector = memo(function StateInspector<
300
384
  ...props.style,
301
385
  }}
302
386
  >
303
- {/* <div style={{ flex: "0", fontSize: "12px" }}>User ID: {userId}</div> */}
304
387
  <DisclosureSection
305
- open
306
- title="Data"
307
- colorScheme={colorScheme}
308
388
  isFirst
389
+ open={showUsers}
390
+ setOpen={setShowUsers}
391
+ title="Users"
392
+ colorScheme={colorScheme}
393
+ style={{
394
+ flex: "0 0 auto",
395
+ maxHeight: "200px",
396
+ }}
309
397
  right={
310
398
  <ToggleButton
311
399
  showInspector={showInspector}
@@ -314,13 +402,41 @@ export const StateInspector = memo(function StateInspector<
314
402
  anchor={anchor}
315
403
  />
316
404
  }
405
+ >
406
+ <div style={{ ...styles.sectionInner, flex: "0 0 auto" }}>
407
+ {connectedUsers?.map((user) => (
408
+ <InspectorRow
409
+ key={user.id}
410
+ colorScheme={colorScheme}
411
+ variant={user.id === userId ? "up" : undefined}
412
+ >
413
+ {user.name} ({ellipsis(user.id.toString(), 8, "middle")})
414
+ </InspectorRow>
415
+ ))}
416
+ {!connectedUsers && (
417
+ <div
418
+ style={{
419
+ padding: "12px",
420
+ fontSize: "12px",
421
+ display: "flex",
422
+ flexDirection: "column",
423
+ gap: "4px",
424
+ }}
425
+ >
426
+ <span>No connected users</span>
427
+ </div>
428
+ )}
429
+ </div>
430
+ </DisclosureSection>
431
+ <DisclosureSection
432
+ title="Data"
433
+ colorScheme={colorScheme}
434
+ setOpen={setShowData}
435
+ open={showData}
317
436
  >
318
437
  <div
319
438
  style={{
320
- flex: "1 1 0",
321
- overflowY: "auto",
322
- display: "flex",
323
- flexDirection: "column",
439
+ ...styles.sectionInner,
324
440
  gap: "1px",
325
441
  padding: "1px 12px",
326
442
  }}
@@ -334,16 +450,7 @@ export const StateInspector = memo(function StateInspector<
334
450
  title="History"
335
451
  colorScheme={colorScheme}
336
452
  >
337
- <div
338
- ref={historyContainerRef}
339
- style={{
340
- flex: "1 1 0",
341
- overflowY: "auto",
342
- overflowX: "hidden",
343
- display: "flex",
344
- flexDirection: "column",
345
- }}
346
- >
453
+ <div ref={historyContainerRef} style={styles.sectionInner}>
347
454
  <div
348
455
  id={`${HISTORY_ELEMENT_PREFIX}0`}
349
456
  style={{
@@ -432,86 +539,51 @@ export const StateInspector = memo(function StateInspector<
432
539
  title="Events"
433
540
  colorScheme={colorScheme}
434
541
  >
435
- <div
436
- ref={eventsContainerRef}
437
- style={{
438
- flex: "1 1 0",
439
- overflowY: "auto",
440
- display: "flex",
441
- flexDirection: "column",
442
- }}
443
- >
444
- {connectionEvents?.map((event, index) => (
445
- <div
446
- key={index}
447
- style={{
448
- borderBottom: `1px solid ${borderColor}`,
449
- }}
450
- >
451
- {event.type === "stateChange" ? (
452
- <div
453
- style={{
454
- padding: "2px 12px",
455
- background:
456
- colorScheme === "light"
457
- ? "rgba(0,0,0,0.05)"
458
- : "rgba(255,255,255,0.05)",
459
- display: "flex",
460
- alignItems: "center",
461
- }}
462
- >
463
- <span
464
- style={{
465
- fontFamily: "Menlo, monospace",
466
- fontSize: "11px",
467
- borderRadius: 4,
468
- display: "inline-block",
469
- }}
470
- >
471
- connection:{" "}
472
- <span style={{ fontStyle: "italic" }}>
473
- {event.state.toLowerCase()}
474
- </span>
475
- </span>
476
- </div>
477
- ) : (
478
- <div
479
- style={{
480
- padding: "0px 12px 1px",
481
- background:
482
- event.type === "send"
483
- ? "rgba(0,255,0,0.1)"
484
- : "rgba(255,0,0,0.1)",
542
+ <div ref={eventsContainerRef} style={styles.sectionInner}>
543
+ {connectionEvents?.map((event, index) =>
544
+ event.type === "stateChange" ? (
545
+ <InspectorRow key={index} colorScheme={colorScheme}>
546
+ connection:{" "}
547
+ <span style={{ fontStyle: "italic" }}>
548
+ {event.state.toLowerCase()}
549
+ </span>
550
+ </InspectorRow>
551
+ ) : (
552
+ <InspectorRow
553
+ key={index}
554
+ colorScheme={colorScheme}
555
+ variant={event.type === "send" ? "up" : "down"}
556
+ style={{
557
+ padding: "0px 12px 1px",
558
+ }}
559
+ >
560
+ <ObjectInspector
561
+ data={event.message}
562
+ theme={theme}
563
+ nodeRenderer={({
564
+ depth,
565
+ name,
566
+ data,
567
+ isNonenumerable,
568
+ expanded,
569
+ }: any) => {
570
+ const direction = event.type === "send" ? "up" : "down";
571
+
572
+ return depth === 0 ? (
573
+ <ObjectRootLabel direction={direction} data={data} />
574
+ ) : (
575
+ <ObjectLabel
576
+ direction={direction}
577
+ name={name}
578
+ data={data}
579
+ isNonenumerable={isNonenumerable}
580
+ />
581
+ );
485
582
  }}
486
- >
487
- <ObjectInspector
488
- data={event.message}
489
- theme={theme}
490
- nodeRenderer={({
491
- depth,
492
- name,
493
- data,
494
- isNonenumerable,
495
- expanded,
496
- }: any) => {
497
- const direction = event.type === "send" ? "up" : "down";
498
-
499
- return depth === 0 ? (
500
- <ObjectRootLabel direction={direction} data={data} />
501
- ) : (
502
- <ObjectLabel
503
- direction={direction}
504
- name={name}
505
- data={data}
506
- isNonenumerable={isNonenumerable}
507
- />
508
- );
509
- }}
510
- />
511
- </div>
512
- )}
513
- </div>
514
- ))}
583
+ />
584
+ </InspectorRow>
585
+ )
586
+ )}
515
587
  {!connectionEvents && (
516
588
  <div
517
589
  style={{
@@ -604,61 +676,6 @@ function Arrow({
604
676
  );
605
677
  }
606
678
 
607
- // <div className="flex py-px flex-col-reverse">
608
- // <button
609
- // type="button"
610
- // className={cx(
611
- // "px-4 py-2 cursor-pointer text-left",
612
- // 0 === historySnapshot.historyIndex && "bg-orange-500/25"
613
- // )}
614
- // onClick={() => {
615
- // // stateManager.goToHistoryIndex(0);
616
- // }}
617
- // >
618
- // <div className="flex justify-between gap-2">
619
- // <span>Initial State</span>
620
- // </div>
621
- // </button>
622
- // {historySnapshot.history.map((entry, index) => (
623
- // <button
624
- // key={index}
625
- // type="button"
626
- // className={cx(
627
- // "px-4 py-2 cursor-pointer text-left",
628
- // index === historySnapshot.historyIndex - 1 &&
629
- // "bg-orange-500/25"
630
- // )}
631
- // onClick={() => {
632
- // // stateManager.goToHistoryIndex(index + 1);
633
- // }}
634
- // >
635
- // <div className="flex justify-between gap-2">
636
- // <span>
637
- // {index + 1}. {entry.metadata.name}
638
- // </span>
639
- // <span>
640
- // {new Date(
641
- // entry.metadata.timestamp
642
- // ).toLocaleTimeString()}
643
- // </span>
644
- // </div>
645
- // <div className="font-mono text-xs">
646
- // {entry.redoPatches?.map((patch, index) => (
647
- // <pre key={index} className="max-w-full">
648
- // {patch.op === "remove"
649
- // ? `Delete ${pathToString(patch.path)}`
650
- // : `${pathToString(patch.path)} = ${JSON.stringify(
651
- // patch.value,
652
- // null,
653
- // 2
654
- // )}`}
655
- // </pre>
656
- // ))}
657
- // </div>
658
- // </button>
659
- // ))}
660
- // </div>
661
-
662
679
  function pathToString(extendedPath: ExtendedPathKey[]) {
663
680
  return extendedPath
664
681
  .map((key) =>
@@ -1,4 +1,8 @@
1
- import { ConnectionEvent, StateManager } from "@noya-app/state-manager";
1
+ import {
2
+ ConnectionEvent,
3
+ MultiplayerUser,
4
+ StateManager,
5
+ } from "@noya-app/state-manager";
2
6
  import React, { ComponentProps, useLayoutEffect } from "react";
3
7
  import { createRoot } from "react-dom/client";
4
8
 
@@ -14,6 +18,8 @@ function createPortalElement() {
14
18
  export function useStateInspector<S extends object, M>({
15
19
  state,
16
20
  connectionEvents,
21
+ connectedUsers,
22
+ userId,
17
23
  disabled = false,
18
24
  colorScheme,
19
25
  anchor,
@@ -21,6 +27,8 @@ export function useStateInspector<S extends object, M>({
21
27
  }: {
22
28
  state: S;
23
29
  connectionEvents?: ConnectionEvent<S>[];
30
+ connectedUsers?: MultiplayerUser[];
31
+ userId?: string;
24
32
  disabled: boolean;
25
33
  stateManager: StateManager<S, M>;
26
34
  } & Pick<ComponentProps<typeof StateInspector>, "colorScheme" | "anchor">) {
@@ -49,10 +57,21 @@ export function useStateInspector<S extends object, M>({
49
57
  <StateInspector
50
58
  state={state}
51
59
  connectionEvents={connectionEvents}
60
+ connectedUsers={connectedUsers}
61
+ userId={userId}
52
62
  colorScheme={colorScheme}
53
63
  anchor={anchor}
54
64
  stateManager={stateManager as StateManager<any, any>}
55
65
  />
56
66
  );
57
- }, [anchor, colorScheme, connectionEvents, root, state, stateManager]);
67
+ }, [
68
+ anchor,
69
+ colorScheme,
70
+ connectedUsers,
71
+ connectionEvents,
72
+ root,
73
+ state,
74
+ stateManager,
75
+ userId,
76
+ ]);
58
77
  }