@adcops/autocore-react 3.0.39 → 3.0.40
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/dist/components/IndicatorButton.d.ts +22 -137
- package/dist/components/IndicatorButton.js +1 -1
- package/dist/core/EventEmitterContext.d.ts +1 -1
- package/dist/core/EventEmitterContext.js +1 -1
- package/package.json +1 -1
- package/src/components/IndicatorButton.tsx +187 -333
- package/src/core/EventEmitterContext.tsx +266 -223
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
* Copyright (C) 2024 Automated Design Corp. All Rights Reserved.
|
|
3
3
|
* Created Date: 2024-01-17 11:45:10
|
|
4
4
|
* -----
|
|
5
|
-
* Last Modified:
|
|
5
|
+
* Last Modified: 2025-08-30 06:54:17
|
|
6
6
|
* Modified By: ADC
|
|
7
7
|
* -----
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
import React, {
|
|
12
|
+
createContext,
|
|
13
|
+
ReactNode,
|
|
14
|
+
useState,
|
|
15
|
+
useMemo,
|
|
16
|
+
useCallback,
|
|
17
|
+
useRef,
|
|
18
|
+
useEffect,
|
|
19
|
+
} from "react";
|
|
13
20
|
import { createHub, Hub } from "../hub";
|
|
14
21
|
import { CommandMessageResult } from "../hub/CommandMessage";
|
|
15
22
|
|
|
@@ -19,26 +26,25 @@ export { Hub };
|
|
|
19
26
|
* Represents an event subsription.
|
|
20
27
|
*/
|
|
21
28
|
export interface Subscription {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
/** ID of the subscription used for unsubscription. */
|
|
30
|
+
id: number;
|
|
31
|
+
/** Callback function. */
|
|
32
|
+
callback: React.Dispatch<any>;
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
|
|
29
35
|
/**
|
|
30
36
|
* Represents the payload data associated with an event.
|
|
31
37
|
*/
|
|
32
38
|
export interface State {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The optional data payload of the event.
|
|
41
|
+
*/
|
|
42
|
+
eventData?: any;
|
|
43
|
+
/** Callback subscription. A list of callback subscriptions matched to a topic. */
|
|
44
|
+
subscriptions: Record<string, Subscription[]>;
|
|
45
|
+
|
|
46
|
+
/** Tracks the next subscription ID that will be assigned. */
|
|
47
|
+
nextSubscriptionId: number;
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
/**
|
|
@@ -47,37 +53,35 @@ export interface State {
|
|
|
47
53
|
* a value of any type.
|
|
48
54
|
*/
|
|
49
55
|
export interface Action {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
payload?: any;
|
|
56
|
+
/**
|
|
57
|
+
* The topic or identifier of the event.
|
|
58
|
+
*/
|
|
59
|
+
topic: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The optional data payload associated with the event.
|
|
63
|
+
*/
|
|
64
|
+
payload?: any;
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
/**
|
|
67
|
+
/**
|
|
63
68
|
* Type declaration for the EventEmitter dispatch function, which
|
|
64
69
|
* publishes an Action globally throughout the EventEmitterContext.
|
|
65
70
|
*/
|
|
66
71
|
export type EmitterDispatchFunction = (action: Action) => void;
|
|
67
72
|
|
|
68
|
-
/**
|
|
73
|
+
/**
|
|
69
74
|
* Type declaration for the EventEmitter dispatch function, which
|
|
70
75
|
* receives an Action on a specific topic broadcast through the EventEmitterContext.
|
|
71
76
|
*/
|
|
72
77
|
export type EmitterSubscribeFunction = (action: Action) => void;
|
|
73
78
|
|
|
74
|
-
/**
|
|
79
|
+
/**
|
|
75
80
|
* Type declaration for the EventEmitter unsubscribe function, which
|
|
76
81
|
* receives an Action on a specific topic broadcast through the EventEmitterContext.
|
|
77
82
|
*/
|
|
78
83
|
export type EmitterUnsubscribeFunction = (action: Action) => void;
|
|
79
84
|
|
|
80
|
-
|
|
81
85
|
/**
|
|
82
86
|
* Defines the context for an event emitter used throughout a front-end application to manage and dispatch events.
|
|
83
87
|
* This interface includes methods for managing the application's state, handling global actions, communicating with the back end,
|
|
@@ -86,64 +90,66 @@ export type EmitterUnsubscribeFunction = (action: Action) => void;
|
|
|
86
90
|
* components and the back end, as well as among components themselves.
|
|
87
91
|
*/
|
|
88
92
|
export interface EventEmitterContextType {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The current state of the event emitter, containing the latest event data.
|
|
95
|
+
*/
|
|
96
|
+
state: State;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A function to dispatch actions globally throughout the front-end,
|
|
100
|
+
* triggering state updates and events.
|
|
101
|
+
*
|
|
102
|
+
* @param action The action to dispatch, containing topic and optional payload.
|
|
103
|
+
*/
|
|
104
|
+
dispatch: (action: Action) => void;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Invoke/send a message to the back end.
|
|
108
|
+
* This does NOT get published to the front end.
|
|
109
|
+
*/
|
|
110
|
+
invoke(
|
|
111
|
+
domain: string,
|
|
112
|
+
fname: string,
|
|
113
|
+
payload?: object
|
|
114
|
+
): Promise<CommandMessageResult>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Subscribe to events identified by the topic.
|
|
118
|
+
* @param topic The subscription topic.
|
|
119
|
+
* @param callback The callback to signal.
|
|
120
|
+
* @returns number Subscription ID used to unsubscribe later.
|
|
121
|
+
*/
|
|
122
|
+
subscribe: (topic: string, callback: React.Dispatch<any>) => number;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Unsubscribe to events.
|
|
126
|
+
* @param subscriptionId The id of the subscription returned by the subscribe method.
|
|
127
|
+
* @returns
|
|
128
|
+
*/
|
|
129
|
+
unsubscribe: (subscriptionId: number) => void;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Global hub for publishing and receiving events throughout the interface, and for exchanging
|
|
133
|
+
* data with the backend.
|
|
134
|
+
*/
|
|
135
|
+
hub: Hub | null;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Retrieves the current subscriptions. Used for debugging purposes.
|
|
139
|
+
* @param topic Optional. The topic to retrieve subscriptions for. If omitted, returns all subscriptions.
|
|
140
|
+
* @returns An object containing the current subscriptions, optionally filtered by topic.
|
|
141
|
+
*/
|
|
142
|
+
getSubscriptions: (
|
|
143
|
+
topic?: string
|
|
144
|
+
) => Record<string, Subscription[]> | Subscription[];
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Returns true if the Hub in use is connected to its source.
|
|
148
|
+
* @returns boolean
|
|
149
|
+
*/
|
|
150
|
+
isConnected: () => boolean;
|
|
144
151
|
}
|
|
145
152
|
|
|
146
|
-
|
|
147
153
|
let globalSubscriptionId = 1;
|
|
148
154
|
|
|
149
155
|
/**
|
|
@@ -153,7 +159,7 @@ let globalSubscriptionId = 1;
|
|
|
153
159
|
* in a React application. It serves as a global event bus that components can subscribe to or emit events, allowing for
|
|
154
160
|
* a loosely coupled architecture. Additionally, it provides a mechanism for invoking backend functions and managing
|
|
155
161
|
* subscriptions, making it easier to integrate React components with backend services.
|
|
156
|
-
*
|
|
162
|
+
*
|
|
157
163
|
* The context includes several key functionalities:
|
|
158
164
|
* - `state`: Maintains the current state of subscriptions and their identifiers.
|
|
159
165
|
* - `dispatch`: Allows components to emit events with specific topics and payloads, which can be listened to by other components.
|
|
@@ -161,21 +167,21 @@ let globalSubscriptionId = 1;
|
|
|
161
167
|
* - `unsubscribe`: Provides a way for components to stop listening to events, helping prevent memory leaks and unnecessary updates.
|
|
162
168
|
* - `invoke`: Facilitates calling backend functions with specific arguments and handling their responses asynchronously.
|
|
163
169
|
* - `getSubscriptions`: Offers insight into current active subscriptions, useful for debugging purposes.
|
|
164
|
-
*
|
|
170
|
+
*
|
|
165
171
|
* This context is essential for applications that require a high degree of inter-component communication or need to
|
|
166
172
|
* interact with a backend efficiently.
|
|
167
|
-
*
|
|
173
|
+
*
|
|
168
174
|
* For more information, see [Additional Documentation](../additional-docs/GlobalEventEmitter.md).
|
|
169
|
-
*
|
|
175
|
+
*
|
|
170
176
|
* ## Usage
|
|
171
|
-
*
|
|
177
|
+
*
|
|
172
178
|
* The entire application should be wrapped in the EventEmitterProvider.
|
|
173
|
-
*
|
|
179
|
+
*
|
|
174
180
|
* App.tsx
|
|
175
181
|
* ```
|
|
176
182
|
* import { EventEmitterProvider } from "@adcops/autocore-react/core/EventEmitterContext.js";
|
|
177
183
|
* function App() {
|
|
178
|
-
*
|
|
184
|
+
*
|
|
179
185
|
* return(
|
|
180
186
|
* <EventEmitterProvider>
|
|
181
187
|
* <PrimeReactProvider>
|
|
@@ -187,31 +193,31 @@ let globalSubscriptionId = 1;
|
|
|
187
193
|
* </PrimeReactProvider>
|
|
188
194
|
* </EventEmitterProvider>
|
|
189
195
|
* );
|
|
190
|
-
*
|
|
196
|
+
*
|
|
191
197
|
* }
|
|
192
|
-
*
|
|
198
|
+
*
|
|
193
199
|
* ```
|
|
194
|
-
*
|
|
200
|
+
*
|
|
195
201
|
* ### Catching and receiving events
|
|
196
202
|
* The EventEmitterContext creates an appropriate instance of the hub, which is derived from HubBase.
|
|
197
203
|
* That hub can be used to publish and subscribe to
|
|
198
204
|
* topics globally in the front-end, regardless of being connected to any backend.
|
|
199
205
|
* Usage within a component is simple.
|
|
200
|
-
*
|
|
206
|
+
*
|
|
201
207
|
* ```
|
|
202
208
|
* const {dispatch, subscribe, unsubscribe} = useContext(EventEmitterContext);
|
|
203
209
|
* const [controlPower, setControlPower] = useState(false);
|
|
204
210
|
* useEffect(() => {
|
|
205
211
|
* const unsubscribeControlPower = subscribe('value-simulator-bBit1', (value) => {
|
|
206
212
|
* setControlPower(value);
|
|
207
|
-
* });
|
|
213
|
+
* });
|
|
208
214
|
*
|
|
209
215
|
*
|
|
210
216
|
* return () => {
|
|
211
217
|
* unsubscribe(unsubscribeControlPower);
|
|
212
218
|
* }
|
|
213
219
|
* }, [] );
|
|
214
|
-
*
|
|
220
|
+
*
|
|
215
221
|
* const onPbPressed = () => {
|
|
216
222
|
* let count = 1;
|
|
217
223
|
* dispatch({
|
|
@@ -219,30 +225,30 @@ let globalSubscriptionId = 1;
|
|
|
219
225
|
* payload: count
|
|
220
226
|
* });
|
|
221
227
|
* }
|
|
222
|
-
*
|
|
223
|
-
* ```
|
|
228
|
+
*
|
|
229
|
+
* ```
|
|
224
230
|
* The hub should also be used for invoking events in the backend.
|
|
225
231
|
* This example will call the function "update_count" in the backend, passing
|
|
226
232
|
* the expected argument "count". Details of the interaction between the Hub and
|
|
227
233
|
* the backend will be handled by the appropriate HubBase sub-class, and should
|
|
228
234
|
* be transparent to the front end.
|
|
229
|
-
*
|
|
235
|
+
*
|
|
230
236
|
* ```
|
|
231
237
|
* const {invoke} = useContext(EventEmitterContext);
|
|
232
238
|
* const incrementCount = () => {
|
|
233
239
|
* count += 1;
|
|
234
|
-
* invoke('update_count', {"count": count});
|
|
240
|
+
* invoke('update_count', {"count": count});
|
|
235
241
|
* };
|
|
236
|
-
*
|
|
242
|
+
*
|
|
237
243
|
* Subscribing to a topic is simple. The type of value received is specific
|
|
238
244
|
* to the topic.
|
|
239
|
-
*
|
|
245
|
+
*
|
|
240
246
|
* Example: Listen to an event 'xarm-position':
|
|
241
247
|
* ```
|
|
242
248
|
* const {subscribe, unsubscribe} = useContext(EventEmitterContext);
|
|
243
249
|
* useEffect(() => {
|
|
244
250
|
* const unsubscripeMp = subscribe('xarm-position', (value) => {
|
|
245
|
-
* // The publisher sent a JSON object of 3D position values.
|
|
251
|
+
* // The publisher sent a JSON object of 3D position values.
|
|
246
252
|
* setX(value.x);
|
|
247
253
|
* setY(value.y);
|
|
248
254
|
* setZ(value.z);
|
|
@@ -250,49 +256,55 @@ let globalSubscriptionId = 1;
|
|
|
250
256
|
* setB(value.yaw);
|
|
251
257
|
* setC(value.pitch);
|
|
252
258
|
* });
|
|
253
|
-
*
|
|
259
|
+
*
|
|
254
260
|
* return () => {
|
|
255
261
|
* unsubscribe(unsubscripeMp);
|
|
256
262
|
* }
|
|
257
|
-
*
|
|
263
|
+
*
|
|
258
264
|
* }, [] );
|
|
259
265
|
*
|
|
260
266
|
* ```
|
|
261
|
-
*
|
|
267
|
+
*
|
|
262
268
|
* For applications that need to access the instance of the hub, get the current instance
|
|
263
269
|
* from the EventEmitterContext:
|
|
264
|
-
*
|
|
270
|
+
*
|
|
265
271
|
* ```
|
|
266
272
|
* const {hub} = useContext(EventEmitterContext);
|
|
267
273
|
* * ```
|
|
268
|
-
*
|
|
269
|
-
*
|
|
274
|
+
*
|
|
275
|
+
*
|
|
270
276
|
*/
|
|
271
277
|
export const EventEmitterContext = createContext<EventEmitterContextType>({
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
278
|
+
state: { subscriptions: {}, nextSubscriptionId: 1 },
|
|
279
|
+
dispatch: () => {},
|
|
280
|
+
subscribe: () => {
|
|
281
|
+
return 0;
|
|
282
|
+
}, // Placeholder for subscription logic
|
|
283
|
+
invoke: async (domain: string, fname: string, payload?: object) => {
|
|
284
|
+
domain;
|
|
285
|
+
fname;
|
|
286
|
+
payload;
|
|
287
|
+
let ret: CommandMessageResult = {
|
|
288
|
+
data: {},
|
|
289
|
+
success: false,
|
|
290
|
+
error_message: "",
|
|
291
|
+
};
|
|
292
|
+
// Placeholder for invoke logic
|
|
293
|
+
// Implement the logic to send a message to the backend and return a promise
|
|
294
|
+
return Promise.resolve(ret); // Example placeholder, replace with actual implementation
|
|
295
|
+
},
|
|
296
|
+
unsubscribe: (subscriptionId: number) => {
|
|
297
|
+
subscriptionId;
|
|
298
|
+
}, // Placeholder for unsubscription logic
|
|
299
|
+
hub: null,
|
|
300
|
+
getSubscriptions: () => {
|
|
301
|
+
return [];
|
|
302
|
+
},
|
|
303
|
+
isConnected: () => {
|
|
304
|
+
return false;
|
|
305
|
+
},
|
|
293
306
|
});
|
|
294
307
|
|
|
295
|
-
|
|
296
308
|
/**
|
|
297
309
|
* A React component that provides the EventEmitterContext to its children.
|
|
298
310
|
*
|
|
@@ -300,16 +312,16 @@ export const EventEmitterContext = createContext<EventEmitterContextType>({
|
|
|
300
312
|
* with the event emitter.
|
|
301
313
|
*
|
|
302
314
|
* @param children The child components to be wrapped in the context.
|
|
303
|
-
*
|
|
315
|
+
*
|
|
304
316
|
* ## Usage
|
|
305
|
-
*
|
|
317
|
+
*
|
|
306
318
|
* The entire application should be wrapped in the EventEmitterProvider.
|
|
307
|
-
*
|
|
319
|
+
*
|
|
308
320
|
* App.tsx
|
|
309
321
|
* ```
|
|
310
322
|
* import { EventEmitterProvider } from "@adcops/autocore-react/core/EventEmitterContext.js";
|
|
311
323
|
* function App() {
|
|
312
|
-
*
|
|
324
|
+
*
|
|
313
325
|
* return(
|
|
314
326
|
* <EventEmitterProvider>
|
|
315
327
|
* <PrimeReactProvider>
|
|
@@ -321,89 +333,120 @@ export const EventEmitterContext = createContext<EventEmitterContextType>({
|
|
|
321
333
|
* </PrimeReactProvider>
|
|
322
334
|
* </EventEmitterProvider>
|
|
323
335
|
* );
|
|
324
|
-
*
|
|
336
|
+
*
|
|
325
337
|
* }
|
|
326
|
-
*
|
|
327
|
-
* ```
|
|
338
|
+
*
|
|
339
|
+
* ```
|
|
328
340
|
*/
|
|
329
|
-
export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
341
|
+
export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
|
|
342
|
+
children,
|
|
343
|
+
}) => {
|
|
344
|
+
const [state, setState] = useState<State>({
|
|
345
|
+
subscriptions: {},
|
|
346
|
+
nextSubscriptionId: 1,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Memoize the hub instance so it's only created once
|
|
350
|
+
const hub = useMemo(() => createHub(), []);
|
|
351
|
+
|
|
352
|
+
// <-- New: the source of truth for subscriptions lives in a ref
|
|
353
|
+
const subsRef = useRef<Record<string, Subscription[]>>({});
|
|
354
|
+
|
|
355
|
+
// Keep the ref in sync for debugging / external reads
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
subsRef.current = state.subscriptions;
|
|
358
|
+
}, [state.subscriptions]);
|
|
359
|
+
|
|
360
|
+
const dispatch = useCallback((action: Action) => {
|
|
361
|
+
const { topic, payload } = action;
|
|
362
|
+
|
|
363
|
+
// Read once, outside setState, so it can't be double-invoked by StrictMode
|
|
364
|
+
const listeners = subsRef.current[topic]?.slice() ?? [];
|
|
365
|
+
for (const sub of listeners) {
|
|
366
|
+
try {
|
|
367
|
+
sub.callback(payload);
|
|
368
|
+
} catch (e) {
|
|
369
|
+
console.error("[EventBus] listener error", e);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Optional: keep last payload in state (pure)
|
|
374
|
+
setState((prev) => ({ ...prev, eventData: payload }));
|
|
375
|
+
}, []);
|
|
376
|
+
|
|
377
|
+
const subscribe = useCallback(
|
|
378
|
+
(topic: string, callback: React.Dispatch<any>): number => {
|
|
379
|
+
const id = ++globalSubscriptionId;
|
|
380
|
+
|
|
381
|
+
// Mutate ref
|
|
382
|
+
const prev = subsRef.current[topic] ?? [];
|
|
383
|
+
const next = [...prev, { id, callback }];
|
|
384
|
+
subsRef.current[topic] = next;
|
|
385
|
+
|
|
386
|
+
// Reflect in state (for debugging/inspection)
|
|
387
|
+
setState((prevState) => ({
|
|
388
|
+
...prevState,
|
|
389
|
+
subscriptions: { ...prevState.subscriptions, [topic]: next },
|
|
390
|
+
nextSubscriptionId: globalSubscriptionId + 1,
|
|
391
|
+
}));
|
|
392
|
+
|
|
393
|
+
return id;
|
|
394
|
+
},
|
|
395
|
+
[]
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
const unsubscribe = useCallback((subscriptionId: number) => {
|
|
399
|
+
const map = subsRef.current;
|
|
400
|
+
for (const t of Object.keys(map)) {
|
|
401
|
+
const next = map[t].filter((s) => s.id !== subscriptionId);
|
|
402
|
+
if (next.length) map[t] = next;
|
|
403
|
+
else delete map[t];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
setState((prev) => ({ ...prev, subscriptions: { ...map } }));
|
|
407
|
+
}, []);
|
|
408
|
+
|
|
409
|
+
// (Nice-to-have) Handle HMR so listeners don’t leak across hot updates
|
|
410
|
+
if (import.meta && (import.meta as any).hot) {
|
|
411
|
+
(import.meta as any).hot.dispose(() => {
|
|
412
|
+
subsRef.current = {};
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// const getSubscriptions = useCallback(
|
|
417
|
+
// (topic?: string): Record<string, Subscription[]> | Subscription[] => {
|
|
418
|
+
// if (topic) {
|
|
419
|
+
// // Return subscriptions for the provided topic, or an empty array if the topic doesn't exist
|
|
420
|
+
// return state.subscriptions[topic] || [];
|
|
421
|
+
// } else {
|
|
422
|
+
// // Return all subscriptions
|
|
423
|
+
// return state.subscriptions;
|
|
424
|
+
// }
|
|
425
|
+
// },
|
|
426
|
+
// []
|
|
427
|
+
// );
|
|
428
|
+
|
|
429
|
+
// Provide the memoized hub instance in the context value
|
|
430
|
+
const contextValue = useMemo(
|
|
431
|
+
() => ({
|
|
432
|
+
state,
|
|
433
|
+
dispatch,
|
|
434
|
+
subscribe,
|
|
435
|
+
unsubscribe,
|
|
436
|
+
invoke: hub.invoke,
|
|
437
|
+
hub,
|
|
438
|
+
getSubscriptions: (topic?: string) =>
|
|
439
|
+
topic ? subsRef.current[topic] ?? [] : subsRef.current,
|
|
440
|
+
isConnected: hub.isConnected,
|
|
441
|
+
}),
|
|
442
|
+
[state, hub, dispatch, subscribe, unsubscribe]
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
hub.setContext(contextValue);
|
|
446
|
+
|
|
447
|
+
return (
|
|
448
|
+
<EventEmitterContext.Provider value={contextValue}>
|
|
449
|
+
{children}
|
|
450
|
+
</EventEmitterContext.Provider>
|
|
451
|
+
);
|
|
409
452
|
};
|