@noya-app/noya-multiplayer-react 0.1.35 → 0.1.37
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 +10 -10
- package/CHANGELOG.md +15 -0
- package/dist/index.bundle.js +8 -8
- package/dist/index.d.mts +25 -7
- package/dist/index.d.ts +25 -7
- package/dist/index.js +78 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +79 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/hooks.ts +15 -2
- package/src/inspector/StateInspector.tsx +56 -1
- package/src/inspector/useStateInspector.tsx +16 -9
- package/src/noyaApp.ts +46 -10
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
AssetManager,
|
|
4
5
|
ConnectionEvent,
|
|
5
6
|
EphemeralUserData,
|
|
6
7
|
ExtendedPathKey,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
} from "react-inspector";
|
|
27
28
|
import { shouldTrackEvents$, shouldTrackTasks$ } from "../globals";
|
|
28
29
|
import { useEphemeralUserData, useManagedHistory } from "../hooks";
|
|
30
|
+
import { useObservable } from "../useObservable";
|
|
29
31
|
import { useLocalStorageState } from "./useLocalStorageState";
|
|
30
32
|
|
|
31
33
|
const lightTheme = {
|
|
@@ -258,6 +260,7 @@ export const StateInspector = memo(function StateInspector<S, M, E>({
|
|
|
258
260
|
unstyled,
|
|
259
261
|
colorScheme = "light",
|
|
260
262
|
multiplayerStateManager,
|
|
263
|
+
assetManager,
|
|
261
264
|
ephemeralUserData,
|
|
262
265
|
anchor = "right",
|
|
263
266
|
forceInit,
|
|
@@ -270,6 +273,7 @@ export const StateInspector = memo(function StateInspector<S, M, E>({
|
|
|
270
273
|
userId?: string;
|
|
271
274
|
unstyled?: boolean;
|
|
272
275
|
multiplayerStateManager: MultiplayerStateManager<S, M>;
|
|
276
|
+
assetManager: AssetManager;
|
|
273
277
|
ephemeralUserData: EphemeralUserData<E>;
|
|
274
278
|
forceInit: () => void;
|
|
275
279
|
} & StateInspectorOptions &
|
|
@@ -311,6 +315,10 @@ export const StateInspector = memo(function StateInspector<S, M, E>({
|
|
|
311
315
|
"noya-multiplayer-react-show-ephemeral",
|
|
312
316
|
false
|
|
313
317
|
);
|
|
318
|
+
const [showAssets, setShowAssets] = useLocalStorageState(
|
|
319
|
+
"noya-multiplayer-react-show-assets",
|
|
320
|
+
false
|
|
321
|
+
);
|
|
314
322
|
|
|
315
323
|
useEffect(() => {
|
|
316
324
|
shouldTrackEvents$.set(showEvents);
|
|
@@ -329,6 +337,7 @@ export const StateInspector = memo(function StateInspector<S, M, E>({
|
|
|
329
337
|
|
|
330
338
|
const ephemeral = useEphemeralUserData(ephemeralUserData);
|
|
331
339
|
const historySnapshot = useManagedHistory(multiplayerStateManager.sm);
|
|
340
|
+
const assets = useObservable(assetManager.assets$);
|
|
332
341
|
|
|
333
342
|
useEffect(() => {
|
|
334
343
|
if (historyContainerRef.current) {
|
|
@@ -680,6 +689,52 @@ export const StateInspector = memo(function StateInspector<S, M, E>({
|
|
|
680
689
|
))}
|
|
681
690
|
</div>
|
|
682
691
|
</DisclosureSection>
|
|
692
|
+
<DisclosureSection
|
|
693
|
+
open={showAssets}
|
|
694
|
+
setOpen={setShowAssets}
|
|
695
|
+
title="Assets"
|
|
696
|
+
colorScheme={colorScheme}
|
|
697
|
+
right={
|
|
698
|
+
<span style={{ display: "flex", gap: "4px" }}>
|
|
699
|
+
<SmallButton
|
|
700
|
+
theme={theme}
|
|
701
|
+
onClick={() => {
|
|
702
|
+
// get file input
|
|
703
|
+
const input = document.createElement("input");
|
|
704
|
+
input.type = "file";
|
|
705
|
+
input.onchange = () => {
|
|
706
|
+
const file = input.files?.[0];
|
|
707
|
+
if (file) {
|
|
708
|
+
const reader = new FileReader();
|
|
709
|
+
reader.onload = () => {
|
|
710
|
+
const buffer = reader.result as ArrayBuffer;
|
|
711
|
+
assetManager.create(new Uint8Array(buffer));
|
|
712
|
+
};
|
|
713
|
+
reader.readAsArrayBuffer(file);
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
input.click();
|
|
717
|
+
}}
|
|
718
|
+
>
|
|
719
|
+
Upload
|
|
720
|
+
</SmallButton>
|
|
721
|
+
</span>
|
|
722
|
+
}
|
|
723
|
+
>
|
|
724
|
+
<div style={styles.sectionInner}>
|
|
725
|
+
{assets.map((asset) => (
|
|
726
|
+
<InspectorRow key={asset.id} colorScheme={colorScheme}>
|
|
727
|
+
<ObjectInspector name={asset.id} data={asset} theme={theme} />
|
|
728
|
+
<SmallButton
|
|
729
|
+
theme={theme}
|
|
730
|
+
onClick={() => assetManager.delete(asset.id)}
|
|
731
|
+
>
|
|
732
|
+
Delete
|
|
733
|
+
</SmallButton>
|
|
734
|
+
</InspectorRow>
|
|
735
|
+
))}
|
|
736
|
+
</div>
|
|
737
|
+
</DisclosureSection>
|
|
683
738
|
<DisclosureSection
|
|
684
739
|
title="Tasks"
|
|
685
740
|
colorScheme={colorScheme}
|
|
@@ -939,7 +994,7 @@ function SmallButton({
|
|
|
939
994
|
display: "inline-flex",
|
|
940
995
|
alignItems: "center",
|
|
941
996
|
justifyContent: "center",
|
|
942
|
-
padding: "
|
|
997
|
+
padding: "0",
|
|
943
998
|
cursor: "pointer",
|
|
944
999
|
...style,
|
|
945
1000
|
}}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
AssetManager,
|
|
4
5
|
ConnectionEvent,
|
|
5
6
|
EphemeralUserData,
|
|
6
7
|
MultiplayerStateManager,
|
|
@@ -13,10 +14,13 @@ import { createRoot } from "react-dom/client";
|
|
|
13
14
|
import { Root } from "react-dom/client";
|
|
14
15
|
import { StateInspector } from "./StateInspector";
|
|
15
16
|
|
|
16
|
-
function
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
function createShadowRootElement() {
|
|
18
|
+
const host = document.createElement("div");
|
|
19
|
+
host.id = "noya-multiplayer-react-inspector-host";
|
|
20
|
+
const shadowRoot = host.attachShadow({ mode: "open" });
|
|
21
|
+
const container = document.createElement("div");
|
|
22
|
+
shadowRoot.appendChild(container);
|
|
23
|
+
return { host, container };
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export function useStateInspector<S, M, E>({
|
|
@@ -29,6 +33,7 @@ export function useStateInspector<S, M, E>({
|
|
|
29
33
|
colorScheme,
|
|
30
34
|
anchor,
|
|
31
35
|
multiplayerStateManager,
|
|
36
|
+
assetManager,
|
|
32
37
|
ephemeralUserData,
|
|
33
38
|
forceInit,
|
|
34
39
|
}: {
|
|
@@ -39,6 +44,7 @@ export function useStateInspector<S, M, E>({
|
|
|
39
44
|
userId?: string;
|
|
40
45
|
disabled: boolean;
|
|
41
46
|
multiplayerStateManager: MultiplayerStateManager<S, M>;
|
|
47
|
+
assetManager: AssetManager;
|
|
42
48
|
ephemeralUserData: EphemeralUserData<E>;
|
|
43
49
|
forceInit: () => void;
|
|
44
50
|
} & Pick<ComponentProps<typeof StateInspector>, "colorScheme" | "anchor">) {
|
|
@@ -47,16 +53,15 @@ export function useStateInspector<S, M, E>({
|
|
|
47
53
|
useLayoutEffect(() => {
|
|
48
54
|
if (disabled) return;
|
|
49
55
|
|
|
50
|
-
const
|
|
56
|
+
const { host, container } = createShadowRootElement();
|
|
57
|
+
const root = createRoot(container);
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
document.body.appendChild(portalElement);
|
|
59
|
+
document.body.appendChild(host);
|
|
55
60
|
|
|
56
61
|
setRoot(root);
|
|
57
62
|
|
|
58
63
|
return () => {
|
|
59
|
-
document.body.removeChild(
|
|
64
|
+
document.body.removeChild(host);
|
|
60
65
|
};
|
|
61
66
|
}, [disabled]);
|
|
62
67
|
|
|
@@ -76,6 +81,7 @@ export function useStateInspector<S, M, E>({
|
|
|
76
81
|
multiplayerStateManager={
|
|
77
82
|
multiplayerStateManager as MultiplayerStateManager<any, any>
|
|
78
83
|
}
|
|
84
|
+
assetManager={assetManager}
|
|
79
85
|
ephemeralUserData={ephemeralUserData as EphemeralUserData<any>}
|
|
80
86
|
/>
|
|
81
87
|
);
|
|
@@ -91,5 +97,6 @@ export function useStateInspector<S, M, E>({
|
|
|
91
97
|
tasks,
|
|
92
98
|
forceInit,
|
|
93
99
|
ephemeralUserData,
|
|
100
|
+
assetManager,
|
|
94
101
|
]);
|
|
95
102
|
}
|
package/src/noyaApp.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
stubSync,
|
|
9
9
|
SyncAdapter,
|
|
10
10
|
TSchema,
|
|
11
|
+
TypeGuard,
|
|
11
12
|
webSocketSync,
|
|
12
13
|
} from "@noya-app/state-manager";
|
|
13
14
|
import { useEffect, useMemo, useState } from "react";
|
|
@@ -159,7 +160,7 @@ export function isEmbeddedApp() {
|
|
|
159
160
|
return window.self !== window.top;
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
export type
|
|
163
|
+
export type UseNoyaStateOptions<
|
|
163
164
|
S,
|
|
164
165
|
M = void,
|
|
165
166
|
E = void,
|
|
@@ -167,7 +168,7 @@ export type UseNoyaAppStateOptions<
|
|
|
167
168
|
localStorageKey?: string;
|
|
168
169
|
};
|
|
169
170
|
|
|
170
|
-
export type
|
|
171
|
+
export type UseNoyaStateResult<S, M = void, E = void> = [
|
|
171
172
|
ReturnType<typeof useMultiplayerState<S, M, E>>[0],
|
|
172
173
|
ReturnType<typeof useMultiplayerState<S, M, E>>[1],
|
|
173
174
|
ReturnType<typeof useMultiplayerState<S, M, E>>[2] & {
|
|
@@ -176,17 +177,42 @@ export type UseNoyaAppStateResult<S, M = void, E = void> = [
|
|
|
176
177
|
},
|
|
177
178
|
];
|
|
178
179
|
|
|
179
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Sync data locally or to a multiplayer server.
|
|
182
|
+
*
|
|
183
|
+
* There are 3 ways to use this hook:
|
|
184
|
+
*
|
|
185
|
+
* 1. Pass an initial state: `useNoyaState(initialState)`
|
|
186
|
+
* 2. Pass an initial state and a schema: `useNoyaState(initialState, { schema })`
|
|
187
|
+
* 3. Pass only a schema, and a conforming initial state is created automatically: `useNoyaState({ schema })`
|
|
188
|
+
*/
|
|
189
|
+
export function useNoyaState<
|
|
180
190
|
S,
|
|
181
191
|
M = void,
|
|
182
192
|
E = void,
|
|
183
|
-
O extends
|
|
193
|
+
O extends UseNoyaStateOptions<S, M, E> = UseNoyaStateOptions<S, M, E>,
|
|
184
194
|
>(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
)
|
|
195
|
+
...args:
|
|
196
|
+
| [
|
|
197
|
+
initialState: O["schema"] extends TSchema
|
|
198
|
+
? Static<O["schema"]> | (() => Static<O["schema"]>)
|
|
199
|
+
: S | (() => S),
|
|
200
|
+
options?: O,
|
|
201
|
+
]
|
|
202
|
+
| [options: O & { schema: TSchema }]
|
|
203
|
+
): UseNoyaStateResult<
|
|
204
|
+
O["schema"] extends TSchema ? Static<O["schema"]> : S,
|
|
205
|
+
M,
|
|
206
|
+
E
|
|
207
|
+
> {
|
|
208
|
+
const [initialState, options] =
|
|
209
|
+
typeof args[0] === "object" &&
|
|
210
|
+
args[0] !== null &&
|
|
211
|
+
"schema" in args[0] &&
|
|
212
|
+
TypeGuard.IsSchema(args[0].schema)
|
|
213
|
+
? [null, args[0]]
|
|
214
|
+
: [args[0], args[1]];
|
|
215
|
+
|
|
190
216
|
// Keep these stable after initial render
|
|
191
217
|
const [
|
|
192
218
|
{
|
|
@@ -224,9 +250,19 @@ export function useNoyaAppState<
|
|
|
224
250
|
return { ...extras, theme, viewType } as const;
|
|
225
251
|
}, [extras, theme, viewType]);
|
|
226
252
|
|
|
227
|
-
return [result[0], result[1], mergedExtras] as
|
|
253
|
+
return [result[0], result[1], mergedExtras] as UseNoyaStateResult<
|
|
228
254
|
O["schema"] extends TSchema ? Static<O["schema"]> : S,
|
|
229
255
|
M,
|
|
230
256
|
E
|
|
231
257
|
>;
|
|
232
258
|
}
|
|
259
|
+
|
|
260
|
+
// function typeTest() {
|
|
261
|
+
// const [a, setA] = useNoyaState(7);
|
|
262
|
+
// const [b, setB] = useNoyaState(7, {
|
|
263
|
+
// schema: Type.Number(),
|
|
264
|
+
// });
|
|
265
|
+
// const [c, setC] = useNoyaState({
|
|
266
|
+
// schema: Type.Number(),
|
|
267
|
+
// });
|
|
268
|
+
// }
|