From c570b80d7b7c982f9c2895337d49aec2010ef447 Mon Sep 17 00:00:00 2001 From: Ilya Daynatovich Date: Mon, 12 Dec 2016 23:13:17 +0200 Subject: [PATCH] moved app initialization to react app --- app.js | 108 +----------------- modules/UI/UI.js | 14 --- react/features/app/components/AbstractApp.js | 2 +- react/features/app/components/App.web.js | 70 ++++++++---- .../{actions.js => actions.native.js} | 0 react/features/base/connection/actions.web.js | 96 ++++++++++++++++ .../{actions.js => actions.native.js} | 0 .../base/lib-jitsi-meet/actions.web.js | 61 ++++++++++ .../{functions.js => functions.native.js} | 0 .../base/lib-jitsi-meet/functions.web.js | 10 ++ .../conference/components/Conference.web.js | 48 +++++++- .../welcome/components/WelcomePage.web.js | 49 ++++++-- react/features/welcome/route.js | 24 +++- 13 files changed, 324 insertions(+), 158 deletions(-) rename react/features/base/connection/{actions.js => actions.native.js} (100%) create mode 100644 react/features/base/connection/actions.web.js rename react/features/base/lib-jitsi-meet/{actions.js => actions.native.js} (100%) create mode 100644 react/features/base/lib-jitsi-meet/actions.web.js rename react/features/base/lib-jitsi-meet/{functions.js => functions.native.js} (100%) create mode 100644 react/features/base/lib-jitsi-meet/functions.web.js diff --git a/app.js b/app.js index 978671546..076557f68 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,4 @@ -/* global $, config, getRoomName, loggingConfig, JitsiMeetJS */ +/* global $, config, loggingConfig, JitsiMeetJS */ /* application specific logic */ const logger = require("jitsi-meet-logger").getLogger(__filename); @@ -23,9 +23,6 @@ const LogCollector = Logger.LogCollector; import JitsiMeetLogStorage from "./modules/util/JitsiMeetLogStorage"; import URLProcessor from "./modules/config/URLProcessor"; -import { - generateRoomWithoutSeparator -} from './react/features/base/util/roomnameGenerator'; import UI from "./modules/UI/UI"; import settings from "./modules/settings/Settings"; @@ -33,59 +30,9 @@ import conference from './conference'; import ConferenceUrl from './modules/URL/ConferenceUrl'; import API from './modules/API/API'; -import UIEvents from './service/UI/UIEvents'; import getTokenData from "./modules/tokendata/TokenData"; import translation from "./modules/translation/translation"; -const ConferenceEvents = JitsiMeetJS.events.conference; - -/** - * Tries to push history state with the following parameters: - * 'VideoChat', `Room: ${roomName}`, URL. If fail, prints the error and returns - * it. - */ -function pushHistoryState(roomName, URL) { - try { - window.history.pushState( - 'VideoChat', `Room: ${roomName}`, URL - ); - } catch (e) { - logger.warn("Push history state failed with parameters:", - 'VideoChat', `Room: ${roomName}`, URL, e); - return e; - } - return null; -} - -/** - * Replaces current history state(replaces the URL displayed by the browser). - * @param {string} newUrl the URL string which is to be displayed by the browser - * to the user. - */ -function replaceHistoryState (newUrl) { - if (window.history - && typeof window.history.replaceState === 'function') { - window.history.replaceState({}, document.title, newUrl); - } -} - -/** - * Builds and returns the room name. - */ -function buildRoomName () { - let roomName = getRoomName(); - - if(!roomName) { - let word = generateRoomWithoutSeparator(); - roomName = word.toLowerCase(); - let historyURL = window.location.href + word; - //Trying to push state with current URL + roomName - pushHistoryState(word, historyURL); - } - - return roomName; -} - /** * Adjusts the logging levels. * @private @@ -191,8 +138,6 @@ function init() { setTokenData(); // Initialize the conference URL handler APP.ConferenceUrl = new ConferenceUrl(window.location); - // Clean up the URL displayed by the browser - replaceHistoryState(APP.ConferenceUrl.getInviteUrl()); // TODO The execution of the mobile app starts from react/index.native.js. // Similarly, the execution of the Web app should start from @@ -201,54 +146,6 @@ function init() { // into the Web app, allow the execution of the Web app to start from app.js // in order to reduce the complexity of the beginning step. require('./react'); - - const isUIReady = APP.UI.start(); - if (isUIReady) { - APP.conference.init({roomName: buildRoomName()}).then(() => { - - if (APP.logCollector) { - // Start the LogCollector's periodic "store logs" task only if - // we're in the conference and not on the welcome page. This is - // determined by the value of "isUIReady" const above. - APP.logCollector.start(); - APP.logCollectorStarted = true; - // Make an attempt to flush in case a lot of logs have been - // cached, before the collector was started. - APP.logCollector.flush(); - - // This event listener will flush the logs, before - // the statistics module (CallStats) is stopped. - // - // NOTE The LogCollector is not stopped, because this event can - // be triggered multiple times during single conference - // (whenever statistics module is stopped). That includes - // the case when Jicofo terminates the single person left in the - // room. It will then restart the media session when someone - // eventually join the room which will start the stats again. - APP.conference.addConferenceListener( - ConferenceEvents.BEFORE_STATISTICS_DISPOSED, - () => { - if (APP.logCollector) { - APP.logCollector.flush(); - } - } - ); - } - - APP.UI.initConference(); - - APP.UI.addListener(UIEvents.LANG_CHANGED, language => { - APP.translation.setLanguage(language); - APP.settings.setLanguage(language); - }); - - APP.keyboardshortcut.init(); - }).catch(err => { - APP.UI.hideRingOverLay(); - APP.API.notifyConferenceLeft(APP.conference.roomName); - logger.error(err); - }); - } } /** @@ -295,9 +192,6 @@ $(document).ready(function () { URLProcessor.setConfigParametersFromUrl(); APP.init(); - - APP.translation.init(settings.getLanguage()); - APP.API.init(APP.tokenData.externalAPISettings); obtainConfigAndInit(); diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 10a502cc2..b80a215bd 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -396,20 +396,6 @@ UI.getSharedVideoManager = function () { */ UI.start = function () { document.title = interfaceConfig.APP_NAME; - var setupWelcomePage = null; - if(config.enableWelcomePage && window.location.pathname == "/" && - Settings.isWelcomePageEnabled()) { - $("#videoconference_page").hide(); - if (!setupWelcomePage) - setupWelcomePage = require("./welcome_page/WelcomePage"); - setupWelcomePage(); - - // Return false to indicate that the UI hasn't been fully started and - // conference ready. We're still waiting for input from the user. - return false; - } - - $("#welcome_page").hide(); // Set the defaults for prompt dialogs. $.prompt.setDefaults({persistent: false}); diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js index 4d766a2a2..7d32adec5 100644 --- a/react/features/app/components/AbstractApp.js +++ b/react/features/app/components/AbstractApp.js @@ -42,7 +42,7 @@ export class AbstractApp extends Component { * The URL, if any, with which the app was launched. */ url: React.PropTypes.string - } + }; /** * Init lib-jitsi-meet and create local participant when component is going diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index 7afd2e46a..7d646f51f 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -1,5 +1,7 @@ +/* global APP, $ */ import React from 'react'; import { Provider } from 'react-redux'; +import { compose } from 'redux'; import { browserHistory, Route, @@ -11,6 +13,7 @@ import { getDomain } from '../../base/connection'; import { RouteRegistry } from '../../base/navigator'; import { AbstractApp } from './AbstractApp'; +import settings from '../../../../modules/settings/Settings'; /** * Root application component. @@ -23,7 +26,7 @@ export class App extends AbstractApp { * * @static */ - static propTypes = AbstractApp.propTypes + static propTypes = AbstractApp.propTypes; /** * Initializes a new App instance. @@ -44,28 +47,19 @@ export class App extends AbstractApp { // Bind event handlers so they are only bound once for every instance. this._onRouteEnter = this._onRouteEnter.bind(this); this._routerCreateElement = this._routerCreateElement.bind(this); + this._getRoute = this._getRoute.bind(this); + this._getRoutes = this._getRoutes.bind(this); } /** - * Temporarily, prevents the super from dispatching Redux actions until they - * are integrated into the Web App. + * Init translation from old app. * - * @returns {void} + * @inheritdoc */ - componentWillMount() { - // FIXME Do not override the super once the dispatching of Redux actions - // is integrated into the Web App. - } + componentWillMount(...args) { + super.componentWillMount(...args); - /** - * Temporarily, prevents the super from dispatching Redux actions until they - * are integrated into the Web App. - * - * @returns {void} - */ - componentWillUnmount() { - // FIXME Do not override the super once the dispatching of Redux actions - // is integrated into the Web App. + APP.translation.init(settings.getLanguage()); } /** @@ -75,19 +69,14 @@ export class App extends AbstractApp { * @returns {ReactElement} */ render() { - const routes = RouteRegistry.getRoutes(); + return ( - { routes.map(r => - - ) } + { this._getRoutes() } ); @@ -114,6 +103,38 @@ export class App extends AbstractApp { return store.dispatch(push(path)); } + /** + * Returns routes for application. + * + * @returns {Array} + * @private + */ + _getRoutes() { + const routes = RouteRegistry.getRoutes(); + + return routes.map(this._getRoute); + } + + /** + * Method returns route for React Router. + * + * @param {Object} route - Object that describes route. + * @returns {ReactElement} + * @private + */ + _getRoute(route) { + const onEnter = route.onEnter || $.noop; + const handler = compose(this._onRouteEnter, onEnter); + + return ( + + ); + } + /** * Invoked by react-router to notify this App that a Route is about to be * rendered. @@ -122,6 +143,7 @@ export class App extends AbstractApp { * @returns {void} */ _onRouteEnter() { + // XXX The following is mandatory. Otherwise, moving back & forward // through the browser's history could leave this App on the Conference // page without a room name. diff --git a/react/features/base/connection/actions.js b/react/features/base/connection/actions.native.js similarity index 100% rename from react/features/base/connection/actions.js rename to react/features/base/connection/actions.native.js diff --git a/react/features/base/connection/actions.web.js b/react/features/base/connection/actions.web.js new file mode 100644 index 000000000..b81e479e9 --- /dev/null +++ b/react/features/base/connection/actions.web.js @@ -0,0 +1,96 @@ +/* global APP, JitsiMeetJS */ + +import { + SET_DOMAIN +} from './actionTypes'; +import './reducer'; +import UIEvents from '../../../../service/UI/UIEvents'; + +const logger = require('jitsi-meet-logger').getLogger(__filename); +const ConferenceEvents = JitsiMeetJS.events.conference; + + +/** + * Opens new connection. + * + * @returns {Promise} + */ +export function connect() { + return (dispatch, getState) => { + const state = getState(); + const room = state['features/base/conference'].room; + + // XXX For web based version we use conference initialization logic + // from the old app (at the moment of writing). + return APP.conference.init({ roomName: room }).then(() => { + if (APP.logCollector) { + // Start the LogCollector's periodic "store logs" task + APP.logCollector.start(); + APP.logCollectorStarted = true; + + // Make an attempt to flush in case a lot of logs have been + // cached, before the collector was started. + APP.logCollector.flush(); + + // This event listener will flush the logs, before + // the statistics module (CallStats) is stopped. + // + // NOTE The LogCollector is not stopped, because this event can + // be triggered multiple times during single conference + // (whenever statistics module is stopped). That includes + // the case when Jicofo terminates the single person left in the + // room. It will then restart the media session when someone + // eventually join the room which will start the stats again. + APP.conference.addConferenceListener( + ConferenceEvents.BEFORE_STATISTICS_DISPOSED, + () => { + if (APP.logCollector) { + APP.logCollector.flush(); + } + } + ); + } + + APP.UI.initConference(); + + APP.UI.addListener(UIEvents.LANG_CHANGED, language => { + APP.translation.setLanguage(language); + APP.settings.setLanguage(language); + }); + + APP.keyboardshortcut.init(); + }) + .catch(err => { + APP.UI.hideRingOverLay(); + APP.API.notifyConferenceLeft(APP.conference.roomName); + logger.error(err); + }); + }; +} + +/** + * Closes connection. + * + * @returns {Function} + */ +export function disconnect() { + // XXX For web based version we use conference + // hanging up logic from the old app. + return () => APP.conference.hangup(); +} + +/** + * Sets connection domain. + * + * @param {string} domain - Domain name. + * @returns {{ + * type: SET_DOMAIN, + * domain: string + * }} + */ +export function setDomain(domain) { + return { + type: SET_DOMAIN, + domain + }; +} diff --git a/react/features/base/lib-jitsi-meet/actions.js b/react/features/base/lib-jitsi-meet/actions.native.js similarity index 100% rename from react/features/base/lib-jitsi-meet/actions.js rename to react/features/base/lib-jitsi-meet/actions.native.js diff --git a/react/features/base/lib-jitsi-meet/actions.web.js b/react/features/base/lib-jitsi-meet/actions.web.js new file mode 100644 index 000000000..02628ea1a --- /dev/null +++ b/react/features/base/lib-jitsi-meet/actions.web.js @@ -0,0 +1,61 @@ +import { + LIB_DISPOSED, + SET_CONFIG +} from './actionTypes'; +import './middleware'; +import './reducer'; + +/** + * Disposes lib-jitsi-meet. + * + * @returns {Function} + */ +export function disposeLib() { + // XXX We're wrapping it with Promise, because: + // a) to be better aligned with initLib() method, which is async. + // b) as currently there is no implementation for it in lib-jitsi-meet, and + // there is a big chance it will be async. + // TODO Currently, lib-jitsi-meet doesn't have any functionality to + // dispose itself. + return dispatch => { + dispatch({ type: LIB_DISPOSED }); + + return Promise.resolve(); + }; +} + +/** + * Initializes lib-jitsi-meet with passed configuration. + * + * @returns {Function} + */ +export function initLib() { + return (dispatch, getState) => { + const config = getState()['features/base/lib-jitsi-meet'].config; + + if (!config) { + throw new Error('Cannot initialize lib-jitsi-meet without config'); + } + + // XXX Temporary solution. Until conference.js hasn't been moved + // to the react app we shouldn't use JitsiMeetJS from react app. + return Promise.resolve(); + }; +} + +/** + * Sets config. + * + * @param {Object} config - Config object accepted by JitsiMeetJS#init() + * method. + * @returns {{ + * type: SET_CONFIG, + * config: Object + * }} + */ +export function setConfig(config) { + return { + type: SET_CONFIG, + config + }; +} diff --git a/react/features/base/lib-jitsi-meet/functions.js b/react/features/base/lib-jitsi-meet/functions.native.js similarity index 100% rename from react/features/base/lib-jitsi-meet/functions.js rename to react/features/base/lib-jitsi-meet/functions.native.js diff --git a/react/features/base/lib-jitsi-meet/functions.web.js b/react/features/base/lib-jitsi-meet/functions.web.js new file mode 100644 index 000000000..5f0206fa6 --- /dev/null +++ b/react/features/base/lib-jitsi-meet/functions.web.js @@ -0,0 +1,10 @@ +/** + * Returns config.js file from global scope. + * We can't use version that's being used for native app + * because the old app uses config from global scope. + * + * @returns {Promise} + */ +export function loadConfig() { + return Promise.resolve(window.config); +} diff --git a/react/features/conference/components/Conference.web.js b/react/features/conference/components/Conference.web.js index cda4e0179..a9f8cd0fd 100644 --- a/react/features/conference/components/Conference.web.js +++ b/react/features/conference/components/Conference.web.js @@ -1,5 +1,12 @@ -/* global interfaceConfig */ +/* global APP, $, interfaceConfig */ import React, { Component } from 'react'; +import { connect as reactReduxConnect } from 'react-redux'; + +import { + connect, + disconnect +} from '../../base/connection'; + /** * For legacy reasons, inline style for display none. @@ -12,7 +19,42 @@ const DISPLAY_NONE_STYLE = { /** * Implements a React Component which renders initial conference layout */ -export default class Conference extends Component { +class Conference extends Component { + + /** + * Until we don't rewrite UI using react components + * we use UI.start from old app. Also method translates + * component right after it has been mounted. + * + * @inheritdoc + */ + componentDidMount() { + APP.UI.start(); + + // XXX Temporary solution until we add React translation. + APP.translation.translateElement($('#videoconference_page')); + + this.props.dispatch(connect()); + } + + /** + * Disconnect from the conference when component will be + * unmounted. + * + * @inheritdoc + */ + componentWillUnmount() { + this.props.dispatch(disconnect()); + } + + /** + * Conference component's property types. + * + * @static + */ + static propTypes = { + dispatch: React.PropTypes.func + }; /** * Initializes Conference component instance. @@ -214,3 +256,5 @@ export default class Conference extends Component { return null; } } + +export default reactReduxConnect()(Conference); diff --git a/react/features/welcome/components/WelcomePage.web.js b/react/features/welcome/components/WelcomePage.web.js index 4eaa2daac..9466bf765 100644 --- a/react/features/welcome/components/WelcomePage.web.js +++ b/react/features/welcome/components/WelcomePage.web.js @@ -1,4 +1,4 @@ -/* global APP, interfaceConfig */ +/* global interfaceConfig, APP, $ */ import React from 'react'; import { connect } from 'react-redux'; @@ -52,6 +52,9 @@ class WelcomePage extends AbstractWelcomePage { if (this.state.generateRoomnames) { this._updateRoomname(); } + + // XXX Temporary solution until we add React translation. + APP.translation.translateElement($('#welcome_page')); } /** @@ -61,12 +64,6 @@ class WelcomePage extends AbstractWelcomePage { * @returns {ReactElement|null} */ render() { - // FIXME The rendering of Conference bellow is a very quick and dirty - // temporary fix for the following issue: when the WelcomePage is - // disabled, app.js expects Conference to be rendered already and only - // then it builds a room name but the App component expects the room - // name to be built already (by looking at the window's location) in - // order to choose between WelcomePage and Conference. return (
@@ -77,7 +74,6 @@ class WelcomePage extends AbstractWelcomePage { this._renderMain() }
-
); } @@ -359,6 +355,43 @@ class WelcomePage extends AbstractWelcomePage { return null; } + /** + * Handles updating roomname. + * + * @private + * @returns {void} + **/ + _onUpdateRoomname() { + this._updateRoomname(); + } + + /** + * Event handler for changing room name input from web. + * + * @inheritdoc + * @override + * @protected + */ + _onRoomChange() { + super._onRoomChange(this.roomNameInput.value); + } + + /** + * Handles 'keydown' event and initiate joining the room if 'return' button + * was pressed. + * + * @param {Event} event - Key down event object. + * @returns {void} + * @private + */ + _onKeyDown(event) { + const RETURN_BUTTON_CODE = 13; + + if (event.keyCode === RETURN_BUTTON_CODE) { + this._onJoin(); + } + } + /** * Renders the main part of this WelcomePage. * diff --git a/react/features/welcome/route.js b/react/features/welcome/route.js index c5b2d7f29..ea7f6e5a6 100644 --- a/react/features/welcome/route.js +++ b/react/features/welcome/route.js @@ -1,11 +1,31 @@ +/* global APP */ import { RouteRegistry } from '../base/navigator'; - +import { generateRoomWithoutSeparator } from '../base/util'; import { WelcomePage } from './components'; + +/** + * Function that checks if welcome page is enabled and if it isn't + * redirects to randomly created conference. + * + * @param {Object} nextState - Next router state. + * @param {Function} replace - Function to redirect to another path. + * @returns {void} + */ +function onEnter(nextState, replace) { + if (!APP.settings.isWelcomePageEnabled()) { + const generatedRoomname = generateRoomWithoutSeparator(); + const normalizedRoomname = generatedRoomname.toLowerCase(); + + replace(`/${normalizedRoomname}`); + } +} + /** * Register route for WelcomePage. */ RouteRegistry.register({ component: WelcomePage, - path: '/' + path: '/', + onEnter });