您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

AbstractApp.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. import React, { Component } from 'react';
  2. import { Provider } from 'react-redux';
  3. import { compose, createStore } from 'redux';
  4. import Thunk from 'redux-thunk';
  5. import { RouteRegistry } from '../../base/navigator';
  6. import {
  7. localParticipantJoined,
  8. localParticipantLeft
  9. } from '../../base/participants';
  10. import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
  11. import {
  12. appNavigate,
  13. appWillMount,
  14. appWillUnmount
  15. } from '../actions';
  16. /**
  17. * Base (abstract) class for main App component.
  18. *
  19. * @abstract
  20. */
  21. export class AbstractApp extends Component {
  22. /**
  23. * AbstractApp component's property types.
  24. *
  25. * @static
  26. */
  27. static propTypes = {
  28. config: React.PropTypes.object,
  29. store: React.PropTypes.object,
  30. /**
  31. * The URL, if any, with which the app was launched.
  32. */
  33. url: React.PropTypes.string
  34. }
  35. /**
  36. * Initializes a new App instance.
  37. *
  38. * @param {Object} props - The read-only React Component props with which
  39. * the new instance is to be initialized.
  40. */
  41. constructor(props) {
  42. super(props);
  43. this.state = {
  44. /**
  45. * The Route rendered by this AbstractApp.
  46. *
  47. * @type {Route}
  48. */
  49. route: undefined,
  50. /**
  51. * The Redux store used by this AbstractApp.
  52. *
  53. * @type {Store}
  54. */
  55. store: this._maybeCreateStore(props)
  56. };
  57. }
  58. /**
  59. * Init lib-jitsi-meet and create local participant when component is going
  60. * to be mounted.
  61. *
  62. * @inheritdoc
  63. */
  64. componentWillMount() {
  65. const dispatch = this._getStore().dispatch;
  66. dispatch(appWillMount(this));
  67. dispatch(localParticipantJoined());
  68. this._openURL(this._getDefaultURL());
  69. }
  70. /**
  71. * Notifies this mounted React Component that it will receive new props.
  72. * Makes sure that this AbstractApp has a Redux store to use.
  73. *
  74. * @inheritdoc
  75. * @param {Object} nextProps - The read-only React Component props that this
  76. * instance will receive.
  77. * @returns {void}
  78. */
  79. componentWillReceiveProps(nextProps) {
  80. // The consumer of this AbstractApp did not provide a Redux store.
  81. if (typeof nextProps.store === 'undefined'
  82. // The consumer of this AbstractApp did provide a Redux store
  83. // before. Which means that the consumer changed their mind. In
  84. // such a case this instance should create its own internal
  85. // Redux store. If the consumer did not provide a Redux store
  86. // before, then this instance is using its own internal Redux
  87. // store already.
  88. && typeof this.props.store !== 'undefined') {
  89. this.setState({
  90. store: this._maybeCreateStore(nextProps)
  91. });
  92. }
  93. }
  94. /**
  95. * Dispose lib-jitsi-meet and remove local participant when component is
  96. * going to be unmounted.
  97. *
  98. * @inheritdoc
  99. */
  100. componentWillUnmount() {
  101. const dispatch = this._getStore().dispatch;
  102. dispatch(localParticipantLeft());
  103. dispatch(appWillUnmount(this));
  104. }
  105. /**
  106. * Implements React's {@link Component#render()}.
  107. *
  108. * @inheritdoc
  109. * @returns {ReactElement}
  110. */
  111. render() {
  112. const route = this.state.route;
  113. if (route) {
  114. return (
  115. <Provider store = { this._getStore() }>
  116. {
  117. this._createElement(route.component)
  118. }
  119. </Provider>
  120. );
  121. }
  122. return null;
  123. }
  124. /**
  125. * Create a ReactElement from the specified component, the specified props
  126. * and the props of this AbstractApp which are suitable for propagation to
  127. * the children of this Component.
  128. *
  129. * @param {Component} component - The component from which the ReactElement
  130. * is to be created.
  131. * @param {Object} props - The read-only React Component props with which
  132. * the ReactElement is to be initialized.
  133. * @returns {ReactElement}
  134. * @protected
  135. */
  136. _createElement(component, props) {
  137. /* eslint-disable no-unused-vars, lines-around-comment */
  138. const {
  139. // Don't propagate the config prop(erty) because the config is
  140. // stored inside the Redux state and, thus, is visible to the
  141. // children anyway.
  142. config,
  143. // Don't propagate the dispatch and store props because they usually
  144. // come from react-redux and programmers don't really expect them to
  145. // be inherited but rather explicitly connected.
  146. dispatch, // eslint-disable-line react/prop-types
  147. store,
  148. // The url property was introduced to be consumed entirely by
  149. // AbstractApp.
  150. url,
  151. // The remaining props, if any, are considered suitable for
  152. // propagation to the children of this Component.
  153. ...thisProps
  154. } = this.props;
  155. /* eslint-enable no-unused-vars, lines-around-comment */
  156. // eslint-disable-next-line object-property-newline
  157. return React.createElement(component, { ...thisProps, ...props });
  158. }
  159. /**
  160. * Initializes a new Redux store instance suitable for use by
  161. * this AbstractApp.
  162. *
  163. * @private
  164. * @returns {Store} - A new Redux store instance suitable for use by
  165. * this AbstractApp.
  166. */
  167. _createStore() {
  168. // Create combined reducer from all reducers in ReducerRegistry.
  169. const reducer = ReducerRegistry.combineReducers();
  170. // Apply all registered middleware from the MiddlewareRegistry and
  171. // additional 3rd party middleware:
  172. // - Thunk - allows us to dispatch async actions easily. For more info
  173. // @see https://github.com/gaearon/redux-thunk.
  174. let middleware = MiddlewareRegistry.applyMiddleware(Thunk);
  175. // Try to enable Redux DevTools Chrome extension in order to make it
  176. // available for the purposes of facilitating development.
  177. let devToolsExtension;
  178. if (typeof window === 'object'
  179. && (devToolsExtension = window.devToolsExtension)) {
  180. middleware = compose(middleware, devToolsExtension());
  181. }
  182. return createStore(reducer, middleware);
  183. }
  184. /**
  185. * Gets the default URL to be opened when this App mounts.
  186. *
  187. * @private
  188. * @returns {string} The default URL to be opened when this App mounts.
  189. */
  190. _getDefaultURL() {
  191. // If the URL was explicitly specified to the React Component, then open
  192. // it.
  193. let url = this.props.url;
  194. if (url) {
  195. return url;
  196. }
  197. // If the execution environment provides a Location abstraction, then
  198. // this App at already at that location but it must be made aware of the
  199. // fact.
  200. const windowLocation = this._getWindowLocation();
  201. if (windowLocation) {
  202. url = windowLocation.toString();
  203. if (url) {
  204. return url;
  205. }
  206. }
  207. // By default, open the domain configured in the configuration file
  208. // which may be the domain at which the whole server infrastructure is
  209. // deployed.
  210. const config = this.props.config;
  211. if (typeof config === 'object') {
  212. const hosts = config.hosts;
  213. if (typeof hosts === 'object') {
  214. const domain = hosts.domain;
  215. if (domain) {
  216. return `https://${domain}`;
  217. }
  218. }
  219. }
  220. return 'https://meet.jit.si';
  221. }
  222. /**
  223. * Gets the Redux store used by this AbstractApp.
  224. *
  225. * @protected
  226. * @returns {Store} - The Redux store used by this AbstractApp.
  227. */
  228. _getStore() {
  229. let store = this.state.store;
  230. if (typeof store === 'undefined') {
  231. store = this.props.store;
  232. }
  233. return store;
  234. }
  235. /**
  236. * Gets a Location object from the window with information about the current
  237. * location of the document. Explicitly defined to allow extenders to
  238. * override because React Native does not usually have a location property
  239. * on its window unless debugging remotely in which case the browser that is
  240. * the remote debugger will provide a location property on the window.
  241. *
  242. * @protected
  243. * @returns {Location} A Location object with information about the current
  244. * location of the document.
  245. */
  246. _getWindowLocation() {
  247. return undefined;
  248. }
  249. /**
  250. * Creates a Redux store to be used by this AbstractApp if such as store is
  251. * not defined by the consumer of this AbstractApp through its
  252. * read-only React Component props.
  253. *
  254. * @param {Object} props - The read-only React Component props that will
  255. * eventually be received by this AbstractApp.
  256. * @private
  257. * @returns {Store} - The Redux store to be used by this AbstractApp.
  258. */
  259. _maybeCreateStore(props) {
  260. // The application Jitsi Meet is architected with Redux. However, I do
  261. // not want consumers of the App React Component to be forced into
  262. // dealing with Redux. If the consumer did not provide an external Redux
  263. // store, utilize an internal Redux store.
  264. let store = props.store;
  265. if (typeof store === 'undefined') {
  266. store = this._createStore();
  267. }
  268. return store;
  269. }
  270. /**
  271. * Navigates to a specific Route.
  272. *
  273. * @param {Route} route - The Route to which to navigate.
  274. * @returns {void}
  275. */
  276. _navigate(route) {
  277. if (RouteRegistry.areRoutesEqual(this.state.route, route)) {
  278. return;
  279. }
  280. let nextState = {
  281. ...this.state,
  282. route
  283. };
  284. // The Web App was using react-router so it utilized react-router's
  285. // onEnter. During the removal of react-router, modifications were
  286. // minimized by preserving the onEnter interface:
  287. // (1) Router would provide its nextState to the Route's onEnter. As the
  288. // role of Router is now this AbstractApp, provide its nextState.
  289. // (2) A replace function would be provided to the Route in case it
  290. // chose to redirect to another path.
  291. this._onRouteEnter(route, nextState, pathname => {
  292. // FIXME In order to minimize the modifications related to the
  293. // removal of react-router, the Web implementation is provided
  294. // bellow because the replace function is used on Web only at the
  295. // time of this writing. Provide a platform-agnostic implementation.
  296. // It should likely find the best Route matching the specified
  297. // pathname and navigate to it.
  298. window.location.pathname = pathname;
  299. // Do not proceed with the route because it chose to redirect to
  300. // another path.
  301. nextState = undefined;
  302. });
  303. nextState && this.setState(nextState);
  304. }
  305. /**
  306. * Notifies this App that a specific Route is about to be rendered.
  307. *
  308. * @param {Route} route - The Route that is about to be rendered.
  309. * @private
  310. * @returns {void}
  311. */
  312. _onRouteEnter(route, ...args) {
  313. // Notify the route that it is about to be entered.
  314. const onEnter = route.onEnter;
  315. if (typeof onEnter === 'function') {
  316. onEnter(...args);
  317. }
  318. }
  319. /**
  320. * Navigates this AbstractApp to (i.e. opens) a specific URL.
  321. *
  322. * @param {string} url - The URL to which to navigate this AbstractApp (i.e.
  323. * the URL to open).
  324. * @protected
  325. * @returns {void}
  326. */
  327. _openURL(url) {
  328. this._getStore().dispatch(appNavigate(url));
  329. }
  330. }