1 | import Reflux, { Store as RefluxStore } from 'reflux';
|
2 | import EventEmitter from 'eventemitter3';
|
3 | import { Actions } from './actions';
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | const INT8_MAX = 127;
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | const STUB_STORE = Reflux.createStore({});
|
17 |
|
18 | interface Role {
|
19 | name: string;
|
20 | component: React.JSXElementConstructor<unknown>;
|
21 | order?: number;
|
22 | }
|
23 |
|
24 | interface Store extends RefluxStore {
|
25 | onActivated?: (appRegistry: AppRegistry) => void;
|
26 | }
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | export class AppRegistry {
|
33 | _emitter: EventEmitter;
|
34 | actions: Record<string, unknown>;
|
35 | components: Record<string, React.JSXElementConstructor<unknown>>;
|
36 | stores: Record<string, Store>;
|
37 | roles: Record<string, Role[]>;
|
38 | storeMisses: Record<string, number>;
|
39 |
|
40 | |
41 |
|
42 |
|
43 | constructor() {
|
44 | this._emitter = new EventEmitter();
|
45 | this.actions = {};
|
46 | this.components = {};
|
47 | this.stores = {};
|
48 | this.roles = {};
|
49 | this.storeMisses = {};
|
50 | }
|
51 |
|
52 |
|
53 | static get Actions(): typeof Actions {
|
54 | return Actions;
|
55 | }
|
56 |
|
57 |
|
58 | static get AppRegistry(): typeof AppRegistry {
|
59 | return AppRegistry;
|
60 | }
|
61 |
|
62 | |
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 | deregisterAction(name: string): this {
|
70 | delete this.actions[name];
|
71 | Actions.actionDeregistered(name);
|
72 | return this;
|
73 | }
|
74 |
|
75 | |
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 | deregisterComponent(name: string): this {
|
83 | delete this.components[name];
|
84 | Actions.componentDeregistered(name);
|
85 | return this;
|
86 | }
|
87 |
|
88 | |
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 | deregisterRole(name: string, object: Role): this {
|
97 | const roles = this.roles[name];
|
98 | roles.splice(roles.indexOf(object), 1);
|
99 | Actions.roleDeregistered(name);
|
100 | return this;
|
101 | }
|
102 |
|
103 | |
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 | deregisterStore(name: string): this {
|
111 | delete this.stores[name];
|
112 | Actions.storeDeregistered(name);
|
113 | return this;
|
114 | }
|
115 |
|
116 | |
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | getAction(name: string): unknown {
|
124 | return this.actions[name];
|
125 | }
|
126 |
|
127 | |
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | getComponent(name: string): React.JSXElementConstructor<unknown> | undefined {
|
135 | return this.components[name];
|
136 | }
|
137 |
|
138 | |
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | getRole(name: string): Role[] | undefined {
|
146 | return this.roles[name];
|
147 | }
|
148 |
|
149 | |
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 | getStore(name: string): Store {
|
157 | const store = this.stores[name];
|
158 | if (store === undefined) {
|
159 | this.storeMisses[name] = (this.storeMisses[name] || 0) + 1;
|
160 | return STUB_STORE;
|
161 | }
|
162 | return store;
|
163 | }
|
164 |
|
165 | |
166 |
|
167 |
|
168 |
|
169 |
|
170 | onActivated(): this {
|
171 | return this._callOnStores((store) => {
|
172 | if (store.onActivated) {
|
173 | store.onActivated(this);
|
174 | }
|
175 | });
|
176 | }
|
177 |
|
178 | |
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | registerAction(name: string, action: unknown): this {
|
187 | const overwrite = Object.prototype.hasOwnProperty.call(this.actions, name);
|
188 | this.actions[name] = action;
|
189 | if (overwrite) {
|
190 | Actions.actionOverridden(name);
|
191 | } else {
|
192 | Actions.actionRegistered(name);
|
193 | }
|
194 | return this;
|
195 | }
|
196 |
|
197 | |
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 | registerComponent(
|
206 | name: string,
|
207 | component: React.JSXElementConstructor<unknown>
|
208 | ): this {
|
209 | const overwrite = Object.prototype.hasOwnProperty.call(
|
210 | this.components,
|
211 | name
|
212 | );
|
213 | this.components[name] = component;
|
214 | if (overwrite) {
|
215 | Actions.componentOverridden(name);
|
216 | } else {
|
217 | Actions.componentRegistered(name);
|
218 | }
|
219 | return this;
|
220 | }
|
221 |
|
222 | |
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 | registerRole(name: string, role: Role): this {
|
231 | if (
|
232 | Object.prototype.hasOwnProperty.call(this.roles, name) &&
|
233 | !this.roles[name].includes(role)
|
234 | ) {
|
235 | this.roles[name].push(role);
|
236 | this.roles[name].sort(this._roleComparator.bind(this));
|
237 | } else {
|
238 | this.roles[name] = [role];
|
239 | }
|
240 | Actions.roleRegistered(name);
|
241 | return this;
|
242 | }
|
243 |
|
244 | |
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 | registerStore(name: string, store: Store): this {
|
253 | const overwrite = Object.prototype.hasOwnProperty.call(this.stores, name);
|
254 | this.stores[name] = store;
|
255 | if (overwrite) {
|
256 | Actions.storeOverridden(name);
|
257 | } else {
|
258 | Actions.storeRegistered(name);
|
259 | }
|
260 | return this;
|
261 | }
|
262 |
|
263 | |
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 | addListener(eventName: string, listener: (...args: any[]) => void): this {
|
272 | return this.on(eventName, listener);
|
273 | }
|
274 |
|
275 | |
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 | emit(eventName: string, ...args: any[]): boolean {
|
284 | return this._emitter.emit(eventName, ...args);
|
285 | }
|
286 |
|
287 | |
288 |
|
289 |
|
290 |
|
291 |
|
292 | eventNames(): string[] {
|
293 | return this._emitter.eventNames() as string[];
|
294 | }
|
295 |
|
296 | |
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 | listenerCount(eventName: string): number {
|
304 | return this._emitter.listeners(eventName).length;
|
305 | }
|
306 |
|
307 | |
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 | listeners(eventName: string): ((...args: any[]) => void)[] {
|
315 | return this._emitter.listeners(eventName);
|
316 | }
|
317 |
|
318 | /**
|
319 | * Adds a listener for the event name to the underlying event emitter.
|
320 | *
|
321 | * @param {String} eventName - The event name.
|
322 | * @param {Function} listener - The listener.
|
323 | *
|
324 | * @returns {AppRegistry} The chainable app registry.
|
325 | */
|
326 | on(eventName: string, listener: (...args: any[]) => void): this {
|
327 | this._emitter.on(eventName, listener);
|
328 | return this;
|
329 | }
|
330 |
|
331 | /**
|
332 | * Adds a listener for the event name to the underlying event emitter
|
333 | * to handle an event only once.
|
334 | *
|
335 | * @param {String} eventName - The event name.
|
336 | * @param {Function} listener - The listener.
|
337 | *
|
338 | * @returns {AppRegistry} The chainable app registry.
|
339 | */
|
340 | once(eventName: string, listener: (...args: any[]) => void): this {
|
341 | this._emitter.once(eventName, listener);
|
342 | return this;
|
343 | }
|
344 |
|
345 | /**
|
346 | * Removes a listener for the event.
|
347 | *
|
348 | * @param {String} eventName - The event name.
|
349 | * @param {Function} listener - The listener.
|
350 | *
|
351 | * @returns {AppRegistry} The chainable app registry.
|
352 | */
|
353 | removeListener(eventName: string, listener: (...args: any[]) => void): this {
|
354 | this._emitter.removeListener(eventName, listener);
|
355 | return this;
|
356 | }
|
357 |
|
358 | /**
|
359 | * Removes all the listeners for the event name.
|
360 | *
|
361 | * @param {String} eventName - The event name.
|
362 | *
|
363 | * @returns {AppRegistry} The chainable app registry.
|
364 | */
|
365 | removeAllListeners(eventName: string): this {
|
366 | this._emitter.removeAllListeners(eventName);
|
367 | return this;
|
368 | }
|
369 |
|
370 | _callOnStores(fn: (store: Store) => void): this {
|
371 | for (const key of Object.keys(this.stores)) {
|
372 | const store = this.stores[key];
|
373 | fn(store);
|
374 | }
|
375 | return this;
|
376 | }
|
377 |
|
378 | _roleComparator(a: Role, b: Role): number {
|
379 | const aOrder = a.order || INT8_MAX;
|
380 | const bOrder = b.order || INT8_MAX;
|
381 | return aOrder - bOrder;
|
382 | }
|
383 | }
|
384 |
|
\ | No newline at end of file |