@pluv/react 0.27.0 → 0.29.0
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 +8 -8
- package/CHANGELOG.md +94 -0
- package/dist/index.d.mts +15 -21
- package/dist/index.d.ts +15 -21
- package/dist/index.js +27 -40
- package/dist/index.mjs +27 -40
- package/package.json +7 -7
- package/src/createBundle.tsx +531 -16
- package/src/index.ts +1 -8
- package/src/createRoomBundle.tsx +0 -559
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "@pluv/client React.js bindings",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,22 +18,22 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"fast-deep-equal": "^3.1.3",
|
|
21
|
-
"@pluv/
|
|
22
|
-
"@pluv/
|
|
23
|
-
"@pluv/
|
|
21
|
+
"@pluv/client": "^0.29.0",
|
|
22
|
+
"@pluv/crdt": "^0.29.0",
|
|
23
|
+
"@pluv/types": "^0.29.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@types/react": "^18.0.0",
|
|
27
27
|
"react": "^18.0.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@types/react": "^18.3.
|
|
30
|
+
"@types/react": "^18.3.8",
|
|
31
31
|
"eslint": "^8.57.0",
|
|
32
32
|
"react": "^18.3.1",
|
|
33
33
|
"tsup": "^8.3.0",
|
|
34
34
|
"typescript": "^5.6.2",
|
|
35
|
-
"@pluv/tsconfig": "^0.
|
|
36
|
-
"eslint-config-pluv": "^0.
|
|
35
|
+
"@pluv/tsconfig": "^0.29.0",
|
|
36
|
+
"eslint-config-pluv": "^0.29.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsup src/index.ts --format esm,cjs --dts --external react",
|
package/src/createBundle.tsx
CHANGED
|
@@ -1,40 +1,151 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import type {
|
|
2
|
+
AbstractRoom,
|
|
3
|
+
CreateRoomOptions,
|
|
4
|
+
MergeEvents,
|
|
5
|
+
MockedRoomEvents,
|
|
6
|
+
PluvClient,
|
|
7
|
+
PluvRoomAddon,
|
|
8
|
+
PluvRoomDebug,
|
|
9
|
+
PluvRouter,
|
|
10
|
+
PluvRouterEventConfig,
|
|
11
|
+
UserInfo,
|
|
12
|
+
WebSocketConnection,
|
|
13
|
+
} from "@pluv/client";
|
|
14
|
+
import { MockedRoom, PluvRoom } from "@pluv/client";
|
|
15
|
+
import type { AbstractCrdtDoc, CrdtType, InferCrdtJson } from "@pluv/crdt";
|
|
16
|
+
import type { Id, InferIOInput, InferIOOutput, IOEventMessage, IOLike, JsonObject } from "@pluv/types";
|
|
17
|
+
import fastDeepEqual from "fast-deep-equal";
|
|
18
|
+
import type { Dispatch, FC, ReactNode } from "react";
|
|
19
|
+
import { createContext, memo, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
20
|
+
import { identity, shallowArrayEqual, useRerender, useSyncExternalStoreWithSelector } from "./internal";
|
|
8
21
|
|
|
9
22
|
export interface PluvProviderProps {
|
|
10
23
|
children?: ReactNode;
|
|
11
24
|
}
|
|
12
25
|
|
|
26
|
+
type BaseRoomProviderProps<TPresence extends JsonObject, TStorage extends Record<string, CrdtType<any, any>>> = {
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
initialStorage?: keyof TStorage extends never ? never : () => TStorage;
|
|
29
|
+
room: string;
|
|
30
|
+
} & (keyof TPresence extends never ? { initialPresence?: never } : { initialPresence: TPresence });
|
|
31
|
+
|
|
32
|
+
export type MockedRoomProviderProps<
|
|
33
|
+
TIO extends IOLike,
|
|
34
|
+
TPresence extends JsonObject,
|
|
35
|
+
TStorage extends Record<string, CrdtType<any, any>>,
|
|
36
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
37
|
+
> = BaseRoomProviderProps<TPresence, TStorage> & {
|
|
38
|
+
events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type PluvRoomProviderProps<
|
|
42
|
+
TIO extends IOLike,
|
|
43
|
+
TMetadata extends JsonObject,
|
|
44
|
+
TPresence extends JsonObject,
|
|
45
|
+
TStorage extends Record<string, CrdtType<any, any>>,
|
|
46
|
+
> = BaseRoomProviderProps<TPresence, TStorage> & {
|
|
47
|
+
debug?: boolean | PluvRoomDebug<TIO>;
|
|
48
|
+
onAuthorizationFail?: (error: Error) => void;
|
|
49
|
+
} & (keyof TMetadata extends never ? { metadata?: undefined } : { metadata: TMetadata });
|
|
50
|
+
|
|
51
|
+
export interface SubscriptionHookOptions<T extends unknown> {
|
|
52
|
+
isEqual?: (a: T, b: T) => boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type UpdateMyPresenceAction<TPresence extends JsonObject> =
|
|
56
|
+
| Partial<TPresence>
|
|
57
|
+
| ((oldPresence: TPresence | null) => Partial<TPresence>);
|
|
58
|
+
|
|
59
|
+
export type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOInput<TIO>>(
|
|
60
|
+
event: TEvent,
|
|
61
|
+
data: Id<InferIOInput<TIO>[TEvent]>,
|
|
62
|
+
) => void) & {
|
|
63
|
+
[event in keyof InferIOInput<TIO>]: (input: Id<InferIOInput<TIO>[event]>) => void;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type EventProxy<TIO extends IOLike> = {
|
|
67
|
+
[event in keyof InferIOOutput<TIO>]: {
|
|
68
|
+
useEvent: (callback: (data: Id<IOEventMessage<TIO, event>>) => void) => void;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
13
72
|
export interface CreateBundle<
|
|
14
73
|
TIO extends IOLike,
|
|
15
74
|
TMetadata extends JsonObject,
|
|
16
75
|
TPresence extends JsonObject = {},
|
|
17
76
|
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
77
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
18
78
|
> {
|
|
19
|
-
// factories
|
|
20
|
-
createRoomBundle: <TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(
|
|
21
|
-
options: CreateRoomBundleOptions<TIO, TMetadata, TPresence, TStorage, TEvents>,
|
|
22
|
-
) => CreateRoomBundle<TIO, TMetadata, TPresence, TStorage, TEvents>;
|
|
23
|
-
|
|
24
79
|
// components
|
|
80
|
+
MockedRoomProvider: FC<MockedRoomProviderProps<TIO, TPresence, TStorage>>;
|
|
25
81
|
PluvProvider: FC<PluvProviderProps>;
|
|
82
|
+
PluvRoomProvider: FC<PluvRoomProviderProps<TIO, TMetadata, TPresence, TStorage>>;
|
|
83
|
+
|
|
84
|
+
// proxies
|
|
85
|
+
event: EventProxy<MergeEvents<TEvents, TIO>>;
|
|
26
86
|
|
|
27
87
|
// hooks
|
|
88
|
+
useBroadcast: () => BroadcastProxy<MergeEvents<TEvents, TIO>>;
|
|
89
|
+
useCanRedo: () => boolean;
|
|
90
|
+
useCanUndo: () => boolean;
|
|
28
91
|
useClient: () => PluvClient<TIO, TPresence, TStorage, TMetadata>;
|
|
92
|
+
useConnection: <T extends unknown = WebSocketConnection>(
|
|
93
|
+
selector: (connection: WebSocketConnection) => T,
|
|
94
|
+
options?: SubscriptionHookOptions<Id<T>>,
|
|
95
|
+
) => Id<T>;
|
|
96
|
+
useDoc: () => AbstractCrdtDoc<TStorage>;
|
|
97
|
+
useEvent: <TType extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
98
|
+
type: TType,
|
|
99
|
+
callback: (data: Id<IOEventMessage<MergeEvents<TEvents, TIO>, TType>>) => void,
|
|
100
|
+
) => void;
|
|
101
|
+
useMyPresence: <T extends unknown = TPresence>(
|
|
102
|
+
selector?: (myPresence: TPresence) => T,
|
|
103
|
+
options?: SubscriptionHookOptions<Id<T> | null>,
|
|
104
|
+
) => [myPresence: Id<T>, updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>];
|
|
105
|
+
useMyself: <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
106
|
+
selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T,
|
|
107
|
+
options?: SubscriptionHookOptions<Id<T> | null>,
|
|
108
|
+
) => Id<T> | null;
|
|
109
|
+
useOther: <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
110
|
+
connectionId: string,
|
|
111
|
+
selector?: (other: UserInfo<TIO, TPresence>) => T,
|
|
112
|
+
options?: SubscriptionHookOptions<T | null>,
|
|
113
|
+
) => T | null;
|
|
114
|
+
useOthers: <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(
|
|
115
|
+
selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
|
|
116
|
+
options?: SubscriptionHookOptions<T>,
|
|
117
|
+
) => T;
|
|
118
|
+
useRedo: () => () => void;
|
|
119
|
+
useRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
|
|
120
|
+
useStorage: <TKey extends keyof TStorage, TData extends unknown = InferCrdtJson<TStorage[TKey]>>(
|
|
121
|
+
key: TKey,
|
|
122
|
+
selector?: (data: InferCrdtJson<TStorage[TKey]>) => TData,
|
|
123
|
+
options?: SubscriptionHookOptions<TData | null>,
|
|
124
|
+
) => [data: TData | null, sharedType: TStorage[TKey] | null];
|
|
125
|
+
useTransact: () => (fn: (storage: TStorage) => void, origin?: string) => void;
|
|
126
|
+
useUndo: () => () => void;
|
|
29
127
|
}
|
|
30
128
|
|
|
129
|
+
export type CreateBundleOptions<
|
|
130
|
+
TIO extends IOLike,
|
|
131
|
+
TMetadata extends JsonObject = {},
|
|
132
|
+
TPresence extends JsonObject = {},
|
|
133
|
+
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
134
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
135
|
+
> = {
|
|
136
|
+
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
|
|
137
|
+
router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
138
|
+
};
|
|
139
|
+
|
|
31
140
|
export const createBundle = <
|
|
32
141
|
TIO extends IOLike,
|
|
33
142
|
TMetadata extends JsonObject = {},
|
|
34
143
|
TPresence extends JsonObject = {},
|
|
35
144
|
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
145
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
36
146
|
>(
|
|
37
147
|
client: PluvClient<TIO, TPresence, TStorage, TMetadata>,
|
|
148
|
+
options: CreateBundleOptions<TIO, TMetadata, TPresence, TStorage, TEvents> = {},
|
|
38
149
|
): CreateBundle<TIO, TMetadata, TPresence, TStorage> => {
|
|
39
150
|
/**
|
|
40
151
|
* !HACK
|
|
@@ -45,6 +156,86 @@ export const createBundle = <
|
|
|
45
156
|
*/
|
|
46
157
|
const PluvContext = createContext<PluvClient<TIO, TPresence, TStorage, TMetadata>>(null as any);
|
|
47
158
|
|
|
159
|
+
/**
|
|
160
|
+
* !HACK
|
|
161
|
+
* @description We'll let the context error out if the room is not provided,
|
|
162
|
+
* and let the users deal with it.
|
|
163
|
+
* @author leedavidcs
|
|
164
|
+
* @date November 11, 2022
|
|
165
|
+
*/
|
|
166
|
+
const PluvRoomContext = createContext<AbstractRoom<TIO, TPresence, TStorage>>(null as any);
|
|
167
|
+
|
|
168
|
+
const MockedRoomContext = createContext<{
|
|
169
|
+
room: AbstractRoom<TIO, TPresence, TStorage> | null;
|
|
170
|
+
}>({ room: null });
|
|
171
|
+
|
|
172
|
+
const MockedRoomProvider = memo<MockedRoomProviderProps<TIO, TPresence, TStorage>>((props) => {
|
|
173
|
+
const { children, events, initialPresence, initialStorage, room: _room } = props;
|
|
174
|
+
|
|
175
|
+
const [room] = useState<MockedRoom<TIO, TPresence, TStorage, TEvents>>(() => {
|
|
176
|
+
return new MockedRoom<TIO, TPresence, TStorage, TEvents>(_room, {
|
|
177
|
+
events,
|
|
178
|
+
initialPresence,
|
|
179
|
+
initialStorage:
|
|
180
|
+
typeof initialStorage === "function"
|
|
181
|
+
? client._defs.initialStorage?.getFactory(initialStorage)
|
|
182
|
+
: client._defs.initialStorage,
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<MockedRoomContext.Provider value={{ room }}>
|
|
188
|
+
<PluvRoomContext.Provider value={room}>{children}</PluvRoomContext.Provider>
|
|
189
|
+
</MockedRoomContext.Provider>
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
MockedRoomProvider.displayName = "MockedRoomProvider";
|
|
194
|
+
|
|
195
|
+
const PluvRoomProvider = memo<PluvRoomProviderProps<TIO, TMetadata, TPresence, TStorage>>((props) => {
|
|
196
|
+
const { children, debug, initialPresence, initialStorage, metadata, onAuthorizationFail, room: _room } = props;
|
|
197
|
+
|
|
198
|
+
const rerender = useRerender();
|
|
199
|
+
const { room: mockedRoom } = useContext(MockedRoomContext);
|
|
200
|
+
|
|
201
|
+
const [room] = useState<PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>>(() => {
|
|
202
|
+
return client.createRoom(_room, {
|
|
203
|
+
addons: options.addons,
|
|
204
|
+
debug,
|
|
205
|
+
initialPresence,
|
|
206
|
+
initialStorage:
|
|
207
|
+
typeof initialStorage === "function"
|
|
208
|
+
? client._defs.initialStorage?.getFactory(initialStorage)
|
|
209
|
+
: client._defs.initialStorage,
|
|
210
|
+
metadata,
|
|
211
|
+
onAuthorizationFail,
|
|
212
|
+
router: options.router,
|
|
213
|
+
} as CreateRoomOptions<TIO, TPresence, TStorage, TMetadata, TEvents>);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
const unsubscribe = room.subscribe("connection", () => {
|
|
218
|
+
rerender();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return () => {
|
|
222
|
+
unsubscribe();
|
|
223
|
+
};
|
|
224
|
+
}, [rerender, room]);
|
|
225
|
+
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
client.enter(room);
|
|
228
|
+
|
|
229
|
+
return () => {
|
|
230
|
+
client.leave(room);
|
|
231
|
+
};
|
|
232
|
+
}, [room]);
|
|
233
|
+
|
|
234
|
+
return <PluvRoomContext.Provider value={mockedRoom ?? room}>{children}</PluvRoomContext.Provider>;
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
238
|
+
|
|
48
239
|
const PluvProvider = memo<PluvProviderProps>((props) => {
|
|
49
240
|
const { children } = props;
|
|
50
241
|
|
|
@@ -55,13 +246,337 @@ export const createBundle = <
|
|
|
55
246
|
|
|
56
247
|
const useClient = (): PluvClient<TIO, TPresence, TStorage, TMetadata> => useContext(PluvContext);
|
|
57
248
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
):
|
|
249
|
+
const useRoom = () => useContext(PluvRoomContext);
|
|
250
|
+
|
|
251
|
+
const useBroadcast = (): BroadcastProxy<MergeEvents<TEvents, TIO>> => {
|
|
252
|
+
const room = useRoom();
|
|
253
|
+
|
|
254
|
+
const broadcast = useCallback(
|
|
255
|
+
<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
256
|
+
event: TEvent,
|
|
257
|
+
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
258
|
+
) => {
|
|
259
|
+
room.broadcast(event, data);
|
|
260
|
+
},
|
|
261
|
+
[room],
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
return useMemo((): BroadcastProxy<MergeEvents<TEvents, TIO>> => {
|
|
265
|
+
return new Proxy(broadcast, {
|
|
266
|
+
get(fn, prop) {
|
|
267
|
+
return (
|
|
268
|
+
data: Id<
|
|
269
|
+
InferIOInput<MergeEvents<TEvents, TIO>>[keyof InferIOInput<MergeEvents<TEvents, TIO>>]
|
|
270
|
+
>,
|
|
271
|
+
): void => {
|
|
272
|
+
return fn(prop as keyof InferIOInput<MergeEvents<TEvents, TIO>>, data);
|
|
273
|
+
};
|
|
274
|
+
},
|
|
275
|
+
}) as BroadcastProxy<MergeEvents<TEvents, TIO>>;
|
|
276
|
+
}, [broadcast]);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const useCanRedo = (): boolean => {
|
|
280
|
+
const room = useRoom();
|
|
281
|
+
|
|
282
|
+
const subscribe = useCallback((onStoreChange: () => void) => room.storageRoot(onStoreChange), [room]);
|
|
283
|
+
|
|
284
|
+
const getSnapshot = useCallback((): boolean => room.canRedo(), [room]);
|
|
285
|
+
|
|
286
|
+
const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
287
|
+
|
|
288
|
+
return canRedo;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const useCanUndo = (): boolean => {
|
|
292
|
+
const room = useRoom();
|
|
293
|
+
|
|
294
|
+
const subscribe = useCallback((onStoreChange: () => void) => room.storageRoot(onStoreChange), [room]);
|
|
295
|
+
|
|
296
|
+
const getSnapshot = useCallback((): boolean => room.canUndo(), [room]);
|
|
297
|
+
|
|
298
|
+
const canUndo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
299
|
+
|
|
300
|
+
return canUndo;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const useConnection = <T extends unknown = WebSocketConnection>(
|
|
304
|
+
selector = identity as (connection: WebSocketConnection) => T,
|
|
305
|
+
options?: SubscriptionHookOptions<Id<T>>,
|
|
306
|
+
): Id<T> => {
|
|
307
|
+
const room = useRoom();
|
|
308
|
+
|
|
309
|
+
const subscribe = useCallback(
|
|
310
|
+
(onStoreChange: () => void) => {
|
|
311
|
+
return room.subscribe("connection", onStoreChange);
|
|
312
|
+
},
|
|
313
|
+
[room],
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const getSnapshot = room.getConnection;
|
|
317
|
+
|
|
318
|
+
const _selector = useCallback((snapshot: WebSocketConnection) => selector(snapshot) as Id<T>, [selector]);
|
|
319
|
+
|
|
320
|
+
return useSyncExternalStoreWithSelector(
|
|
321
|
+
subscribe,
|
|
322
|
+
getSnapshot,
|
|
323
|
+
getSnapshot,
|
|
324
|
+
_selector,
|
|
325
|
+
options?.isEqual ?? fastDeepEqual,
|
|
326
|
+
);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const useDoc = () => {
|
|
330
|
+
const room = useRoom();
|
|
331
|
+
|
|
332
|
+
return room.getDoc();
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const useEvent = <TType extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
336
|
+
type: TType,
|
|
337
|
+
callback: (data: Id<IOEventMessage<MergeEvents<TEvents, TIO>, TType>>) => void,
|
|
338
|
+
): void => {
|
|
339
|
+
const room = useRoom();
|
|
340
|
+
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
const unsubscribe = room.event(type, callback);
|
|
343
|
+
|
|
344
|
+
return () => {
|
|
345
|
+
unsubscribe();
|
|
346
|
+
};
|
|
347
|
+
}, [callback, room, type]);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const event = new Proxy(
|
|
351
|
+
{},
|
|
352
|
+
{
|
|
353
|
+
get(_, prop) {
|
|
354
|
+
const useProxyEvent = (
|
|
355
|
+
callback: (
|
|
356
|
+
data: Id<
|
|
357
|
+
IOEventMessage<MergeEvents<TEvents, TIO>, keyof InferIOOutput<MergeEvents<TEvents, TIO>>>
|
|
358
|
+
>,
|
|
359
|
+
) => void,
|
|
360
|
+
): void => {
|
|
361
|
+
return useEvent(prop as keyof InferIOOutput<MergeEvents<TEvents, TIO>>, callback);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
return { useEvent: useProxyEvent };
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
) as EventProxy<MergeEvents<TEvents, TIO>>;
|
|
368
|
+
|
|
369
|
+
const useMyPresence = <T extends unknown = TPresence>(
|
|
370
|
+
selector = identity as (myPresence: TPresence) => T,
|
|
371
|
+
options?: SubscriptionHookOptions<Id<T> | null>,
|
|
372
|
+
): [Id<T>, Dispatch<UpdateMyPresenceAction<TPresence>>] => {
|
|
373
|
+
const room = useRoom();
|
|
374
|
+
|
|
375
|
+
const subscribe = useCallback(
|
|
376
|
+
(onStoreChange: () => void) => {
|
|
377
|
+
return room.subscribe("my-presence", onStoreChange);
|
|
378
|
+
},
|
|
379
|
+
[room],
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
const getSnapshot = room.getMyPresence;
|
|
383
|
+
|
|
384
|
+
const _selector = useCallback((snapshot: TPresence) => selector(snapshot) as Id<T>, [selector]);
|
|
385
|
+
|
|
386
|
+
const myPresence = useSyncExternalStoreWithSelector(
|
|
387
|
+
subscribe,
|
|
388
|
+
getSnapshot,
|
|
389
|
+
getSnapshot,
|
|
390
|
+
_selector,
|
|
391
|
+
options?.isEqual ?? fastDeepEqual,
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
return [myPresence, room.updateMyPresence];
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const useMyself = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
398
|
+
selector = identity as (myself: Id<UserInfo<TIO, TPresence>>) => T,
|
|
399
|
+
options?: SubscriptionHookOptions<Id<T> | null>,
|
|
400
|
+
): Id<T> | null => {
|
|
401
|
+
const room = useRoom();
|
|
402
|
+
|
|
403
|
+
const subscribe = useCallback(
|
|
404
|
+
(onStoreChange: () => void) => {
|
|
405
|
+
return room.subscribe("myself", onStoreChange);
|
|
406
|
+
},
|
|
407
|
+
[room],
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
const getSnapshot = room.getMyself;
|
|
411
|
+
|
|
412
|
+
const _selector = useCallback(
|
|
413
|
+
(snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
|
|
414
|
+
return !snapshot ? null : (selector(snapshot) as Id<T>);
|
|
415
|
+
},
|
|
416
|
+
[selector],
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
return useSyncExternalStoreWithSelector(
|
|
420
|
+
subscribe,
|
|
421
|
+
getSnapshot,
|
|
422
|
+
getSnapshot,
|
|
423
|
+
_selector,
|
|
424
|
+
options?.isEqual ?? fastDeepEqual,
|
|
425
|
+
);
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const useOther = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
429
|
+
connectionId: string,
|
|
430
|
+
selector = identity as (other: UserInfo<TIO, TPresence>) => T,
|
|
431
|
+
options?: SubscriptionHookOptions<T | null>,
|
|
432
|
+
): T | null => {
|
|
433
|
+
const room = useRoom();
|
|
434
|
+
|
|
435
|
+
const subscribe = useCallback(
|
|
436
|
+
(onStoreChange: () => void) => {
|
|
437
|
+
return room.other(connectionId, onStoreChange);
|
|
438
|
+
},
|
|
439
|
+
[room, connectionId],
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const getSnapshot = useCallback(() => room.getOther(connectionId), [room, connectionId]);
|
|
443
|
+
|
|
444
|
+
const _selector = useCallback(
|
|
445
|
+
(snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
|
|
446
|
+
return !snapshot ? null : (selector(snapshot) as T);
|
|
447
|
+
},
|
|
448
|
+
[selector],
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
return useSyncExternalStoreWithSelector(
|
|
452
|
+
subscribe,
|
|
453
|
+
getSnapshot,
|
|
454
|
+
getSnapshot,
|
|
455
|
+
_selector,
|
|
456
|
+
options?.isEqual ?? fastDeepEqual,
|
|
457
|
+
);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const useOthers = <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(
|
|
461
|
+
selector = identity as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
|
|
462
|
+
options?: SubscriptionHookOptions<T>,
|
|
463
|
+
): T => {
|
|
464
|
+
const room = useRoom();
|
|
465
|
+
|
|
466
|
+
const subscribe = useCallback(
|
|
467
|
+
(onStoreChange: () => void) => {
|
|
468
|
+
return room.subscribe("others", onStoreChange);
|
|
469
|
+
},
|
|
470
|
+
[room],
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const getSnapshot = room.getOthers;
|
|
474
|
+
|
|
475
|
+
return useSyncExternalStoreWithSelector(
|
|
476
|
+
subscribe,
|
|
477
|
+
getSnapshot,
|
|
478
|
+
getSnapshot,
|
|
479
|
+
selector as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
|
|
480
|
+
options?.isEqual ??
|
|
481
|
+
((a, b) => {
|
|
482
|
+
/**
|
|
483
|
+
* !HACK
|
|
484
|
+
* @description Assume the return type will always be an array when this occurs
|
|
485
|
+
* and do a shallow comparison instead like how standard React dependency
|
|
486
|
+
* arrays work. This is for performance's sake and may lead to bugs if the
|
|
487
|
+
* selector doesn't always return an array
|
|
488
|
+
* @date June 22, 2024
|
|
489
|
+
*/
|
|
490
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
491
|
+
return shallowArrayEqual(a, b);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return fastDeepEqual(a, b);
|
|
495
|
+
}),
|
|
496
|
+
);
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const useRedo = () => {
|
|
500
|
+
const room = useRoom();
|
|
501
|
+
|
|
502
|
+
return room.redo;
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const useStorage = <TKey extends keyof TStorage, TData extends unknown = InferCrdtJson<TStorage[TKey]>>(
|
|
506
|
+
key: TKey,
|
|
507
|
+
selector = identity as (data: InferCrdtJson<TStorage[TKey]>) => TData,
|
|
508
|
+
options?: SubscriptionHookOptions<TData | null>,
|
|
509
|
+
): [data: TData | null, sharedType: TStorage[TKey] | null] => {
|
|
510
|
+
const room = useRoom();
|
|
511
|
+
const rerender = useRerender();
|
|
512
|
+
|
|
513
|
+
room.subscribe("storage-loaded", () => {
|
|
514
|
+
rerender();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const subscribe = useCallback((onStoreChange: () => void) => room.storage(key, onStoreChange), [key, room]);
|
|
518
|
+
|
|
519
|
+
const getSnapshot = useCallback((): InferCrdtJson<TStorage[TKey]> | null => {
|
|
520
|
+
return room.getStorageJson(key);
|
|
521
|
+
}, [key, room]);
|
|
522
|
+
|
|
523
|
+
const _selector = useCallback(
|
|
524
|
+
(snapshot: InferCrdtJson<TStorage[TKey]> | null) => {
|
|
525
|
+
return snapshot === null ? null : selector(snapshot);
|
|
526
|
+
},
|
|
527
|
+
[selector],
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
const data = useSyncExternalStoreWithSelector(
|
|
531
|
+
subscribe,
|
|
532
|
+
getSnapshot,
|
|
533
|
+
getSnapshot,
|
|
534
|
+
_selector,
|
|
535
|
+
options?.isEqual ?? fastDeepEqual,
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
const sharedType = room.getStorage(key) ?? null;
|
|
539
|
+
|
|
540
|
+
return [data, sharedType];
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
const useTransact = () => {
|
|
544
|
+
const room = useRoom();
|
|
545
|
+
|
|
546
|
+
return room.transact;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const useUndo = () => {
|
|
550
|
+
const room = useRoom();
|
|
551
|
+
|
|
552
|
+
return room.undo;
|
|
553
|
+
};
|
|
61
554
|
|
|
62
555
|
return {
|
|
63
|
-
|
|
556
|
+
// components
|
|
557
|
+
MockedRoomProvider,
|
|
64
558
|
PluvProvider,
|
|
559
|
+
PluvRoomProvider,
|
|
560
|
+
|
|
561
|
+
// proxies
|
|
562
|
+
event,
|
|
563
|
+
|
|
564
|
+
// hooks
|
|
565
|
+
useBroadcast,
|
|
566
|
+
useCanRedo,
|
|
567
|
+
useCanUndo,
|
|
65
568
|
useClient,
|
|
569
|
+
useConnection,
|
|
570
|
+
useDoc,
|
|
571
|
+
useEvent,
|
|
572
|
+
useMyPresence,
|
|
573
|
+
useMyself,
|
|
574
|
+
useOther,
|
|
575
|
+
useOthers,
|
|
576
|
+
useRedo,
|
|
577
|
+
useRoom,
|
|
578
|
+
useStorage,
|
|
579
|
+
useTransact,
|
|
580
|
+
useUndo,
|
|
66
581
|
};
|
|
67
582
|
};
|
package/src/index.ts
CHANGED
|
@@ -2,11 +2,4 @@ export { ConnectionState, createClient } from "@pluv/client";
|
|
|
2
2
|
export type { CrdtType, InferCrdtJson } from "@pluv/crdt";
|
|
3
3
|
export type { BaseUser, EventMessage, EventRecord, IOEventMessage } from "@pluv/types";
|
|
4
4
|
export { createBundle } from "./createBundle";
|
|
5
|
-
export type { CreateBundle, PluvProviderProps } from "./createBundle";
|
|
6
|
-
export type {
|
|
7
|
-
CreateRoomBundle,
|
|
8
|
-
CreateRoomBundleOptions,
|
|
9
|
-
InferRoomStorage,
|
|
10
|
-
PluvRoomProviderProps,
|
|
11
|
-
SubscriptionHookOptions,
|
|
12
|
-
} from "./createRoomBundle";
|
|
5
|
+
export type { CreateBundle, PluvProviderProps, PluvRoomProviderProps, SubscriptionHookOptions } from "./createBundle";
|