|
@@ -1,11 +1,14 @@
|
1
|
1
|
import React, { Component } from 'react';
|
2
|
2
|
import { Provider } from 'react-redux';
|
|
3
|
+import { compose, createStore } from 'redux';
|
|
4
|
+import Thunk from 'redux-thunk';
|
3
|
5
|
|
4
|
6
|
import { RouteRegistry } from '../../base/navigator';
|
5
|
7
|
import {
|
6
|
8
|
localParticipantJoined,
|
7
|
9
|
localParticipantLeft
|
8
|
10
|
} from '../../base/participants';
|
|
11
|
+import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
|
9
|
12
|
|
10
|
13
|
import {
|
11
|
14
|
appNavigate,
|
|
@@ -45,11 +48,18 @@ export class AbstractApp extends Component {
|
45
|
48
|
|
46
|
49
|
this.state = {
|
47
|
50
|
/**
|
48
|
|
- * The Route rendered by this App.
|
|
51
|
+ * The Route rendered by this AbstractApp.
|
49
|
52
|
*
|
50
|
53
|
* @type {Route}
|
51
|
54
|
*/
|
52
|
|
- route: undefined
|
|
55
|
+ route: undefined,
|
|
56
|
+
|
|
57
|
+ /**
|
|
58
|
+ * The Redux store used by this AbstractApp.
|
|
59
|
+ *
|
|
60
|
+ * @type {Store}
|
|
61
|
+ */
|
|
62
|
+ store: this._maybeCreateStore(props)
|
53
|
63
|
};
|
54
|
64
|
}
|
55
|
65
|
|
|
@@ -60,7 +70,7 @@ export class AbstractApp extends Component {
|
60
|
70
|
* @inheritdoc
|
61
|
71
|
*/
|
62
|
72
|
componentWillMount() {
|
63
|
|
- const dispatch = this.props.store.dispatch;
|
|
73
|
+ const dispatch = this._getStore().dispatch;
|
64
|
74
|
|
65
|
75
|
dispatch(appWillMount(this));
|
66
|
76
|
|
|
@@ -69,6 +79,32 @@ export class AbstractApp extends Component {
|
69
|
79
|
this._openURL(this._getDefaultURL());
|
70
|
80
|
}
|
71
|
81
|
|
|
82
|
+ /**
|
|
83
|
+ * Notifies this mounted React Component that it will receive new props.
|
|
84
|
+ * Makes sure that this AbstractApp has a Redux store to use.
|
|
85
|
+ *
|
|
86
|
+ * @inheritdoc
|
|
87
|
+ * @param {Object} nextProps - The read-only React Component props that this
|
|
88
|
+ * instance will receive.
|
|
89
|
+ * @returns {void}
|
|
90
|
+ */
|
|
91
|
+ componentWillReceiveProps(nextProps) {
|
|
92
|
+ // The consumer of this AbstractApp did not provide a Redux store.
|
|
93
|
+ if (typeof nextProps.store === 'undefined'
|
|
94
|
+
|
|
95
|
+ // The consumer of this AbstractApp did provide a Redux store
|
|
96
|
+ // before. Which means that the consumer changed their mind. In
|
|
97
|
+ // such a case this instance should create its own internal
|
|
98
|
+ // Redux store. If the consumer did not provide a Redux store
|
|
99
|
+ // before, then this instance is using its own internal Redux
|
|
100
|
+ // store already.
|
|
101
|
+ && typeof this.props.store !== 'undefined') {
|
|
102
|
+ this.setState({
|
|
103
|
+ store: this._maybeCreateStore(nextProps)
|
|
104
|
+ });
|
|
105
|
+ }
|
|
106
|
+ }
|
|
107
|
+
|
72
|
108
|
/**
|
73
|
109
|
* Dispose lib-jitsi-meet and remove local participant when component is
|
74
|
110
|
* going to be unmounted.
|
|
@@ -76,7 +112,7 @@ export class AbstractApp extends Component {
|
76
|
112
|
* @inheritdoc
|
77
|
113
|
*/
|
78
|
114
|
componentWillUnmount() {
|
79
|
|
- const dispatch = this.props.store.dispatch;
|
|
115
|
+ const dispatch = this._getStore().dispatch;
|
80
|
116
|
|
81
|
117
|
dispatch(localParticipantLeft());
|
82
|
118
|
|
|
@@ -94,7 +130,7 @@ export class AbstractApp extends Component {
|
94
|
130
|
|
95
|
131
|
if (route) {
|
96
|
132
|
return (
|
97
|
|
- <Provider store = { this.props.store }>
|
|
133
|
+ <Provider store = { this._getStore() }>
|
98
|
134
|
{
|
99
|
135
|
this._createElement(route.component)
|
100
|
136
|
}
|
|
@@ -142,6 +178,36 @@ export class AbstractApp extends Component {
|
142
|
178
|
return React.createElement(component, { ...thisProps, ...props });
|
143
|
179
|
}
|
144
|
180
|
|
|
181
|
+ /**
|
|
182
|
+ * Initializes a new Redux store instance suitable for use by
|
|
183
|
+ * this AbstractApp.
|
|
184
|
+ *
|
|
185
|
+ * @private
|
|
186
|
+ * @returns {Store} - A new Redux store instance suitable for use by
|
|
187
|
+ * this AbstractApp.
|
|
188
|
+ */
|
|
189
|
+ _createStore() {
|
|
190
|
+ // Create combined reducer from all reducers in ReducerRegistry.
|
|
191
|
+ const reducer = ReducerRegistry.combineReducers();
|
|
192
|
+
|
|
193
|
+ // Apply all registered middleware from the MiddlewareRegistry and
|
|
194
|
+ // additional 3rd party middleware:
|
|
195
|
+ // - Thunk - allows us to dispatch async actions easily. For more info
|
|
196
|
+ // @see https://github.com/gaearon/redux-thunk.
|
|
197
|
+ let middleware = MiddlewareRegistry.applyMiddleware(Thunk);
|
|
198
|
+
|
|
199
|
+ // Try to enable Redux DevTools Chrome extension in order to make it
|
|
200
|
+ // available for the purposes of facilitating development.
|
|
201
|
+ let devToolsExtension;
|
|
202
|
+
|
|
203
|
+ if (typeof window === 'object'
|
|
204
|
+ && (devToolsExtension = window.devToolsExtension)) {
|
|
205
|
+ middleware = compose(middleware, devToolsExtension());
|
|
206
|
+ }
|
|
207
|
+
|
|
208
|
+ return createStore(reducer, middleware);
|
|
209
|
+ }
|
|
210
|
+
|
145
|
211
|
/**
|
146
|
212
|
* Gets the default URL to be opened when this App mounts.
|
147
|
213
|
*
|
|
@@ -189,6 +255,22 @@ export class AbstractApp extends Component {
|
189
|
255
|
return 'https://meet.jit.si';
|
190
|
256
|
}
|
191
|
257
|
|
|
258
|
+ /**
|
|
259
|
+ * Gets the Redux store used by this AbstractApp.
|
|
260
|
+ *
|
|
261
|
+ * @protected
|
|
262
|
+ * @returns {Store} - The Redux store used by this AbstractApp.
|
|
263
|
+ */
|
|
264
|
+ _getStore() {
|
|
265
|
+ let store = this.state.store;
|
|
266
|
+
|
|
267
|
+ if (typeof store === 'undefined') {
|
|
268
|
+ store = this.props.store;
|
|
269
|
+ }
|
|
270
|
+
|
|
271
|
+ return store;
|
|
272
|
+ }
|
|
273
|
+
|
192
|
274
|
/**
|
193
|
275
|
* Gets a Location object from the window with information about the current
|
194
|
276
|
* location of the document. Explicitly defined to allow extenders to
|
|
@@ -204,6 +286,30 @@ export class AbstractApp extends Component {
|
204
|
286
|
return undefined;
|
205
|
287
|
}
|
206
|
288
|
|
|
289
|
+ /**
|
|
290
|
+ * Creates a Redux store to be used by this AbstractApp if such as store is
|
|
291
|
+ * not defined by the consumer of this AbstractApp through its
|
|
292
|
+ * read-only React Component props.
|
|
293
|
+ *
|
|
294
|
+ * @param {Object} props - The read-only React Component props that will
|
|
295
|
+ * eventually be received by this AbstractApp.
|
|
296
|
+ * @private
|
|
297
|
+ * @returns {Store} - The Redux store to be used by this AbstractApp.
|
|
298
|
+ */
|
|
299
|
+ _maybeCreateStore(props) {
|
|
300
|
+ // The application Jitsi Meet is architected with Redux. However, I do
|
|
301
|
+ // not want consumers of the App React Component to be forced into
|
|
302
|
+ // dealing with Redux. If the consumer did not provide an external Redux
|
|
303
|
+ // store, utilize an internal Redux store.
|
|
304
|
+ let store = props.store;
|
|
305
|
+
|
|
306
|
+ if (typeof store === 'undefined') {
|
|
307
|
+ store = this._createStore();
|
|
308
|
+ }
|
|
309
|
+
|
|
310
|
+ return store;
|
|
311
|
+ }
|
|
312
|
+
|
207
|
313
|
/**
|
208
|
314
|
* Navigates to a specific Route.
|
209
|
315
|
*
|
|
@@ -269,6 +375,6 @@ export class AbstractApp extends Component {
|
269
|
375
|
* @returns {void}
|
270
|
376
|
*/
|
271
|
377
|
_openURL(url) {
|
272
|
|
- this.props.store.dispatch(appNavigate(url));
|
|
378
|
+ this._getStore().dispatch(appNavigate(url));
|
273
|
379
|
}
|
274
|
380
|
}
|