Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

BaseApp.js 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. // @flow
  2. import { jitsiLocalStorage } from '@jitsi/js-utils';
  3. import _ from 'lodash';
  4. import React, { Component, Fragment } from 'react';
  5. import { I18nextProvider } from 'react-i18next';
  6. import { Provider } from 'react-redux';
  7. import { compose, createStore } from 'redux';
  8. import Thunk from 'redux-thunk';
  9. import { i18next } from '../../i18n';
  10. import {
  11. MiddlewareRegistry,
  12. PersistenceRegistry,
  13. ReducerRegistry,
  14. StateListenerRegistry
  15. } from '../../redux';
  16. import { SoundCollection } from '../../sounds';
  17. import { createDeferred } from '../../util';
  18. import { appWillMount, appWillUnmount } from '../actions';
  19. import logger from '../logger';
  20. declare var APP: Object;
  21. /**
  22. * The type of the React {@code Component} state of {@link BaseApp}.
  23. */
  24. type State = {
  25. /**
  26. * The {@code Route} rendered by the {@code BaseApp}.
  27. */
  28. route: Object,
  29. /**
  30. * The redux store used by the {@code BaseApp}.
  31. */
  32. store: Object
  33. };
  34. /**
  35. * Base (abstract) class for main App component.
  36. *
  37. * @abstract
  38. */
  39. export default class BaseApp extends Component<*, State> {
  40. /**
  41. * The deferred for the initialisation {{promise, resolve, reject}}.
  42. */
  43. _init: Object;
  44. /**
  45. * Initializes a new {@code BaseApp} instance.
  46. *
  47. * @param {Object} props - The read-only React {@code Component} props with
  48. * which the new instance is to be initialized.
  49. */
  50. constructor(props: Object) {
  51. super(props);
  52. this.state = {
  53. route: {},
  54. store: undefined
  55. };
  56. }
  57. /**
  58. * Initializes the app.
  59. *
  60. * @inheritdoc
  61. */
  62. async componentDidMount() {
  63. /**
  64. * Make the mobile {@code BaseApp} wait until the {@code AsyncStorage}
  65. * implementation of {@code Storage} initializes fully.
  66. *
  67. * @private
  68. * @see {@link #_initStorage}
  69. * @type {Promise}
  70. */
  71. this._init = createDeferred();
  72. try {
  73. await this._initStorage();
  74. const setStatePromise = new Promise(resolve => {
  75. this.setState({
  76. store: this._createStore()
  77. }, resolve);
  78. });
  79. await setStatePromise;
  80. await this._extraInit();
  81. } catch (err) {
  82. /* BaseApp should always initialize! */
  83. logger.error(err);
  84. }
  85. this.state.store.dispatch(appWillMount(this));
  86. this._init.resolve();
  87. }
  88. /**
  89. * De-initializes the app.
  90. *
  91. * @inheritdoc
  92. */
  93. componentWillUnmount() {
  94. this.state.store.dispatch(appWillUnmount(this));
  95. }
  96. /**
  97. * Logs for errors that were not caught.
  98. *
  99. * @param {Error} error - The error that was thrown.
  100. * @param {Object} info - Info about the error(stack trace);.
  101. *
  102. * @returns {void}
  103. */
  104. componentDidCatch(error: Error, info: Object) {
  105. logger.error(error, info);
  106. }
  107. /**
  108. * Delays this {@code BaseApp}'s startup until the {@code Storage}
  109. * implementation of {@code localStorage} initializes. While the
  110. * initialization is instantaneous on Web (with Web Storage API), it is
  111. * asynchronous on mobile/react-native.
  112. *
  113. * @private
  114. * @returns {Promise}
  115. */
  116. _initStorage(): Promise<*> {
  117. const _initializing = jitsiLocalStorage.getItem('_initializing');
  118. return _initializing || Promise.resolve();
  119. }
  120. /**
  121. * Extra initialisation that subclasses might require.
  122. *
  123. * @returns {void}
  124. */
  125. _extraInit() {
  126. // To be implemented by subclass.
  127. }
  128. /**
  129. * Implements React's {@link Component#render()}.
  130. *
  131. * @inheritdoc
  132. * @returns {ReactElement}
  133. */
  134. render() {
  135. const { route: { component, props }, store } = this.state;
  136. if (store) {
  137. return (
  138. <I18nextProvider i18n = { i18next }>
  139. <Provider store = { store }>
  140. <Fragment>
  141. { this._createMainElement(component, props) }
  142. <SoundCollection />
  143. { this._createExtraElement() }
  144. { this._renderDialogContainer() }
  145. </Fragment>
  146. </Provider>
  147. </I18nextProvider>
  148. );
  149. }
  150. return null;
  151. }
  152. /**
  153. * Creates an extra {@link ReactElement}s to be added (unconditionally)
  154. * alongside the main element.
  155. *
  156. * @returns {ReactElement}
  157. * @abstract
  158. * @protected
  159. */
  160. _createExtraElement() {
  161. return null;
  162. }
  163. /**
  164. * Creates a {@link ReactElement} from the specified component, the
  165. * specified props and the props of this {@code AbstractApp} which are
  166. * suitable for propagation to the children of this {@code Component}.
  167. *
  168. * @param {Component} component - The component from which the
  169. * {@code ReactElement} is to be created.
  170. * @param {Object} props - The read-only React {@code Component} props with
  171. * which the {@code ReactElement} is to be initialized.
  172. * @returns {ReactElement}
  173. * @protected
  174. */
  175. _createMainElement(component, props) {
  176. return component ? React.createElement(component, props || {}) : null;
  177. }
  178. /**
  179. * Initializes a new redux store instance suitable for use by this
  180. * {@code AbstractApp}.
  181. *
  182. * @private
  183. * @returns {Store} - A new redux store instance suitable for use by
  184. * this {@code AbstractApp}.
  185. */
  186. _createStore() {
  187. // Create combined reducer from all reducers in ReducerRegistry.
  188. const reducer = ReducerRegistry.combineReducers();
  189. // Apply all registered middleware from the MiddlewareRegistry and
  190. // additional 3rd party middleware:
  191. // - Thunk - allows us to dispatch async actions easily. For more info
  192. // @see https://github.com/gaearon/redux-thunk.
  193. const middleware = MiddlewareRegistry.applyMiddleware(Thunk);
  194. const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
  195. const store = createStore(reducer, PersistenceRegistry.getPersistedState(), composeEnhancers(middleware));
  196. // StateListenerRegistry
  197. StateListenerRegistry.subscribe(store);
  198. // This is temporary workaround to be able to dispatch actions from
  199. // non-reactified parts of the code (conference.js for example).
  200. // Don't use in the react code!!!
  201. // FIXME: remove when the reactification is finished!
  202. if (typeof APP !== 'undefined') {
  203. APP.store = store;
  204. }
  205. return store;
  206. }
  207. /**
  208. * Navigates to a specific Route.
  209. *
  210. * @param {Route} route - The Route to which to navigate.
  211. * @returns {Promise}
  212. */
  213. _navigate(route): Promise<*> {
  214. if (_.isEqual(route, this.state.route)) {
  215. return Promise.resolve();
  216. }
  217. if (route.href) {
  218. // This navigation requires loading a new URL in the browser.
  219. window.location.href = route.href;
  220. return Promise.resolve();
  221. }
  222. // XXX React's setState is asynchronous which means that the value of
  223. // this.state.route above may not even be correct. If the check is
  224. // performed before setState completes, the app may not navigate to the
  225. // expected route. In order to mitigate the problem, _navigate was
  226. // changed to return a Promise.
  227. return new Promise(resolve => {
  228. this.setState({ route }, resolve);
  229. });
  230. }
  231. /**
  232. * Renders the platform specific dialog container.
  233. *
  234. * @returns {React$Element}
  235. */
  236. _renderDialogContainer: () => React$Element<*>;
  237. }