@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.
package/dist/index.mjs CHANGED
@@ -46,7 +46,8 @@ function useObservable(observable) {
46
46
  }, [observable]);
47
47
  return value;
48
48
  }
49
- var trackEvents = new Observable(false);
49
+ var shouldTrackEvents$ = new Observable(false);
50
+ var shouldTrackTasks$ = new Observable(false);
50
51
 
51
52
  // src/inspector/useStateInspector.tsx
52
53
  import React3, { useLayoutEffect as useLayoutEffect2 } from "react";
@@ -223,7 +224,7 @@ function DisclosureSection({
223
224
  }
224
225
  }
225
226
  ),
226
- /* @__PURE__ */ React2.createElement("span", { style: { flex: "1 1 0" } }, title),
227
+ /* @__PURE__ */ React2.createElement("span", { style: { flex: "1 1 0", userSelect: "none" } }, title),
227
228
  right
228
229
  ),
229
230
  open && children
@@ -274,11 +275,13 @@ var StateInspector = memo(function StateInspector2({
274
275
  state,
275
276
  connectionEvents,
276
277
  connectedUsers,
278
+ tasks,
277
279
  userId,
278
280
  unstyled,
279
281
  colorScheme = "light",
280
- stateManager,
282
+ multiplayerStateManager,
281
283
  anchor = "right",
284
+ forceInit,
282
285
  ...props
283
286
  }) {
284
287
  const [didMount, setDidMount] = React2.useState(false);
@@ -307,15 +310,22 @@ var StateInspector = memo(function StateInspector2({
307
310
  "noya-multiplayer-react-show-data",
308
311
  true
309
312
  );
313
+ const [showTasks, setShowTasks] = useLocalStorageState(
314
+ "noya-multiplayer-react-show-tasks",
315
+ false
316
+ );
310
317
  useEffect2(() => {
311
- trackEvents.set(showEvents);
318
+ shouldTrackEvents$.set(showEvents);
312
319
  }, [showEvents]);
320
+ useEffect2(() => {
321
+ shouldTrackTasks$.set(showTasks);
322
+ }, [showTasks]);
313
323
  useEffect2(() => {
314
324
  if (eventsContainerRef.current) {
315
325
  eventsContainerRef.current.scrollTop = eventsContainerRef.current.scrollHeight;
316
326
  }
317
327
  }, [connectionEvents]);
318
- const historySnapshot = useManagedHistory(stateManager);
328
+ const historySnapshot = useManagedHistory(multiplayerStateManager.sm);
319
329
  useEffect2(() => {
320
330
  if (historyContainerRef.current) {
321
331
  historyContainerRef.current.scrollTop = historyContainerRef.current.scrollHeight;
@@ -339,7 +349,7 @@ var StateInspector = memo(function StateInspector2({
339
349
  const baseStyle = {
340
350
  position: "fixed",
341
351
  top: 12,
342
- ...anchor === "right" ? { right: 12 } : { left: 12 },
352
+ ...anchor.includes("right") ? { right: 12 } : { left: 12 },
343
353
  bottom: 12,
344
354
  width: 400,
345
355
  background: colorScheme === "light" ? "rgba(255,255,255,0.95)" : "rgba(0,0,0,0.95)",
@@ -365,8 +375,8 @@ var StateInspector = memo(function StateInspector2({
365
375
  style: {
366
376
  ...baseStyle,
367
377
  padding: "4px 10px",
368
- bottom: void 0,
369
- width: void 0
378
+ width: void 0,
379
+ ...anchor.includes("bottom") ? { bottom: 12, top: void 0 } : { bottom: void 0 }
370
380
  },
371
381
  onClick: () => setShowInspector(true)
372
382
  },
@@ -443,19 +453,42 @@ var StateInspector = memo(function StateInspector2({
443
453
  title: "Data",
444
454
  colorScheme,
445
455
  setOpen: setShowData,
446
- open: showData
456
+ open: showData,
457
+ right: /* @__PURE__ */ React2.createElement(
458
+ "span",
459
+ {
460
+ style: {
461
+ display: "flex",
462
+ gap: "4px"
463
+ }
464
+ },
465
+ /* @__PURE__ */ React2.createElement(
466
+ SmallButton,
467
+ {
468
+ theme,
469
+ onClick: () => {
470
+ forceInit();
471
+ }
472
+ },
473
+ "Reset"
474
+ )
475
+ )
447
476
  },
448
- /* @__PURE__ */ React2.createElement(
449
- "div",
477
+ /* @__PURE__ */ React2.createElement("div", { style: styles.sectionInner }, /* @__PURE__ */ React2.createElement(InspectorRow, { colorScheme }, /* @__PURE__ */ React2.createElement(
478
+ ObjectInspector,
450
479
  {
451
- style: {
452
- ...styles.sectionInner,
453
- gap: "1px",
454
- padding: "1px 12px"
455
- }
456
- },
457
- /* @__PURE__ */ React2.createElement(ObjectInspector, { data: state, theme })
458
- )
480
+ name: multiplayerStateManager.sm.schema ? "state" : void 0,
481
+ data: state,
482
+ theme
483
+ }
484
+ )), multiplayerStateManager.sm.schema && /* @__PURE__ */ React2.createElement(InspectorRow, { colorScheme }, /* @__PURE__ */ React2.createElement(
485
+ ObjectInspector,
486
+ {
487
+ name: "schema",
488
+ data: multiplayerStateManager.sm.schema,
489
+ theme
490
+ }
491
+ )))
459
492
  ),
460
493
  /* @__PURE__ */ React2.createElement(
461
494
  DisclosureSection,
@@ -463,7 +496,68 @@ var StateInspector = memo(function StateInspector2({
463
496
  open: showHistory,
464
497
  setOpen: setShowHistory,
465
498
  title: "History",
466
- colorScheme
499
+ colorScheme,
500
+ right: /* @__PURE__ */ React2.createElement(
501
+ "span",
502
+ {
503
+ style: {
504
+ display: "flex",
505
+ gap: "4px"
506
+ }
507
+ },
508
+ /* @__PURE__ */ React2.createElement(
509
+ SmallButton,
510
+ {
511
+ disabled: !historySnapshot.canUndo,
512
+ theme,
513
+ onClick: () => {
514
+ multiplayerStateManager.sm.undo();
515
+ }
516
+ },
517
+ /* @__PURE__ */ React2.createElement("span", { style: { width: "12px", height: "12px" } }, /* @__PURE__ */ React2.createElement(
518
+ "svg",
519
+ {
520
+ xmlns: "http://www.w3.org/2000/svg",
521
+ width: "1em",
522
+ height: "1em",
523
+ viewBox: "0 0 20 20"
524
+ },
525
+ /* @__PURE__ */ React2.createElement(
526
+ "path",
527
+ {
528
+ fill: "currentColor",
529
+ 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"
530
+ }
531
+ )
532
+ ))
533
+ ),
534
+ /* @__PURE__ */ React2.createElement(
535
+ SmallButton,
536
+ {
537
+ disabled: !historySnapshot.canRedo,
538
+ theme,
539
+ onClick: () => {
540
+ multiplayerStateManager.sm.redo();
541
+ }
542
+ },
543
+ /* @__PURE__ */ React2.createElement("span", { style: { width: "12px", height: "12px" } }, /* @__PURE__ */ React2.createElement(
544
+ "svg",
545
+ {
546
+ xmlns: "http://www.w3.org/2000/svg",
547
+ width: "1em",
548
+ height: "1em",
549
+ viewBox: "0 0 20 20"
550
+ },
551
+ /* @__PURE__ */ React2.createElement(
552
+ "path",
553
+ {
554
+ fill: "currentColor",
555
+ 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"
556
+ }
557
+ )
558
+ ))
559
+ )
560
+ )
467
561
  },
468
562
  /* @__PURE__ */ React2.createElement("div", { ref: historyContainerRef, style: styles.sectionInner }, /* @__PURE__ */ React2.createElement(
469
563
  "div",
@@ -518,6 +612,33 @@ var StateInspector = memo(function StateInspector2({
518
612
  ))
519
613
  )))
520
614
  ),
615
+ /* @__PURE__ */ React2.createElement(
616
+ DisclosureSection,
617
+ {
618
+ title: "Tasks",
619
+ colorScheme,
620
+ open: showTasks,
621
+ setOpen: setShowTasks
622
+ },
623
+ /* @__PURE__ */ React2.createElement("div", { style: styles.sectionInner }, tasks?.map((task) => /* @__PURE__ */ React2.createElement(
624
+ InspectorRow,
625
+ {
626
+ key: task.id,
627
+ colorScheme,
628
+ style: {
629
+ backgroundColor: task.status === "done" ? "rgba(0,255,0,0.2)" : task.status === "error" ? "rgba(255,0,0,0.2)" : void 0
630
+ }
631
+ },
632
+ /* @__PURE__ */ React2.createElement(
633
+ ObjectInspector,
634
+ {
635
+ name: task.name,
636
+ data: task.payload,
637
+ theme
638
+ }
639
+ )
640
+ )))
641
+ ),
521
642
  /* @__PURE__ */ React2.createElement(
522
643
  DisclosureSection,
523
644
  {
@@ -648,6 +769,42 @@ function ellipsis(str, maxLength, position = "end") {
648
769
  return `${str.slice(0, maxLength)}\u2026`;
649
770
  }
650
771
  }
772
+ function SmallButton({
773
+ children,
774
+ onClick,
775
+ style,
776
+ theme,
777
+ disabled
778
+ }) {
779
+ return /* @__PURE__ */ React2.createElement(
780
+ "button",
781
+ {
782
+ type: "button",
783
+ disabled,
784
+ onClick: (event) => {
785
+ event.stopPropagation();
786
+ onClick();
787
+ },
788
+ style: {
789
+ flex: "0",
790
+ appearance: "none",
791
+ background: "none",
792
+ color: theme.BASE_COLOR,
793
+ opacity: disabled ? 0.25 : 1,
794
+ border: "none",
795
+ fontSize: "12px",
796
+ whiteSpace: "nowrap",
797
+ display: "inline-flex",
798
+ alignItems: "center",
799
+ justifyContent: "center",
800
+ padding: "2px 0",
801
+ cursor: "pointer",
802
+ ...style
803
+ }
804
+ },
805
+ children
806
+ );
807
+ }
651
808
 
652
809
  // src/inspector/useStateInspector.tsx
653
810
  function createPortalElement() {
@@ -659,11 +816,13 @@ function useStateInspector({
659
816
  state,
660
817
  connectionEvents,
661
818
  connectedUsers,
819
+ tasks,
662
820
  userId,
663
821
  disabled = false,
664
822
  colorScheme,
665
823
  anchor,
666
- stateManager
824
+ multiplayerStateManager,
825
+ forceInit
667
826
  }) {
668
827
  const [root, setRoot] = React3.useState(null);
669
828
  useLayoutEffect2(() => {
@@ -687,10 +846,12 @@ function useStateInspector({
687
846
  state,
688
847
  connectionEvents,
689
848
  connectedUsers,
849
+ tasks,
690
850
  userId,
691
851
  colorScheme,
692
852
  anchor,
693
- stateManager
853
+ forceInit,
854
+ multiplayerStateManager
694
855
  }
695
856
  )
696
857
  );
@@ -701,8 +862,10 @@ function useStateInspector({
701
862
  connectionEvents,
702
863
  root,
703
864
  state,
704
- stateManager,
705
- userId
865
+ multiplayerStateManager,
866
+ userId,
867
+ tasks,
868
+ forceInit
706
869
  ]);
707
870
  }
708
871
 
@@ -774,58 +937,87 @@ function useSyncMultiplayerStateManager(multiplayerStateManager) {
774
937
  }
775
938
  var defaultStubSync = stubSync();
776
939
  function useMultiplayerState(initialState, options) {
777
- const { sync = defaultStubSync, ...rest } = options;
940
+ const { sync = defaultStubSync, ...rest } = options ?? {};
778
941
  const [multiplayerStateManager] = useState2(
779
942
  () => new MultiplayerStateManager(initialState, rest)
780
943
  );
781
944
  const syncRef = useRef(sync);
782
945
  const [connectionEvents, setConnectionEvents] = useState2();
783
946
  const [connectedUsers, setConnectedUsers] = useState2([]);
947
+ const [tasks, setTasks] = useState2([]);
784
948
  const [userId, setUserId] = useState2();
785
949
  const extras = useMemo(() => {
786
950
  return {
951
+ tasks,
787
952
  connectionEvents,
788
953
  connectedUsers
954
+ // evaluate: async (code: string) => {
955
+ // const payload = await rpcManager.request({ type: "eval", code });
956
+ // if (payload.type !== "eval") {
957
+ // throw new Error(`Unexpected rpc response type: ${payload.type}`);
958
+ // }
959
+ // },
789
960
  };
790
- }, [connectionEvents, connectedUsers]);
791
- const globalTrackEvents = useObservable(trackEvents);
792
- const trackConnectionEvents = options.trackConnectionEvents ?? globalTrackEvents;
961
+ }, [tasks, connectionEvents, connectedUsers]);
962
+ const globalTrackEvents = useObservable(shouldTrackEvents$);
963
+ const globalTrackTasks = useObservable(shouldTrackTasks$);
964
+ const shouldTrackEvents = options?.trackConnectionEvents ?? globalTrackEvents;
965
+ const shouldTrackTasks = options?.trackTasks ?? globalTrackTasks;
793
966
  const trackEventsCallbackRef = useRef();
794
967
  const trackConnectedUsersCallbackRef = useRef();
968
+ const trackTasksCallbackRef = useRef();
795
969
  useEffect3(() => {
796
- trackEventsCallbackRef.current = trackConnectionEvents ? (e) => setConnectionEvents((events) => {
970
+ trackEventsCallbackRef.current = shouldTrackEvents ? (e) => setConnectionEvents((events) => {
797
971
  const updated = events ? [...events, e] : [e];
798
972
  return updated.length > 50 ? updated.slice(-50) : updated;
799
973
  }) : void 0;
800
- }, [trackConnectionEvents]);
974
+ }, [shouldTrackEvents]);
801
975
  useEffect3(() => {
802
976
  trackConnectedUsersCallbackRef.current = (users, userId2) => {
803
977
  setConnectedUsers(users);
804
978
  setUserId(userId2);
805
979
  };
806
- });
980
+ }, []);
981
+ useEffect3(() => {
982
+ trackTasksCallbackRef.current = shouldTrackTasks ? (tasks2) => {
983
+ setTasks((old) => {
984
+ const newIds = new Set(tasks2.map((t) => t.id));
985
+ const unchanged = old.filter((t) => !newIds.has(t.id));
986
+ return [...unchanged, ...tasks2];
987
+ });
988
+ } : void 0;
989
+ }, [shouldTrackTasks]);
807
990
  useEffect3(() => {
808
991
  if (syncRef.current) {
809
992
  return syncRef.current({
810
993
  ms: multiplayerStateManager,
811
994
  onConnectionEvent: (e) => trackEventsCallbackRef.current?.(e),
812
- onChangeConnectedUsers: (users, userId2) => trackConnectedUsersCallbackRef.current?.(users, userId2)
995
+ onChangeConnectedUsers: (users, userId2) => trackConnectedUsersCallbackRef.current?.(users, userId2),
996
+ onChangeTasks: (tasks2) => trackTasksCallbackRef.current?.(tasks2)
813
997
  });
814
998
  }
815
999
  }, [multiplayerStateManager]);
816
1000
  const state = useSyncMultiplayerStateManager(multiplayerStateManager);
1001
+ const forceInit = useCallback(() => {
1002
+ const state2 = typeof initialState === "function" ? initialState() : initialState;
1003
+ const schema = options?.schema;
1004
+ multiplayerStateManager.sm.clearHistory();
1005
+ multiplayerStateManager.sendInit({ force: true, state: state2, schema });
1006
+ }, [initialState, multiplayerStateManager, options?.schema]);
817
1007
  useStateInspector({
818
- stateManager: multiplayerStateManager.sm,
1008
+ multiplayerStateManager,
819
1009
  state,
820
1010
  connectionEvents,
821
1011
  connectedUsers,
1012
+ tasks,
822
1013
  userId,
823
- disabled: !options.inspector,
824
- ...typeof options.inspector === "object" && options.inspector
1014
+ forceInit,
1015
+ disabled: !options?.inspector,
1016
+ ...typeof options?.inspector === "object" && options.inspector
825
1017
  });
826
1018
  return [
827
1019
  state,
828
- multiplayerStateManager.setOptimisticState,
1020
+ multiplayerStateManager.setState,
829
1021
  multiplayerStateManager,
830
1022
  extras
831
1023
  ];