@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.
- package/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +8 -0
- package/dist/index.d.mts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +217 -149
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +217 -149
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/hooks.ts +26 -5
- package/src/inspector/StateInspector.tsx +172 -155
- package/src/inspector/useStateInspector.tsx +21 -2
|
@@ -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={() =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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 {
|
|
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
|
-
}, [
|
|
67
|
+
}, [
|
|
68
|
+
anchor,
|
|
69
|
+
colorScheme,
|
|
70
|
+
connectedUsers,
|
|
71
|
+
connectionEvents,
|
|
72
|
+
root,
|
|
73
|
+
state,
|
|
74
|
+
stateManager,
|
|
75
|
+
userId,
|
|
76
|
+
]);
|
|
58
77
|
}
|