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;
}
&__button {
float: none !important;
&-filmstrip-only {
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 {
background-color: $primaryButtonBackground;
border: 1px solid $primaryButtonBackground;
@ -86,4 +98,4 @@
&_center {
float: none !important;
}
}
}

View File

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

View File

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

View File

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

View File

@ -15,13 +15,13 @@
"defaultLink": "e.g. __url__",
"callingName": "__name__",
"userMedia": {
"react-nativeGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
"chromeGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
"androidGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
"firefoxGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Share Selected Device</i></b> button",
"operaGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
"iexplorerGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>OK</i></b> button",
"safariGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>OK</i></b> button",
"react-nativeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"chromeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"androidGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"firefoxGrantPermissions": "Select <b><i>Share Selected Device</i></b> when your browser asks for permissions.",
"operaGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"iexplorerGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
"safariGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
"nwjsGrantPermissions": "Please grant permissions to use your camera and microphone"
},
"keyboardShortcuts": {
@ -87,6 +87,7 @@
},
"suspendedoverlay": {
"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"
},
"toolbar": {
@ -229,12 +230,11 @@
"detectext": "Error when trying to detect desktopsharing extension.",
"failtoinstall": "Failed to install desktop sharing extension",
"failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
"conferenceReloadTitle": "Unfortunately, something went wrong",
"conferenceReloadMsg": "We're trying to fix this",
"conferenceDisconnectTitle": "You have been disconnected. You may want to check your network connection.",
"conferenceDisconnectMsg": "Reconnecting in...",
"reconnectNow": "Reconnect now",
"conferenceReloadTimeLeft": "__seconds__ sec.",
"conferenceReloadTitle": "Unfortunately, something went wrong.",
"conferenceReloadMsg": "We're trying to fix this. Reconnecting in __seconds__ sec...",
"conferenceDisconnectTitle": "You have been disconnected.",
"conferenceDisconnectMsg": "You may want to check your network connection. Reconnecting in __seconds__ sec...",
"rejoinNow": "Rejoin now",
"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",
"lockMessage": "Failed to lock the conference.",

View File

@ -1,6 +1,6 @@
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
@ -67,7 +67,14 @@ export default class ConferenceUrl {
* Reloads the conference using original URL with all of the parameters.
*/
reload() {
logger.info("Reloading the conference using URL: " + this.originalURL);
replace(this.originalURL);
logger.info(`Reloading the conference using URL: ${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 { connect } from 'react-redux';
import PageReloadFilmStripOnlyOverlay from './PageReloadFilmStripOnlyOverlay';
import PageReloadOverlay from './PageReloadOverlay';
import SuspendedFilmStripOnlyOverlay from './SuspendedFilmStripOnlyOverlay';
import SuspendedOverlay from './SuspendedOverlay';
import UserMediaPermissionsFilmStripOnlyOverlay
from './UserMediaPermissionsFilmStripOnlyOverlay';
import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay';
declare var APP: Object;
declare var interfaceConfig: Object;
/**
* Implements a React Component that will display the correct overlay when
* needed.
@ -94,6 +99,25 @@ class OverlayContainer extends Component {
_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.
*
@ -117,25 +141,28 @@ class OverlayContainer extends Component {
* @public
*/
render() {
const filmStripOnlyMode = this.state.filmStripOnly;
let overlayComponent, props;
if (this.props._connectionEstablished && this.props._haveToReload) {
return (
<PageReloadOverlay
isNetworkFailure = { this.props._isNetworkFailure }
reason = { this.props._reason } />
);
overlayComponent = filmStripOnlyMode
? PageReloadFilmStripOnlyOverlay : PageReloadOverlay;
props = {
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) {
return (
<SuspendedOverlay />
);
}
if (this.props._isMediaPermissionPromptVisible) {
return (
<UserMediaPermissionsOverlay
browser = { this.props._browser } />
);
if (overlayComponent) {
return React.createElement(overlayComponent, props);
}
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 { translate } from '../../base/i18n';
import { randomInt } from '../../base/util';
import AbstractOverlay from './AbstractOverlay';
import ReloadTimer from './ReloadTimer';
declare var APP: Object;
const logger = require('jitsi-meet-logger').getLogger(__filename);
import AbstractPageReloadOverlay from './AbstractPageReloadOverlay';
import OverlayFrame from './OverlayFrame';
/**
* Implements a React Component for page reload overlay. Shown before the
* conference is reloaded. Shows a warning message and counts down towards the
* reload.
*/
class PageReloadOverlay extends AbstractOverlay {
class PageReloadOverlay extends AbstractPageReloadOverlay {
/**
* PageReloadOverlay 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,
...AbstractPageReloadOverlay.propTypes,
/**
* The reason for the error that will cause the reload.
* NOTE: Used by PageReloadOverlay only.
* The function to translate human-readable text.
*
* @public
* @type {string}
* @type {Function}
*/
reason: React.PropTypes.string
t: React.PropTypes.func
}
/**
* Initializes a new PageReloadOverlay 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 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.
* Implements React's {@link Component#render()}.
*
* @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}
* @private
*/
_renderButton() {
if (this.props.isNetworkFailure) {
const className
= '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 */
render() {
const { isNetworkFailure, t } = this.props;
const { message, timeLeft, title } = this.state;
return (
<div className = 'inlay'>
<span
className = 'reload_overlay_title'>
{ t(this.state.title) }
</span>
<span
className = 'reload_overlay_text'>
{ t(this.state.message) }
</span>
<ReloadTimer
end = { 0 }
interval = { 1 }
onFinish = { this._reconnectNow }
start = { this.state.timeoutSeconds }
step = { -1 } />
{ this._renderButton() }
</div>
<OverlayFrame isLightOverlay = { isNetworkFailure }>
<div className = 'inlay'>
<span
className = 'reload_overlay_title'>
{ t(title) }
</span>
<span className = 'reload_overlay_text'>
{ t(message, { seconds: timeLeft }) }
</span>
{ this._renderProgressBar() }
{ this._renderButton() }
</div>
</OverlayFrame>
);
/* 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
* 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}
* @override
* @protected
* @static
*/
_renderOverlayContent() {
const btnClass = 'inlay__button button-control button-control_primary';
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;
/* eslint-disable react/jsx-handler-names */
return (
<div className = 'inlay'>
<span className = 'inlay__icon icon-microphone' />
<span className = 'inlay__icon icon-camera' />
<h3
className = 'inlay__title'>
{ t('suspendedoverlay.title') }
</h3>
<button
className = { btnClass }
onClick = { this._reconnectNow }>
{ t('suspendedoverlay.rejoinKeyTitle') }
</button>
</div>
<OverlayFrame>
<div className = 'inlay'>
<span className = 'inlay__icon icon-microphone' />
<span className = 'inlay__icon icon-camera' />
<h3
className = 'inlay__title'>
{ t('suspendedoverlay.title') }
</h3>
<span className = 'inlay__text'>
{
translateToHTML(t, 'suspendedoverlay.title')
}
</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 */
import React from 'react';
import React, { Component } from 'react';
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
* gUM prompt.
*/
class UserMediaPermissionsOverlay extends AbstractOverlay {
class UserMediaPermissionsOverlay extends Component {
/**
* UserMediaPermissionsOverlay component's property types.
*
@ -24,7 +24,15 @@ class UserMediaPermissionsOverlay extends AbstractOverlay {
* @public
* @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
* with gUM prompt.
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement|null}
* @override
* @protected
*/
_renderOverlayContent() {
render() {
const { browser, t } = this.props;
return (
<div>
<OverlayFrame>
<div className = 'inlay'>
<span className = 'inlay__icon icon-microphone' />
<span className = 'inlay__icon icon-camera' />
<h3 className = 'inlay__title'>
{
t(
'startupoverlay.title',
t('startupoverlay.title',
{ postProcess: 'resolveAppName' })
}
</h3>
<span className = 'inlay__text'>
{
translateToHTML(
t,
translateToHTML(t,
`userMedia.${browser}GrantPermissions`)
}
</span>
</div>
<div className = 'policy overlay__policy'>
<p className = 'policy__text'>
{ t('startupoverlay.policyText') }
{ translateToHTML(t, 'startupoverlay.policyText') }
</p>
{
this._renderPolicyLogo()
}
</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();
}