@noya-app/noya-multiplayer-react 0.1.66 → 0.1.67
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 +8 -0
- package/dist/index.bundle.js +19 -19
- package/dist/index.d.mts +95 -25
- package/dist/index.d.ts +95 -25
- package/dist/index.js +419 -177
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +381 -145
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/NoyaStateContext.tsx +179 -90
- package/src/__tests__/NoyaStateContext.test.ts +3 -2
- package/src/__tests__/resourcePayload.test.ts +263 -0
- package/src/__tests__/serialize.test.ts +8 -6
- package/src/hooks.ts +46 -47
- package/src/index.ts +1 -0
- package/src/inspector/StateInspector.tsx +16 -7
- package/src/inspector/serialization.ts +6 -12
- package/src/inspector/useStateInspector.tsx +15 -7
- package/src/noyaApp.ts +37 -17
- package/src/singleton.tsx +140 -0
|
@@ -14,11 +14,16 @@ const asset1Id = "2717f281-307d-4e9e-aa3b-68d29d4a1723";
|
|
|
14
14
|
const asset1DataUrl = `data:application/octet-stream;base64,${Base64.encode(
|
|
15
15
|
asset1Data
|
|
16
16
|
)}`;
|
|
17
|
-
const asset1 = {
|
|
17
|
+
const asset1: Asset = {
|
|
18
18
|
id: asset1Id,
|
|
19
|
+
stableId: "stable-id",
|
|
19
20
|
url: asset1DataUrl,
|
|
20
21
|
createdAt: new Date().toISOString(),
|
|
21
22
|
size: asset1Data.byteLength,
|
|
23
|
+
width: 100,
|
|
24
|
+
height: 100,
|
|
25
|
+
userId: "123",
|
|
26
|
+
contentType: "image/png",
|
|
22
27
|
};
|
|
23
28
|
const assets: Asset[] = [asset1];
|
|
24
29
|
const assetMap = {
|
|
@@ -61,7 +66,7 @@ it("should import and export the noya manager state", async () => {
|
|
|
61
66
|
const initialState: Static<typeof schema> = {
|
|
62
67
|
name: "John",
|
|
63
68
|
age: 30,
|
|
64
|
-
assetRef:
|
|
69
|
+
assetRef: asset1.stableId,
|
|
65
70
|
};
|
|
66
71
|
|
|
67
72
|
const noyaManager = new NoyaManager(initialState, {
|
|
@@ -107,10 +112,7 @@ it("should import and export the noya manager state", async () => {
|
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
expect(newNoyaManager.multiplayerStateManager.optimisticState$.get()).toEqual(
|
|
110
|
-
|
|
111
|
-
...initialState,
|
|
112
|
-
assetRef: firstAsset.id,
|
|
113
|
-
}
|
|
115
|
+
initialState
|
|
114
116
|
);
|
|
115
117
|
|
|
116
118
|
expect(firstAsset).toMatchObject({
|
package/src/hooks.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { GetAtPath } from "@noya-app/observable";
|
|
4
3
|
import {
|
|
5
4
|
ActionHandler,
|
|
6
5
|
NoyaManager,
|
|
@@ -26,30 +25,6 @@ import { StateInspectorOptions } from "./inspector/StateInspector";
|
|
|
26
25
|
import { useStateInspector } from "./inspector/useStateInspector";
|
|
27
26
|
import { useObservable } from "./useObservable";
|
|
28
27
|
|
|
29
|
-
export function useSyncStateManager<S, M>(stateManager: StateManager<S, M>): S;
|
|
30
|
-
export function useSyncStateManager<S, M, P extends string>(
|
|
31
|
-
stateManager: StateManager<S, M>,
|
|
32
|
-
path: P
|
|
33
|
-
): GetAtPath<S, P>;
|
|
34
|
-
export function useSyncStateManager<S, M>(
|
|
35
|
-
stateManager: StateManager<S, M>,
|
|
36
|
-
path?: string
|
|
37
|
-
) {
|
|
38
|
-
const getSnapshot = useCallback(
|
|
39
|
-
() =>
|
|
40
|
-
typeof path === "string"
|
|
41
|
-
? stateManager.getState(path)
|
|
42
|
-
: stateManager.getState(),
|
|
43
|
-
[stateManager, path]
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
return useSyncExternalStore(
|
|
47
|
-
stateManager.addListener,
|
|
48
|
-
getSnapshot,
|
|
49
|
-
getSnapshot
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
28
|
export function useManagedState<S, M = void>(
|
|
54
29
|
createInitialState: () => S,
|
|
55
30
|
options?: StateManagerOptions<S, M>
|
|
@@ -58,7 +33,15 @@ export function useManagedState<S, M = void>(
|
|
|
58
33
|
() => new StateManager<S, M>(createInitialState(), options)
|
|
59
34
|
);
|
|
60
35
|
|
|
61
|
-
const
|
|
36
|
+
const getSnapshot = useCallback((): S => {
|
|
37
|
+
return stateManager.getState();
|
|
38
|
+
}, [stateManager]);
|
|
39
|
+
|
|
40
|
+
const state = useSyncExternalStore(
|
|
41
|
+
stateManager.addListener,
|
|
42
|
+
getSnapshot,
|
|
43
|
+
getSnapshot
|
|
44
|
+
);
|
|
62
45
|
|
|
63
46
|
const setState = useCallback(
|
|
64
47
|
(
|
|
@@ -130,30 +113,19 @@ export type UseMultiplayerStateOptions<
|
|
|
130
113
|
inspector?: boolean | StateInspectorOptions;
|
|
131
114
|
};
|
|
132
115
|
|
|
133
|
-
|
|
116
|
+
// Bind an existing NoyaManager to React and wire up extras, inspector, and sync
|
|
117
|
+
export function useBindNoyaManager<
|
|
134
118
|
S,
|
|
135
119
|
M extends object = object,
|
|
136
120
|
E extends object = object,
|
|
137
121
|
MenuT extends string = string,
|
|
138
122
|
>(
|
|
139
|
-
|
|
140
|
-
options?:
|
|
123
|
+
noyaManager: NoyaManager<S, M, E, MenuT>,
|
|
124
|
+
options?: Pick<
|
|
125
|
+
UseMultiplayerStateOptions<S, M, E, MenuT>,
|
|
126
|
+
"inspector" | "sync"
|
|
127
|
+
>
|
|
141
128
|
) {
|
|
142
|
-
const {
|
|
143
|
-
sync = stubSync<
|
|
144
|
-
unknown,
|
|
145
|
-
object,
|
|
146
|
-
object,
|
|
147
|
-
string
|
|
148
|
-
>() as unknown as SyncAdapter<S, M, E, MenuT>,
|
|
149
|
-
inspector,
|
|
150
|
-
...rest
|
|
151
|
-
} = options ?? {};
|
|
152
|
-
|
|
153
|
-
const [noyaManager] = useState(
|
|
154
|
-
() => new NoyaManager<S, M, E, MenuT>(initialState, rest)
|
|
155
|
-
);
|
|
156
|
-
|
|
157
129
|
const state = useObservable(
|
|
158
130
|
noyaManager.multiplayerStateManager.optimisticState$
|
|
159
131
|
);
|
|
@@ -197,7 +169,7 @@ export function useMultiplayerState<
|
|
|
197
169
|
};
|
|
198
170
|
}, [noyaManager, assets]);
|
|
199
171
|
|
|
200
|
-
const syncRef = useRef(sync);
|
|
172
|
+
const syncRef = useRef(options?.sync);
|
|
201
173
|
|
|
202
174
|
useEffect(() => {
|
|
203
175
|
if (syncRef.current) {
|
|
@@ -209,13 +181,40 @@ export function useMultiplayerState<
|
|
|
209
181
|
|
|
210
182
|
useStateInspector<S, M, E, MenuT>({
|
|
211
183
|
noyaManager,
|
|
212
|
-
disabled: !inspector,
|
|
213
|
-
...(typeof inspector === "object" && inspector),
|
|
184
|
+
disabled: !options?.inspector,
|
|
185
|
+
...(typeof options?.inspector === "object" && options?.inspector),
|
|
214
186
|
});
|
|
215
187
|
|
|
216
188
|
return [state, noyaManager.multiplayerStateManager.setState, extras] as const;
|
|
217
189
|
}
|
|
218
190
|
|
|
191
|
+
export function useMultiplayerState<
|
|
192
|
+
S,
|
|
193
|
+
M extends object = object,
|
|
194
|
+
E extends object = object,
|
|
195
|
+
MenuT extends string = string,
|
|
196
|
+
>(
|
|
197
|
+
initialState: S | (() => S),
|
|
198
|
+
options?: UseMultiplayerStateOptions<S, M, E, MenuT>
|
|
199
|
+
) {
|
|
200
|
+
const {
|
|
201
|
+
sync = stubSync<
|
|
202
|
+
unknown,
|
|
203
|
+
object,
|
|
204
|
+
object,
|
|
205
|
+
string
|
|
206
|
+
>() as unknown as SyncAdapter<S, M, E, MenuT>,
|
|
207
|
+
inspector,
|
|
208
|
+
...rest
|
|
209
|
+
} = options ?? {};
|
|
210
|
+
|
|
211
|
+
const [noyaManager] = useState(
|
|
212
|
+
() => new NoyaManager<S, M, E, MenuT>(initialState, rest)
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return useBindNoyaManager<S, M, E, MenuT>(noyaManager, { sync, inspector });
|
|
216
|
+
}
|
|
217
|
+
|
|
219
218
|
function useErrorOverlay<MenuT extends string = string>(
|
|
220
219
|
noyaManager: NoyaManager<any, any, any, MenuT>
|
|
221
220
|
) {
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./inspector/StateInspector";
|
|
|
7
7
|
export * from "./inspector/useStateInspector";
|
|
8
8
|
export * from "./noyaApp";
|
|
9
9
|
export * from "./NoyaStateContext";
|
|
10
|
+
export * from "./singleton";
|
|
10
11
|
export * from "./useObservable";
|
|
11
12
|
export { WebSocketConnection } from "./WebSocketConnection";
|
|
12
13
|
export type { ConnectionEvent, ConnectionOptions } from "./WebSocketConnection";
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
4
|
import { Input, OutputTransform } from "@noya-app/noya-schemas";
|
|
5
|
-
import { Base64, uuid } from "@noya-app/noya-utils";
|
|
5
|
+
import { Base64, memoize, uuid } from "@noya-app/noya-utils";
|
|
6
6
|
import { downloadBlob, memoGeneric } from "@noya-app/react-utils";
|
|
7
|
-
import { NoyaManager } from "@noya-app/state-manager";
|
|
7
|
+
import { NoyaAsset, NoyaManager } from "@noya-app/state-manager";
|
|
8
8
|
import React, {
|
|
9
9
|
CSSProperties,
|
|
10
10
|
ComponentPropsWithoutRef,
|
|
@@ -432,7 +432,11 @@ export const StateInspector = memoGeneric(function StateInspector<
|
|
|
432
432
|
<StateInspectorDisclosureRowInner>
|
|
433
433
|
{assets.map((asset) => (
|
|
434
434
|
<StateInspectorRow key={asset.id} colorScheme={colorScheme}>
|
|
435
|
-
<ObjectInspector
|
|
435
|
+
<ObjectInspector
|
|
436
|
+
name={asset.id}
|
|
437
|
+
data={truncateAsset(asset)}
|
|
438
|
+
theme={theme}
|
|
439
|
+
/>
|
|
436
440
|
<StateInspectorButton
|
|
437
441
|
theme={theme}
|
|
438
442
|
onClick={() => assetManager.delete(asset.id)}
|
|
@@ -462,10 +466,8 @@ export const StateInspector = memoGeneric(function StateInspector<
|
|
|
462
466
|
"Are you sure you want to delete all resources?"
|
|
463
467
|
);
|
|
464
468
|
if (!ok) return;
|
|
465
|
-
await
|
|
466
|
-
resources.map((resource) =>
|
|
467
|
-
resourceManager.deleteResource({ id: resource.id })
|
|
468
|
-
)
|
|
469
|
+
await resourceManager.deleteResource(
|
|
470
|
+
resources.map((resource) => ({ id: resource.id }))
|
|
469
471
|
);
|
|
470
472
|
}}
|
|
471
473
|
>
|
|
@@ -706,3 +708,10 @@ export const StateInspector = memoGeneric(function StateInspector<
|
|
|
706
708
|
</div>
|
|
707
709
|
);
|
|
708
710
|
});
|
|
711
|
+
|
|
712
|
+
const truncateAsset = memoize((asset: NoyaAsset) => {
|
|
713
|
+
if (asset.url.startsWith("data:") || asset.url.startsWith("blob:")) {
|
|
714
|
+
return { ...asset, url: ellipsis(asset.url, 40) };
|
|
715
|
+
}
|
|
716
|
+
return asset;
|
|
717
|
+
});
|
|
@@ -96,28 +96,22 @@ export async function importAll(
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
if (assets.length) {
|
|
99
|
-
// Delete all existing assets
|
|
100
99
|
await Promise.all(
|
|
101
100
|
noyaManager.assetManager.assets$
|
|
102
101
|
.get()
|
|
103
|
-
.map((asset) => noyaManager.assetManager.delete(asset.
|
|
102
|
+
.map((asset) => noyaManager.assetManager.delete(asset.stableId))
|
|
104
103
|
);
|
|
105
104
|
|
|
106
|
-
// Add new assets
|
|
107
|
-
|
|
108
|
-
assets.map(async (asset)
|
|
109
|
-
|
|
105
|
+
// Add new assets, preserving stableIds
|
|
106
|
+
await Promise.all(
|
|
107
|
+
assets.map(async (asset) => {
|
|
108
|
+
await noyaManager.assetManager.create({
|
|
110
109
|
data: assetMap[asset.id],
|
|
111
110
|
contentType: asset.contentType,
|
|
111
|
+
stableId: asset.stableId,
|
|
112
112
|
});
|
|
113
|
-
|
|
114
|
-
return [asset.id, newAsset.id];
|
|
115
113
|
})
|
|
116
114
|
);
|
|
117
|
-
|
|
118
|
-
const replacementMap = Object.fromEntries(idPairs);
|
|
119
|
-
|
|
120
|
-
state = replaceDeep(state, replacementMap);
|
|
121
115
|
}
|
|
122
116
|
|
|
123
117
|
// if (schema) {
|
|
@@ -17,6 +17,19 @@ function createShadowRootElement() {
|
|
|
17
17
|
return { host, container };
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export function createStateInspectorRoot() {
|
|
21
|
+
const { host, container } = createShadowRootElement();
|
|
22
|
+
const root = createRoot(container);
|
|
23
|
+
document.body.appendChild(host);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
root,
|
|
27
|
+
unmount: () => {
|
|
28
|
+
document.body.removeChild(host);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
export function useStateInspector<
|
|
21
34
|
S,
|
|
22
35
|
M extends object,
|
|
@@ -36,16 +49,11 @@ export function useStateInspector<
|
|
|
36
49
|
useIsomorphicLayoutEffect(() => {
|
|
37
50
|
if (disabled) return;
|
|
38
51
|
|
|
39
|
-
const {
|
|
40
|
-
const root = createRoot(container);
|
|
41
|
-
|
|
42
|
-
document.body.appendChild(host);
|
|
52
|
+
const { root, unmount } = createStateInspectorRoot();
|
|
43
53
|
|
|
44
54
|
setRoot(root);
|
|
45
55
|
|
|
46
|
-
return
|
|
47
|
-
document.body.removeChild(host);
|
|
48
|
-
};
|
|
56
|
+
return unmount;
|
|
49
57
|
}, [disabled]);
|
|
50
58
|
|
|
51
59
|
useIsomorphicLayoutEffect(() => {
|
package/src/noyaApp.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createOrCastValue,
|
|
6
6
|
isEmbeddedApp,
|
|
7
7
|
localStorageSync,
|
|
8
|
+
NoyaManager,
|
|
8
9
|
parentFrameSync,
|
|
9
10
|
Static,
|
|
10
11
|
stubSync,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
webSocketSync,
|
|
15
16
|
} from "@noya-app/state-manager";
|
|
16
17
|
import { useMemo, useState } from "react";
|
|
17
|
-
import {
|
|
18
|
+
import { useBindNoyaManager, UseMultiplayerStateOptions } from "./hooks";
|
|
18
19
|
import { StateInspectorOptions } from "./inspector/StateInspector";
|
|
19
20
|
|
|
20
21
|
export type AppViewType = "editable" | "readOnly" | "preview";
|
|
@@ -96,6 +97,27 @@ export function getAppData<State>(
|
|
|
96
97
|
return appData as AppData<State>;
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
export function getSyncAdapter<
|
|
101
|
+
S,
|
|
102
|
+
M extends object = object,
|
|
103
|
+
E extends object = object,
|
|
104
|
+
MenuT extends string = string,
|
|
105
|
+
>(params?: {
|
|
106
|
+
multiplayerUrl?: string;
|
|
107
|
+
offlineStorageKey?: string;
|
|
108
|
+
debug?: boolean;
|
|
109
|
+
}): SyncAdapter<S, M, E, MenuT> {
|
|
110
|
+
const { multiplayerUrl, offlineStorageKey, debug } = params ?? {};
|
|
111
|
+
|
|
112
|
+
return isEmbeddedApp()
|
|
113
|
+
? parentFrameSync<S, M, E, MenuT>({ debug })
|
|
114
|
+
: multiplayerUrl
|
|
115
|
+
? webSocketSync<S, M, E, MenuT>({ url: multiplayerUrl, debug })
|
|
116
|
+
: offlineStorageKey
|
|
117
|
+
? localStorageSync<S, M, E, MenuT>({ key: offlineStorageKey })
|
|
118
|
+
: stubSync<S, M, E, MenuT>();
|
|
119
|
+
}
|
|
120
|
+
|
|
99
121
|
export type UseNoyaStateOptions<
|
|
100
122
|
S,
|
|
101
123
|
M extends object = object,
|
|
@@ -111,9 +133,9 @@ export type UseNoyaStateResult<
|
|
|
111
133
|
E extends object = object,
|
|
112
134
|
MenuT extends string = string,
|
|
113
135
|
> = [
|
|
114
|
-
ReturnType<typeof
|
|
115
|
-
ReturnType<typeof
|
|
116
|
-
ReturnType<typeof
|
|
136
|
+
ReturnType<typeof useBindNoyaManager<S, M, E, MenuT>>[0],
|
|
137
|
+
ReturnType<typeof useBindNoyaManager<S, M, E, MenuT>>[1],
|
|
138
|
+
ReturnType<typeof useBindNoyaManager<S, M, E, MenuT>>[2] & {
|
|
117
139
|
theme: AppTheme;
|
|
118
140
|
viewType: AppViewType;
|
|
119
141
|
},
|
|
@@ -128,7 +150,7 @@ export type UseNoyaStateResult<
|
|
|
128
150
|
* 2. Pass an initial state and a schema: `useNoyaState(initialState, { schema })`
|
|
129
151
|
* 3. Pass only a schema, and a conforming initial state is created automatically: `useNoyaState({ schema })`
|
|
130
152
|
*/
|
|
131
|
-
export function
|
|
153
|
+
export function useNoyaStateInternal<
|
|
132
154
|
S,
|
|
133
155
|
M extends object = object,
|
|
134
156
|
E extends object = object,
|
|
@@ -178,20 +200,18 @@ export function useNoyaState<
|
|
|
178
200
|
});
|
|
179
201
|
|
|
180
202
|
const sync = useMemo((): SyncAdapter<S, M, E, MenuT> => {
|
|
181
|
-
return
|
|
182
|
-
|
|
183
|
-
:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
debug: options?.debug,
|
|
187
|
-
})
|
|
188
|
-
: options?.offlineStorageKey
|
|
189
|
-
? localStorageSync<S, M, E, MenuT>({ key: options.offlineStorageKey })
|
|
190
|
-
: stubSync<S, M, E, MenuT>();
|
|
203
|
+
return getSyncAdapter<S, M, E, MenuT>({
|
|
204
|
+
multiplayerUrl,
|
|
205
|
+
offlineStorageKey: options?.offlineStorageKey,
|
|
206
|
+
debug: options?.debug,
|
|
207
|
+
});
|
|
191
208
|
}, [multiplayerUrl, options?.offlineStorageKey, options?.debug]);
|
|
192
209
|
|
|
193
|
-
const
|
|
194
|
-
|
|
210
|
+
const [noyaManager] = useState(
|
|
211
|
+
() => new NoyaManager<S, M, E, MenuT>(noyaAppInitialState as S, options)
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const result = useBindNoyaManager<S, M, E, MenuT>(noyaManager, {
|
|
195
215
|
inspector: options?.inspector ?? inspector,
|
|
196
216
|
sync: options?.sync ?? sync,
|
|
197
217
|
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
NoyaManager,
|
|
5
|
+
NoyaManagerOptions,
|
|
6
|
+
type SyncAdapter,
|
|
7
|
+
} from "@noya-app/state-manager";
|
|
8
|
+
import React from "react";
|
|
9
|
+
import type { StateInspectorOptions } from "./inspector/StateInspector";
|
|
10
|
+
import { StateInspector } from "./inspector/StateInspector";
|
|
11
|
+
import { createStateInspectorRoot } from "./inspector/useStateInspector";
|
|
12
|
+
import {
|
|
13
|
+
type AppTheme,
|
|
14
|
+
type AppViewType,
|
|
15
|
+
getAppData,
|
|
16
|
+
getSyncAdapter,
|
|
17
|
+
} from "./noyaApp";
|
|
18
|
+
|
|
19
|
+
let noyaManagerSingleton: NoyaManager<any, any, any, any> | undefined;
|
|
20
|
+
let noyaSingletonBindings: AnyNoyaSingletonBindings | undefined;
|
|
21
|
+
|
|
22
|
+
export type AnyNoyaSingletonBindings = {
|
|
23
|
+
sync?: SyncAdapter<any, any, any, any>;
|
|
24
|
+
inspector?: boolean | StateInspectorOptions;
|
|
25
|
+
theme: AppTheme;
|
|
26
|
+
viewType: AppViewType;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type AnyNoyaSingletonOptions<
|
|
30
|
+
S,
|
|
31
|
+
M extends object,
|
|
32
|
+
E extends object,
|
|
33
|
+
MenuT extends string,
|
|
34
|
+
> = NoyaManagerOptions<S, M> & {
|
|
35
|
+
initialState?: S | (() => S);
|
|
36
|
+
sync?: SyncAdapter<S, M, E, MenuT>;
|
|
37
|
+
inspector?: boolean | StateInspectorOptions;
|
|
38
|
+
theme?: AppTheme;
|
|
39
|
+
viewType?: AppViewType;
|
|
40
|
+
offlineStorageKey?: string;
|
|
41
|
+
multiplayerUrl?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function initializeNoyaSingleton<
|
|
45
|
+
S,
|
|
46
|
+
M extends object,
|
|
47
|
+
E extends object,
|
|
48
|
+
MenuT extends string,
|
|
49
|
+
>(options?: AnyNoyaSingletonOptions<S, M, E, MenuT>) {
|
|
50
|
+
if (noyaManagerSingleton) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"Noya has already been initialized. Call `useNoyaState` instead."
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const {
|
|
57
|
+
initialState,
|
|
58
|
+
sync,
|
|
59
|
+
inspector,
|
|
60
|
+
theme,
|
|
61
|
+
viewType,
|
|
62
|
+
offlineStorageKey,
|
|
63
|
+
multiplayerUrl,
|
|
64
|
+
...managerOptions
|
|
65
|
+
} = (options ?? {}) as AnyNoyaSingletonOptions<S, M, E, MenuT>;
|
|
66
|
+
|
|
67
|
+
const appData = getAppData<S>(
|
|
68
|
+
(initialState ?? null) as S | (() => S),
|
|
69
|
+
managerOptions?.schema,
|
|
70
|
+
{ overrideExistingState: managerOptions?.overrideExistingState }
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
noyaManagerSingleton = new NoyaManager<S, M, E, MenuT>(
|
|
74
|
+
appData.initialState as S,
|
|
75
|
+
managerOptions
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const resolvedSync =
|
|
79
|
+
sync ??
|
|
80
|
+
getSyncAdapter<S, M, E, MenuT>({
|
|
81
|
+
multiplayerUrl: multiplayerUrl ?? appData.multiplayerUrl,
|
|
82
|
+
offlineStorageKey,
|
|
83
|
+
debug: managerOptions?.debug,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Activate sync immediately for the singleton
|
|
87
|
+
try {
|
|
88
|
+
resolvedSync({ noyaManager: noyaManagerSingleton });
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.warn("Failed to activate sync", error);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const resolvedInspector = inspector ?? appData.inspector;
|
|
94
|
+
const resolvedTheme = theme ?? appData.theme ?? "light";
|
|
95
|
+
const resolvedViewType = viewType ?? appData.viewType ?? "editable";
|
|
96
|
+
|
|
97
|
+
if (resolvedInspector) {
|
|
98
|
+
const { root } = createStateInspectorRoot();
|
|
99
|
+
|
|
100
|
+
root.render(
|
|
101
|
+
<StateInspector
|
|
102
|
+
noyaManager={noyaManagerSingleton}
|
|
103
|
+
colorScheme={
|
|
104
|
+
resolvedInspector === true
|
|
105
|
+
? resolvedTheme
|
|
106
|
+
: resolvedInspector.colorScheme
|
|
107
|
+
}
|
|
108
|
+
anchor={
|
|
109
|
+
resolvedInspector === true ? "bottom right" : resolvedInspector.anchor
|
|
110
|
+
}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
noyaSingletonBindings = {
|
|
116
|
+
sync: resolvedSync,
|
|
117
|
+
inspector: resolvedInspector,
|
|
118
|
+
theme: resolvedTheme,
|
|
119
|
+
viewType: resolvedViewType,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function getNoyaManagerSingleton() {
|
|
124
|
+
if (!noyaManagerSingleton || !noyaSingletonBindings) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
"Noya has not been initialized. Call initializeNoya once before using Noya hooks."
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return noyaManagerSingleton;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getNoyaSingletonBindings() {
|
|
134
|
+
return noyaSingletonBindings;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function deleteNoyaSingleton() {
|
|
138
|
+
noyaManagerSingleton = undefined;
|
|
139
|
+
noyaSingletonBindings = undefined;
|
|
140
|
+
}
|