Introduce unsupported browser page
This commit is contained in:
parent
57ba702dda
commit
2e81b8493e
3
Makefile
3
Makefile
|
@ -8,7 +8,6 @@ OUTPUT_DIR = .
|
||||||
STYLES_BUNDLE = css/all.bundle.css
|
STYLES_BUNDLE = css/all.bundle.css
|
||||||
STYLES_DESTINATION = css/all.css
|
STYLES_DESTINATION = css/all.css
|
||||||
STYLES_MAIN = css/main.scss
|
STYLES_MAIN = css/main.scss
|
||||||
STYLES_UNSUPPORTED_BROWSER = css/unsupported_browser.scss
|
|
||||||
WEBPACK = ./node_modules/.bin/webpack
|
WEBPACK = ./node_modules/.bin/webpack
|
||||||
|
|
||||||
all: update-deps compile deploy clean
|
all: update-deps compile deploy clean
|
||||||
|
@ -47,7 +46,6 @@ deploy-lib-jitsi-meet:
|
||||||
$(DEPLOY_DIR)
|
$(DEPLOY_DIR)
|
||||||
|
|
||||||
deploy-css:
|
deploy-css:
|
||||||
$(NODE_SASS) css/unsupported_browser.scss css/unsupported_browser.css ; \
|
|
||||||
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
|
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
|
||||||
$(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
|
$(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
|
||||||
rm $(STYLES_BUNDLE)
|
rm $(STYLES_BUNDLE)
|
||||||
|
@ -59,6 +57,5 @@ source-package:
|
||||||
mkdir -p source_package/jitsi-meet/css && \
|
mkdir -p source_package/jitsi-meet/css && \
|
||||||
cp -r *.js *.html connection_optimization favicon.ico fonts images libs sounds LICENSE lang source_package/jitsi-meet && \
|
cp -r *.js *.html connection_optimization favicon.ico fonts images libs sounds LICENSE lang source_package/jitsi-meet && \
|
||||||
cp css/all.css source_package/jitsi-meet/css && \
|
cp css/all.css source_package/jitsi-meet/css && \
|
||||||
cp css/unsupported_browser.css source_package/jitsi-meet/css && \
|
|
||||||
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
|
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
|
||||||
rm -rf source_package
|
rm -rf source_package
|
||||||
|
|
|
@ -131,6 +131,6 @@ $linkHoverFontColor: #287ade;
|
||||||
/**
|
/**
|
||||||
* Landing
|
* Landing
|
||||||
*/
|
*/
|
||||||
$landingButtonBgColor: #ff9a00;
|
$unsupportedBrowserButtonBgColor: #ff9a00;
|
||||||
$landingTextColor: #4a4a4a;
|
$unsupportedBrowserTextColor: #4a4a4a;
|
||||||
$primaryLandingButtonBgColor: #17a0db;
|
$primaryUnsupportedBrowserButtonBgColor: #17a0db;
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
@import '404';
|
@import '404';
|
||||||
@import 'policy';
|
@import 'policy';
|
||||||
@import 'filmstrip';
|
@import 'filmstrip';
|
||||||
@import 'landing';
|
@import 'unsupported-browser/mobile-browser-page';
|
||||||
|
@import 'unsupported-browser/unsupported_browser';
|
||||||
|
|
||||||
/* Modules END */
|
/* Modules END */
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
.landing {
|
.mobile-browser-page {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
padding: 35px 0;
|
padding: 35px 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
|
||||||
&__body {
|
&__body {
|
||||||
color: $landingTextColor;
|
color: $unsupportedBrowserTextColor;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
max-width: 40em;
|
max-width: 40em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -44,25 +44,25 @@
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
width: 98%;
|
width: 98%;
|
||||||
@include border-radius(8px);
|
@include border-radius(8px);
|
||||||
background-color: $landingButtonBgColor;
|
background-color: $unsupportedBrowserButtonBgColor;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
text-shadow: 0px 1px 2px $landingTextColor;
|
text-shadow: 0px 1px 2px $unsupportedBrowserTextColor;
|
||||||
|
|
||||||
// Disable standard button effects.
|
// Disable standard button effects.
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: $landingButtonBgColor;
|
background-color: $unsupportedBrowserButtonBgColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_primary {
|
&_primary {
|
||||||
background-color: $primaryLandingButtonBgColor;
|
background-color: $primaryUnsupportedBrowserButtonBgColor;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: $primaryLandingButtonBgColor;
|
background-color: $primaryUnsupportedBrowserButtonBgColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
.browser {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 1em 7px;
|
||||||
|
width: 138px;
|
||||||
|
vertical-align: middle;
|
||||||
|
color: #929391;
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
background-color: #62c82a;
|
||||||
|
border: 1px solid #3c8117;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
width: 115px;
|
||||||
|
height: 26px;
|
||||||
|
padding-top: 13px;
|
||||||
|
margin: 15px auto 0px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
color: #087dba;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-list
|
||||||
|
{
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__logo {
|
||||||
|
margin: 20px auto 0px auto;
|
||||||
|
|
||||||
|
&_chrome {
|
||||||
|
width: 78px;
|
||||||
|
height: 78px;
|
||||||
|
background-image: url('../../images/chrome.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
&_chromium {
|
||||||
|
width: 77px;
|
||||||
|
height: 78px;
|
||||||
|
background-image: url('../../images/chromium.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
&_firefox {
|
||||||
|
width: 86px;
|
||||||
|
height: 80px;
|
||||||
|
background-image: url('../../images/firefox.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
&_opera {
|
||||||
|
width: 73px;
|
||||||
|
height: 78px;
|
||||||
|
background-image: url('../../images/opera.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
&_ie {
|
||||||
|
width: 80px;
|
||||||
|
height: 78px;
|
||||||
|
background-image: url('../../images/ie.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
&_safari {
|
||||||
|
width: 78px;
|
||||||
|
height: 79px;
|
||||||
|
background-image: url('../../images/safari.png');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text
|
||||||
|
{
|
||||||
|
line-height: 1.2em;
|
||||||
|
|
||||||
|
&_small {
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tile {
|
||||||
|
width: 138px;
|
||||||
|
height: 163px;
|
||||||
|
margin-top: 5px;
|
||||||
|
background-color: #e8e8e8;
|
||||||
|
border: 1px solid #cfcfcf;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.unsupported-browser {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width:500px;
|
||||||
|
height: 565px;
|
||||||
|
overflow:hidden;
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
top: 0; left: 0; bottom: 0; right: 0;
|
||||||
|
|
||||||
|
&__page {
|
||||||
|
display:inline-block;
|
||||||
|
font-size: 28px;
|
||||||
|
vertical-align:middle;
|
||||||
|
padding-top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-wrapper {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,138 +0,0 @@
|
||||||
@import 'variables';
|
|
||||||
|
|
||||||
body {
|
|
||||||
width:100%;
|
|
||||||
height:100%;
|
|
||||||
background-color: white;
|
|
||||||
color: #424242;
|
|
||||||
font-family: $baseFontFamily;
|
|
||||||
font-size: 28px;
|
|
||||||
margin:0;
|
|
||||||
padding:0;
|
|
||||||
}
|
|
||||||
#wrap{
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
width:500px;
|
|
||||||
height: 565px;
|
|
||||||
overflow:hidden;
|
|
||||||
text-align: center;
|
|
||||||
margin: auto;
|
|
||||||
top: 0; left: 0; bottom: 0; right: 0;
|
|
||||||
}
|
|
||||||
.firefox{
|
|
||||||
font-size: 11pt;
|
|
||||||
color: #c8c8c8;
|
|
||||||
width: 468px;
|
|
||||||
text-align: center;
|
|
||||||
margin: 30px auto 0px auto;
|
|
||||||
padding-left: 15px;
|
|
||||||
}
|
|
||||||
#text{
|
|
||||||
display:inline-block;
|
|
||||||
font-size: 28px;
|
|
||||||
/* width: 568px; */
|
|
||||||
vertical-align:middle;
|
|
||||||
padding-top: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #087dba;
|
|
||||||
text-decoration:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.browser {
|
|
||||||
width: 138px;
|
|
||||||
height: 163px;
|
|
||||||
margin-top: 5px;
|
|
||||||
background-color: #e8e8e8;
|
|
||||||
border: 1px solid #cfcfcf;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.browser_wrapper
|
|
||||||
{
|
|
||||||
width: 138px;
|
|
||||||
/* height: 188px; */
|
|
||||||
vertical-align: middle;
|
|
||||||
color: #929391;
|
|
||||||
font-size: 20px;
|
|
||||||
float: left;
|
|
||||||
margin-left: 15px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.browser_text
|
|
||||||
{
|
|
||||||
height: 2em;
|
|
||||||
}
|
|
||||||
.supported_browsers
|
|
||||||
{
|
|
||||||
margin: 0px auto 0px auto;
|
|
||||||
/* width: 660px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear
|
|
||||||
{
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button
|
|
||||||
{
|
|
||||||
background-color: #62c82a;
|
|
||||||
border: 1px solid #3c8117;
|
|
||||||
border-radius: 10px;
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
width: 115px;
|
|
||||||
height: 26px;
|
|
||||||
padding-top: 13px;
|
|
||||||
margin: 15px auto 0px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo
|
|
||||||
{
|
|
||||||
margin: 20px auto 0px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#chrome_logo
|
|
||||||
{
|
|
||||||
width: 78px;
|
|
||||||
height: 78px;
|
|
||||||
background-image: url('../images/chrome.png');
|
|
||||||
}
|
|
||||||
#chromium_logo
|
|
||||||
{
|
|
||||||
width: 77px;
|
|
||||||
height: 78px;
|
|
||||||
background-image: url('../images/chromium.png');
|
|
||||||
}
|
|
||||||
#firefox_logo
|
|
||||||
{
|
|
||||||
width: 86px;
|
|
||||||
height: 80px;
|
|
||||||
background-image: url('../images/firefox.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
#opera_logo
|
|
||||||
{
|
|
||||||
width: 73px;
|
|
||||||
height: 78px;
|
|
||||||
background-image: url('../images/opera.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
#safari_logo
|
|
||||||
{
|
|
||||||
width: 78px;
|
|
||||||
height: 79px;
|
|
||||||
background-image: url('../images/safari.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
#ie_logo
|
|
||||||
{
|
|
||||||
width: 80px;
|
|
||||||
height: 78px;
|
|
||||||
background-image: url('../images/ie.png');
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,19 +6,10 @@ import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
|
||||||
import {
|
import {
|
||||||
_getRoomAndDomainFromUrlString,
|
_getRoomAndDomainFromUrlString,
|
||||||
_getRouteToRender,
|
_getRouteToRender,
|
||||||
areRoutesEqual,
|
|
||||||
init
|
init
|
||||||
} from './functions';
|
} from './functions';
|
||||||
import './reducer';
|
import './reducer';
|
||||||
|
|
||||||
/**
|
|
||||||
* Variable saving current route of the app. Later it will be extracted as
|
|
||||||
* a property of the class with Router specific logic.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
let currentRoute = {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporary solution. Should dispatch actions related to initial settings of
|
* Temporary solution. Should dispatch actions related to initial settings of
|
||||||
* the app like setting log levels, reading the config parameters from query
|
* the app like setting log levels, reading the config parameters from query
|
||||||
|
@ -73,8 +64,8 @@ export function appNavigate(urlOrRoom) {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const link = typeof room === 'undefined'
|
const link = typeof room === 'undefined'
|
||||||
&& typeof domain === 'undefined'
|
&& typeof domain === 'undefined'
|
||||||
? urlOrRoom
|
? urlOrRoom
|
||||||
: room;
|
: room;
|
||||||
|
|
||||||
dispatch(_setRoomAndNavigate(link));
|
dispatch(_setRoomAndNavigate(link));
|
||||||
});
|
});
|
||||||
|
@ -153,9 +144,6 @@ function _setRoomAndNavigate(newRoom) {
|
||||||
const { app } = state['features/app'];
|
const { app } = state['features/app'];
|
||||||
const newRoute = _getRouteToRender(state);
|
const newRoute = _getRouteToRender(state);
|
||||||
|
|
||||||
if (!areRoutesEqual(newRoute, currentRoute)) {
|
app._navigate(newRoute);
|
||||||
currentRoute = newRoute;
|
|
||||||
app._navigate(newRoute);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
localParticipantJoined,
|
localParticipantJoined,
|
||||||
localParticipantLeft
|
localParticipantLeft
|
||||||
} from '../../base/participants';
|
} from '../../base/participants';
|
||||||
|
import { RouteRegistry } from '../../base/navigator';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
appNavigate,
|
appNavigate,
|
||||||
|
@ -31,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.
|
||||||
|
@ -210,33 +211,38 @@ export class AbstractApp extends Component {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_navigate(route) {
|
_navigate(route) {
|
||||||
let nextState = {
|
const currentRoute = this.state.route || {};
|
||||||
...this.state,
|
|
||||||
route
|
|
||||||
};
|
|
||||||
|
|
||||||
// The Web App was using react-router so it utilized react-router's
|
if (!RouteRegistry.areRoutesEqual(route, currentRoute)) {
|
||||||
// onEnter. During the removal of react-router, modifications were
|
let nextState = {
|
||||||
// minimized by preserving the onEnter interface:
|
...this.state,
|
||||||
// (1) Router would provide its nextState to the Route's onEnter. As the
|
route
|
||||||
// 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
|
// The Web App was using react-router so it utilized react-router's
|
||||||
// another path.
|
// onEnter. During the removal of react-router, modifications were
|
||||||
nextState = undefined;
|
// 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;
|
||||||
|
|
||||||
nextState && this.setState(nextState);
|
// Do not proceed with the route because it chose to redirect to
|
||||||
|
// another path.
|
||||||
|
nextState = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
nextState && this.setState(nextState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { isRoomValid } from '../base/conference';
|
||||||
import { RouteRegistry } from '../base/navigator';
|
import { RouteRegistry } from '../base/navigator';
|
||||||
import { Platform } from '../base/react';
|
import { Platform } from '../base/react';
|
||||||
import { Conference } from '../conference';
|
import { Conference } from '../conference';
|
||||||
import { Landing } from '../unsupported-browser';
|
import { MobileBrowserPage } from '../unsupported-browser';
|
||||||
import { WelcomePage } from '../welcome';
|
import { WelcomePage } from '../welcome';
|
||||||
|
|
||||||
import URLProcessor from '../../../modules/config/URLProcessor';
|
import URLProcessor from '../../../modules/config/URLProcessor';
|
||||||
|
@ -33,37 +33,19 @@ export function _getRouteToRender(stateOrGetState) {
|
||||||
? stateOrGetState()
|
? stateOrGetState()
|
||||||
: stateOrGetState;
|
: stateOrGetState;
|
||||||
|
|
||||||
// If landing was shown, there is no need to show it again.
|
// If mobile browser page was shown, there is no need to show it again.
|
||||||
const { landingIsShown } = state['features/unsupported-browser'];
|
const { mobileBrowserPageIsShown } = state['features/unsupported-browser'];
|
||||||
const { room } = state['features/base/conference'];
|
const { room } = state['features/base/conference'];
|
||||||
const component = isRoomValid(room) ? Conference : WelcomePage;
|
const component = isRoomValid(room) ? Conference : WelcomePage;
|
||||||
|
const route = RouteRegistry.getRouteByComponent(component);
|
||||||
|
|
||||||
// We're using spread operator here to create copy of the route registered
|
if ((OS === 'android' || OS === 'ios') && !mobileBrowserPageIsShown) {
|
||||||
// in registry. If we overwrite some of its properties (like 'component')
|
route.component = MobileBrowserPage;
|
||||||
// they will stay unchanged in the registry.
|
|
||||||
const route = { ...RouteRegistry.getRouteByComponent(component) };
|
|
||||||
|
|
||||||
if ((OS === 'android' || OS === 'ios') && !landingIsShown) {
|
|
||||||
route.component = Landing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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}
|
|
||||||
*/
|
|
||||||
export function areRoutesEqual(newRoute, oldRoute) {
|
|
||||||
return Object.keys(newRoute)
|
|
||||||
.every(key => newRoute[key] === oldRoute[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporary solution. Later we'll get rid of global APP and set its properties
|
* Temporary solution. Later we'll get rid of global APP and set its properties
|
||||||
* in redux store.
|
* in redux store.
|
||||||
|
|
|
@ -11,6 +11,20 @@
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { Symbol } from '../base/react';
|
import { Symbol } from '../base/react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the Redux action which signals that a mobile landing is shown.
|
* The type of the Redux action which signals that a mobile browser page
|
||||||
|
* is shown.
|
||||||
*
|
*
|
||||||
* {
|
* {
|
||||||
* type: LANDING_IS_SHOWN
|
* type: MOBILE_BROWSER_PAGE_IS_SHOWN
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const LANDING_IS_SHOWN = Symbol('LANDING_IS_SHOWN');
|
// eslint-disable-next-line max-len
|
||||||
|
export const MOBILE_BROWSER_PAGE_IS_SHOWN = Symbol('MOBILE_BROWSER_PAGE_IS_SHOWN');
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { LANDING_IS_SHOWN } from './actionTypes';
|
import { MOBILE_BROWSER_PAGE_IS_SHOWN } from './actionTypes';
|
||||||
import './reducer';
|
import './reducer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an action that mobile landing is shown
|
* Returns an action that mobile browser page is shown and there is no need
|
||||||
* and there is no need to show it on other pages.
|
* to show it on other pages.
|
||||||
*
|
*
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* type: LANDING_IS_SHOWN
|
* type: MOBILE_BROWSER_PAGE_IS_SHOWN
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function landingIsShown() {
|
export function mobileBrowserPageIsShown() {
|
||||||
return {
|
return {
|
||||||
type: LANDING_IS_SHOWN
|
type: MOBILE_BROWSER_PAGE_IS_SHOWN
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||||
import { Platform } from '../../base/react';
|
import { Platform } from '../../base/react';
|
||||||
|
|
||||||
import { appNavigate } from '../../app';
|
import { appNavigate } from '../../app';
|
||||||
import { landingIsShown } from '../actions';
|
import { mobileBrowserPageIsShown } from '../actions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The map of platforms to URLs at which the mobile app for the associated
|
* The map of platforms to URLs at which the mobile app for the associated
|
||||||
|
@ -16,14 +16,14 @@ const URLS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React component representing mobile landing page.
|
* React component representing mobile browser page.
|
||||||
*
|
*
|
||||||
* @class Landing
|
* @class MobileBrowserPage
|
||||||
*/
|
*/
|
||||||
class Landing extends Component {
|
class MobileBrowserPage extends Component {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor of Landing component.
|
* Constructor of MobileBrowserPage 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.
|
||||||
|
@ -36,14 +36,14 @@ class Landing extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Landing component's property types.
|
* Mobile browser page component's property types.
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dispatch: React.PropTypes.func,
|
dispatch: React.PropTypes.func,
|
||||||
room: React.PropTypes.string
|
room: React.PropTypes.string
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React lifecycle method triggered after component is mounted.
|
* React lifecycle method triggered after component is mounted.
|
||||||
|
@ -51,7 +51,7 @@ class Landing extends Component {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.dispatch(landingIsShown());
|
this.props.dispatch(mobileBrowserPageIsShown());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,21 +90,25 @@ class Landing extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders landing component.
|
* Renders component.
|
||||||
*
|
*
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { btnText } = this.state;
|
const { btnText } = this.state;
|
||||||
const primaryButtonClasses = 'landing__button landing__button_primary';
|
const blockPrefix = 'mobile-browser-page';
|
||||||
|
const textClasses = `${blockPrefix}__text ${blockPrefix}__text_small`;
|
||||||
|
let primaryButtonClasses = `${blockPrefix}__button`;
|
||||||
|
|
||||||
|
primaryButtonClasses += `${blockPrefix}__button_primary`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className = 'landing'>
|
<div className = { blockPrefix }>
|
||||||
<div className = 'landing__body'>
|
<div className = { `${blockPrefix}__body` }>
|
||||||
<img
|
<img
|
||||||
className = 'landing__logo'
|
className = { `${blockPrefix}__logo` }
|
||||||
src = '/images/logo-blue.svg' />
|
src = '/images/logo-blue.svg' />
|
||||||
<p className = 'landing__text'>
|
<p className = { `${blockPrefix}__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>
|
||||||
|
@ -113,13 +117,13 @@ class Landing extends Component {
|
||||||
Download the App
|
Download the App
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
<p className = 'landing__text landing__text_small'>
|
<p className = { textClasses }>
|
||||||
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 = 'landing__button'
|
className = 'mobile-browser-page__button'
|
||||||
onClick = { this._onClickJoin }>
|
onClick = { this._onClickJoin }>
|
||||||
{ btnText }
|
{ btnText }
|
||||||
</button>
|
</button>
|
||||||
|
@ -130,7 +134,7 @@ class Landing extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps (parts of) the Redux state to the associated Landing's props.
|
* Maps (parts of) the Redux state to the associated MobileBrowserPage's props.
|
||||||
*
|
*
|
||||||
* @param {Object} state - Redux state.
|
* @param {Object} state - Redux state.
|
||||||
* @returns {{
|
* @returns {{
|
||||||
|
@ -143,4 +147,4 @@ function mapStateToProps(state) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps)(Landing);
|
export default connect(mapStateToProps)(MobileBrowserPage);
|
|
@ -0,0 +1,117 @@
|
||||||
|
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'>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
export { default as Landing } from './Landing';
|
export { default as MobileBrowserPage } from './MobileBrowserPage';
|
||||||
|
export { default as UnsupportedBrowserPage } from './UnsupportedBrowserPage';
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import { ReducerRegistry } from '../base/redux';
|
import { ReducerRegistry } from '../base/redux';
|
||||||
|
|
||||||
import { LANDING_IS_SHOWN } from './actionTypes';
|
import { MOBILE_BROWSER_PAGE_IS_SHOWN } from './actionTypes';
|
||||||
|
|
||||||
ReducerRegistry.register(
|
ReducerRegistry.register(
|
||||||
'features/unsupported-browser',
|
'features/unsupported-browser',
|
||||||
(state = {}, action) => {
|
(state = {}, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case LANDING_IS_SHOWN:
|
case MOBILE_BROWSER_PAGE_IS_SHOWN:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag that shows that mobile landing is shown.
|
* Flag that shows that mobile browser page is shown.
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
landingIsShown: true
|
mobileBrowserPageIsShown: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Jitsi Meet: Unsupported Browser</title>
|
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="css/unsupported_browser.css" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<!-- wrap starts here -->
|
|
||||||
<div id="wrap">
|
|
||||||
<div id="text">
|
|
||||||
<p>This application is currently only supported by</p>
|
|
||||||
|
|
||||||
<div class="supported_browsers">
|
|
||||||
<div class="browser_wrapper">
|
|
||||||
Chrome 44+
|
|
||||||
<div class="browser">
|
|
||||||
<div class="logo" id="chrome_logo"></div>
|
|
||||||
<a href="http://google.com/chrome"><div class="button">DOWNLOAD</div></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="browser_wrapper">
|
|
||||||
Chromium 44+
|
|
||||||
<div class="browser">
|
|
||||||
<div class="logo" id="chromium_logo"></div>
|
|
||||||
<a href="http://www.chromium.org/"><div class="button">DOWNLOAD</div></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="browser_wrapper">
|
|
||||||
Opera 32+
|
|
||||||
<div class="browser">
|
|
||||||
<div class="logo" id="opera_logo"></div>
|
|
||||||
<a href="http://www.opera.com"><div class="button">DOWNLOAD</div></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="browser_wrapper">
|
|
||||||
<div class="browser_text">
|
|
||||||
Firefox and Iceweasel 40+</div>
|
|
||||||
<div class="browser">
|
|
||||||
<div class="logo" id="firefox_logo"></div>
|
|
||||||
<a href="http://www.getfirefox.com/"><div class="button">DOWNLOAD</div></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="browser_wrapper">
|
|
||||||
<div class="browser_text">
|
|
||||||
IE <br /> <span style="font-size: small">(Temasys 0.8.854+)</span></div>
|
|
||||||
<div class="browser">
|
|
||||||
<div class="logo" id="ie_logo"></div>
|
|
||||||
<a href="https://temasys.atlassian.net/wiki/display/TWPP/WebRTC+Plugins"><div class="button">DOWNLOAD</div></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="browser_wrapper">
|
|
||||||
<div class="browser_text">
|
|
||||||
Safari <br /> <span style="font-size: small">(Temasys 0.8.854+)</span></div>
|
|
||||||
<div class="browser">
|
|
||||||
<div class="logo" id="safari_logo"></div>
|
|
||||||
<a href="https://temasys.atlassian.net/wiki/display/TWPP/WebRTC+Plugins"><div class="button">DOWNLOAD</div></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clear"></div>
|
|
||||||
</div>
|
|
||||||
<!-- wrap ends here -->
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in New Issue