@noya-app/noya-multiplayer-react 0.1.62 → 0.1.64
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 +11 -11
- package/CHANGELOG.md +20 -0
- package/dist/index.bundle.js +50 -17
- package/dist/index.d.mts +87 -24
- package/dist/index.d.ts +87 -24
- package/dist/index.js +2556 -708
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2523 -692
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/NoyaStateContext.tsx +248 -28
- 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 +266 -741
- 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/StateInspectorTitleLabel.tsx +13 -0
- package/src/inspector/StateInspectorToggleButton.tsx +82 -0
- package/src/inspector/inspectorTheme.ts +21 -0
- package/src/inspector/sections/ActivityEventsSection.tsx +136 -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 +3 -2
- 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.64",
|
|
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.50",
|
|
16
16
|
"@noya-app/emitter": "0.1.0",
|
|
17
|
-
"@noya-app/observable": "0.1.
|
|
17
|
+
"@noya-app/observable": "0.1.12",
|
|
18
18
|
"@noya-app/task-runner": "0.1.6",
|
|
19
|
-
"@noya-app/react-utils": "0.1.
|
|
19
|
+
"@noya-app/react-utils": "0.1.25",
|
|
20
20
|
"react-inspector": "6.0.2"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
package/src/NoyaStateContext.tsx
CHANGED
|
@@ -1,31 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import { MenuItem } from "@noya-app/noya-designsystem";
|
|
2
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
diffResourceMaps,
|
|
6
|
+
Input,
|
|
7
|
+
MediaItem,
|
|
8
|
+
OutputTransform,
|
|
9
|
+
Resource,
|
|
10
|
+
ResourceCreateMap,
|
|
11
|
+
ResourceMap,
|
|
12
|
+
} from "@noya-app/noya-schemas";
|
|
3
13
|
import {
|
|
4
14
|
GetAtPath,
|
|
5
15
|
Observable,
|
|
6
16
|
ObservableOptions,
|
|
7
17
|
PathKey,
|
|
8
18
|
} from "@noya-app/observable";
|
|
9
|
-
import { useStableCallback } from "@noya-app/react-utils";
|
|
19
|
+
import { useJsonMemo, useStableCallback } from "@noya-app/react-utils";
|
|
10
20
|
import {
|
|
21
|
+
ActivityEventsManager,
|
|
11
22
|
ConnectedUsersManager,
|
|
12
23
|
CreateParams,
|
|
13
24
|
EphemeralUserDataManager,
|
|
25
|
+
MultiplayerPatchMetadata,
|
|
14
26
|
MultiplayerStateManager,
|
|
15
27
|
NoyaManager,
|
|
16
28
|
PublishCallback,
|
|
29
|
+
ResourceManager,
|
|
17
30
|
SafeEval,
|
|
18
31
|
StateManagerOptions,
|
|
19
32
|
Static,
|
|
33
|
+
StreamFilter,
|
|
20
34
|
TSchema,
|
|
21
35
|
} from "@noya-app/state-manager";
|
|
22
36
|
import React, {
|
|
23
37
|
createContext,
|
|
38
|
+
Dispatch,
|
|
24
39
|
SetStateAction,
|
|
25
40
|
useCallback,
|
|
26
41
|
useContext,
|
|
27
42
|
useEffect,
|
|
28
43
|
useMemo,
|
|
44
|
+
useState,
|
|
29
45
|
} from "react";
|
|
30
46
|
import {
|
|
31
47
|
AppTheme,
|
|
@@ -204,6 +220,27 @@ export function useConnectedUsersManager() {
|
|
|
204
220
|
return useContext(ConnectedUsersContext);
|
|
205
221
|
}
|
|
206
222
|
|
|
223
|
+
export function useConnectedUsers() {
|
|
224
|
+
const connectedUsersManager = useConnectedUsersManager();
|
|
225
|
+
return useObservable(connectedUsersManager?.connectedUsers$);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function useConnectedUser(userId?: string | null) {
|
|
229
|
+
const connectedUsersManager = useConnectedUsersManager();
|
|
230
|
+
return useObservable(
|
|
231
|
+
connectedUsersManager?.connectedUsers$,
|
|
232
|
+
useCallback(
|
|
233
|
+
(users) => {
|
|
234
|
+
return users.find((user) => {
|
|
235
|
+
const prefix = user.id.slice(0, 8);
|
|
236
|
+
return userId?.includes(prefix);
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
[userId]
|
|
240
|
+
)
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
207
244
|
export const AnyEphemeralUserDataManagerContext = createContext<
|
|
208
245
|
EphemeralUserDataManager<object> | undefined
|
|
209
246
|
>(undefined);
|
|
@@ -217,6 +254,163 @@ export function useCurrentUserId() {
|
|
|
217
254
|
return useObservable(connectedUsersManager?.currentUserId$);
|
|
218
255
|
}
|
|
219
256
|
|
|
257
|
+
export function useIsProcessing() {
|
|
258
|
+
const { noyaManager } = useAnyNoyaStateContext();
|
|
259
|
+
return useObservable(noyaManager.isProcessing$);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function useResourceManager(): ResourceManager {
|
|
263
|
+
const { noyaManager } = useAnyNoyaStateContext();
|
|
264
|
+
return noyaManager.resourceManager;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// export function useResources(): Resource[] {
|
|
268
|
+
// const { noyaManager } = useAnyNoyaStateContext();
|
|
269
|
+
// const resources = useObservable(noyaManager.resourceManager.resources$);
|
|
270
|
+
// return resources;
|
|
271
|
+
// }
|
|
272
|
+
|
|
273
|
+
export function resourceToMediaItem(resource: Resource): MediaItem {
|
|
274
|
+
switch (resource.type) {
|
|
275
|
+
case "asset":
|
|
276
|
+
return { kind: "asset", id: resource.id, assetId: resource.assetId };
|
|
277
|
+
case "file":
|
|
278
|
+
return { kind: "noyaFile", id: resource.id, fileId: resource.fileId };
|
|
279
|
+
default:
|
|
280
|
+
return { kind: "folder", id: resource.id };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function useResources({
|
|
285
|
+
shouldDeleteAssets = true,
|
|
286
|
+
}: {
|
|
287
|
+
shouldDeleteAssets?: boolean;
|
|
288
|
+
} = {}): [ResourceMap, Dispatch<SetStateAction<ResourceCreateMap>>] {
|
|
289
|
+
const resourceManager = useResourceManager();
|
|
290
|
+
const assetManager = useAssetManager();
|
|
291
|
+
|
|
292
|
+
const resourceMap = useObservable(
|
|
293
|
+
resourceManager.optimisticResources$,
|
|
294
|
+
useCallback((resources) => {
|
|
295
|
+
return Object.fromEntries(
|
|
296
|
+
resources.map((resource) => [resource.path, resource])
|
|
297
|
+
);
|
|
298
|
+
}, [])
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const setResourceMap = useCallback(
|
|
302
|
+
async (action: SetStateAction<ResourceCreateMap>) => {
|
|
303
|
+
const newResourceMap =
|
|
304
|
+
action instanceof Function ? action(resourceMap) : action;
|
|
305
|
+
|
|
306
|
+
const { addedResources, removedResources, modifiedResources } =
|
|
307
|
+
diffResourceMaps(resourceMap, newResourceMap, "id");
|
|
308
|
+
|
|
309
|
+
if (addedResources.length > 0) {
|
|
310
|
+
const payload = addedResources.map((resource) => {
|
|
311
|
+
switch (resource.type) {
|
|
312
|
+
case "directory":
|
|
313
|
+
return {
|
|
314
|
+
path: resource.path,
|
|
315
|
+
type: "directory" as const,
|
|
316
|
+
};
|
|
317
|
+
case "asset":
|
|
318
|
+
return {
|
|
319
|
+
path: resource.path,
|
|
320
|
+
type: "asset" as const,
|
|
321
|
+
assetId: resource.assetId,
|
|
322
|
+
asset: resource.asset,
|
|
323
|
+
};
|
|
324
|
+
case "file":
|
|
325
|
+
return {
|
|
326
|
+
path: resource.path,
|
|
327
|
+
type: "file" as const,
|
|
328
|
+
fileId: resource.fileId,
|
|
329
|
+
};
|
|
330
|
+
case "resource":
|
|
331
|
+
return {
|
|
332
|
+
path: resource.path,
|
|
333
|
+
type: "resource" as const,
|
|
334
|
+
fileId: resource.fileId,
|
|
335
|
+
resourceId: resource.resourceId,
|
|
336
|
+
};
|
|
337
|
+
default:
|
|
338
|
+
throw new Error(
|
|
339
|
+
`Unknown resource type: ${(resource as any).type}`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await resourceManager.createResource(payload);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for (const resource of removedResources) {
|
|
348
|
+
await resourceManager.deleteResource({ id: resource.id });
|
|
349
|
+
|
|
350
|
+
if (shouldDeleteAssets && resource.type === "asset") {
|
|
351
|
+
try {
|
|
352
|
+
await assetManager.delete(resource.assetId);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.error("Failed to delete asset:", error);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
await Promise.all(
|
|
360
|
+
modifiedResources.map(({ id, path, assetId, asset }) =>
|
|
361
|
+
resourceManager.updateResource({
|
|
362
|
+
id,
|
|
363
|
+
path,
|
|
364
|
+
assetId,
|
|
365
|
+
asset,
|
|
366
|
+
})
|
|
367
|
+
)
|
|
368
|
+
);
|
|
369
|
+
},
|
|
370
|
+
[resourceMap, resourceManager, shouldDeleteAssets, assetManager]
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
return [resourceMap, setResourceMap];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const emptyArray: unknown[] = [];
|
|
377
|
+
|
|
378
|
+
export function useActivityEvents(streamFilter: StreamFilter) {
|
|
379
|
+
const { noyaManager } = useAnyNoyaStateContext();
|
|
380
|
+
return useActivityEventsForManager(
|
|
381
|
+
streamFilter,
|
|
382
|
+
noyaManager.activityEventsManager
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function useActivityEventsForManager(
|
|
387
|
+
streamFilter: StreamFilter | undefined,
|
|
388
|
+
activityEventsManager: ActivityEventsManager
|
|
389
|
+
) {
|
|
390
|
+
const [streamId, setStreamId] = useState<string | undefined>(undefined);
|
|
391
|
+
const stableStreamFilter = useJsonMemo(streamFilter);
|
|
392
|
+
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
if (!stableStreamFilter) return;
|
|
395
|
+
|
|
396
|
+
const streamId = activityEventsManager.subscribe(stableStreamFilter);
|
|
397
|
+
|
|
398
|
+
setStreamId(streamId);
|
|
399
|
+
|
|
400
|
+
return () => {
|
|
401
|
+
setStreamId(undefined);
|
|
402
|
+
|
|
403
|
+
activityEventsManager.unsubscribe(streamId);
|
|
404
|
+
};
|
|
405
|
+
}, [activityEventsManager, stableStreamFilter]);
|
|
406
|
+
|
|
407
|
+
const observable = streamId
|
|
408
|
+
? activityEventsManager.getStream(streamId)
|
|
409
|
+
: undefined;
|
|
410
|
+
|
|
411
|
+
return useObservable(observable) ?? emptyArray;
|
|
412
|
+
}
|
|
413
|
+
|
|
220
414
|
export const FallbackUntilInitialized = ({
|
|
221
415
|
children,
|
|
222
416
|
fallback,
|
|
@@ -244,13 +438,16 @@ export function createNoyaContext<
|
|
|
244
438
|
schema: Schema;
|
|
245
439
|
mergeHistoryEntries?: StateManagerOptions<
|
|
246
440
|
Static<Schema>,
|
|
247
|
-
M
|
|
441
|
+
M & Pick<MultiplayerPatchMetadata, "name" | "timestamp">
|
|
248
442
|
>["mergeHistoryEntries"];
|
|
249
443
|
safeEval?: SafeEval;
|
|
250
444
|
outputTransforms?: CreateParams<OutputTransform>[];
|
|
251
445
|
inputs?: CreateParams<Input>[];
|
|
252
446
|
}) {
|
|
253
447
|
type S = Static<Schema>;
|
|
448
|
+
type PartialM = Partial<
|
|
449
|
+
M & Pick<MultiplayerPatchMetadata, "name" | "timestamp">
|
|
450
|
+
>;
|
|
254
451
|
|
|
255
452
|
const NoyaStateContext = AnyNoyaStateContext as React.Context<
|
|
256
453
|
NoyaManager<S, M, E> | undefined
|
|
@@ -261,6 +458,28 @@ export function createNoyaContext<
|
|
|
261
458
|
EphemeralUserDataManager<E> | undefined
|
|
262
459
|
>;
|
|
263
460
|
|
|
461
|
+
function NoyaContextProvider({
|
|
462
|
+
children,
|
|
463
|
+
contextValue,
|
|
464
|
+
}: {
|
|
465
|
+
children: React.ReactNode;
|
|
466
|
+
contextValue: AnyNoyaStateContextValue;
|
|
467
|
+
}) {
|
|
468
|
+
return (
|
|
469
|
+
<AnyNoyaStateContext.Provider value={contextValue}>
|
|
470
|
+
<ConnectedUsersContext.Provider
|
|
471
|
+
value={contextValue.noyaManager.connectedUsersManager}
|
|
472
|
+
>
|
|
473
|
+
<EphemeralUserDataManagerContext.Provider
|
|
474
|
+
value={contextValue.noyaManager.ephemeralUserDataManager}
|
|
475
|
+
>
|
|
476
|
+
{children}
|
|
477
|
+
</EphemeralUserDataManagerContext.Provider>
|
|
478
|
+
</ConnectedUsersContext.Provider>
|
|
479
|
+
</AnyNoyaStateContext.Provider>
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
264
483
|
function NoyaStateProvider({
|
|
265
484
|
children,
|
|
266
485
|
initialState,
|
|
@@ -288,23 +507,15 @@ export function createNoyaContext<
|
|
|
288
507
|
);
|
|
289
508
|
|
|
290
509
|
return (
|
|
291
|
-
<
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
{children}
|
|
301
|
-
</FallbackUntilInitialized>
|
|
302
|
-
) : (
|
|
303
|
-
children
|
|
304
|
-
)}
|
|
305
|
-
</EphemeralUserDataManagerContext.Provider>
|
|
306
|
-
</ConnectedUsersContext.Provider>
|
|
307
|
-
</AnyNoyaStateContext.Provider>
|
|
510
|
+
<NoyaContextProvider contextValue={contextValue}>
|
|
511
|
+
{options.fallback !== undefined ? (
|
|
512
|
+
<FallbackUntilInitialized fallback={options.fallback}>
|
|
513
|
+
{children}
|
|
514
|
+
</FallbackUntilInitialized>
|
|
515
|
+
) : (
|
|
516
|
+
children
|
|
517
|
+
)}
|
|
518
|
+
</NoyaContextProvider>
|
|
308
519
|
);
|
|
309
520
|
}
|
|
310
521
|
|
|
@@ -339,14 +550,14 @@ export function createNoyaContext<
|
|
|
339
550
|
|
|
340
551
|
function useSetValue(): (
|
|
341
552
|
...args:
|
|
342
|
-
| [metadata:
|
|
553
|
+
| [metadata: PartialM, value: SetStateAction<S>]
|
|
343
554
|
| [value: SetStateAction<S>]
|
|
344
555
|
) => void;
|
|
345
556
|
function useSetValue<P extends PathKey[] | string>(
|
|
346
557
|
path: P
|
|
347
558
|
): (
|
|
348
559
|
...args:
|
|
349
|
-
| [metadata:
|
|
560
|
+
| [metadata: PartialM, value: SetStateAction<GetAtPath<S, P>>]
|
|
350
561
|
| [value: SetStateAction<GetAtPath<S, P>>]
|
|
351
562
|
) => void;
|
|
352
563
|
function useSetValue<P extends PathKey[] | string>(path?: P) {
|
|
@@ -355,7 +566,7 @@ export function createNoyaContext<
|
|
|
355
566
|
const setValue = useCallback(
|
|
356
567
|
(
|
|
357
568
|
...args:
|
|
358
|
-
| [metadata:
|
|
569
|
+
| [metadata: PartialM, value: SetStateAction<GetAtPath<S, P>>]
|
|
359
570
|
| [value: SetStateAction<GetAtPath<S, P>>]
|
|
360
571
|
): void => {
|
|
361
572
|
const [metadata, value] = args.length === 2 ? args : [{}, args[0]];
|
|
@@ -376,7 +587,7 @@ export function createNoyaContext<
|
|
|
376
587
|
S,
|
|
377
588
|
(
|
|
378
589
|
...args:
|
|
379
|
-
| [metadata:
|
|
590
|
+
| [metadata: PartialM, value: SetStateAction<S>]
|
|
380
591
|
| [value: SetStateAction<S>]
|
|
381
592
|
) => void,
|
|
382
593
|
];
|
|
@@ -387,7 +598,7 @@ export function createNoyaContext<
|
|
|
387
598
|
A,
|
|
388
599
|
(
|
|
389
600
|
...args:
|
|
390
|
-
| [metadata:
|
|
601
|
+
| [metadata: PartialM, value: SetStateAction<S>]
|
|
391
602
|
| [value: SetStateAction<S>]
|
|
392
603
|
) => void,
|
|
393
604
|
];
|
|
@@ -397,7 +608,7 @@ export function createNoyaContext<
|
|
|
397
608
|
GetAtPath<S, P>,
|
|
398
609
|
(
|
|
399
610
|
...args:
|
|
400
|
-
| [metadata:
|
|
611
|
+
| [metadata: PartialM, value: SetStateAction<GetAtPath<S, P>>]
|
|
401
612
|
| [value: SetStateAction<GetAtPath<S, P>>]
|
|
402
613
|
) => void,
|
|
403
614
|
];
|
|
@@ -417,7 +628,10 @@ export function createNoyaContext<
|
|
|
417
628
|
function useStateManager() {
|
|
418
629
|
const { noyaManager } = useAnyNoyaStateContext();
|
|
419
630
|
|
|
420
|
-
return noyaManager.multiplayerStateManager as MultiplayerStateManager<
|
|
631
|
+
return noyaManager.multiplayerStateManager as MultiplayerStateManager<
|
|
632
|
+
S,
|
|
633
|
+
PartialM
|
|
634
|
+
>;
|
|
421
635
|
}
|
|
422
636
|
|
|
423
637
|
function useEphemeralUserDataManager() {
|
|
@@ -433,7 +647,12 @@ export function createNoyaContext<
|
|
|
433
647
|
}
|
|
434
648
|
|
|
435
649
|
function useNoyaManager() {
|
|
436
|
-
return useAnyNoyaStateContext().noyaManager as NoyaManager<
|
|
650
|
+
return useAnyNoyaStateContext().noyaManager as NoyaManager<
|
|
651
|
+
S,
|
|
652
|
+
PartialM,
|
|
653
|
+
E,
|
|
654
|
+
MenuT
|
|
655
|
+
>;
|
|
437
656
|
}
|
|
438
657
|
|
|
439
658
|
function useLeftMenuItems() {
|
|
@@ -499,6 +718,7 @@ export function createNoyaContext<
|
|
|
499
718
|
return {
|
|
500
719
|
Context: NoyaStateContext,
|
|
501
720
|
Provider: NoyaStateProvider,
|
|
721
|
+
NoyaContextProvider,
|
|
502
722
|
useValue,
|
|
503
723
|
useSetValue,
|
|
504
724
|
useValueState,
|
|
@@ -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
|
+
};
|