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

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,8 +1,11 @@
1
+ "use client";
2
+
1
3
  import {
2
4
  ConnectionEvent,
3
5
  ExtendedPathKey,
6
+ MultiplayerStateManager,
4
7
  MultiplayerUser,
5
- StateManager,
8
+ Task,
6
9
  } from "@noya-app/state-manager";
7
10
  import React, {
8
11
  CSSProperties,
@@ -20,7 +23,7 @@ import {
20
23
  chromeDark,
21
24
  chromeLight,
22
25
  } from "react-inspector";
23
- import { trackEvents } from "../globals";
26
+ import { shouldTrackEvents$, shouldTrackTasks$ } from "../globals";
24
27
  import { useManagedHistory } from "../hooks";
25
28
  import { useLocalStorageState } from "./useLocalStorageState";
26
29
 
@@ -170,7 +173,7 @@ function DisclosureSection({
170
173
  }}
171
174
  />
172
175
  )}
173
- <span style={{ flex: "1 1 0" }}>{title}</span>
176
+ <span style={{ flex: "1 1 0", userSelect: "none" }}>{title}</span>
174
177
  {right}
175
178
  </div>
176
179
  {open && children}
@@ -234,7 +237,7 @@ function InspectorRow({
234
237
 
235
238
  const HISTORY_ELEMENT_PREFIX = "noya-multiplayer-history-";
236
239
 
237
- type Anchor = "left" | "right";
240
+ type Anchor = "left" | "right" | "bottom left" | "bottom right";
238
241
 
239
242
  export const StateInspector = memo(function StateInspector<
240
243
  S extends object,
@@ -243,21 +246,25 @@ export const StateInspector = memo(function StateInspector<
243
246
  state,
244
247
  connectionEvents,
245
248
  connectedUsers,
249
+ tasks,
246
250
  userId,
247
251
  unstyled,
248
252
  colorScheme = "light",
249
- stateManager,
253
+ multiplayerStateManager,
250
254
  anchor = "right",
255
+ forceInit,
251
256
  ...props
252
257
  }: {
253
258
  state: S;
254
259
  connectionEvents?: ConnectionEvent<S>[];
255
260
  connectedUsers?: MultiplayerUser[];
261
+ tasks?: Task[];
256
262
  userId?: string;
257
263
  unstyled?: boolean;
258
264
  colorScheme?: "light" | "dark";
259
- stateManager: StateManager<S, M>;
265
+ multiplayerStateManager: MultiplayerStateManager<S, M>;
260
266
  anchor?: Anchor;
267
+ forceInit: () => void;
261
268
  } & ComponentPropsWithoutRef<"div">) {
262
269
  const [didMount, setDidMount] = React.useState(false);
263
270
 
@@ -288,11 +295,19 @@ export const StateInspector = memo(function StateInspector<
288
295
  "noya-multiplayer-react-show-data",
289
296
  true
290
297
  );
298
+ const [showTasks, setShowTasks] = useLocalStorageState(
299
+ "noya-multiplayer-react-show-tasks",
300
+ false
301
+ );
291
302
 
292
303
  useEffect(() => {
293
- trackEvents.set(showEvents);
304
+ shouldTrackEvents$.set(showEvents);
294
305
  }, [showEvents]);
295
306
 
307
+ useEffect(() => {
308
+ shouldTrackTasks$.set(showTasks);
309
+ }, [showTasks]);
310
+
296
311
  useEffect(() => {
297
312
  if (eventsContainerRef.current) {
298
313
  eventsContainerRef.current.scrollTop =
@@ -300,7 +315,7 @@ export const StateInspector = memo(function StateInspector<
300
315
  }
301
316
  }, [connectionEvents]);
302
317
 
303
- const historySnapshot = useManagedHistory(stateManager);
318
+ const historySnapshot = useManagedHistory(multiplayerStateManager.sm);
304
319
 
305
320
  useEffect(() => {
306
321
  if (historyContainerRef.current) {
@@ -334,7 +349,7 @@ export const StateInspector = memo(function StateInspector<
334
349
  const baseStyle: CSSProperties = {
335
350
  position: "fixed",
336
351
  top: 12,
337
- ...(anchor === "right" ? { right: 12 } : { left: 12 }),
352
+ ...(anchor.includes("right") ? { right: 12 } : { left: 12 }),
338
353
  bottom: 12,
339
354
  width: 400,
340
355
  background:
@@ -361,8 +376,10 @@ export const StateInspector = memo(function StateInspector<
361
376
  style={{
362
377
  ...baseStyle,
363
378
  padding: "4px 10px",
364
- bottom: undefined,
365
379
  width: undefined,
380
+ ...(anchor.includes("bottom")
381
+ ? { bottom: 12, top: undefined }
382
+ : { bottom: undefined }),
366
383
  }}
367
384
  onClick={() => setShowInspector(true)}
368
385
  >
@@ -433,15 +450,42 @@ export const StateInspector = memo(function StateInspector<
433
450
  colorScheme={colorScheme}
434
451
  setOpen={setShowData}
435
452
  open={showData}
453
+ right={
454
+ <span
455
+ style={{
456
+ display: "flex",
457
+ gap: "4px",
458
+ }}
459
+ >
460
+ <SmallButton
461
+ // disabled={!historySnapshot.canUndo}
462
+ theme={theme}
463
+ onClick={() => {
464
+ forceInit();
465
+ }}
466
+ >
467
+ Reset
468
+ </SmallButton>
469
+ </span>
470
+ }
436
471
  >
437
- <div
438
- style={{
439
- ...styles.sectionInner,
440
- gap: "1px",
441
- padding: "1px 12px",
442
- }}
443
- >
444
- <ObjectInspector data={state} theme={theme} />
472
+ <div style={styles.sectionInner}>
473
+ <InspectorRow colorScheme={colorScheme}>
474
+ <ObjectInspector
475
+ name={multiplayerStateManager.sm.schema ? "state" : undefined}
476
+ data={state}
477
+ theme={theme}
478
+ />
479
+ </InspectorRow>
480
+ {multiplayerStateManager.sm.schema && (
481
+ <InspectorRow colorScheme={colorScheme}>
482
+ <ObjectInspector
483
+ name="schema"
484
+ data={multiplayerStateManager.sm.schema}
485
+ theme={theme}
486
+ />
487
+ </InspectorRow>
488
+ )}
445
489
  </div>
446
490
  </DisclosureSection>
447
491
  <DisclosureSection
@@ -449,6 +493,57 @@ export const StateInspector = memo(function StateInspector<
449
493
  setOpen={setShowHistory}
450
494
  title="History"
451
495
  colorScheme={colorScheme}
496
+ right={
497
+ <span
498
+ style={{
499
+ display: "flex",
500
+ gap: "4px",
501
+ }}
502
+ >
503
+ <SmallButton
504
+ disabled={!historySnapshot.canUndo}
505
+ theme={theme}
506
+ onClick={() => {
507
+ multiplayerStateManager.sm.undo();
508
+ }}
509
+ >
510
+ <span style={{ width: "12px", height: "12px" }}>
511
+ <svg
512
+ xmlns="http://www.w3.org/2000/svg"
513
+ width="1em"
514
+ height="1em"
515
+ viewBox="0 0 20 20"
516
+ >
517
+ <path
518
+ fill="currentColor"
519
+ d="M12 5H7V2L1 6l6 4V7h5c2.2 0 4 1.8 4 4s-1.8 4-4 4H7v2h5c3.3 0 6-2.7 6-6s-2.7-6-6-6"
520
+ />
521
+ </svg>
522
+ </span>
523
+ </SmallButton>
524
+ <SmallButton
525
+ disabled={!historySnapshot.canRedo}
526
+ theme={theme}
527
+ onClick={() => {
528
+ multiplayerStateManager.sm.redo();
529
+ }}
530
+ >
531
+ <span style={{ width: "12px", height: "12px" }}>
532
+ <svg
533
+ xmlns="http://www.w3.org/2000/svg"
534
+ width="1em"
535
+ height="1em"
536
+ viewBox="0 0 20 20"
537
+ >
538
+ <path
539
+ fill="currentColor"
540
+ d="M8 5h5V2l6 4l-6 4V7H8c-2.2 0-4 1.8-4 4s1.8 4 4 4h5v2H8c-3.3 0-6-2.7-6-6s2.7-6 6-6"
541
+ />
542
+ </svg>
543
+ </span>
544
+ </SmallButton>
545
+ </span>
546
+ }
452
547
  >
453
548
  <div ref={historyContainerRef} style={styles.sectionInner}>
454
549
  <div
@@ -533,6 +628,35 @@ export const StateInspector = memo(function StateInspector<
533
628
  ))}
534
629
  </div>
535
630
  </DisclosureSection>
631
+ <DisclosureSection
632
+ title="Tasks"
633
+ colorScheme={colorScheme}
634
+ open={showTasks}
635
+ setOpen={setShowTasks}
636
+ >
637
+ <div style={styles.sectionInner}>
638
+ {tasks?.map((task) => (
639
+ <InspectorRow
640
+ key={task.id}
641
+ colorScheme={colorScheme}
642
+ style={{
643
+ backgroundColor:
644
+ task.status === "done"
645
+ ? "rgba(0,255,0,0.2)"
646
+ : task.status === "error"
647
+ ? "rgba(255,0,0,0.2)"
648
+ : undefined,
649
+ }}
650
+ >
651
+ <ObjectInspector
652
+ name={task.name}
653
+ data={task.payload}
654
+ theme={theme}
655
+ />
656
+ </InspectorRow>
657
+ ))}
658
+ </div>
659
+ </DisclosureSection>
536
660
  <DisclosureSection
537
661
  open={showEvents}
538
662
  setOpen={setShowEvents}
@@ -710,3 +834,46 @@ function ellipsis(
710
834
  return `${str.slice(0, maxLength)}…`;
711
835
  }
712
836
  }
837
+
838
+ function SmallButton({
839
+ children,
840
+ onClick,
841
+ style,
842
+ theme,
843
+ disabled,
844
+ }: {
845
+ children: React.ReactNode;
846
+ onClick: () => void;
847
+ style?: CSSProperties;
848
+ theme: typeof chromeLight;
849
+ disabled?: boolean;
850
+ }) {
851
+ return (
852
+ <button
853
+ type="button"
854
+ disabled={disabled}
855
+ onClick={(event) => {
856
+ event.stopPropagation();
857
+ onClick();
858
+ }}
859
+ style={{
860
+ flex: "0",
861
+ appearance: "none",
862
+ background: "none",
863
+ color: theme.BASE_COLOR,
864
+ opacity: disabled ? 0.25 : 1,
865
+ border: "none",
866
+ fontSize: "12px",
867
+ whiteSpace: "nowrap",
868
+ display: "inline-flex",
869
+ alignItems: "center",
870
+ justifyContent: "center",
871
+ padding: "2px 0",
872
+ cursor: "pointer",
873
+ ...style,
874
+ }}
875
+ >
876
+ {children}
877
+ </button>
878
+ );
879
+ }
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import React from "react";
2
4
 
3
5
  const localStorage = typeof window !== "undefined" ? window.localStorage : null;
@@ -1,7 +1,10 @@
1
+ "use client";
2
+
1
3
  import {
2
4
  ConnectionEvent,
5
+ MultiplayerStateManager,
3
6
  MultiplayerUser,
4
- StateManager,
7
+ Task,
5
8
  } from "@noya-app/state-manager";
6
9
  import React, { ComponentProps, useLayoutEffect } from "react";
7
10
  import { createRoot } from "react-dom/client";
@@ -19,18 +22,22 @@ export function useStateInspector<S extends object, M>({
19
22
  state,
20
23
  connectionEvents,
21
24
  connectedUsers,
25
+ tasks,
22
26
  userId,
23
27
  disabled = false,
24
28
  colorScheme,
25
29
  anchor,
26
- stateManager,
30
+ multiplayerStateManager,
31
+ forceInit,
27
32
  }: {
28
33
  state: S;
29
34
  connectionEvents?: ConnectionEvent<S>[];
30
35
  connectedUsers?: MultiplayerUser[];
36
+ tasks?: Task[];
31
37
  userId?: string;
32
38
  disabled: boolean;
33
- stateManager: StateManager<S, M>;
39
+ multiplayerStateManager: MultiplayerStateManager<S, M>;
40
+ forceInit: () => void;
34
41
  } & Pick<ComponentProps<typeof StateInspector>, "colorScheme" | "anchor">) {
35
42
  const [root, setRoot] = React.useState<Root | null>(null);
36
43
 
@@ -58,10 +65,14 @@ export function useStateInspector<S extends object, M>({
58
65
  state={state}
59
66
  connectionEvents={connectionEvents}
60
67
  connectedUsers={connectedUsers}
68
+ tasks={tasks}
61
69
  userId={userId}
62
70
  colorScheme={colorScheme}
63
71
  anchor={anchor}
64
- stateManager={stateManager as StateManager<any, any>}
72
+ forceInit={forceInit}
73
+ multiplayerStateManager={
74
+ multiplayerStateManager as MultiplayerStateManager<any, any>
75
+ }
65
76
  />
66
77
  );
67
78
  }, [
@@ -71,7 +82,9 @@ export function useStateInspector<S extends object, M>({
71
82
  connectionEvents,
72
83
  root,
73
84
  state,
74
- stateManager,
85
+ multiplayerStateManager,
75
86
  userId,
87
+ tasks,
88
+ forceInit,
76
89
  ]);
77
90
  }