Comply w/ coding style

This commit is contained in:
Lyubomir Marinov 2017-01-25 16:11:44 -06:00
parent 1fa4a53a48
commit cbcee201f0
13 changed files with 304 additions and 280 deletions

View File

@ -131,6 +131,6 @@ $linkHoverFontColor: #287ade;
/** /**
* Landing * Landing
*/ */
$primaryUnsupportedBrowserButtonBgColor: #17a0db;
$unsupportedBrowserButtonBgColor: #ff9a00; $unsupportedBrowserButtonBgColor: #ff9a00;
$unsupportedBrowserTextColor: #4a4a4a; $unsupportedBrowserTextColor: #4a4a4a;
$primaryUnsupportedBrowserButtonBgColor: #17a0db;

View File

@ -67,7 +67,7 @@
@import '404'; @import '404';
@import 'policy'; @import 'policy';
@import 'filmstrip'; @import 'filmstrip';
@import 'unsupported-browser/mobile-browser-page'; @import 'unsupported-browser/unsupported-desktop-browser';
@import 'unsupported-browser/unsupported_browser'; @import 'unsupported-browser/unsupported-mobile-browser';
/* Modules END */ /* Modules END */

View File

@ -1,10 +1,10 @@
.browser { .supported-browser {
display: inline-block;
margin: 1em 7px;
width: 138px;
vertical-align: middle;
color: #929391; color: #929391;
display: inline-block;
font-size: 20px; font-size: 20px;
margin: 1em 7px;
vertical-align: middle;
width: 138px;
&__button { &__button {
background-color: #62c82a; background-color: #62c82a;
@ -12,11 +12,11 @@
border-radius: 10px; border-radius: 10px;
color: #FFFFFF; color: #FFFFFF;
font-size: 12px; font-size: 12px;
height: 26px;
margin: 15px auto 0px auto;
padding-top: 13px;
text-align: center; text-align: center;
width: 115px; width: 115px;
height: 26px;
padding-top: 13px;
margin: 15px auto 0px auto;
} }
&__link { &__link {
@ -45,39 +45,39 @@
margin: 20px auto 0px auto; margin: 20px auto 0px auto;
&_chrome { &_chrome {
width: 78px;
height: 78px;
background-image: url('../../images/chrome.png'); background-image: url('../../images/chrome.png');
height: 78px;
width: 78px;
} }
&_chromium { &_chromium {
width: 77px;
height: 78px;
background-image: url('../../images/chromium.png'); background-image: url('../../images/chromium.png');
height: 78px;
width: 77px;
} }
&_firefox { &_firefox {
width: 86px;
height: 80px;
background-image: url('../../images/firefox.png'); background-image: url('../../images/firefox.png');
height: 80px;
width: 86px;
} }
&_opera { &_opera {
width: 73px;
height: 78px;
background-image: url('../../images/opera.png'); background-image: url('../../images/opera.png');
height: 78px;
width: 73px;
} }
&_ie { &_ie {
width: 80px;
height: 78px;
background-image: url('../../images/ie.png'); background-image: url('../../images/ie.png');
height: 78px;
width: 80px;
} }
&_safari { &_safari {
width: 78px;
height: 79px;
background-image: url('../../images/safari.png'); background-image: url('../../images/safari.png');
height: 79px;
width: 78px;
} }
} }
@ -91,30 +91,30 @@
} }
&__tile { &__tile {
width: 138px;
height: 163px;
margin-top: 5px;
background-color: #e8e8e8; background-color: #e8e8e8;
border: 1px solid #cfcfcf; border: 1px solid #cfcfcf;
border-radius: 10px; border-radius: 10px;
height: 163px;
margin-top: 5px;
width: 138px;
} }
} }
.unsupported-browser { .unsupported-desktop-browser {
display: block; display: block;
position: absolute;
width:500px;
height: 565px; height: 565px;
overflow:hidden;
text-align: center;
margin: auto; margin: auto;
overflow:hidden;
position: absolute;
text-align: center;
top: 0; left: 0; bottom: 0; right: 0; top: 0; left: 0; bottom: 0; right: 0;
width:500px;
&__page { &__page {
display:inline-block; display:inline-block;
font-size: 28px; font-size: 28px;
vertical-align:middle;
padding-top: 25px; padding-top: 25px;
vertical-align:middle;
} }
&__title { &__title {
@ -123,10 +123,10 @@
} }
&-wrapper { &-wrapper {
background: #fff;
display: block; display: block;
height: 100%;
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%;
background: #fff;
} }
} }

View File

@ -1,4 +1,4 @@
.mobile-browser-page { .unsupported-mobile-browser {
background-color: #fff; background-color: #fff;
height: 100vh; height: 100vh;
padding: 35px 0; padding: 35px 0;
@ -22,9 +22,9 @@
margin-bottom: 0.65em; margin-bottom: 0.65em;
&_small { &_small {
font-size: 1.5em;
margin-bottom: 1em; margin-bottom: 1em;
margin-top: em(21, 18); margin-top: em(21, 18);
font-size: 1.5em;
strong { strong {
font-size: em(21, 18); font-size: em(21, 18);

View File

@ -42,13 +42,7 @@ export function appNavigate(urlOrRoom) {
// domain. // domain.
if (typeof domain === 'undefined' || oldDomain === domain) { if (typeof domain === 'undefined' || oldDomain === domain) {
// If both domain and room vars became undefined, that means we're dispatchSetRoomAndNavigate();
// actually dealing with just room name and not with URL.
dispatch(
_setRoomAndNavigate(
typeof room === 'undefined' && typeof domain === 'undefined'
? urlOrRoom
: room));
} else if (oldDomain !== domain) { } else if (oldDomain !== domain) {
// Update domain without waiting for config to be loaded to prevent // Update domain without waiting for config to be loaded to prevent
// race conditions when we will start to load config multiple times. // race conditions when we will start to load config multiple times.
@ -61,14 +55,7 @@ export function appNavigate(urlOrRoom) {
.then( .then(
config => configLoaded(/* err */ undefined, config), config => configLoaded(/* err */ undefined, config),
err => configLoaded(err, /* config */ undefined)) err => configLoaded(err, /* config */ undefined))
.then(() => { .then(dispatchSetRoomAndNavigate);
const link = typeof room === 'undefined'
&& typeof domain === 'undefined'
? urlOrRoom
: room;
dispatch(_setRoomAndNavigate(link));
});
} }
/** /**
@ -94,6 +81,21 @@ export function appNavigate(urlOrRoom) {
dispatch(setConfig(config)); dispatch(setConfig(config));
} }
/**
* Dispatches _setRoomAndNavigate in the Redux store.
*
* @returns {void}
*/
function dispatchSetRoomAndNavigate() {
// If both domain and room vars became undefined, that means we're
// actually dealing with just room name and not with URL.
dispatch(
_setRoomAndNavigate(
typeof room === 'undefined' && typeof domain === 'undefined'
? urlOrRoom
: room));
}
}; };
} }
@ -129,6 +131,21 @@ export function appWillUnmount(app) {
}; };
} }
/**
* Navigates to a route in accord with a specific Redux state.
*
* @param {Object} state - The Redux state which determines/identifies the route
* to navigate to.
* @private
* @returns {void}
*/
function _navigate(state) {
const app = state['features/app'].app;
const routeToRender = _getRouteToRender(state);
app._navigate(routeToRender);
}
/** /**
* Sets room and navigates to new route if needed. * Sets room and navigates to new route if needed.
* *
@ -139,11 +156,6 @@ export function appWillUnmount(app) {
function _setRoomAndNavigate(newRoom) { function _setRoomAndNavigate(newRoom) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(setRoom(newRoom)); dispatch(setRoom(newRoom));
_navigate(getState());
const state = getState();
const { app } = state['features/app'];
const newRoute = _getRouteToRender(state);
app._navigate(newRoute);
}; };
} }

View File

@ -1,11 +1,11 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { RouteRegistry } from '../../base/navigator';
import { import {
localParticipantJoined, localParticipantJoined,
localParticipantLeft localParticipantLeft
} from '../../base/participants'; } from '../../base/participants';
import { RouteRegistry } from '../../base/navigator';
import { import {
appNavigate, appNavigate,
@ -32,7 +32,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
}; }
/** /**
* Initializes a new App instance. * Initializes a new App instance.
@ -211,38 +211,37 @@ export class AbstractApp extends Component {
* @returns {void} * @returns {void}
*/ */
_navigate(route) { _navigate(route) {
const currentRoute = this.state.route || {}; if (RouteRegistry.areRoutesEqual(this.state.route, route)) {
return;
if (!RouteRegistry.areRoutesEqual(route, currentRoute)) {
let nextState = {
...this.state,
route
};
// The Web App was using react-router so it utilized react-router's
// onEnter. During the removal of react-router, modifications were
// minimized by preserving the onEnter interface:
// (1) Router would provide its nextState to the Route's onEnter.
// As the role of Router is now this AbstractApp, provide its
// nextState.
// (2) A replace function would be provided to the Route in case it
// chose to redirect to another path.
this._onRouteEnter(route, nextState, pathname => {
// FIXME In order to minimize the modifications related to the
// removal of react-router, the Web implementation is provided
// bellow because the replace function is used on Web only at
// the time of this writing. Provide a platform-agnostic
// implementation. It should likely find the best Route matching
// the specified pathname and navigate to it.
window.location.pathname = pathname;
// Do not proceed with the route because it chose to redirect to
// another path.
nextState = undefined;
});
nextState && this.setState(nextState);
} }
let nextState = {
...this.state,
route
};
// The Web App was using react-router so it utilized react-router's
// onEnter. During the removal of react-router, modifications were
// minimized by preserving the onEnter interface:
// (1) Router would provide its nextState to the Route's onEnter. As the
// role of Router is now this AbstractApp, provide its nextState.
// (2) A replace function would be provided to the Route in case it
// chose to redirect to another path.
this._onRouteEnter(route, nextState, pathname => {
// FIXME In order to minimize the modifications related to the
// removal of react-router, the Web implementation is provided
// bellow because the replace function is used on Web only at the
// time of this writing. Provide a platform-agnostic implementation.
// It should likely find the best Route matching the specified
// pathname and navigate to it.
window.location.pathname = pathname;
// Do not proceed with the route because it chose to redirect to
// another path.
nextState = undefined;
});
nextState && this.setState(nextState);
} }
/** /**

View File

@ -11,20 +11,6 @@
* without needing to create additional inter-feature dependencies. * without needing to create additional inter-feature dependencies.
*/ */
class RouteRegistry { class RouteRegistry {
/**
* Method checking whether route objects are equal by value. Returns true if
* and only if key values of the first object are equal to key values of
* the second one.
*
* @param {Object} newRoute - New route object to be compared.
* @param {Object} oldRoute - Old route object to be compared.
* @returns {boolean}
*/
areRoutesEqual(newRoute, oldRoute) {
return Object.keys(newRoute)
.every(key => newRoute[key] === oldRoute[key]);
}
/** /**
* Initializes a new RouteRegistry instance. * Initializes a new RouteRegistry instance.
*/ */
@ -37,6 +23,31 @@ class RouteRegistry {
this._routeRegistry = new Set(); this._routeRegistry = new Set();
} }
/**
* Determines whether two specific Routes are equal i.e. they describe one
* and the same abstract route.
*
* @param {Object} a - The Route to compare to b.
* @param {Object} b - The Route to compare to a.
* @returns {boolean} True if the specified a and b describe one and the
* same abstract route; otherwise, false.
*/
areRoutesEqual(a, b) {
if (a === b) { // reflexive
return true;
}
if (!a) {
return !b;
}
if (!b) {
return !a;
}
return (
Object.keys(a).every(key => a[key] === b[key])
&& /* symmetric */ this.areRoutesEqual(b, a));
}
/** /**
* Returns all registered routes. * Returns all registered routes.
* *

View File

@ -1,3 +1,3 @@
export * from './interceptComponent';
export * from './loadScript'; export * from './loadScript';
export * from './roomnameGenerator'; export * from './roomnameGenerator';
export * from './componentInterceptor';

View File

@ -1,8 +1,5 @@
import { Platform } from '../react'; import { Platform } from '../react';
import { UnsupportedMobileBrowser } from '../../unsupported-browser';
import {
MobileBrowserPage
} from '../../unsupported-browser';
/** /**
* Array of rules defining whether we should intercept component to render * Array of rules defining whether we should intercept component to render
@ -21,16 +18,16 @@ const RULES = [
* WebRTC support on Android). * WebRTC support on Android).
* *
* @param {Object} state - Object containing Redux state. * @param {Object} state - Object containing Redux state.
* @returns {MobileBrowserPage|void} If the rule is satisfied then * @returns {UnsupportedMobileBrowser|void} If the rule is satisfied then
* we should intercept existing component by MobileBrowserPage. * we should intercept existing component by UnsupportedMobileBrowser.
*/ */
state => { state => {
const OS = Platform.OS; const OS = Platform.OS;
const { mobileBrowserPageIsShown } const { mobileBrowserPageIsShown }
= state['features/unsupported-browser']; = state['features/unsupported-browser'];
if ((OS === 'android' || OS === 'ios') && !mobileBrowserPageIsShown) { if ((OS === 'android' || OS === 'ios') && !mobileBrowserPageIsShown) {
return MobileBrowserPage; return UnsupportedMobileBrowser;
} }
} }
]; ];
@ -49,12 +46,11 @@ export function interceptComponent(stateOrGetState, currentComponent) {
let result; let result;
const state const state
= typeof stateOrGetState === 'function' = typeof stateOrGetState === 'function'
? stateOrGetState() ? stateOrGetState()
: stateOrGetState; : stateOrGetState;
for (const rule of RULES) { for (const rule of RULES) {
result = rule(state); result = rule(state);
if (result) { if (result) {
break; break;
} }

View File

@ -1,119 +0,0 @@
import React, { Component } from 'react';
/**
* Array of all supported browsers.
*/
const SUPPORTED_BROWSERS = [
{
link: 'http://google.com/chrome',
name: 'chrome',
plugin: false,
title: 'Chrome 44+'
}, {
link: 'http://www.chromium.org/',
name: 'chromium',
plugin: false,
title: 'Chromium 44+'
}, {
link: 'http://www.opera.com',
name: 'opera',
plugin: false,
title: 'Opera 32+'
}, {
link: 'http://www.getfirefox.com/',
name: 'firefox',
plugin: false,
title: 'Firefox and Iceweasel 40+'
}, {
link: 'https://temasys.atlassian.net/wiki/display/TWPP/WebRTC+Plugins',
name: 'ie',
plugin: 'Temasys 0.8.854+',
title: 'IE'
}, {
link: 'https://temasys.atlassian.net/wiki/display/TWPP/WebRTC+Plugins',
name: 'safari',
plugin: 'Temasys 0.8.854+',
title: 'Safari'
}
];
/**
* React component representing unsupported browser page.
*
* @class UnsupportedBrowserPage
*/
export default class UnsupportedBrowserPage extends Component {
/**
* Renders the component.
*
* @returns {ReactElement}
*/
render() {
return (
<div className = 'unsupported-browser-wrapper'>
<div className = 'unsupported-browser'>
<div className = 'unsupported-browser__content'>
<h2 className = 'unsupported-browser__title'>
This application is currently only supported by
</h2>
{ this._getSupportedBrowsersLayout() }
</div>
</div>
</div>
);
}
/**
* Generates layout for the list of supported browsers.
*
* @returns {ReactElement}
* @private
*/
_getSupportedBrowsersLayout() {
return (
<div className = 'browser-list'>
{ SUPPORTED_BROWSERS.map(this._getSupportedBrowser) }
</div>
);
}
/**
* Method that generated layout for supported browser object.
*
* @param {Object} browser - Object containing information about supported
* browser.
* @returns {ReactElement}
* @private
*/
_getSupportedBrowser(browser) {
let pluginHtml = null;
const logoClassName = `browser__logo browser__logo_${browser.name}`;
// Browsers not supporting WebRTC could support application
// with Temasys plugin installed.
if (browser.plugin) {
const className = 'browser__text_small';
pluginHtml = <p className = { className }>({ browser.plugin })</p>;
}
return (
<div
className = 'browser'
key = { browser.name }>
<div className = 'browser__text'>
{ browser.title }
{ pluginHtml }
</div>
<div className = 'browser__tile'>
<div className = { logoClassName } />
<a
className = 'browser__link'
href = { browser.link }>
<div className = 'browser__button'>DOWNLOAD</div>
</a>
</div>
</div>
);
}
}

View File

@ -0,0 +1,125 @@
import React, { Component } from 'react';
/**
* The list of all browsers supported by the application.
*/
const SUPPORTED_BROWSERS = [
{
link: 'http://google.com/chrome',
name: 'chrome',
title: 'Chrome 44+'
}, {
link: 'http://www.chromium.org/',
name: 'chromium',
title: 'Chromium 44+'
}, {
link: 'http://www.getfirefox.com/',
name: 'firefox',
title: 'Firefox and Iceweasel 40+'
}, {
link: 'http://www.opera.com',
name: 'opera',
title: 'Opera 32+'
}, {
link: 'https://temasys.atlassian.net/wiki/display/TWPP/WebRTC+Plugins',
name: 'ie',
plugin: 'Temasys 0.8.854+',
title: 'IE'
}, {
link: 'https://temasys.atlassian.net/wiki/display/TWPP/WebRTC+Plugins',
name: 'safari',
plugin: 'Temasys 0.8.854+',
title: 'Safari'
}
];
/**
* React component representing unsupported browser page.
*
* @class UnsupportedDesktopBrowser
*/
export default class UnsupportedDesktopBrowser extends Component {
/**
* Renders the component.
*
* @returns {ReactElement}
*/
render() {
const ns = 'unsupported-desktop-browser';
return (
<div className = { `${ns}-wrapper` }>
<div className = { ns }>
<div className = { `${ns}__content` }>
<h2 className = { `${ns}__title` }>
This application is currently only supported by
</h2>
{
this._renderSupportedBrowsers()
}
</div>
</div>
</div>
);
}
/**
* Renders a specific browser supported by the application.
*
* @param {Object} browser - The (information about the) browser supported
* by the application to render.
* @private
* @returns {ReactElement}
*/
_renderSupportedBrowser(browser) {
const { link, name, plugin, title } = browser;
const ns = 'supported-browser';
// Browsers which do not support WebRTC could support the application
// with the Temasys plugin.
const pluginElement
= plugin
? <p className = { `${ns}__text_small` }>{ plugin }</p>
: null;
return (
<div
className = { ns }
key = { name }>
<div className = { `${ns}__text` }>
{
title
}
{
pluginElement
}
</div>
<div className = { `${ns}__tile` }>
<div
className = { `${ns}__logo ${ns}__logo_${name}` } />
<a
className = { `${ns}__link` }
href = { link }>
<div className = { `${ns}__button` }>DOWNLOAD</div>
</a>
</div>
</div>
);
}
/**
* Renders the list of browsers supported by the application.
*
* @private
* @returns {ReactElement}
*/
_renderSupportedBrowsers() {
return (
<div className = 'supported-browser-list'>
{
SUPPORTED_BROWSERS.map(this._renderSupportedBrowser)
}
</div>
);
}
}

View File

@ -1,9 +1,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { appNavigate } from '../../app';
import { Platform } from '../../base/react'; import { Platform } from '../../base/react';
import { appNavigate } from '../../app';
import { mobileBrowserPageIsShown } from '../actions'; import { mobileBrowserPageIsShown } from '../actions';
/** /**
@ -18,12 +18,21 @@ const URLS = {
/** /**
* React component representing mobile browser page. * React component representing mobile browser page.
* *
* @class MobileBrowserPage * @class UnsupportedMobileBrowser
*/ */
class MobileBrowserPage extends Component { class UnsupportedMobileBrowser extends Component {
/**
* Mobile browser page component's property types.
*
* @static
*/
static propTypes = {
dispatch: React.PropTypes.func,
room: React.PropTypes.string
}
/** /**
* Constructor of MobileBrowserPage component. * Constructor of UnsupportedMobileBrowser component.
* *
* @param {Object} props - The read-only React Component props with which * @param {Object} props - The read-only React Component props with which
* the new instance is to be initialized. * the new instance is to be initialized.
@ -35,16 +44,6 @@ class MobileBrowserPage extends Component {
this._onClickJoin = this._onClickJoin.bind(this); this._onClickJoin = this._onClickJoin.bind(this);
} }
/**
* Mobile browser page component's property types.
*
* @static
*/
static propTypes = {
dispatch: React.PropTypes.func,
room: React.PropTypes.string
};
/** /**
* React lifecycle method triggered after component is mounted. * React lifecycle method triggered after component is mounted.
* *
@ -62,13 +61,14 @@ class MobileBrowserPage extends Component {
componentWillMount() { componentWillMount() {
const { room } = this.props; const { room } = this.props;
let btnText; let btnText;
let link = ''; let link;
if (room) { if (room) {
btnText = 'Join the conversation'; btnText = 'Join the conversation';
link += room; link = room;
} else { } else {
btnText = 'Start a conference'; btnText = 'Start a conference';
link = '';
} }
this.setState({ this.setState({
@ -77,38 +77,23 @@ class MobileBrowserPage extends Component {
}); });
} }
/**
* Navigates to the next state of the app.
*
* @returns {void}
* @private
*/
_onClickJoin() {
const { link } = this.state;
this.props.dispatch(appNavigate(link));
}
/** /**
* Renders component. * Renders component.
* *
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const { btnText } = this.state; const ns = 'unsupported-mobile-browser';
const blockPrefix = 'mobile-browser-page'; const primaryButtonClasses
const textClasses = `${blockPrefix}__text ${blockPrefix}__text_small`; = `${ns}__button ${ns}__button_primary`;
let primaryButtonClasses = `${blockPrefix}__button`;
primaryButtonClasses += ` ${blockPrefix}__button_primary`;
return ( return (
<div className = { blockPrefix }> <div className = { ns }>
<div className = { `${blockPrefix}__body` }> <div className = { `${ns}__body` }>
<img <img
className = { `${blockPrefix}__logo` } className = { `${ns}__logo` }
src = '/images/logo-blue.svg' /> src = '/images/logo-blue.svg' />
<p className = { `${blockPrefix}__text` }> <p className = { `${ns}__text` }>
You need <strong>Jitsi Meet</strong> to join a You need <strong>Jitsi Meet</strong> to join a
conversation on your mobile conversation on your mobile
</p> </p>
@ -117,24 +102,37 @@ class MobileBrowserPage extends Component {
Download the App Download the App
</button> </button>
</a> </a>
<p className = { textClasses }> <p className = { `${ns}__text ${ns}__text_small` }>
or if you already have it or if you already have it
<br /> <br />
<strong>then</strong> <strong>then</strong>
</p> </p>
<button <button
className = 'mobile-browser-page__button' className = { `${ns}__button` }
onClick = { this._onClickJoin }> onClick = { this._onClickJoin }>
{ btnText } {
this.state.btnText
}
</button> </button>
</div> </div>
</div> </div>
); );
} }
/**
* Navigates to the next state of the app.
*
* @private
* @returns {void}
*/
_onClickJoin() {
this.props.dispatch(appNavigate(this.state.link));
}
} }
/** /**
* Maps (parts of) the Redux state to the associated MobileBrowserPage's props. * Maps (parts of) the Redux state to the associated UnsupportedMobileBrowser's
* props.
* *
* @param {Object} state - Redux state. * @param {Object} state - Redux state.
* @returns {{ * @returns {{
@ -147,4 +145,4 @@ function mapStateToProps(state) {
}; };
} }
export default connect(mapStateToProps)(MobileBrowserPage); export default connect(mapStateToProps)(UnsupportedMobileBrowser);

View File

@ -1,2 +1,4 @@
export { default as MobileBrowserPage } from './MobileBrowserPage'; export { default as UnsupportedDesktopBrowser }
export { default as UnsupportedBrowserPage } from './UnsupportedBrowserPage'; from './UnsupportedDesktopBrowser';
export { default as UnsupportedMobileBrowser }
from './UnsupportedMobileBrowser';