@noya-app/noya-multiplayer-react 0.1.61 → 0.1.63
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/.eslintrc.js +1 -5
- package/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +19 -0
- package/dist/index.bundle.js +50 -17
- package/dist/index.d.mts +62 -22
- package/dist/index.d.ts +62 -22
- package/dist/index.js +1352 -585
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1350 -593
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/NoyaStateContext.tsx +23 -9
- package/src/__tests__/serialize.test.ts +126 -0
- package/src/ai.ts +2 -0
- package/src/index.ts +1 -0
- package/src/inspector/ColoredDot.tsx +17 -0
- package/src/inspector/ObjectRootLabel.tsx +48 -0
- package/src/inspector/StateInspector.tsx +125 -721
- package/src/inspector/StateInspectorArrow.tsx +28 -0
- package/src/inspector/StateInspectorButton.tsx +45 -0
- package/src/inspector/StateInspectorDisclosureSection.tsx +97 -0
- package/src/inspector/StateInspectorRow.tsx +57 -0
- package/src/inspector/StateInspectorToggleButton.tsx +82 -0
- package/src/inspector/inspectorTheme.ts +21 -0
- package/src/inspector/sections/EventsSection.tsx +89 -0
- package/src/inspector/sections/HistorySection.tsx +236 -0
- package/src/inspector/serialization.ts +202 -0
- package/src/inspector/utils.ts +60 -0
- package/src/inspector/zip/TinyZip.ts +464 -0
- package/src/inspector/zip/crc32.ts +16 -0
- package/src/inspector/zip/struct.ts +117 -0
- package/src/noyaApp.ts +2 -1
- package/src/useObservable.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-multiplayer-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.63",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"dev": "npm run build -- --watch"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@noya-app/state-manager": "0.1.
|
|
15
|
+
"@noya-app/state-manager": "0.1.49",
|
|
16
16
|
"@noya-app/emitter": "0.1.0",
|
|
17
|
-
"@noya-app/observable": "0.1.
|
|
17
|
+
"@noya-app/observable": "0.1.11",
|
|
18
18
|
"@noya-app/task-runner": "0.1.6",
|
|
19
|
-
"@noya-app/react-utils": "0.1.
|
|
19
|
+
"@noya-app/react-utils": "0.1.24",
|
|
20
20
|
"react-inspector": "6.0.2"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
package/src/NoyaStateContext.tsx
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { MenuItem } from "@noya-app/noya-designsystem";
|
|
2
4
|
import { Input, OutputTransform } from "@noya-app/noya-schemas";
|
|
3
5
|
import {
|
|
@@ -11,6 +13,7 @@ import {
|
|
|
11
13
|
ConnectedUsersManager,
|
|
12
14
|
CreateParams,
|
|
13
15
|
EphemeralUserDataManager,
|
|
16
|
+
MultiplayerPatchMetadata,
|
|
14
17
|
MultiplayerStateManager,
|
|
15
18
|
NoyaManager,
|
|
16
19
|
PublishCallback,
|
|
@@ -244,13 +247,16 @@ export function createNoyaContext<
|
|
|
244
247
|
schema: Schema;
|
|
245
248
|
mergeHistoryEntries?: StateManagerOptions<
|
|
246
249
|
Static<Schema>,
|
|
247
|
-
M
|
|
250
|
+
M & Pick<MultiplayerPatchMetadata, "name" | "timestamp">
|
|
248
251
|
>["mergeHistoryEntries"];
|
|
249
252
|
safeEval?: SafeEval;
|
|
250
253
|
outputTransforms?: CreateParams<OutputTransform>[];
|
|
251
254
|
inputs?: CreateParams<Input>[];
|
|
252
255
|
}) {
|
|
253
256
|
type S = Static<Schema>;
|
|
257
|
+
type PartialM = Partial<
|
|
258
|
+
M & Pick<MultiplayerPatchMetadata, "name" | "timestamp">
|
|
259
|
+
>;
|
|
254
260
|
|
|
255
261
|
const NoyaStateContext = AnyNoyaStateContext as React.Context<
|
|
256
262
|
NoyaManager<S, M, E> | undefined
|
|
@@ -339,14 +345,14 @@ export function createNoyaContext<
|
|
|
339
345
|
|
|
340
346
|
function useSetValue(): (
|
|
341
347
|
...args:
|
|
342
|
-
| [metadata:
|
|
348
|
+
| [metadata: PartialM, value: SetStateAction<S>]
|
|
343
349
|
| [value: SetStateAction<S>]
|
|
344
350
|
) => void;
|
|
345
351
|
function useSetValue<P extends PathKey[] | string>(
|
|
346
352
|
path: P
|
|
347
353
|
): (
|
|
348
354
|
...args:
|
|
349
|
-
| [metadata:
|
|
355
|
+
| [metadata: PartialM, value: SetStateAction<GetAtPath<S, P>>]
|
|
350
356
|
| [value: SetStateAction<GetAtPath<S, P>>]
|
|
351
357
|
) => void;
|
|
352
358
|
function useSetValue<P extends PathKey[] | string>(path?: P) {
|
|
@@ -355,7 +361,7 @@ export function createNoyaContext<
|
|
|
355
361
|
const setValue = useCallback(
|
|
356
362
|
(
|
|
357
363
|
...args:
|
|
358
|
-
| [metadata:
|
|
364
|
+
| [metadata: PartialM, value: SetStateAction<GetAtPath<S, P>>]
|
|
359
365
|
| [value: SetStateAction<GetAtPath<S, P>>]
|
|
360
366
|
): void => {
|
|
361
367
|
const [metadata, value] = args.length === 2 ? args : [{}, args[0]];
|
|
@@ -376,7 +382,7 @@ export function createNoyaContext<
|
|
|
376
382
|
S,
|
|
377
383
|
(
|
|
378
384
|
...args:
|
|
379
|
-
| [metadata:
|
|
385
|
+
| [metadata: PartialM, value: SetStateAction<S>]
|
|
380
386
|
| [value: SetStateAction<S>]
|
|
381
387
|
) => void,
|
|
382
388
|
];
|
|
@@ -387,7 +393,7 @@ export function createNoyaContext<
|
|
|
387
393
|
A,
|
|
388
394
|
(
|
|
389
395
|
...args:
|
|
390
|
-
| [metadata:
|
|
396
|
+
| [metadata: PartialM, value: SetStateAction<S>]
|
|
391
397
|
| [value: SetStateAction<S>]
|
|
392
398
|
) => void,
|
|
393
399
|
];
|
|
@@ -397,7 +403,7 @@ export function createNoyaContext<
|
|
|
397
403
|
GetAtPath<S, P>,
|
|
398
404
|
(
|
|
399
405
|
...args:
|
|
400
|
-
| [metadata:
|
|
406
|
+
| [metadata: PartialM, value: SetStateAction<GetAtPath<S, P>>]
|
|
401
407
|
| [value: SetStateAction<GetAtPath<S, P>>]
|
|
402
408
|
) => void,
|
|
403
409
|
];
|
|
@@ -417,7 +423,10 @@ export function createNoyaContext<
|
|
|
417
423
|
function useStateManager() {
|
|
418
424
|
const { noyaManager } = useAnyNoyaStateContext();
|
|
419
425
|
|
|
420
|
-
return noyaManager.multiplayerStateManager as MultiplayerStateManager<
|
|
426
|
+
return noyaManager.multiplayerStateManager as MultiplayerStateManager<
|
|
427
|
+
S,
|
|
428
|
+
PartialM
|
|
429
|
+
>;
|
|
421
430
|
}
|
|
422
431
|
|
|
423
432
|
function useEphemeralUserDataManager() {
|
|
@@ -433,7 +442,12 @@ export function createNoyaContext<
|
|
|
433
442
|
}
|
|
434
443
|
|
|
435
444
|
function useNoyaManager() {
|
|
436
|
-
return useAnyNoyaStateContext().noyaManager as NoyaManager<
|
|
445
|
+
return useAnyNoyaStateContext().noyaManager as NoyaManager<
|
|
446
|
+
S,
|
|
447
|
+
PartialM,
|
|
448
|
+
E,
|
|
449
|
+
MenuT
|
|
450
|
+
>;
|
|
437
451
|
}
|
|
438
452
|
|
|
439
453
|
function useLeftMenuItems() {
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Asset } from "@noya-app/noya-schemas";
|
|
2
|
+
import { Base64 } from "@noya-app/noya-utils";
|
|
3
|
+
import { NoyaManager, Static, Type } from "@noya-app/state-manager";
|
|
4
|
+
import { expect, it } from "bun:test";
|
|
5
|
+
import {
|
|
6
|
+
decodeAll,
|
|
7
|
+
encodeAll,
|
|
8
|
+
exportAll,
|
|
9
|
+
importAll,
|
|
10
|
+
} from "../inspector/serialization";
|
|
11
|
+
|
|
12
|
+
const asset1Data = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
|
|
13
|
+
const asset1Id = "2717f281-307d-4e9e-aa3b-68d29d4a1723";
|
|
14
|
+
const asset1DataUrl = `data:application/octet-stream;base64,${Base64.encode(
|
|
15
|
+
asset1Data
|
|
16
|
+
)}`;
|
|
17
|
+
const asset1 = {
|
|
18
|
+
id: asset1Id,
|
|
19
|
+
url: asset1DataUrl,
|
|
20
|
+
createdAt: new Date().toISOString(),
|
|
21
|
+
size: asset1Data.byteLength,
|
|
22
|
+
};
|
|
23
|
+
const assets: Asset[] = [asset1];
|
|
24
|
+
const assetMap = {
|
|
25
|
+
[asset1Id]: asset1Data,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
it("should serialize and deserialize the state", () => {
|
|
29
|
+
const schema = Type.Object({
|
|
30
|
+
name: Type.String(),
|
|
31
|
+
age: Type.Number(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const initialState: Static<typeof schema> = {
|
|
35
|
+
name: "John",
|
|
36
|
+
age: 30,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const buffer = encodeAll({
|
|
40
|
+
state: initialState,
|
|
41
|
+
schema,
|
|
42
|
+
assets,
|
|
43
|
+
assetMap,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const decoded = decodeAll(buffer);
|
|
47
|
+
|
|
48
|
+
expect(decoded.state).toEqual(initialState);
|
|
49
|
+
expect(decoded.schema).toEqual(schema);
|
|
50
|
+
expect(decoded.assets).toEqual(assets);
|
|
51
|
+
expect(decoded.assetMap).toEqual(assetMap);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should import and export the noya manager state", async () => {
|
|
55
|
+
const schema = Type.Object({
|
|
56
|
+
name: Type.String(),
|
|
57
|
+
age: Type.Number(),
|
|
58
|
+
assetRef: Type.String(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const initialState: Static<typeof schema> = {
|
|
62
|
+
name: "John",
|
|
63
|
+
age: 30,
|
|
64
|
+
assetRef: asset1Id,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const noyaManager = new NoyaManager(initialState, {
|
|
68
|
+
schema,
|
|
69
|
+
initialAssets: assets,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const buffer = await exportAll(noyaManager);
|
|
73
|
+
|
|
74
|
+
const stateToReplace = {
|
|
75
|
+
name: "Jane",
|
|
76
|
+
age: 20,
|
|
77
|
+
assetRef: "not-found",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const schemaToReplace = Type.Object({
|
|
81
|
+
name: Type.String(),
|
|
82
|
+
age: Type.Number(),
|
|
83
|
+
isAdmin: Type.Boolean(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const newNoyaManager = new NoyaManager(stateToReplace, {
|
|
87
|
+
schema: schemaToReplace,
|
|
88
|
+
mode: "standalone",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
let didCall = false;
|
|
92
|
+
|
|
93
|
+
await importAll(buffer, newNoyaManager, {
|
|
94
|
+
shouldAllowSchemaChange: () => {
|
|
95
|
+
didCall = true;
|
|
96
|
+
return true;
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(didCall).toBe(true);
|
|
101
|
+
|
|
102
|
+
const importedAssets = newNoyaManager.assetManager.assets$.get();
|
|
103
|
+
const firstAsset = importedAssets.at(0);
|
|
104
|
+
|
|
105
|
+
if (!firstAsset) {
|
|
106
|
+
throw new Error("First asset not found");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
expect(newNoyaManager.multiplayerStateManager.optimisticState$.get()).toEqual(
|
|
110
|
+
{
|
|
111
|
+
...initialState,
|
|
112
|
+
assetRef: firstAsset.id,
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
expect(firstAsset).toMatchObject({
|
|
117
|
+
size: asset1.size,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// actualFetch to handle blob: urls
|
|
121
|
+
const firstAssetData = await (globalThis as any)
|
|
122
|
+
.actualFetch(firstAsset.url)
|
|
123
|
+
.then((res: any) => res.arrayBuffer());
|
|
124
|
+
|
|
125
|
+
expect(new Uint8Array(firstAssetData)).toEqual(asset1Data);
|
|
126
|
+
});
|
package/src/ai.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from "@noya-app/state-manager";
|
|
|
2
2
|
export * from "./ai";
|
|
3
3
|
export * from "./components/UserPointersOverlay";
|
|
4
4
|
export * from "./hooks";
|
|
5
|
+
export * from "./inspector/serialization";
|
|
5
6
|
export * from "./inspector/StateInspector";
|
|
6
7
|
export * from "./inspector/useStateInspector";
|
|
7
8
|
export * from "./noyaApp";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export function ColoredDot({ type }: { type: "success" | "error" }) {
|
|
4
|
+
return (
|
|
5
|
+
<span
|
|
6
|
+
style={{
|
|
7
|
+
display: "inline-block",
|
|
8
|
+
width: 6,
|
|
9
|
+
height: 6,
|
|
10
|
+
background:
|
|
11
|
+
type === "success" ? "rgba(0,200,0,0.8)" : "rgba(200,0,0,0.8)",
|
|
12
|
+
borderRadius: "50%",
|
|
13
|
+
verticalAlign: "middle",
|
|
14
|
+
}}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { FC } from "react";
|
|
2
|
+
import { ObjectName, ObjectPreview } from "react-inspector";
|
|
3
|
+
|
|
4
|
+
type MessageDirection = "up" | "down";
|
|
5
|
+
|
|
6
|
+
export const ObjectRootLabel: FC<{
|
|
7
|
+
name?: string;
|
|
8
|
+
data: any;
|
|
9
|
+
direction?: MessageDirection;
|
|
10
|
+
}> = ({ name, data, direction }) => {
|
|
11
|
+
if (typeof name === "string") {
|
|
12
|
+
return (
|
|
13
|
+
<span>
|
|
14
|
+
<ObjectName name={name} />
|
|
15
|
+
<span>: </span>
|
|
16
|
+
<ObjectPreview data={data} />
|
|
17
|
+
</span>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
if (direction === "up" || direction === "down") {
|
|
21
|
+
const arrow = direction === "up" ? "↑" : "↓";
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<span>
|
|
25
|
+
<span
|
|
26
|
+
style={{
|
|
27
|
+
background:
|
|
28
|
+
direction === "up" ? "rgba(0,255,0,0.2)" : "rgba(255,0,0,0.2)",
|
|
29
|
+
// color: "white",
|
|
30
|
+
width: 12,
|
|
31
|
+
height: 12,
|
|
32
|
+
borderRadius: 2,
|
|
33
|
+
display: "inline-flex",
|
|
34
|
+
justifyContent: "center",
|
|
35
|
+
alignItems: "center",
|
|
36
|
+
marginRight: 4,
|
|
37
|
+
lineHeight: "12px",
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{arrow}
|
|
41
|
+
</span>
|
|
42
|
+
<ObjectPreview data={data} />
|
|
43
|
+
</span>
|
|
44
|
+
);
|
|
45
|
+
} else {
|
|
46
|
+
return <ObjectPreview data={data} />;
|
|
47
|
+
}
|
|
48
|
+
};
|