Merge pull request #1393 from jitsi/filmstrip_overlays

Filmstrip overlays
This commit is contained in:
yanas 2017-03-27 14:54:45 -05:00 committed by GitHub
commit e8068cf5ac
20 changed files with 929 additions and 323 deletions

View File

@ -27,7 +27,86 @@
font-size: 50px; font-size: 50px;
} }
&__button { &-filmstrip-only {
float: none !important; background-color: $inlayFilmstripOnlyBg;
color: $inlayFilmstripOnlyColor;
margin-left: 20px;
margin-right: 20px;
margin-top: 20px;
bottom: 30px;
position: absolute;
display: flex;
max-height: 120px;
height: 80%;
right: 0px;
border-radius: 4px;
overflow: hidden;
&__content {
padding: 20px;
display: flex;
justify-content: center;
position: relative;
> .button-control {
align-self: center;
}
> #reloadProgressBar {
position: absolute;
left: 0px;
bottom: 0px;
margin-bottom: 0px;
width: 100%;
border-radius: 0px;
> .aui-progress-indicator-value {
border-radius: 0px;
}
}
}
&__title {
font-size: 18px;
font-weight: 600;
}
&__container {
align-self: center;
}
&__text {
margin-top: 10px;
font-size: 14px;
font-weight: 600;
}
&__icon {
font-size: 50px;
align-self: center;
color: $inlayIconColor;
opacity: 0.6;
}
&__icon-container {
text-align: center;
display: flex;
justify-content: center;
position: absolute;
width: 100%;
height: 100%;
top: 0px;
}
&__avatar-container {
position: relative;
> img {
height: 100%;
}
}
&__icon-background {
background: $inlayIconBg;
opacity: 0.6;
position: absolute;
width: 100%;
height: 100%;
top: 0px;
}
} }
}
}

View File

@ -57,6 +57,18 @@
} }
} }
&_overlay {
color: $primaryButtonColor;
background-color: $overlayButtonBg;
border-radius: 2px;
border: none;
&:hover {
background-color: $primaryButtonBackground;
border: none;
}
}
&_primary { &_primary {
background-color: $primaryButtonBackground; background-color: $primaryButtonBackground;
border: 1px solid $primaryButtonBackground; border: 1px solid $primaryButtonBackground;
@ -86,4 +98,4 @@
&_center { &_center {
float: none !important; float: none !important;
} }
} }

View File

@ -8,10 +8,16 @@
position: fixed; position: fixed;
z-index: $overlayZ; z-index: $overlayZ;
background: $defaultBackground; background: $defaultBackground;
&.filmstrip-only {
@include transparentBg($filmStripOnlyOverlayBg, 0.8);
}
} }
&__container-light { &__container-light {
@include transparentBg($defaultBackground, 0.7); @include transparentBg($defaultBackground, 0.7);
&.filmstrip-only {
@include transparentBg($filmStripOnlyOverlayBg, 0.2);
}
} }
&__content { &__content {
@ -21,6 +27,11 @@
width: 56%; width: 56%;
left: 50%; left: 50%;
@include transform(translateX(-50%)); @include transform(translateX(-50%));
&.filmstrip-only {
left: 0px;
width: 100%;
@include transform(none);
}
&_bottom { &_bottom {
position: absolute; position: absolute;
@ -33,4 +44,4 @@
bottom: 24px; bottom: 24px;
width: 100%; width: 100%;
} }
} }

View File

@ -13,4 +13,7 @@
#reloadProgressBar { #reloadProgressBar {
width: 180px; width: 180px;
margin: 5px auto; margin: 5px auto;
> .aui-progress-indicator-value {
background: $reloadProgressBarBg;
}
} }

View File

@ -35,10 +35,14 @@ $primaryButtonFontWeight: 400;
$buttonShadowColor: #192d4f; $buttonShadowColor: #192d4f;
$overlayButtonBg: #0074E0;
/** /**
* Color variables * Color variables
**/ **/
$defaultBackground: #474747; $defaultBackground: #474747;
$filmStripOnlyOverlayBg: #000;
$reloadProgressBarBg: #0074E0;
/** /**
* Connection indicator * Connection indicator
@ -60,6 +64,10 @@ $dialogTitleFontWeight: 400;
**/ **/
$inlayColorBg: lighten($defaultBackground, 20%); $inlayColorBg: lighten($defaultBackground, 20%);
$inlayBorderColor: lighten($auiDialogContentBg, 10%); $inlayBorderColor: lighten($auiDialogContentBg, 10%);
$inlayIconBg: #000;
$inlayIconColor: #fff;
$inlayFilmstripOnlyColor: #474747;
$inlayFilmstripOnlyBg: #fff;
// Main controls // Main controls
$inputBackground: $controlBackground; $inputBackground: $controlBackground;

View File

@ -15,13 +15,13 @@
"defaultLink": "e.g. __url__", "defaultLink": "e.g. __url__",
"callingName": "__name__", "callingName": "__name__",
"userMedia": { "userMedia": {
"react-nativeGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button", "react-nativeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"chromeGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button", "chromeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"androidGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button", "androidGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"firefoxGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Share Selected Device</i></b> button", "firefoxGrantPermissions": "Select <b><i>Share Selected Device</i></b> when your browser asks for permissions.",
"operaGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button", "operaGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"iexplorerGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>OK</i></b> button", "iexplorerGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
"safariGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>OK</i></b> button", "safariGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
"nwjsGrantPermissions": "Please grant permissions to use your camera and microphone" "nwjsGrantPermissions": "Please grant permissions to use your camera and microphone"
}, },
"keyboardShortcuts": { "keyboardShortcuts": {
@ -87,6 +87,7 @@
}, },
"suspendedoverlay": { "suspendedoverlay": {
"title": "Your video call was interrupted, because this computer went to sleep.", "title": "Your video call was interrupted, because this computer went to sleep.",
"text": "Press <i>Rejoin</i> button to connect back to your conversation.",
"rejoinKeyTitle": "Rejoin" "rejoinKeyTitle": "Rejoin"
}, },
"toolbar": { "toolbar": {
@ -229,12 +230,11 @@
"detectext": "Error when trying to detect desktopsharing extension.", "detectext": "Error when trying to detect desktopsharing extension.",
"failtoinstall": "Failed to install desktop sharing extension", "failtoinstall": "Failed to install desktop sharing extension",
"failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.", "failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
"conferenceReloadTitle": "Unfortunately, something went wrong", "conferenceReloadTitle": "Unfortunately, something went wrong.",
"conferenceReloadMsg": "We're trying to fix this", "conferenceReloadMsg": "We're trying to fix this. Reconnecting in __seconds__ sec...",
"conferenceDisconnectTitle": "You have been disconnected. You may want to check your network connection.", "conferenceDisconnectTitle": "You have been disconnected.",
"conferenceDisconnectMsg": "Reconnecting in...", "conferenceDisconnectMsg": "You may want to check your network connection. Reconnecting in __seconds__ sec...",
"reconnectNow": "Reconnect now", "rejoinNow": "Rejoin now",
"conferenceReloadTimeLeft": "__seconds__ sec.",
"maxUsersLimitReached": "The limit for maximum number of participants in the conference has been reached. The conference is full. Please try again later!", "maxUsersLimitReached": "The limit for maximum number of participants in the conference has been reached. The conference is full. Please try again later!",
"lockTitle": "Lock failed", "lockTitle": "Lock failed",
"lockMessage": "Failed to lock the conference.", "lockMessage": "Failed to lock the conference.",

View File

@ -1,6 +1,6 @@
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require("jitsi-meet-logger").getLogger(__filename);
import { replace } from '../util/helpers'; import { reload, replace } from '../util/helpers';
/** /**
* The modules stores information about the URL used to start the conference and * The modules stores information about the URL used to start the conference and
@ -67,7 +67,14 @@ export default class ConferenceUrl {
* Reloads the conference using original URL with all of the parameters. * Reloads the conference using original URL with all of the parameters.
*/ */
reload() { reload() {
logger.info("Reloading the conference using URL: " + this.originalURL); logger.info(`Reloading the conference using URL: ${this.originalURL}`);
replace(this.originalURL);
// Check if we are in an iframe and reload with the reload() utility
// because replace() is not working on an iframe.
if(window.self !== window.top) {
reload();
} else {
replace(this.originalURL);
}
} }
} }

View File

@ -1,80 +0,0 @@
/* global APP */
import React, { Component } from 'react';
/**
* Implements an abstract React Component for overlay - the components which are
* displayed on top of the application covering the whole screen.
*
* @abstract
*/
export default class AbstractOverlay extends Component {
/**
* Initializes a new AbstractOverlay instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
* @public
*/
constructor(props) {
super(props);
this.state = {
/**
* Indicates the CSS style of the overlay. If true, then ighter;
* darker, otherwise.
*
* @type {boolean}
*/
isLightOverlay: false
};
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement|null}
*/
render() {
const containerClass
= this.state.isLightOverlay
? 'overlay__container-light'
: 'overlay__container';
return (
<div
className = { containerClass }
id = 'overlay'>
<div className = 'overlay__content'>
{
this._renderOverlayContent()
}
</div>
</div>
);
}
/**
* Reloads the page.
*
* @returns {void}
* @protected
*/
_reconnectNow() {
// FIXME: In future we should dispatch an action here that will result
// in reload.
APP.ConferenceUrl.reload();
}
/**
* Abstract method which should be used by subclasses to provide the overlay
* content.
*
* @returns {ReactElement|null}
* @protected
*/
_renderOverlayContent() {
return null;
}
}

View File

@ -0,0 +1,192 @@
import React, { Component } from 'react';
import { randomInt } from '../../base/util';
import { reconnectNow } from '../functions';
import ReloadButton from './ReloadButton';
declare var AJS: Object;
declare var APP: Object;
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* Implements abstract React Component for the page reload overlays.
*/
export default class AbstractPageReloadOverlay extends Component {
/**
* AbstractPageReloadOverlay component's property types.
*
* @static
*/
static propTypes = {
/**
* The indicator which determines whether the reload was caused by
* network failure.
*
* @public
* @type {boolean}
*/
isNetworkFailure: React.PropTypes.bool,
/**
* The reason for the error that will cause the reload.
* NOTE: Used by PageReloadOverlay only.
*
* @public
* @type {string}
*/
reason: React.PropTypes.string
}
/**
* Initializes a new AbstractPageReloadOverlay instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
* @public
*/
constructor(props) {
super(props);
/**
* How long the overlay dialog will be displayed, before the conference
* will be reloaded.
*
* @type {number}
*/
const timeoutSeconds = 10 + randomInt(0, 20);
let message, title;
if (this.props.isNetworkFailure) {
title = 'dialog.conferenceDisconnectTitle';
message = 'dialog.conferenceDisconnectMsg';
} else {
title = 'dialog.conferenceReloadTitle';
message = 'dialog.conferenceReloadMsg';
}
this.state = {
/**
* The translation key for the title of the overlay.
*
* @type {string}
*/
message,
/**
* Current value(time) of the timer.
*
* @type {number}
*/
timeLeft: timeoutSeconds,
/**
* How long the overlay dialog will be displayed before the
* conference will be reloaded.
*
* @type {number}
*/
timeoutSeconds,
/**
* The translation key for the title of the overlay.
*
* @type {string}
*/
title
};
}
/**
* Renders the button for relaod the page if necessary.
*
* @returns {ReactElement|null}
* @private
*/
_renderButton() {
if (this.props.isNetworkFailure) {
return (
<ReloadButton textKey = 'dialog.rejoinNow' />
);
}
return null;
}
/**
* Renders the progress bar.
*
* @returns {ReactElement|null}
* @protected
*/
_renderProgressBar() {
return (
<div
className = 'aui-progress-indicator'
id = 'reloadProgressBar'>
<span className = 'aui-progress-indicator-value' />
</div>
);
}
/**
* React Component method that executes once component is mounted.
*
* @inheritdoc
* @returns {void}
* @protected
*/
componentDidMount() {
// FIXME (CallStats - issue) This event will not make it to CallStats
// because the log queue is not flushed before "fabric terminated" is
// sent to the backed.
// FIXME: We should dispatch action for this.
APP.conference.logEvent(
'page.reload',
/* value */ undefined,
/* label */ this.props.reason);
logger.info(
'The conference will be reloaded after '
+ `${this.state.timeoutSeconds} seconds.`);
AJS.progressBars.update('#reloadProgressBar', 0);
this.intervalId = setInterval(() => {
if (this.state.timeLeft === 0) {
clearInterval(this.intervalId);
reconnectNow();
} else {
this.setState(prevState => {
return {
timeLeft: prevState.timeLeft - 1
};
});
}
}, 1000);
}
/**
* React Component method that executes once component is updated.
*
* @inheritdoc
* @returns {void}
* @protected
*/
componentDidUpdate() {
AJS.progressBars.update('#reloadProgressBar',
(this.state.timeoutSeconds - this.state.timeLeft)
/ this.state.timeoutSeconds);
}
/**
* Clears the timer interval.
*
* @inheritdoc
* @returns {void}
*/
componentWillUnmount() {
clearInterval(this.intervalId);
}
}

View File

@ -0,0 +1,133 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
Avatar,
getAvatarURL,
getLocalParticipant
} from '../../base/participants';
import OverlayFrame from './OverlayFrame';
/**
* Implements a React Component for the frame of the overlays in filmstrip only
* mode.
*/
class FilmStripOnlyOverlayFrame extends Component {
/**
* FilmStripOnlyOverlayFrame component's property types.
*
* @static
*/
static propTypes = {
/**
* The source (e.g. URI, URL) of the avatar image of the local
* participant.
*
* @private
*/
_avatar: React.PropTypes.string,
/**
* The children components to be displayed into the overlay frame for
* filmstrip only mode.
*
* @type {ReactElement}
*/
children: React.PropTypes.node.isRequired,
/**
* The css class name for the icon that will be displayed over the
* avatar.
*
* @type {string}
*/
icon: React.PropTypes.string,
/**
* Indicates the css style of the overlay. If true, then lighter;
* darker, otherwise.
*
* @type {boolean}
*/
isLightOverlay: React.PropTypes.bool
}
/**
* Renders content related to the icon.
*
* @returns {ReactElement|null}
* @private
*/
_renderIcon() {
if (!this.props.icon) {
return null;
}
const iconClass = `inlay-filmstrip-only__icon ${this.props.icon}`;
const iconBGClass = 'inlay-filmstrip-only__icon-background';
return (
<div>
<div className = { iconBGClass } />
<div className = 'inlay-filmstrip-only__icon-container'>
<span className = { iconClass } />
</div>
</div>
);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement|null}
*/
render() {
return (
<OverlayFrame isLightOverlay = { this.props.isLightOverlay }>
<div className = 'inlay-filmstrip-only'>
<div className = 'inlay-filmstrip-only__content'>
{
this.props.children
}
</div>
<div className = 'inlay-filmstrip-only__avatar-container'>
<Avatar uri = { this.props._avatar } />
{
this._renderIcon()
}
</div>
</div>
</OverlayFrame>
);
}
}
/**
* Maps (parts of) the Redux state to the associated FilmStripOnlyOverlayFrame
* props.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _avatar: string
* }}
*/
function _mapStateToProps(state) {
const participant
= getLocalParticipant(
state['features/base/participants']);
const { avatarId, avatarUrl, email } = participant || {};
return {
_avatar: getAvatarURL({
avatarId,
avatarUrl,
email,
participantId: participant.id
})
};
}
export default connect(_mapStateToProps)(FilmStripOnlyOverlayFrame);

View File

@ -1,12 +1,17 @@
/* global APP */
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PageReloadFilmStripOnlyOverlay from './PageReloadFilmStripOnlyOverlay';
import PageReloadOverlay from './PageReloadOverlay'; import PageReloadOverlay from './PageReloadOverlay';
import SuspendedFilmStripOnlyOverlay from './SuspendedFilmStripOnlyOverlay';
import SuspendedOverlay from './SuspendedOverlay'; import SuspendedOverlay from './SuspendedOverlay';
import UserMediaPermissionsFilmStripOnlyOverlay
from './UserMediaPermissionsFilmStripOnlyOverlay';
import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay'; import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay';
declare var APP: Object;
declare var interfaceConfig: Object;
/** /**
* Implements a React Component that will display the correct overlay when * Implements a React Component that will display the correct overlay when
* needed. * needed.
@ -94,6 +99,25 @@ class OverlayContainer extends Component {
_suspendDetected: React.PropTypes.bool _suspendDetected: React.PropTypes.bool
} }
/**
* Initializes a new ReloadTimer instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
* @public
*/
constructor(props) {
super(props);
this.state = {
/**
* Indicates whether the film strip only mode is enabled or not.
*
* @type {boolean}
*/
filmStripOnly: interfaceConfig.filmStripOnly
};
}
/** /**
* React Component method that executes once component is updated. * React Component method that executes once component is updated.
* *
@ -117,25 +141,28 @@ class OverlayContainer extends Component {
* @public * @public
*/ */
render() { render() {
const filmStripOnlyMode = this.state.filmStripOnly;
let overlayComponent, props;
if (this.props._connectionEstablished && this.props._haveToReload) { if (this.props._connectionEstablished && this.props._haveToReload) {
return ( overlayComponent = filmStripOnlyMode
<PageReloadOverlay ? PageReloadFilmStripOnlyOverlay : PageReloadOverlay;
isNetworkFailure = { this.props._isNetworkFailure } props = {
reason = { this.props._reason } /> isNetworkFailure: this.props._isNetworkFailure,
); reason: this.props._reason
};
} else if (this.props._suspendDetected) {
overlayComponent = filmStripOnlyMode
? SuspendedFilmStripOnlyOverlay : SuspendedOverlay;
} else if (this.props._isMediaPermissionPromptVisible) {
overlayComponent = filmStripOnlyMode
? UserMediaPermissionsFilmStripOnlyOverlay
: UserMediaPermissionsOverlay;
props = { browser: this.props._browser };
} }
if (this.props._suspendDetected) { if (overlayComponent) {
return ( return React.createElement(overlayComponent, props);
<SuspendedOverlay />
);
}
if (this.props._isMediaPermissionPromptVisible) {
return (
<UserMediaPermissionsOverlay
browser = { this.props._browser } />
);
} }
return null; return null;

View File

@ -0,0 +1,77 @@
import React, { Component } from 'react';
declare var interfaceConfig: Object;
/**
* Implements a React Component for the frame of the overlays.
*/
export default class OverlayFrame extends Component {
/**
* OverlayFrame component's property types.
*
* @static
*/
static propTypes = {
/**
* The children components to be displayed into the overlay frame.
*/
children: React.PropTypes.node.isRequired,
/**
* Indicates the css style of the overlay. If true, then lighter;
* darker, otherwise.
*
* @type {boolean}
*/
isLightOverlay: React.PropTypes.bool
}
/**
* Initializes a new AbstractOverlay instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
* @public
*/
constructor(props) {
super(props);
this.state = {
/**
* Indicates whether the film strip only mode is enabled or not.
*
* @type {boolean}
*/
filmStripOnly: interfaceConfig.filmStripOnly
};
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement|null}
*/
render() {
let containerClass = this.props.isLightOverlay
? 'overlay__container-light' : 'overlay__container';
let contentClass = 'overlay__content';
if (this.state.filmStripOnly) {
containerClass += ' filmstrip-only';
contentClass += ' filmstrip-only';
}
return (
<div
className = { containerClass }
id = 'overlay'>
<div className = { contentClass }>
{
this.props.children
}
</div>
</div>
);
}
}

View File

@ -0,0 +1,62 @@
import React from 'react';
import { translate } from '../../base/i18n';
import AbstractPageReloadOverlay from './AbstractPageReloadOverlay';
import FilmStripOnlyOverlayFrame from './FilmStripOnlyOverlayFrame';
/**
* Implements a React Component for page reload overlay for filmstrip only
* mode. Shown before the conference is reloaded. Shows a warning message and
* counts down towards the reload.
*/
class PageReloadFilmStripOnlyOverlay extends AbstractPageReloadOverlay {
/**
* PageReloadFilmStripOnlyOverlay component's property types.
*
* @static
*/
static propTypes = {
...AbstractPageReloadOverlay.propTypes,
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement|null}
*/
render() {
const { t } = this.props;
const { message, timeLeft, title } = this.state;
return (
<FilmStripOnlyOverlayFrame>
<div className = 'inlay-filmstrip-only__container'>
<div className = 'inlay-filmstrip-only__title'>
{ t(title) }
</div>
<div className = 'inlay-filmstrip-only__text'>
{ t(message, { seconds: timeLeft }) }
</div>
</div>
{
this._renderButton()
}
{
this._renderProgressBar()
}
</FilmStripOnlyOverlayFrame>
);
}
}
export default translate(PageReloadFilmStripOnlyOverlay);

View File

@ -1,194 +1,58 @@
import React from 'react'; import React from 'react';
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n';
import { randomInt } from '../../base/util';
import AbstractOverlay from './AbstractOverlay'; import AbstractPageReloadOverlay from './AbstractPageReloadOverlay';
import ReloadTimer from './ReloadTimer'; import OverlayFrame from './OverlayFrame';
declare var APP: Object;
const logger = require('jitsi-meet-logger').getLogger(__filename);
/** /**
* Implements a React Component for page reload overlay. Shown before the * Implements a React Component for page reload overlay. Shown before the
* conference is reloaded. Shows a warning message and counts down towards the * conference is reloaded. Shows a warning message and counts down towards the
* reload. * reload.
*/ */
class PageReloadOverlay extends AbstractOverlay { class PageReloadOverlay extends AbstractPageReloadOverlay {
/** /**
* PageReloadOverlay component's property types. * PageReloadOverlay component's property types.
* *
* @static * @static
*/ */
static propTypes = { static propTypes = {
/** ...AbstractPageReloadOverlay.propTypes,
* The indicator which determines whether the reload was caused by
* network failure.
* @public
* @type {boolean}
*/
isNetworkFailure: React.PropTypes.bool,
/** /**
* The reason for the error that will cause the reload. * The function to translate human-readable text.
* NOTE: Used by PageReloadOverlay only. *
* @public * @public
* @type {string} * @type {Function}
*/ */
reason: React.PropTypes.string t: React.PropTypes.func
} }
/** /**
* Initializes a new PageReloadOverlay instance. * Implements React's {@link Component#render()}.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
* @public
*/
constructor(props) {
super(props);
/**
* How long the overlay dialog will be displayed, before the conference
* will be reloaded.
* @type {number}
*/
const timeoutSeconds = 10 + randomInt(0, 20);
let isLightOverlay, message, title;
if (this.props.isNetworkFailure) {
title = 'dialog.conferenceDisconnectTitle';
message = 'dialog.conferenceDisconnectMsg';
isLightOverlay = true;
} else {
title = 'dialog.conferenceReloadTitle';
message = 'dialog.conferenceReloadMsg';
isLightOverlay = false;
}
this.state = {
...this.state,
/**
* Indicates the css style of the overlay. If true, then lighter;
* darker, otherwise.
*
* @type {boolean}
*/
isLightOverlay,
/**
* The translation key for the title of the overlay.
*
* @type {string}
*/
message,
/**
* How long the overlay dialog will be displayed before the
* conference will be reloaded.
*
* @type {number}
*/
timeoutSeconds,
/**
* The translation key for the title of the overlay.
*
* @type {string}
*/
title
};
}
/**
* This method is executed when comonent is mounted.
* *
* @inheritdoc * @inheritdoc
* @returns {void}
*/
componentDidMount() {
super.componentDidMount();
// FIXME (CallStats - issue) This event will not make it to CallStats
// because the log queue is not flushed before "fabric terminated" is
// sent to the backed.
// FIXME: We should dispatch action for this.
APP.conference.logEvent(
'page.reload',
/* value */ undefined,
/* label */ this.props.reason);
logger.info(
'The conference will be reloaded after '
+ `${this.state.timeoutSeconds} seconds.`);
}
/**
* Renders the button for relaod the page if necessary.
*
* @returns {ReactElement|null} * @returns {ReactElement|null}
* @private
*/ */
_renderButton() { render() {
if (this.props.isNetworkFailure) { const { isNetworkFailure, t } = this.props;
const className const { message, timeLeft, title } = this.state;
= 'button-control button-control_primary button-control_center';
const { t } = this.props;
/* eslint-disable react/jsx-handler-names */
return (
<button
className = { className }
id = 'reconnectNow'
onClick = { this._reconnectNow }>
{ t('dialog.reconnectNow') }
</button>
);
/* eslint-enable react/jsx-handler-names */
}
return null;
}
/**
* Constructs overlay body with the warning message and count down towards
* the conference reload.
*
* @returns {ReactElement|null}
* @override
* @protected
*/
_renderOverlayContent() {
const { t } = this.props;
/* eslint-disable react/jsx-handler-names */
return ( return (
<div className = 'inlay'> <OverlayFrame isLightOverlay = { isNetworkFailure }>
<span <div className = 'inlay'>
className = 'reload_overlay_title'> <span
{ t(this.state.title) } className = 'reload_overlay_title'>
</span> { t(title) }
<span </span>
className = 'reload_overlay_text'> <span className = 'reload_overlay_text'>
{ t(this.state.message) } { t(message, { seconds: timeLeft }) }
</span> </span>
<ReloadTimer { this._renderProgressBar() }
end = { 0 } { this._renderButton() }
interval = { 1 } </div>
onFinish = { this._reconnectNow } </OverlayFrame>
start = { this.state.timeoutSeconds }
step = { -1 } />
{ this._renderButton() }
</div>
); );
/* eslint-enable react/jsx-handler-names */
} }
} }

View File

@ -0,0 +1,60 @@
import React, { Component } from 'react';
import { translate } from '../../base/i18n';
import { reconnectNow } from '../functions';
/**
* Implements a React Component for button for the overlays that will reload
* the page.
*/
class ReloadButton extends Component {
/**
* PageReloadOverlay component's property types.
*
* @static
*/
static propTypes = {
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func,
/**
* The translation key for the text in the button.
*
* @type {string}
*/
textKey: React.PropTypes.string.isRequired
}
/**
* Renders the button for relaod the page if necessary.
*
* @returns {ReactElement|null}
* @private
*/
render() {
const className
= 'button-control button-control_overlay button-control_center';
const { t } = this.props;
/* eslint-disable react/jsx-handler-names */
return (
<button
className = { className }
onClick = { reconnectNow }>
{ t(this.props.textKey) }
</button>
);
/* eslint-enable react/jsx-handler-names */
}
}
export default translate(ReloadButton);

View File

@ -0,0 +1,53 @@
import React, { Component } from 'react';
import { translate, translateToHTML } from '../../base/i18n';
import FilmStripOnlyOverlayFrame from './FilmStripOnlyOverlayFrame';
import ReloadButton from './ReloadButton';
/**
* Implements a React Component for suspended overlay for filmstrip only mode.
* Shown when suspended is detected.
*/
class SuspendedFilmStripOnlyOverlay extends Component {
/**
* SuspendedFilmStripOnlyOverlay component's property types.
*
* @static
*/
static propTypes = {
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement|null}
*/
render() {
const { t } = this.props;
return (
<FilmStripOnlyOverlayFrame isLightOverlay = { true }>
<div className = 'inlay-filmstrip-only__container'>
<div className = 'inlay-filmstrip-only__title'>
{ t('suspendedoverlay.title') }
</div>
<div className = 'inlay-filmstrip-only__text'>
{ translateToHTML(t, 'suspendedoverlay.text') }
</div>
</div>
<ReloadButton textKey = 'suspendedoverlay.rejoinKeyTitle' />
</FilmStripOnlyOverlayFrame>
);
}
}
export default translate(SuspendedFilmStripOnlyOverlay);

View File

@ -1,44 +1,58 @@
import React from 'react'; import React, { Component } from 'react';
import { translate } from '../../base/i18n'; import { translate, translateToHTML } from '../../base/i18n';
import AbstractOverlay from './AbstractOverlay'; import OverlayFrame from './OverlayFrame';
import ReloadButton from './ReloadButton';
/** /**
* Implements a React Component for suspended overlay. Shown when a suspend is * Implements a React Component for suspended overlay. Shown when a suspend is
* detected. * detected.
*/ */
class SuspendedOverlay extends AbstractOverlay { class SuspendedOverlay extends Component {
/** /**
* Constructs overlay body with the message and a button to rejoin. * SuspendedOverlay component's property types.
* *
* @returns {ReactElement|null} * @static
* @override
* @protected
*/ */
_renderOverlayContent() { static propTypes = {
const btnClass = 'inlay__button button-control button-control_primary'; /**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement|null}
*/
render() {
const { t } = this.props; const { t } = this.props;
/* eslint-disable react/jsx-handler-names */
return ( return (
<div className = 'inlay'> <OverlayFrame>
<span className = 'inlay__icon icon-microphone' /> <div className = 'inlay'>
<span className = 'inlay__icon icon-camera' /> <span className = 'inlay__icon icon-microphone' />
<h3 <span className = 'inlay__icon icon-camera' />
className = 'inlay__title'> <h3
{ t('suspendedoverlay.title') } className = 'inlay__title'>
</h3> { t('suspendedoverlay.title') }
<button </h3>
className = { btnClass } <span className = 'inlay__text'>
onClick = { this._reconnectNow }> {
{ t('suspendedoverlay.rejoinKeyTitle') } translateToHTML(t, 'suspendedoverlay.title')
</button> }
</div> </span>
<ReloadButton
textKey = 'suspendedoverlay.rejoinKeyTitle' />
</div>
</OverlayFrame>
); );
/* eslint-enable react/jsx-handler-names */
} }
} }

View File

@ -0,0 +1,68 @@
import React, { Component } from 'react';
import { translate, translateToHTML } from '../../base/i18n';
import FilmStripOnlyOverlayFrame from './FilmStripOnlyOverlayFrame';
/**
* Implements a React Component for overlay with guidance how to proceed with
* gUM prompt. This component will be displayed only for filmstrip only mode.
*/
class UserMediaPermissionsFilmStripOnlyOverlay extends Component {
/**
* UserMediaPermissionsFilmStripOnlyOverlay component's property types.
*
* @static
*/
static propTypes = {
/**
* The browser which is used currently. The text is different for every
* browser.
*
* @public
* @type {string}
*/
browser: React.PropTypes.string,
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement|null}
*/
render() {
const { t } = this.props;
const textKey = `userMedia.${this.props.browser}GrantPermissions`;
return (
<FilmStripOnlyOverlayFrame
icon = 'icon-mic-camera-combined'
isLightOverlay = { true }>
<div className = 'inlay-filmstrip-only__container'>
<div className = 'inlay-filmstrip-only__title'>
{
t('startupoverlay.title',
{ postProcess: 'resolveAppName' })
}
</div>
<div className = 'inlay-filmstrip-only__text'>
{
translateToHTML(t, textKey)
}
</div>
</div>
</FilmStripOnlyOverlayFrame>
);
}
}
export default translate(UserMediaPermissionsFilmStripOnlyOverlay);

View File

@ -1,16 +1,16 @@
/* global interfaceConfig */ /* global interfaceConfig */
import React from 'react'; import React, { Component } from 'react';
import { translate, translateToHTML } from '../../base/i18n'; import { translate, translateToHTML } from '../../base/i18n';
import AbstractOverlay from './AbstractOverlay'; import OverlayFrame from './OverlayFrame';
/** /**
* Implements a React Component for overlay with guidance how to proceed with * Implements a React Component for overlay with guidance how to proceed with
* gUM prompt. * gUM prompt.
*/ */
class UserMediaPermissionsOverlay extends AbstractOverlay { class UserMediaPermissionsOverlay extends Component {
/** /**
* UserMediaPermissionsOverlay component's property types. * UserMediaPermissionsOverlay component's property types.
* *
@ -24,7 +24,15 @@ class UserMediaPermissionsOverlay extends AbstractOverlay {
* @public * @public
* @type {string} * @type {string}
*/ */
browser: React.PropTypes.string browser: React.PropTypes.string,
/**
* The function to translate human-readable text.
*
* @public
* @type {Function}
*/
t: React.PropTypes.func
} }
/** /**
@ -48,45 +56,41 @@ class UserMediaPermissionsOverlay extends AbstractOverlay {
} }
/** /**
* Constructs overlay body with the message with guidance how to proceed * Implements React's {@link Component#render()}.
* with gUM prompt.
* *
* @inheritdoc
* @returns {ReactElement|null} * @returns {ReactElement|null}
* @override
* @protected
*/ */
_renderOverlayContent() { render() {
const { browser, t } = this.props; const { browser, t } = this.props;
return ( return (
<div> <OverlayFrame>
<div className = 'inlay'> <div className = 'inlay'>
<span className = 'inlay__icon icon-microphone' /> <span className = 'inlay__icon icon-microphone' />
<span className = 'inlay__icon icon-camera' /> <span className = 'inlay__icon icon-camera' />
<h3 className = 'inlay__title'> <h3 className = 'inlay__title'>
{ {
t( t('startupoverlay.title',
'startupoverlay.title',
{ postProcess: 'resolveAppName' }) { postProcess: 'resolveAppName' })
} }
</h3> </h3>
<span className = 'inlay__text'> <span className = 'inlay__text'>
{ {
translateToHTML( translateToHTML(t,
t,
`userMedia.${browser}GrantPermissions`) `userMedia.${browser}GrantPermissions`)
} }
</span> </span>
</div> </div>
<div className = 'policy overlay__policy'> <div className = 'policy overlay__policy'>
<p className = 'policy__text'> <p className = 'policy__text'>
{ t('startupoverlay.policyText') } { translateToHTML(t, 'startupoverlay.policyText') }
</p> </p>
{ {
this._renderPolicyLogo() this._renderPolicyLogo()
} }
</div> </div>
</div> </OverlayFrame>
); );
} }

View File

@ -0,0 +1,12 @@
/* global APP */
/**
* Reloads the page.
*
* @returns {void}
* @protected
*/
export function reconnectNow() {
// FIXME: In future we should dispatch an action here that will result
// in reload.
APP.ConferenceUrl.reload();
}