-
Notifications
You must be signed in to change notification settings - Fork 0
/
component-registry.ts
111 lines (97 loc) · 3.86 KB
/
component-registry.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import React from "react";
import { EventEmitter } from "../../utils/events";
import { ComponentSelector, ComponentSignal, SignalSelector, WithComponentSelector } from "./types";
import { matchComponentSelector } from "./utils";
export interface ComponentRegistryEvents {
registered: { component: React.Component & WithComponentSelector };
signal: { componentId: number; signal: ComponentSignal };
}
export default class ComponentRegistry {
events = new EventEmitter<ComponentRegistryEvents>();
generatedIds = 0;
componentsById: { [id: number]: React.Component & WithComponentSelector } = {};
componentsByName: { [name: string]: { [id: number]: React.Component & WithComponentSelector } } = {};
emittedSignals: { [componentId: number]: Array<ComponentSignal> } = {};
registerComponent(component: React.Component & WithComponentSelector) {
const name = Object.getPrototypeOf(component).constructor.name;
const id = ++this.generatedIds;
this.componentsById[id] = component;
this.componentsByName[name] = this.componentsByName[name] ?? {};
this.componentsByName[name][id] = component;
this.emittedSignals[id] = [];
this.events.emit("registered", { component });
return {
id,
unregister: () => {
delete this.componentsById[id];
delete this.componentsByName[name][id];
delete this.emittedSignals[id];
},
};
}
emitSignal(componentId: number, signal: ComponentSignal) {
this.emittedSignals[componentId].push(signal);
this.events.emit("signal", { componentId, signal });
}
getComponent(componentName: string, selector?: ComponentSelector) {
selector = selector ?? {};
for (const component of Object.values(this.componentsByName[componentName] ?? {})) {
if (matchComponentSelector(component.componentSelector ?? {}, selector)) {
return component;
}
}
return null;
}
hasEmittedSignal(componentId: number, searchSignal: ComponentSignal) {
const selector = searchSignal.selector ?? {};
for (const emittedSignal of Object.values(this.emittedSignals[componentId] ?? {})) {
if (matchComponentSelector(emittedSignal.selector ?? {}, selector)) {
return true;
}
}
return false;
}
async waitForComponent(componentName: string, selector?: ComponentSelector) {
selector = selector ?? {};
const existingComponent = this.getComponent(componentName, selector);
if (existingComponent) {
return existingComponent;
}
return new Promise<React.Component>((resolve) => {
const unsubscribe = this.events.on("registered", ({ component }) => {
if (matchComponentSelector(component.componentSelector ?? {}, selector!)) {
unsubscribe();
resolve(component);
}
});
});
}
async waitForSignal(componentName: string, componentSelector: ComponentSelector, searchSignal: ComponentSignal) {
const rawComponent = await this.waitForComponent(componentName, componentSelector);
const component = rawComponent as React.Component & { _componentId: number };
const searchComponentId = component._componentId;
if (this.hasEmittedSignal(searchComponentId, searchSignal)) {
return;
}
return new Promise<void>((resolve) => {
const unsubscribe = this.events.on("signal", ({ componentId, signal }) => {
let match = componentId === searchComponentId;
match &&= signal.name === searchSignal.name;
match &&= matchComponentSelector(signal.selector ?? {}, searchSignal.selector ?? {});
if (match) {
unsubscribe();
resolve();
}
});
});
}
async componentMethod(
componentName: string,
methodName: string,
data: { [key: string]: any },
selector?: ComponentSelector
) {
const component = (await this.waitForComponent(componentName, selector)) as any;
await component[methodName](data);
}
}