moved app initialization to react app

This commit is contained in:
Ilya Daynatovich 2016-12-12 23:13:17 +02:00
parent fd6c91715f
commit c570b80d7b
13 changed files with 324 additions and 158 deletions

108
app.js
View File

@ -1,4 +1,4 @@
/* global $, config, getRoomName, loggingConfig, JitsiMeetJS */ /* global $, config, loggingConfig, JitsiMeetJS */
/* application specific logic */ /* application specific logic */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require("jitsi-meet-logger").getLogger(__filename);
@ -23,9 +23,6 @@ const LogCollector = Logger.LogCollector;
import JitsiMeetLogStorage from "./modules/util/JitsiMeetLogStorage"; import JitsiMeetLogStorage from "./modules/util/JitsiMeetLogStorage";
import URLProcessor from "./modules/config/URLProcessor"; import URLProcessor from "./modules/config/URLProcessor";
import {
generateRoomWithoutSeparator
} from './react/features/base/util/roomnameGenerator';
import UI from "./modules/UI/UI"; import UI from "./modules/UI/UI";
import settings from "./modules/settings/Settings"; import settings from "./modules/settings/Settings";
@ -33,59 +30,9 @@ import conference from './conference';
import ConferenceUrl from './modules/URL/ConferenceUrl'; import ConferenceUrl from './modules/URL/ConferenceUrl';
import API from './modules/API/API'; import API from './modules/API/API';
import UIEvents from './service/UI/UIEvents';
import getTokenData from "./modules/tokendata/TokenData"; import getTokenData from "./modules/tokendata/TokenData";
import translation from "./modules/translation/translation"; 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. * Adjusts the logging levels.
* @private * @private
@ -191,8 +138,6 @@ function init() {
setTokenData(); setTokenData();
// Initialize the conference URL handler // Initialize the conference URL handler
APP.ConferenceUrl = new ConferenceUrl(window.location); 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. // TODO The execution of the mobile app starts from react/index.native.js.
// Similarly, the execution of the Web app should start from // 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 // 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. // in order to reduce the complexity of the beginning step.
require('./react'); 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(); URLProcessor.setConfigParametersFromUrl();
APP.init(); APP.init();
APP.translation.init(settings.getLanguage());
APP.API.init(APP.tokenData.externalAPISettings); APP.API.init(APP.tokenData.externalAPISettings);
obtainConfigAndInit(); obtainConfigAndInit();

View File

@ -396,20 +396,6 @@ UI.getSharedVideoManager = function () {
*/ */
UI.start = function () { UI.start = function () {
document.title = interfaceConfig.APP_NAME; 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. // Set the defaults for prompt dialogs.
$.prompt.setDefaults({persistent: false}); $.prompt.setDefaults({persistent: false});

View File

@ -42,7 +42,7 @@ export class AbstractApp extends Component {
* The URL, if any, with which the app was launched. * The URL, if any, with which the app was launched.
*/ */
url: React.PropTypes.string url: React.PropTypes.string
} };
/** /**
* Init lib-jitsi-meet and create local participant when component is going * Init lib-jitsi-meet and create local participant when component is going

View File

@ -1,5 +1,7 @@
/* global APP, $ */
import React from 'react'; import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { compose } from 'redux';
import { import {
browserHistory, browserHistory,
Route, Route,
@ -11,6 +13,7 @@ import { getDomain } from '../../base/connection';
import { RouteRegistry } from '../../base/navigator'; import { RouteRegistry } from '../../base/navigator';
import { AbstractApp } from './AbstractApp'; import { AbstractApp } from './AbstractApp';
import settings from '../../../../modules/settings/Settings';
/** /**
* Root application component. * Root application component.
@ -23,7 +26,7 @@ export class App extends AbstractApp {
* *
* @static * @static
*/ */
static propTypes = AbstractApp.propTypes static propTypes = AbstractApp.propTypes;
/** /**
* Initializes a new App instance. * 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. // Bind event handlers so they are only bound once for every instance.
this._onRouteEnter = this._onRouteEnter.bind(this); this._onRouteEnter = this._onRouteEnter.bind(this);
this._routerCreateElement = this._routerCreateElement.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 * Init translation from old app.
* are integrated into the Web App.
* *
* @returns {void} * @inheritdoc
*/ */
componentWillMount() { componentWillMount(...args) {
// FIXME Do not override the super once the dispatching of Redux actions super.componentWillMount(...args);
// is integrated into the Web App.
}
/** APP.translation.init(settings.getLanguage());
* 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.
} }
/** /**
@ -75,19 +69,14 @@ export class App extends AbstractApp {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const routes = RouteRegistry.getRoutes();
return ( return (
<Provider store = { this.props.store }> <Provider store = { this.props.store }>
<Router <Router
createElement = { this._routerCreateElement } createElement = { this._routerCreateElement }
history = { this.history }> history = { this.history }>
{ routes.map(r => { this._getRoutes() }
<Route
component = { r.component }
key = { r.component }
path = { r.path } />
) }
</Router> </Router>
</Provider> </Provider>
); );
@ -114,6 +103,38 @@ export class App extends AbstractApp {
return store.dispatch(push(path)); 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 (
<Route
component = { route.component }
key = { route.component }
onEnter = { handler }
path = { route.path } />
);
}
/** /**
* Invoked by react-router to notify this App that a Route is about to be * Invoked by react-router to notify this App that a Route is about to be
* rendered. * rendered.
@ -122,6 +143,7 @@ export class App extends AbstractApp {
* @returns {void} * @returns {void}
*/ */
_onRouteEnter() { _onRouteEnter() {
// XXX The following is mandatory. Otherwise, moving back & forward // XXX The following is mandatory. Otherwise, moving back & forward
// through the browser's history could leave this App on the Conference // through the browser's history could leave this App on the Conference
// page without a room name. // page without a room name.

View File

@ -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<JitsiConnection>}
*/
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
};
}

View File

@ -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
};
}

View File

@ -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<Object>}
*/
export function loadConfig() {
return Promise.resolve(window.config);
}

View File

@ -1,5 +1,12 @@
/* global interfaceConfig */ /* global APP, $, interfaceConfig */
import React, { Component } from 'react'; 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. * 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 * 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. * Initializes Conference component instance.
@ -214,3 +256,5 @@ export default class Conference extends Component {
return null; return null;
} }
} }
export default reactReduxConnect()(Conference);

View File

@ -1,4 +1,4 @@
/* global APP, interfaceConfig */ /* global interfaceConfig, APP, $ */
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -52,6 +52,9 @@ class WelcomePage extends AbstractWelcomePage {
if (this.state.generateRoomnames) { if (this.state.generateRoomnames) {
this._updateRoomname(); 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} * @returns {ReactElement|null}
*/ */
render() { 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 ( return (
<div> <div>
<div id = 'welcome_page'> <div id = 'welcome_page'>
@ -77,7 +74,6 @@ class WelcomePage extends AbstractWelcomePage {
this._renderMain() this._renderMain()
} }
</div> </div>
<Conference />
</div> </div>
); );
} }
@ -359,6 +355,43 @@ class WelcomePage extends AbstractWelcomePage {
return null; 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. * Renders the main part of this WelcomePage.
* *

View File

@ -1,11 +1,31 @@
/* global APP */
import { RouteRegistry } from '../base/navigator'; import { RouteRegistry } from '../base/navigator';
import { generateRoomWithoutSeparator } from '../base/util';
import { WelcomePage } from './components'; 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. * Register route for WelcomePage.
*/ */
RouteRegistry.register({ RouteRegistry.register({
component: WelcomePage, component: WelcomePage,
path: '/' path: '/',
onEnter
}); });