|
@@ -0,0 +1,159 @@
|
|
1
|
+// @flow
|
|
2
|
+
|
|
3
|
+const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
4
|
+
|
|
5
|
+/**
|
|
6
|
+ * The type listener supported for registration with
|
|
7
|
+ * {@link StateListenerRegistry} in association with a {@link Selector}.
|
|
8
|
+ *
|
|
9
|
+ * @param {any} selection - The value derived from the redux store/state by the
|
|
10
|
+ * associated {@code Selector}. Immutable!
|
|
11
|
+ * @param {Store} store - The redux store. Provided in case the {@code Listener}
|
|
12
|
+ * needs to {@code dispatch} or {@code getState}. The latter is advisable only
|
|
13
|
+ * if the {@code Listener} is not to respond to changes to that state.
|
|
14
|
+ * @param {any} prevSelection - The value previously derived from the redux
|
|
15
|
+ * store/state by the associated {@code Selector}. The {@code Listener} is
|
|
16
|
+ * invoked only if {@code prevSelection} and {@code selection} are different.
|
|
17
|
+ * Immutable!
|
|
18
|
+ */
|
|
19
|
+type Listener = (selection: any, store: Store, prevSelection: any) => void;
|
|
20
|
+
|
|
21
|
+/**
|
|
22
|
+ * The type selector supported for registration with
|
|
23
|
+ * {@link StateListenerRegistry} in association with a {@link Listener}.
|
|
24
|
+ *
|
|
25
|
+ * @param {Object} state - The redux state from which the {@code Selector} is to
|
|
26
|
+ * derive data.
|
|
27
|
+ * @param {any} prevSelection - The value previously derived from the redux
|
|
28
|
+ * store/state by the {@code Selector}. Provided in case the {@code Selector}
|
|
29
|
+ * needs to derive the returned value from the specified {@code state} and
|
|
30
|
+ * {@code prevSelection}. Immutable!
|
|
31
|
+ * @returns {any} The value derived from the specified {@code state} and/or
|
|
32
|
+ * {@code prevSelection}. The associated {@code Listener} will only be invoked
|
|
33
|
+ * if the returned value is other than {@code prevSelection}.
|
|
34
|
+ */
|
|
35
|
+type Selector = (state: Object, prevSelection: any) => any;
|
|
36
|
+
|
|
37
|
+/**
|
|
38
|
+ * A type of a {@link Selector}-{@link Listener} association in which the
|
|
39
|
+ * {@code Listener} listens to changes in the values derived from a redux
|
|
40
|
+ * store/state by the {@code Selector}.
|
|
41
|
+ */
|
|
42
|
+type SelectorListener = {
|
|
43
|
+
|
|
44
|
+ /**
|
|
45
|
+ * The {@code Listener} which listens to changes in the values selected by
|
|
46
|
+ * {@link selector}.
|
|
47
|
+ */
|
|
48
|
+ listener: Listener,
|
|
49
|
+
|
|
50
|
+ /**
|
|
51
|
+ * The {@code Selector} which selects values whose changes are listened to
|
|
52
|
+ * by {@link listener}.
|
|
53
|
+ */
|
|
54
|
+ selector: Selector
|
|
55
|
+};
|
|
56
|
+
|
|
57
|
+/**
|
|
58
|
+ * A registry listeners which listen to changes in a redux store/state.
|
|
59
|
+ */
|
|
60
|
+class StateListenerRegistry {
|
|
61
|
+ /**
|
|
62
|
+ * The {@link Listener}s registered with this {@code StateListenerRegistry}
|
|
63
|
+ * to be notified when the values derived by associated {@link Selector}s
|
|
64
|
+ * from a redux store/state change.
|
|
65
|
+ */
|
|
66
|
+ _selectorListeners: Set<SelectorListener> = new Set();
|
|
67
|
+
|
|
68
|
+ _listener: (Store) => void;
|
|
69
|
+
|
|
70
|
+ /**
|
|
71
|
+ * Invoked by a specific redux store any time an action is dispatched, and
|
|
72
|
+ * some part of the state (tree) may potentially have changed.
|
|
73
|
+ *
|
|
74
|
+ * @param {Object} context - The redux store invoking the listener and the
|
|
75
|
+ * private state of this {@code StateListenerRegistry} associated with the
|
|
76
|
+ * redux store.
|
|
77
|
+ * @returns {void}
|
|
78
|
+ */
|
|
79
|
+ _listener({ prevSelections, store }: {
|
|
80
|
+ prevSelections: Map<SelectorListener, any>,
|
|
81
|
+ store: Store
|
|
82
|
+ }) {
|
|
83
|
+ for (const selectorListener of this._selectorListeners) {
|
|
84
|
+ const prevSelection = prevSelections.get(selectorListener);
|
|
85
|
+
|
|
86
|
+ try {
|
|
87
|
+ const selection
|
|
88
|
+ = selectorListener.selector(
|
|
89
|
+ store.getState(),
|
|
90
|
+ prevSelection);
|
|
91
|
+
|
|
92
|
+ if (prevSelection !== selection) {
|
|
93
|
+ prevSelections.set(selectorListener, selection);
|
|
94
|
+ selectorListener.listener(selection, store, prevSelection);
|
|
95
|
+ }
|
|
96
|
+ } catch (e) {
|
|
97
|
+ // Don't let one faulty listener prevent other listeners from
|
|
98
|
+ // being notified about their associated changes.
|
|
99
|
+ logger.error(e);
|
|
100
|
+ }
|
|
101
|
+ }
|
|
102
|
+ }
|
|
103
|
+
|
|
104
|
+ /**
|
|
105
|
+ * Registers a specific listener to be notified when the value derived by a
|
|
106
|
+ * specific {@code selector} from a redux store/state changes.
|
|
107
|
+ *
|
|
108
|
+ * @param {Function} selector - The pure {@code Function} of the redux
|
|
109
|
+ * store/state (and the previous selection of made by {@code selector})
|
|
110
|
+ * which selects the value listened to by the specified {@code listener}.
|
|
111
|
+ * @param {Function} listener - The listener to register with this
|
|
112
|
+ * {@code StateListenerRegistry} so that it gets invoked when the value
|
|
113
|
+ * returned by the specified {@code selector} changes.
|
|
114
|
+ * @returns {void}
|
|
115
|
+ */
|
|
116
|
+ register(selector: Selector, listener: Listener) {
|
|
117
|
+ this._selectorListeners.add({
|
|
118
|
+ listener,
|
|
119
|
+ selector
|
|
120
|
+ });
|
|
121
|
+ }
|
|
122
|
+
|
|
123
|
+ /**
|
|
124
|
+ * Subscribes to a specific redux store (so that this instance gets notified
|
|
125
|
+ * any time an action is dispatched, and some part of the state (tree) of
|
|
126
|
+ * the specified redux store may potentially have changed).
|
|
127
|
+ *
|
|
128
|
+ * @param {Store} store - The redux store to which this
|
|
129
|
+ * {@code StateListenerRegistry} is to {@code subscribe}.
|
|
130
|
+ * @returns {void}
|
|
131
|
+ */
|
|
132
|
+ subscribe(store: Store) {
|
|
133
|
+ // XXX If StateListenerRegistry is not utilized by the app to listen to
|
|
134
|
+ // state changes, do not bother subscribing to the store at all.
|
|
135
|
+ if (this._selectorListeners.size) {
|
|
136
|
+ store.subscribe(
|
|
137
|
+ this._listener.bind(
|
|
138
|
+ this,
|
|
139
|
+ {
|
|
140
|
+ /**
|
|
141
|
+ * The previous selections of the {@code Selector}s
|
|
142
|
+ * registered with this {@code StateListenerRegistry}.
|
|
143
|
+ *
|
|
144
|
+ * @type Map<any>
|
|
145
|
+ */
|
|
146
|
+ prevSelections: new Map(),
|
|
147
|
+
|
|
148
|
+ /**
|
|
149
|
+ * The redux store.
|
|
150
|
+ *
|
|
151
|
+ * @type Store
|
|
152
|
+ */
|
|
153
|
+ store
|
|
154
|
+ }));
|
|
155
|
+ }
|
|
156
|
+ }
|
|
157
|
+}
|
|
158
|
+
|
|
159
|
+export default new StateListenerRegistry();
|