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 ( -
({ browser.plugin })
; - } - - return ( -{ plugin }
+ : null; + + 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