diff --git a/css/_variables.scss b/css/_variables.scss index 32e2a47b6..626fad08b 100644 --- a/css/_variables.scss +++ b/css/_variables.scss @@ -131,6 +131,6 @@ $linkHoverFontColor: #287ade; /** * Landing */ +$primaryUnsupportedBrowserButtonBgColor: #17a0db; $unsupportedBrowserButtonBgColor: #ff9a00; $unsupportedBrowserTextColor: #4a4a4a; -$primaryUnsupportedBrowserButtonBgColor: #17a0db; diff --git a/css/main.scss b/css/main.scss index 140b957e4..eb3915af4 100644 --- a/css/main.scss +++ b/css/main.scss @@ -67,7 +67,7 @@ @import '404'; @import 'policy'; @import 'filmstrip'; -@import 'unsupported-browser/mobile-browser-page'; -@import 'unsupported-browser/unsupported_browser'; +@import 'unsupported-browser/unsupported-desktop-browser'; +@import 'unsupported-browser/unsupported-mobile-browser'; /* Modules END */ diff --git a/css/unsupported-browser/_unsupported_browser.scss b/css/unsupported-browser/_unsupported-desktop-browser.scss similarity index 97% rename from css/unsupported-browser/_unsupported_browser.scss rename to css/unsupported-browser/_unsupported-desktop-browser.scss index 3faf0c68a..720f99aba 100644 --- a/css/unsupported-browser/_unsupported_browser.scss +++ b/css/unsupported-browser/_unsupported-desktop-browser.scss @@ -1,10 +1,10 @@ -.browser { - display: inline-block; - margin: 1em 7px; - width: 138px; - vertical-align: middle; +.supported-browser { color: #929391; + display: inline-block; font-size: 20px; + margin: 1em 7px; + vertical-align: middle; + width: 138px; &__button { background-color: #62c82a; @@ -12,11 +12,11 @@ border-radius: 10px; color: #FFFFFF; font-size: 12px; + height: 26px; + margin: 15px auto 0px auto; + padding-top: 13px; text-align: center; width: 115px; - height: 26px; - padding-top: 13px; - margin: 15px auto 0px auto; } &__link { @@ -45,39 +45,39 @@ margin: 20px auto 0px auto; &_chrome { - width: 78px; - height: 78px; background-image: url('../../images/chrome.png'); + height: 78px; + width: 78px; } &_chromium { - width: 77px; - height: 78px; background-image: url('../../images/chromium.png'); + height: 78px; + width: 77px; } &_firefox { - width: 86px; - height: 80px; background-image: url('../../images/firefox.png'); + height: 80px; + width: 86px; } &_opera { - width: 73px; - height: 78px; background-image: url('../../images/opera.png'); + height: 78px; + width: 73px; } &_ie { - width: 80px; - height: 78px; background-image: url('../../images/ie.png'); + height: 78px; + width: 80px; } &_safari { - width: 78px; - height: 79px; background-image: url('../../images/safari.png'); + height: 79px; + width: 78px; } } @@ -91,30 +91,30 @@ } &__tile { - width: 138px; - height: 163px; - margin-top: 5px; background-color: #e8e8e8; border: 1px solid #cfcfcf; border-radius: 10px; + height: 163px; + margin-top: 5px; + width: 138px; } } -.unsupported-browser { +.unsupported-desktop-browser { display: block; - position: absolute; - width:500px; height: 565px; - overflow:hidden; - text-align: center; margin: auto; + overflow:hidden; + position: absolute; + text-align: center; top: 0; left: 0; bottom: 0; right: 0; + width:500px; &__page { display:inline-block; font-size: 28px; - vertical-align:middle; padding-top: 25px; + vertical-align:middle; } &__title { @@ -123,10 +123,10 @@ } &-wrapper { + background: #fff; display: block; + height: 100%; position: absolute; width: 100%; - height: 100%; - background: #fff; } } diff --git a/css/unsupported-browser/_mobile-browser-page.scss b/css/unsupported-browser/_unsupported-mobile-browser.scss similarity index 98% rename from css/unsupported-browser/_mobile-browser-page.scss rename to css/unsupported-browser/_unsupported-mobile-browser.scss index ab5b110a7..5a5e53458 100644 --- a/css/unsupported-browser/_mobile-browser-page.scss +++ b/css/unsupported-browser/_unsupported-mobile-browser.scss @@ -1,4 +1,4 @@ -.mobile-browser-page { +.unsupported-mobile-browser { background-color: #fff; height: 100vh; padding: 35px 0; @@ -22,9 +22,9 @@ margin-bottom: 0.65em; &_small { + font-size: 1.5em; margin-bottom: 1em; margin-top: em(21, 18); - font-size: 1.5em; strong { font-size: em(21, 18); diff --git a/react/features/app/actions.js b/react/features/app/actions.js index 5f2153ddd..f6c5e6ad1 100644 --- a/react/features/app/actions.js +++ b/react/features/app/actions.js @@ -42,13 +42,7 @@ export function appNavigate(urlOrRoom) { // domain. if (typeof domain === 'undefined' || oldDomain === domain) { - // 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)); + dispatchSetRoomAndNavigate(); } else if (oldDomain !== domain) { // Update domain without waiting for config to be loaded to prevent // race conditions when we will start to load config multiple times. @@ -61,14 +55,7 @@ export function appNavigate(urlOrRoom) { .then( config => configLoaded(/* err */ undefined, config), err => configLoaded(err, /* config */ undefined)) - .then(() => { - const link = typeof room === 'undefined' - && typeof domain === 'undefined' - ? urlOrRoom - : room; - - dispatch(_setRoomAndNavigate(link)); - }); + .then(dispatchSetRoomAndNavigate); } /** @@ -94,6 +81,21 @@ export function appNavigate(urlOrRoom) { 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. * @@ -139,11 +156,6 @@ export function appWillUnmount(app) { function _setRoomAndNavigate(newRoom) { return (dispatch, getState) => { dispatch(setRoom(newRoom)); - - const state = getState(); - const { app } = state['features/app']; - const newRoute = _getRouteToRender(state); - - app._navigate(newRoute); + _navigate(getState()); }; } diff --git a/react/features/app/components/AbstractApp.js b/react/features/app/components/AbstractApp.js index fb75fa733..6ae1435a4 100644 --- a/react/features/app/components/AbstractApp.js +++ b/react/features/app/components/AbstractApp.js @@ -1,11 +1,11 @@ import React, { Component } from 'react'; import { Provider } from 'react-redux'; +import { RouteRegistry } from '../../base/navigator'; import { localParticipantJoined, localParticipantLeft } from '../../base/participants'; -import { RouteRegistry } from '../../base/navigator'; import { appNavigate, @@ -32,7 +32,7 @@ export class AbstractApp extends Component { * The URL, if any, with which the app was launched. */ url: React.PropTypes.string - }; + } /** * Initializes a new App instance. @@ -211,38 +211,37 @@ export class AbstractApp extends Component { * @returns {void} */ _navigate(route) { - const currentRoute = this.state.route || {}; - - 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); + if (RouteRegistry.areRoutesEqual(this.state.route, route)) { + return; } + + 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); } /** diff --git a/react/features/base/navigator/RouteRegistry.js b/react/features/base/navigator/RouteRegistry.js index c22cbdf2f..0374ed0a4 100644 --- a/react/features/base/navigator/RouteRegistry.js +++ b/react/features/base/navigator/RouteRegistry.js @@ -11,20 +11,6 @@ * without needing to create additional inter-feature dependencies. */ 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. */ @@ -37,6 +23,31 @@ class RouteRegistry { 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. * diff --git a/react/features/base/util/index.js b/react/features/base/util/index.js index 272a8c252..f8ac08743 100644 --- a/react/features/base/util/index.js +++ b/react/features/base/util/index.js @@ -1,3 +1,3 @@ +export * from './interceptComponent'; export * from './loadScript'; export * from './roomnameGenerator'; -export * from './componentInterceptor'; diff --git a/react/features/base/util/componentInterceptor.js b/react/features/base/util/interceptComponent.js similarity index 79% rename from react/features/base/util/componentInterceptor.js rename to react/features/base/util/interceptComponent.js index a3550c4e0..1156215d9 100644 --- a/react/features/base/util/componentInterceptor.js +++ b/react/features/base/util/interceptComponent.js @@ -1,8 +1,5 @@ import { Platform } from '../react'; - -import { - MobileBrowserPage -} from '../../unsupported-browser'; +import { UnsupportedMobileBrowser } from '../../unsupported-browser'; /** * Array of rules defining whether we should intercept component to render @@ -21,16 +18,16 @@ const RULES = [ * WebRTC support on Android). * * @param {Object} state - Object containing Redux state. - * @returns {MobileBrowserPage|void} If the rule is satisfied then - * we should intercept existing component by MobileBrowserPage. + * @returns {UnsupportedMobileBrowser|void} If the rule is satisfied then + * we should intercept existing component by UnsupportedMobileBrowser. */ state => { const OS = Platform.OS; const { mobileBrowserPageIsShown } - = state['features/unsupported-browser']; + = state['features/unsupported-browser']; if ((OS === 'android' || OS === 'ios') && !mobileBrowserPageIsShown) { - return MobileBrowserPage; + return UnsupportedMobileBrowser; } } ]; @@ -49,12 +46,11 @@ export function interceptComponent(stateOrGetState, currentComponent) { let result; const state = typeof stateOrGetState === 'function' - ? stateOrGetState() - : stateOrGetState; + ? stateOrGetState() + : stateOrGetState; for (const rule of RULES) { result = rule(state); - if (result) { break; } diff --git a/react/features/unsupported-browser/components/UnsupportedBrowserPage.js b/react/features/unsupported-browser/components/UnsupportedBrowserPage.js deleted file mode 100644 index 028fa3949..000000000 --- a/react/features/unsupported-browser/components/UnsupportedBrowserPage.js +++ /dev/null @@ -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 ( -
-
-
-

- This application is currently only supported by -

- { this._getSupportedBrowsersLayout() } -
-
-
- ); - } - - /** - * Generates layout for the list of supported browsers. - * - * @returns {ReactElement} - * @private - */ - _getSupportedBrowsersLayout() { - return ( -
- { SUPPORTED_BROWSERS.map(this._getSupportedBrowser) } -
- ); - } - - /** - * 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 =

({ browser.plugin })

; - } - - return ( -
-
- { browser.title } - { pluginHtml } -
-
-
- -
DOWNLOAD
-
-
-
- ); - } -} diff --git a/react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js b/react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js new file mode 100644 index 000000000..545c2c2c9 --- /dev/null +++ b/react/features/unsupported-browser/components/UnsupportedDesktopBrowser.js @@ -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 ( +
+
+
+

+ This application is currently only supported by +

+ { + this._renderSupportedBrowsers() + } +
+
+
+ ); + } + + /** + * 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 + ?

{ plugin }

+ : null; + + return ( +
+
+ { + title + } + { + pluginElement + } +
+
+
+ +
DOWNLOAD
+
+
+
+ ); + } + + /** + * Renders the list of browsers supported by the application. + * + * @private + * @returns {ReactElement} + */ + _renderSupportedBrowsers() { + return ( +
+ { + SUPPORTED_BROWSERS.map(this._renderSupportedBrowser) + } +
+ ); + } +} diff --git a/react/features/unsupported-browser/components/MobileBrowserPage.js b/react/features/unsupported-browser/components/UnsupportedMobileBrowser.js similarity index 72% rename from react/features/unsupported-browser/components/MobileBrowserPage.js rename to react/features/unsupported-browser/components/UnsupportedMobileBrowser.js index ab62080f0..bd7eb0304 100644 --- a/react/features/unsupported-browser/components/MobileBrowserPage.js +++ b/react/features/unsupported-browser/components/UnsupportedMobileBrowser.js @@ -1,9 +1,9 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; +import { appNavigate } from '../../app'; import { Platform } from '../../base/react'; -import { appNavigate } from '../../app'; import { mobileBrowserPageIsShown } from '../actions'; /** @@ -18,12 +18,21 @@ const URLS = { /** * 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 * the new instance is to be initialized. @@ -35,16 +44,6 @@ class MobileBrowserPage extends Component { 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. * @@ -62,13 +61,14 @@ class MobileBrowserPage extends Component { componentWillMount() { const { room } = this.props; let btnText; - let link = ''; + let link; if (room) { btnText = 'Join the conversation'; - link += room; + link = room; } else { btnText = 'Start a conference'; + link = ''; } 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. * * @returns {ReactElement} */ render() { - const { btnText } = this.state; - const blockPrefix = 'mobile-browser-page'; - const textClasses = `${blockPrefix}__text ${blockPrefix}__text_small`; - let primaryButtonClasses = `${blockPrefix}__button`; - - primaryButtonClasses += ` ${blockPrefix}__button_primary`; + const ns = 'unsupported-mobile-browser'; + const primaryButtonClasses + = `${ns}__button ${ns}__button_primary`; return ( -
-
+
+
-

+

You need Jitsi Meet to join a conversation on your mobile

@@ -117,24 +102,37 @@ class MobileBrowserPage extends Component { Download the App -

+

or if you already have it
then

); } + + /** + * 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. * @returns {{ @@ -147,4 +145,4 @@ function mapStateToProps(state) { }; } -export default connect(mapStateToProps)(MobileBrowserPage); +export default connect(mapStateToProps)(UnsupportedMobileBrowser); diff --git a/react/features/unsupported-browser/components/index.js b/react/features/unsupported-browser/components/index.js index a56404191..84560afef 100644 --- a/react/features/unsupported-browser/components/index.js +++ b/react/features/unsupported-browser/components/index.js @@ -1,2 +1,4 @@ -export { default as MobileBrowserPage } from './MobileBrowserPage'; -export { default as UnsupportedBrowserPage } from './UnsupportedBrowserPage'; +export { default as UnsupportedDesktopBrowser } + from './UnsupportedDesktopBrowser'; +export { default as UnsupportedMobileBrowser } + from './UnsupportedMobileBrowser';