mirror of https://github.com/jitsi/jitsi-meet
parent
f3269070b2
commit
92d0589a37
@ -1,90 +0,0 @@ |
||||
/* global interfaceConfig */ |
||||
|
||||
import Overlay from '../overlay/Overlay'; |
||||
|
||||
/** |
||||
* An overlay with guidance how to proceed with gUM prompt. |
||||
*/ |
||||
class GUMOverlayImpl extends Overlay { |
||||
|
||||
/** |
||||
* Constructs overlay with guidance how to proceed with gUM prompt. |
||||
* @param {string} browser - name of browser for which to construct the |
||||
* guidance overlay. |
||||
* @override |
||||
*/ |
||||
constructor(browser) { |
||||
super(); |
||||
this.browser = browser; |
||||
} |
||||
|
||||
/** |
||||
* @inheritDoc |
||||
*/ |
||||
_buildOverlayContent() { |
||||
let textKey = `userMedia.${this.browser}GrantPermissions`; |
||||
let titleKey = 'startupoverlay.title'; |
||||
let titleOptions = '{ "postProcess": "resolveAppName" }'; |
||||
let policyTextKey = 'startupoverlay.policyText'; |
||||
let policyLogo = ''; |
||||
let policyLogoSrc = interfaceConfig.POLICY_LOGO; |
||||
if (policyLogoSrc) { |
||||
policyLogo += ( |
||||
`<div class="policy__logo">
|
||||
<img src="${policyLogoSrc}"/> |
||||
</div>` |
||||
); |
||||
} |
||||
|
||||
return ( |
||||
`<div class="inlay">
|
||||
<span class="inlay__icon icon-microphone"></span> |
||||
<span class="inlay__icon icon-camera"></span> |
||||
<h3 class="inlay__title" data-i18n="${titleKey}" |
||||
data-i18n-options='${titleOptions}'></h3> |
||||
<span class='inlay__text'data-i18n='[html]${textKey}'></span> |
||||
</div> |
||||
<div class="policy overlay__policy"> |
||||
<p class="policy__text" data-i18n="[html]${policyTextKey}"></p> |
||||
${policyLogo} |
||||
</div>` |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Stores GUM overlay instance. |
||||
* @type {GUMOverlayImpl} |
||||
*/ |
||||
let overlay; |
||||
|
||||
export default { |
||||
/** |
||||
* Checks whether the overlay is currently visible. |
||||
* @return {boolean} <tt>true</tt> if the overlay is visible |
||||
* or <tt>false</tt> otherwise. |
||||
*/ |
||||
isVisible () { |
||||
return overlay && overlay.isVisible(); |
||||
}, |
||||
/** |
||||
* Shows browser-specific overlay with guidance how to proceed with |
||||
* gUM prompt. |
||||
* @param {string} browser - name of browser for which to show the |
||||
* guidance overlay. |
||||
*/ |
||||
show(browser) { |
||||
if (!overlay) { |
||||
overlay = new GUMOverlayImpl(browser); |
||||
} |
||||
overlay.show(); |
||||
}, |
||||
|
||||
/** |
||||
* Hides browser-specific overlay with guidance how to proceed with |
||||
* gUM prompt. |
||||
*/ |
||||
hide() { |
||||
overlay && overlay.hide(); |
||||
} |
||||
}; |
@ -1,94 +0,0 @@ |
||||
/* global $, APP */ |
||||
|
||||
/** |
||||
* Base class for overlay components - the components which are displayed on |
||||
* top of the application with semi-transparent background covering the whole |
||||
* screen. |
||||
*/ |
||||
export default class Overlay{ |
||||
/** |
||||
* Creates new <tt>Overlay</tt> instance. |
||||
*/ |
||||
constructor() { |
||||
/** |
||||
* |
||||
* @type {jQuery} |
||||
*/ |
||||
this.$overlay = null; |
||||
|
||||
/** |
||||
* Indicates if this overlay should use the light look & feel or the |
||||
* standard one. |
||||
* @type {boolean} |
||||
*/ |
||||
this.isLightOverlay = false; |
||||
} |
||||
/** |
||||
* Template method which should be used by subclasses to provide the overlay |
||||
* content. The contents provided by this method are later subject to |
||||
* the translation using {@link APP.translation.translateElement}. |
||||
* @return {string} HTML representation of the overlay dialog contents. |
||||
* @protected |
||||
*/ |
||||
_buildOverlayContent() { |
||||
return ''; |
||||
} |
||||
/** |
||||
* Constructs the HTML body of the overlay dialog. |
||||
* |
||||
* @private |
||||
*/ |
||||
_buildOverlayHtml() { |
||||
|
||||
let overlayContent = this._buildOverlayContent(); |
||||
|
||||
let containerClass = this.isLightOverlay ? "overlay__container-light" |
||||
: "overlay__container"; |
||||
|
||||
this.$overlay = $(` |
||||
<div class=${containerClass}> |
||||
<div class='overlay__content'> |
||||
${overlayContent} |
||||
</div> |
||||
</div>`); |
||||
|
||||
APP.translation.translateElement(this.$overlay); |
||||
} |
||||
/** |
||||
* Checks whether the page reload overlay has been displayed. |
||||
* @return {boolean} <tt>true</tt> if the page reload overlay is currently |
||||
* visible or <tt>false</tt> otherwise. |
||||
*/ |
||||
isVisible() { |
||||
return this.$overlay && this.$overlay.parents('body').length > 0; |
||||
} |
||||
/** |
||||
* Template method called just after the overlay is displayed for the first |
||||
* time. |
||||
* @protected |
||||
*/ |
||||
_onShow() { |
||||
// To be overridden by subclasses.
|
||||
} |
||||
/** |
||||
* Shows the overlay dialog and attaches the underlying HTML representation |
||||
* to the DOM. |
||||
*/ |
||||
show() { |
||||
|
||||
!this.$overlay && this._buildOverlayHtml(); |
||||
|
||||
if (!this.isVisible()) { |
||||
this.$overlay.appendTo('body'); |
||||
this._onShow(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Hides the overlay dialog and detaches it's HTML representation from |
||||
* the DOM. |
||||
*/ |
||||
hide() { |
||||
this.$overlay && this.$overlay.detach(); |
||||
} |
||||
} |
@ -1,175 +0,0 @@ |
||||
/* global $, APP, AJS */ |
||||
const logger = require("jitsi-meet-logger").getLogger(__filename); |
||||
|
||||
import Overlay from "../overlay/Overlay"; |
||||
|
||||
/** |
||||
* An overlay dialog which is shown before the conference is reloaded. Shows |
||||
* a warning message and counts down towards the reload. |
||||
*/ |
||||
class PageReloadOverlayImpl extends Overlay{ |
||||
/** |
||||
* Creates new <tt>PageReloadOverlayImpl</tt> |
||||
* @param {number} timeoutSeconds how long the overlay dialog will be |
||||
* displayed, before the conference will be reloaded. |
||||
* @param {string} title the title of the overlay message |
||||
* @param {string} message the message of the overlay |
||||
* @param {string} buttonHtml the button html or an empty string if there's |
||||
* no button |
||||
* @param {boolean} isLightOverlay indicates if the overlay should be a |
||||
* light overlay or a standard one |
||||
*/ |
||||
constructor(timeoutSeconds, title, message, buttonHtml, isLightOverlay) { |
||||
super(); |
||||
/** |
||||
* Conference reload counter in seconds. |
||||
* @type {number} |
||||
*/ |
||||
this.timeLeft = timeoutSeconds; |
||||
/** |
||||
* Conference reload timeout in seconds. |
||||
* @type {number} |
||||
*/ |
||||
this.timeout = timeoutSeconds; |
||||
|
||||
this.title = title; |
||||
this.message = message; |
||||
this.buttonHtml = buttonHtml; |
||||
this.isLightOverlay = isLightOverlay; |
||||
} |
||||
/** |
||||
* Constructs overlay body with the warning message and count down towards |
||||
* the conference reload. |
||||
* @override |
||||
*/ |
||||
_buildOverlayContent() { |
||||
return `<div class="inlay">
|
||||
<span data-i18n=${this.title} |
||||
class='reload_overlay_title'></span> |
||||
<span data-i18n=${this.message} |
||||
class='reload_overlay_msg'></span> |
||||
<div> |
||||
<div id='reloadProgressBar' |
||||
class="aui-progress-indicator"> |
||||
<span class="aui-progress-indicator-value"></span> |
||||
</div> |
||||
<span id='reloadSecRemaining' |
||||
data-i18n="dialog.conferenceReloadTimeLeft" |
||||
class='reload_overlay_msg'> |
||||
</span> |
||||
</div> |
||||
${this.buttonHtml} |
||||
</div>`; |
||||
} |
||||
|
||||
/** |
||||
* Updates the progress indicator position and the label with the time left. |
||||
*/ |
||||
updateDisplay() { |
||||
|
||||
APP.translation.translateElement( |
||||
$("#reloadSecRemaining"), { seconds: this.timeLeft }); |
||||
|
||||
const ratio = (this.timeout - this.timeLeft) / this.timeout; |
||||
AJS.progressBars.update("#reloadProgressBar", ratio); |
||||
} |
||||
|
||||
/** |
||||
* Starts the reload countdown with the animation. |
||||
* @override |
||||
*/ |
||||
_onShow() { |
||||
$("#reconnectNow").click(() => { |
||||
APP.ConferenceUrl.reload(); |
||||
}); |
||||
|
||||
// Initialize displays
|
||||
this.updateDisplay(); |
||||
|
||||
var intervalId = window.setInterval(function() { |
||||
|
||||
if (this.timeLeft >= 1) { |
||||
this.timeLeft -= 1; |
||||
} |
||||
|
||||
this.updateDisplay(); |
||||
|
||||
if (this.timeLeft === 0) { |
||||
window.clearInterval(intervalId); |
||||
APP.ConferenceUrl.reload(); |
||||
} |
||||
}.bind(this), 1000); |
||||
|
||||
logger.info( |
||||
"The conference will be reloaded after " |
||||
+ this.timeLeft + " seconds."); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Holds the page reload overlay instance. |
||||
* |
||||
* {@type PageReloadOverlayImpl} |
||||
*/ |
||||
let overlay; |
||||
|
||||
/** |
||||
* Checks whether the page reload overlay has been displayed. |
||||
* @return {boolean} <tt>true</tt> if the page reload overlay is currently |
||||
* visible or <tt>false</tt> otherwise. |
||||
*/ |
||||
export function isVisible() { |
||||
return overlay && overlay.isVisible(); |
||||
} |
||||
|
||||
/** |
||||
* Shows the page reload overlay which will do the conference reload after |
||||
* the given amount of time. |
||||
* |
||||
* @param {number} timeoutSeconds how many seconds before the conference |
||||
* reload will happen. |
||||
* @param {boolean} isNetworkFailure <tt>true</tt> indicates that it's |
||||
* caused by network related failure or <tt>false</tt> when it's |
||||
* the infrastructure. |
||||
* @param {string} reason a label string identifying the reason for the page |
||||
* reload which will be included in details of the log event |
||||
*/ |
||||
export function show(timeoutSeconds, isNetworkFailure, reason) { |
||||
let title; |
||||
let message; |
||||
let buttonHtml; |
||||
let isLightOverlay; |
||||
|
||||
if (isNetworkFailure) { |
||||
title = "dialog.conferenceDisconnectTitle"; |
||||
message = "dialog.conferenceDisconnectMsg"; |
||||
buttonHtml |
||||
= `<button id="reconnectNow" data-i18n="dialog.reconnectNow"
|
||||
class="button-control button-control_primary |
||||
button-control_center"></button>`; |
||||
isLightOverlay = true; |
||||
} |
||||
else { |
||||
title = "dialog.conferenceReloadTitle"; |
||||
message = "dialog.conferenceReloadMsg"; |
||||
buttonHtml = ""; |
||||
isLightOverlay = false; |
||||
} |
||||
|
||||
if (!overlay) { |
||||
overlay = new PageReloadOverlayImpl(timeoutSeconds, |
||||
title, |
||||
message, |
||||
buttonHtml, |
||||
isLightOverlay); |
||||
} |
||||
// Log the page reload event
|
||||
if (!this.isVisible()) { |
||||
// FIXME (CallStats - issue) this event will not make it to
|
||||
// the CallStats, because the log queue is not flushed, before
|
||||
// "fabric terminated" is sent to the backed
|
||||
APP.conference.logEvent( |
||||
'page.reload', undefined /* value */, reason /* label */); |
||||
} |
||||
overlay.show(); |
||||
} |
@ -1,63 +0,0 @@ |
||||
/* global $, APP */ |
||||
|
||||
import Overlay from '../overlay/Overlay'; |
||||
|
||||
/** |
||||
* An overlay dialog which is shown when a suspended event is detected. |
||||
*/ |
||||
class SuspendedOverlayImpl extends Overlay{ |
||||
/** |
||||
* Creates new <tt>SuspendedOverlayImpl</tt> |
||||
*/ |
||||
constructor() { |
||||
super(); |
||||
|
||||
$(document).on('click', '#rejoin', () => { |
||||
APP.ConferenceUrl.reload(); |
||||
}); |
||||
} |
||||
/** |
||||
* Constructs overlay body with the message and a button to rejoin. |
||||
* @override |
||||
*/ |
||||
_buildOverlayContent() { |
||||
return ( |
||||
`<div class="inlay">
|
||||
<span class="inlay__icon icon-microphone"></span> |
||||
<span class="inlay__icon icon-camera"></span> |
||||
<h3 class="inlay__title" data-i18n="suspendedoverlay.title"></h3> |
||||
<button id="rejoin"
|
||||
data-i18n="suspendedoverlay.rejoinKeyTitle"
|
||||
class="inlay__button button-control button-control_primary"> |
||||
</button> |
||||
</div>`); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Holds the page suspended overlay instance. |
||||
* |
||||
* {@type SuspendedOverlayImpl} |
||||
*/ |
||||
let overlay; |
||||
|
||||
export default { |
||||
/** |
||||
* Checks whether the page suspended overlay has been displayed. |
||||
* @return {boolean} <tt>true</tt> if the page suspended overlay is |
||||
* currently visible or <tt>false</tt> otherwise. |
||||
*/ |
||||
isVisible() { |
||||
return overlay && overlay.isVisible(); |
||||
}, |
||||
/** |
||||
* Shows the page suspended overlay. |
||||
*/ |
||||
show() { |
||||
|
||||
if (!overlay) { |
||||
overlay = new SuspendedOverlayImpl(); |
||||
} |
||||
overlay.show(); |
||||
} |
||||
}; |
@ -0,0 +1,25 @@ |
||||
import { Symbol } from '../base/react'; |
||||
|
||||
/** |
||||
* The type of the Redux action which signals that a suspend was detected. |
||||
* |
||||
* { |
||||
* type: SUSPEND_DETECTED |
||||
* } |
||||
* @public |
||||
*/ |
||||
export const SUSPEND_DETECTED = Symbol('SUSPEND_DETECTED'); |
||||
|
||||
/** |
||||
* The type of the Redux action which signals that the prompt for media |
||||
* permission is visible or not. |
||||
* |
||||
* { |
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED, |
||||
* isVisible: {boolean}, |
||||
* browser: {string} |
||||
* } |
||||
* @public |
||||
*/ |
||||
export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED |
||||
= Symbol('MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED'); |
@ -0,0 +1,40 @@ |
||||
import { |
||||
SUSPEND_DETECTED, |
||||
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED |
||||
} from './actionTypes'; |
||||
import './reducer'; |
||||
|
||||
/** |
||||
* Signals that suspend was detected. |
||||
* |
||||
* @returns {{ |
||||
* type: SUSPEND_DETECTED |
||||
* }} |
||||
* @public |
||||
*/ |
||||
export function suspendDetected() { |
||||
return { |
||||
type: SUSPEND_DETECTED |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Signals that the prompt for media permission is visible or not. |
||||
* |
||||
* @param {boolean} isVisible - If the value is true - the prompt for media |
||||
* permission is visible otherwise the value is false/undefined. |
||||
* @param {string} browser - The name of the current browser. |
||||
* @returns {{ |
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED, |
||||
* isVisible: {boolean}, |
||||
* browser: {string} |
||||
* }} |
||||
* @public |
||||
*/ |
||||
export function mediaPermissionPromptVisibilityChanged(isVisible, browser) { |
||||
return { |
||||
type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED, |
||||
isVisible, |
||||
browser |
||||
}; |
||||
} |
@ -0,0 +1,86 @@ |
||||
/* 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 - lighter and |
||||
* darker otherwise. |
||||
* @type {boolean} |
||||
*/ |
||||
isLightOverlay: false |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Abstract method which should be used by subclasses to provide the overlay |
||||
* content. |
||||
* |
||||
* @returns {ReactElement|null} |
||||
* @protected |
||||
*/ |
||||
_renderOverlayContent() { |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* This method is executed when comonent is mounted. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {void} |
||||
*/ |
||||
componentDidMount() { |
||||
// XXX Temporary solution until we add React translation.
|
||||
APP.translation.translateElement($('#overlay')); |
||||
} |
||||
|
||||
/** |
||||
* Reloads the page. |
||||
* |
||||
* @returns {void} |
||||
* @protected |
||||
*/ |
||||
_reconnectNow() { |
||||
// FIXME: In future we should dispatch an action here that will result
|
||||
// in reload.
|
||||
APP.ConferenceUrl.reload(); |
||||
} |
||||
|
||||
/** |
||||
* 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> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,213 @@ |
||||
/* global APP */ |
||||
|
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import PageReloadOverlay from './PageReloadOverlay'; |
||||
import SuspendedOverlay from './SuspendedOverlay'; |
||||
import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay'; |
||||
|
||||
/** |
||||
* Implements a React Component that will display the correct overlay when |
||||
* needed. |
||||
*/ |
||||
class OverlayContainer extends Component { |
||||
/** |
||||
* OverlayContainer component's property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The browser which is used currently. |
||||
* NOTE: Used by UserMediaPermissionsOverlay only. |
||||
* @private |
||||
* @type {string} |
||||
*/ |
||||
_browser: React.PropTypes.string, |
||||
|
||||
/** |
||||
* The indicator which determines whether the status of |
||||
* JitsiConnection object has been "established" or not. |
||||
* NOTE: Used by PageReloadOverlay only. |
||||
* @private |
||||
* @type {boolean} |
||||
*/ |
||||
_connectionEstablished: React.PropTypes.bool, |
||||
|
||||
/** |
||||
* The indicator which determines whether a critical error for reload |
||||
* has been received. |
||||
* NOTE: Used by PageReloadOverlay only. |
||||
* @private |
||||
* @type {boolean} |
||||
*/ |
||||
_haveToReload: React.PropTypes.bool, |
||||
|
||||
/** |
||||
* The indicator which determines whether the reload was caused by |
||||
* network failure. |
||||
* NOTE: Used by PageReloadOverlay only. |
||||
* @private |
||||
* @type {boolean} |
||||
*/ |
||||
_isNetworkFailure: React.PropTypes.bool, |
||||
|
||||
/** |
||||
* The indicator which determines whether the GUM permissions prompt |
||||
* is displayed or not. |
||||
* NOTE: Used by UserMediaPermissionsOverlay only. |
||||
* @private |
||||
* @type {boolean} |
||||
*/ |
||||
_mediaPermissionPromptVisible: React.PropTypes.bool, |
||||
|
||||
/** |
||||
* The reason for the error that will cause the reload. |
||||
* NOTE: Used by PageReloadOverlay only. |
||||
* @private |
||||
* @type {string} |
||||
*/ |
||||
_reason: React.PropTypes.string, |
||||
|
||||
/** |
||||
* The indicator which determines whether the GUM permissions prompt |
||||
* is displayed or not. |
||||
* NOTE: Used by SuspendedOverlay only. |
||||
* @private |
||||
* @type {string} |
||||
*/ |
||||
_suspendDetected: React.PropTypes.bool |
||||
} |
||||
|
||||
/** |
||||
* React Component method that executes once component is updated. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {void} |
||||
* @protected |
||||
*/ |
||||
componentDidUpdate() { |
||||
// FIXME: Temporary workaround until everything is moved to react.
|
||||
APP.UI.overlayVisible |
||||
= (this.props._connectionEstablished && this.props._haveToReload) |
||||
|| this.props._suspendDetected |
||||
|| this.props._mediaPermissionPromptVisible; |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement|null} |
||||
* @public |
||||
*/ |
||||
render() { |
||||
if (this.props._connectionEstablished && this.props._haveToReload) { |
||||
return ( |
||||
<PageReloadOverlay |
||||
isNetworkFailure = { this.props._isNetworkFailure } |
||||
reason = { this.props._reason } /> |
||||
); |
||||
} |
||||
|
||||
if (this.props._suspendDetected) { |
||||
return ( |
||||
<SuspendedOverlay /> |
||||
); |
||||
} |
||||
|
||||
if (this.props._mediaPermissionPromptVisible) { |
||||
return ( |
||||
<UserMediaPermissionsOverlay |
||||
browser = { this.props._browser } /> |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the Redux state to the associated OverlayContainer's props. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @returns {{ |
||||
* _browser: string, |
||||
* _connectionEstablished: bool, |
||||
* _haveToReload: bool, |
||||
* _isNetworkFailure: bool, |
||||
* _mediaPermissionPromptVisible: bool, |
||||
* _reason: string, |
||||
* _suspendDetected: bool |
||||
* }} |
||||
* @private |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
return { |
||||
/** |
||||
* The browser which is used currently. |
||||
* NOTE: Used by UserMediaPermissionsOverlay only. |
||||
* @private |
||||
* @type {string} |
||||
*/ |
||||
_browser: state['features/overlay'].browser, |
||||
|
||||
/** |
||||
* The indicator which determines whether the status of |
||||
* JitsiConnection object has been "established" or not. |
||||
* NOTE: Used by PageReloadOverlay only. |
||||
* @private |
||||
* @type {boolean} |
||||
*/ |
||||
_connectionEstablished: |
||||
state['features/overlay'].connectionEstablished, |
||||
|
||||
/** |
||||
* The indicator which determines whether a critical error for reload |
||||
* has been received. |
||||
* NOTE: Used by PageReloadOverlay only. |
||||
* @private |
||||
* @type {boolean} |
||||
*/ |
||||
_haveToReload: state['features/overlay'].haveToReload, |
||||
|
||||
/** |
||||
* The indicator which determines whether the reload was caused by |
||||
* network failure. |
||||
* NOTE: Used by PageReloadOverlay only. |
||||
* @private |
||||
* @type {boolean} |
||||
*/ |
||||
_isNetworkFailure: state['features/overlay'].isNetworkFailure, |
||||
|
||||
/** |
||||
* The indicator which determines whether the GUM permissions prompt |
||||
* is displayed or not. |
||||
* NOTE: Used by UserMediaPermissionsOverlay only. |
||||
* @private |
||||
* @type {boolean} |
||||
*/ |
||||
_mediaPermissionPromptVisible: |
||||
state['features/overlay'].mediaPermissionPromptVisible, |
||||
|
||||
/** |
||||
* The reason for the error that will cause the reload. |
||||
* NOTE: Used by PageReloadOverlay only. |
||||
* @private |
||||
* @type {string} |
||||
*/ |
||||
_reason: state['features/overlay'].reason, |
||||
|
||||
/** |
||||
* The indicator which determines whether the GUM permissions prompt |
||||
* is displayed or not. |
||||
* NOTE: Used by SuspendedOverlay only. |
||||
* @private |
||||
* @type {string} |
||||
*/ |
||||
_suspendDetected: state['features/overlay'].suspendDetected |
||||
}; |
||||
} |
||||
|
||||
export default connect(_mapStateToProps)(OverlayContainer); |
@ -0,0 +1,298 @@ |
||||
/* global APP, AJS */ |
||||
|
||||
import React, { Component } from 'react'; |
||||
|
||||
import { randomInt } from '../../base/util/randomUtil'; |
||||
|
||||
import AbstractOverlay from './AbstractOverlay'; |
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename); |
||||
|
||||
/** |
||||
* Implements a React Component for the reload timer. Starts counter from |
||||
* props.start, adds props.step to the current value on every props.interval |
||||
* seconds until the current value reaches props.end. Also displays progress |
||||
* bar. |
||||
*/ |
||||
class ReloadTimer extends Component { |
||||
/** |
||||
* ReloadTimer component's property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The end of the timer. When this.state.current reaches this |
||||
* value the timer will stop and call onFinish function. |
||||
* @public |
||||
* @type {number} |
||||
*/ |
||||
end: React.PropTypes.number, |
||||
|
||||
/** |
||||
* The interval in sec for adding this.state.step to this.state.current |
||||
* @public |
||||
* @type {number} |
||||
*/ |
||||
interval: React.PropTypes.number, |
||||
|
||||
/** |
||||
* The function that will be executed when timer finish (when |
||||
* this.state.current === this.props.end) |
||||
*/ |
||||
onFinish: React.PropTypes.func, |
||||
|
||||
/** |
||||
* The start of the timer. The initial value for this.state.current. |
||||
* @public |
||||
* @type {number} |
||||
*/ |
||||
start: React.PropTypes.number, |
||||
|
||||
/** |
||||
* The value which will be added to this.state.current on every step. |
||||
* @public |
||||
* @type {number} |
||||
*/ |
||||
step: React.PropTypes.number |
||||
} |
||||
|
||||
/** |
||||
* 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 = { |
||||
current: this.props.start, |
||||
time: Math.abs(this.props.end - this.props.start) |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* React Component method that executes once component is mounted. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {void} |
||||
* @protected |
||||
*/ |
||||
componentDidMount() { |
||||
AJS.progressBars.update('#reloadProgressBar', 0); |
||||
const intervalId = setInterval(() => { |
||||
if (this.state.current === this.props.end) { |
||||
clearInterval(intervalId); |
||||
this.props.onFinish(); |
||||
|
||||
return; |
||||
} |
||||
this.setState((prevState, props) => { |
||||
return { current: prevState.current + props.step }; |
||||
}); |
||||
}, Math.abs(this.props.interval) * 1000); |
||||
} |
||||
|
||||
/** |
||||
* React Component method that executes once component is updated. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {void} |
||||
* @protected |
||||
*/ |
||||
componentDidUpdate() { |
||||
AJS.progressBars.update('#reloadProgressBar', |
||||
Math.abs(this.state.current - this.props.start) / this.state.time); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement|null} |
||||
* @public |
||||
*/ |
||||
render() { |
||||
return ( |
||||
<div> |
||||
<div |
||||
className = 'aui-progress-indicator' |
||||
id = 'reloadProgressBar'> |
||||
<span className = 'aui-progress-indicator-value' /> |
||||
</div> |
||||
<span |
||||
className = 'reload_overlay_text' |
||||
id = 'reloadSeconds'> |
||||
{ this.state.current } |
||||
<span data-i18n = 'dialog.conferenceReloadTimeLeft' /> |
||||
</span> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Implements a React Component for page reload overlay. Shown before |
||||
* the conference is reloaded. Shows a warning message and counts down towards |
||||
* the reload. |
||||
*/ |
||||
export default class PageReloadOverlay extends AbstractOverlay { |
||||
/** |
||||
* 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, |
||||
|
||||
/** |
||||
* 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 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 - lighter and |
||||
* 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 |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Renders the button for relaod the page if necessary. |
||||
* |
||||
* @returns {ReactElement|null} |
||||
* @private |
||||
*/ |
||||
_renderButton() { |
||||
if (this.props.isNetworkFailure) { |
||||
const cName = 'button-control button-control_primary ' |
||||
+ 'button-control_center'; |
||||
|
||||
/* eslint-disable react/jsx-handler-names */ |
||||
|
||||
return ( |
||||
<button |
||||
className = { cName } |
||||
data-i18n = 'dialog.reconnectNow' |
||||
id = 'reconnectNow' |
||||
onClick = { this._reconnectNow } /> |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Constructs overlay body with the warning message and count down towards |
||||
* the conference reload. |
||||
* |
||||
* @returns {ReactElement|null} |
||||
* @override |
||||
* @protected |
||||
*/ |
||||
_renderOverlayContent() { |
||||
|
||||
/* eslint-disable react/jsx-handler-names */ |
||||
|
||||
return ( |
||||
<div className = 'inlay'> |
||||
<span |
||||
className = 'reload_overlay_title' |
||||
data-i18n = { this.state.title } /> |
||||
<span |
||||
className = 'reload_overlay_text' |
||||
data-i18n = { this.state.message } /> |
||||
<ReloadTimer |
||||
end = { 0 } |
||||
interval = { 1 } |
||||
onFinish = { this._reconnectNow } |
||||
start = { this.state.timeoutSeconds } |
||||
step = { -1 } /> |
||||
{ this._renderButton() } |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* This method is executed when comonent is mounted. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {void} |
||||
*/ |
||||
componentDidMount() { |
||||
super.componentDidMount(); |
||||
|
||||
// FIXME (CallStats - issue) this event will not make it to
|
||||
// the 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', undefined /* value */, |
||||
this.props.reason /* label */); |
||||
logger.info(`The conference will be reloaded after
|
||||
${this.state.timeoutSeconds} seconds.`);
|
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
import React from 'react'; |
||||
|
||||
import AbstractOverlay from './AbstractOverlay'; |
||||
|
||||
/** |
||||
* Implements a React Component for suspended overlay. Shown when suspended |
||||
* is detected. |
||||
*/ |
||||
export default class SuspendedOverlay extends AbstractOverlay { |
||||
/** |
||||
* Constructs overlay body with the message and a button to rejoin. |
||||
* |
||||
* @returns {ReactElement|null} |
||||
* @override |
||||
* @protected |
||||
*/ |
||||
_renderOverlayContent() { |
||||
const btnClass = 'inlay__button button-control button-control_primary'; |
||||
|
||||
/* 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' |
||||
data-i18n = 'suspendedoverlay.title' /> |
||||
<button |
||||
className = { btnClass } |
||||
data-i18n = 'suspendedoverlay.rejoinKeyTitle' |
||||
id = 'rejoin' |
||||
onClick = { this._reconnectNow } /> |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
/* global interfaceConfig */ |
||||
|
||||
import React from 'react'; |
||||
|
||||
import AbstractOverlay from './AbstractOverlay'; |
||||
|
||||
/** |
||||
* Implements a React Component for overlay with guidance how to proceed with |
||||
* gUM prompt. |
||||
*/ |
||||
export default class UserMediaPermissionsOverlay extends AbstractOverlay { |
||||
/** |
||||
* UserMediaPermissionsOverlay 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 |
||||
} |
||||
|
||||
/** |
||||
* Initializes a new SuspendedOverlay instance. |
||||
* |
||||
* @param {Object} props - The read-only properties with which the new |
||||
* instance is to be initialized. |
||||
* @public |
||||
*/ |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
this.state = { |
||||
/** |
||||
* The src value of the image for the policy logo. |
||||
* @type {string} |
||||
*/ |
||||
policyLogoSrc: interfaceConfig.POLICY_LOGO |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Constructs overlay body with the message with guidance how to proceed |
||||
* with gUM prompt. |
||||
* |
||||
* @returns {ReactElement|null} |
||||
* @override |
||||
* @protected |
||||
*/ |
||||
_renderOverlayContent() { |
||||
const textKey = `userMedia.${this.props.browser}GrantPermissions`; |
||||
|
||||
return ( |
||||
<div> |
||||
<div className = 'inlay'> |
||||
<span className = 'inlay__icon icon-microphone' /> |
||||
<span className = 'inlay__icon icon-camera' /> |
||||
<h3 |
||||
className = 'inlay__title' |
||||
data-i18n = 'startupoverlay.title' |
||||
data-i18n-options |
||||
= '{"postProcess": "resolveAppName"}' /> |
||||
<span |
||||
className = 'inlay__text' |
||||
data-i18n = { `[html]${textKey}` } /> |
||||
</div> |
||||
<div className = 'policy overlay__policy'> |
||||
<p |
||||
className = 'policy__text' |
||||
data-i18n = '[html]startupoverlay.policyText' /> |
||||
{ this._renderPolicyLogo() } |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Renders the policy logo. |
||||
* |
||||
* @returns {ReactElement|null} |
||||
* @private |
||||
*/ |
||||
_renderPolicyLogo() { |
||||
if (this.state.policyLogoSrc) { |
||||
return ( |
||||
<div className = 'policy__logo'> |
||||
<img src = { this.state.policyLogoSrc } /> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
export { default as OverlayContainer } from './OverlayContainer'; |
@ -0,0 +1,2 @@ |
||||
export * from './components'; |
||||
export * from './actions'; |
@ -0,0 +1,145 @@ |
||||
/* global JitsiMeetJS */ |
||||
|
||||
import { CONFERENCE_FAILED } from '../base/conference'; |
||||
import { |
||||
CONNECTION_ESTABLISHED, |
||||
CONNECTION_FAILED |
||||
} from '../base/connection'; |
||||
import { |
||||
ReducerRegistry, |
||||
setStateProperty, |
||||
setStateProperties |
||||
} from '../base/redux'; |
||||
|
||||
import { |
||||
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED, |
||||
SUSPEND_DETECTED |
||||
} from './actionTypes'; |
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename); |
||||
|
||||
/** |
||||
* Reduces the Redux actions of the feature overlay. |
||||
*/ |
||||
ReducerRegistry.register('features/overlay', (state = {}, action) => { |
||||
switch (action.type) { |
||||
case CONFERENCE_FAILED: |
||||
return _conferenceFailed(state, action); |
||||
|
||||
case CONNECTION_ESTABLISHED: |
||||
return _connectionEstablished(state, action); |
||||
|
||||
case CONNECTION_FAILED: |
||||
return _connectionFailed(state, action); |
||||
|
||||
case MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED: |
||||
return _mediaPermissionPromptVisibilityChanged(state, action); |
||||
|
||||
case SUSPEND_DETECTED: |
||||
return _suspendDetected(state, action); |
||||
} |
||||
|
||||
return state; |
||||
}); |
||||
|
||||
/** |
||||
* Reduces a specific Redux action CONFERENCE_FAILED of the feature |
||||
* overlay. |
||||
* |
||||
* @param {Object} state - The Redux state of the feature overlay. |
||||
* @param {Action} action - The Redux action CONFERENCE_FAILED to reduce. |
||||
* @returns {Object} The new state of the feature base/connection after the |
||||
* reduction of the specified action. |
||||
* @private |
||||
*/ |
||||
function _conferenceFailed(state, action) { |
||||
const ConferenceErrors = JitsiMeetJS.errors.conference; |
||||
|
||||
if (action.error === ConferenceErrors.FOCUS_LEFT |
||||
|| action.error === ConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE) { |
||||
return setStateProperties(state, { |
||||
haveToReload: true, |
||||
isNetworkFailure: false, |
||||
reason: action.errorMessage |
||||
}); |
||||
} |
||||
|
||||
return state; |
||||
} |
||||
|
||||
/** |
||||
* Reduces a specific Redux action CONNECTION_ESTABLISHED of the feature |
||||
* overlay. |
||||
* |
||||
* @param {Object} state - The Redux state of the feature overlay. |
||||
* @returns {Object} The new state of the feature overlay after the |
||||
* reduction of the specified action. |
||||
* @private |
||||
*/ |
||||
function _connectionEstablished(state) { |
||||
return setStateProperty(state, 'connectionEstablished', true); |
||||
} |
||||
|
||||
/** |
||||
* Reduces a specific Redux action CONNECTION_FAILED of the feature |
||||
* overlay. |
||||
* |
||||
* @param {Object} state - The Redux state of the feature overlay. |
||||
* @param {Action} action - The Redux action CONNECTION_FAILED to reduce. |
||||
* @returns {Object} The new state of the feature overlay after the |
||||
* reduction of the specified action. |
||||
* @private |
||||
*/ |
||||
function _connectionFailed(state, action) { |
||||
const ConnectionErrors = JitsiMeetJS.errors.connection; |
||||
|
||||
switch (action.error) { |
||||
case ConnectionErrors.CONNECTION_DROPPED_ERROR: |
||||
case ConnectionErrors.OTHER_ERROR: |
||||
case ConnectionErrors.SERVER_ERROR: { |
||||
logger.error(`XMPP connection error: ${action.errorMessage}`); |
||||
|
||||
// From all of the cases above only CONNECTION_DROPPED_ERROR
|
||||
// is considered a network type of failure
|
||||
return setStateProperties(state, { |
||||
haveToReload: true, |
||||
isNetworkFailure: |
||||
action.error === ConnectionErrors.CONNECTION_DROPPED_ERROR, |
||||
reason: `xmpp-conn-dropped: ${action.errorMessage}` |
||||
}); |
||||
} |
||||
} |
||||
|
||||
return state; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Reduces a specific Redux action MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED |
||||
* of the feature overlay. |
||||
* |
||||
* @param {Object} state - The Redux state of the feature overlay. |
||||
* @param {Action} action - The Redux action to reduce. |
||||
* @returns {Object} The new state of the feature overlay after the |
||||
* reduction of the specified action. |
||||
* @private |
||||
*/ |
||||
function _mediaPermissionPromptVisibilityChanged(state, action) { |
||||
return setStateProperties(state, { |
||||
mediaPermissionPromptVisible: action.isVisible, |
||||
browser: action.browser |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Reduces a specific Redux action SUSPEND_DETECTED of the feature |
||||
* overlay. |
||||
* |
||||
* @param {Object} state - The Redux state of the feature overlay. |
||||
* @returns {Object} The new state of the feature overlay after the |
||||
* reduction of the specified action. |
||||
* @private |
||||
*/ |
||||
function _suspendDetected(state) { |
||||
return setStateProperty(state, 'suspendDetected', true); |
||||
} |
Loading…
Reference in new issue