[React] Call/ring overlay

Rewrite the non-React RingOverlay into the React Component CallOverlay
in a way which makes it easier to port to React Native.
This commit is contained in:
Lyubo Marinov 2017-06-05 13:19:25 -05:00
parent 409255f056
commit d437f3db03
15 changed files with 517 additions and 258 deletions

View File

@ -386,9 +386,8 @@ class ConferenceConnector {
_onConferenceFailed(err, ...params) {
APP.store.dispatch(conferenceFailed(room, err, ...params));
logger.error('CONFERENCE FAILED:', err, ...params);
APP.UI.hideRingOverlay();
switch (err) {
switch (err) {
case ConferenceErrors.CONNECTION_ERROR:
{
let [msg] = params;
@ -2027,7 +2026,7 @@ export default {
*/
hangup(requestFeedback = false) {
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
APP.UI.hideRingOverlay();
let requestFeedbackPromise = requestFeedback
? APP.UI.requestFeedbackOnHangup()
// false - because the thank you dialog shouldn't be displayed

View File

@ -70,7 +70,13 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
ENABLE_FEEDBACK_ANIMATION: false,
DISABLE_FOCUS_INDICATOR: false,
DISABLE_DOMINANT_SPEAKER_INDICATOR: false,
// disables the ringing sound when the RingOverlay is shown.
/**
* Whether the ringing sound in the call/ring overlay is disabled. If
* {@code undefined}, defaults to {@code false}.
*
* @type {boolean}
*/
DISABLE_RINGING: false,
AUDIO_LEVEL_PRIMARY_COLOR: "rgba(255,255,255,0.4)",
AUDIO_LEVEL_SECONDARY_COLOR: "rgba(255,255,255,0.2)",
@ -82,8 +88,8 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
/**
* Whether the mobile app Jitsi Meet is to be promoted to participants
* attempting to join a conference in a mobile Web browser. If undefined,
* default to true.
* attempting to join a conference in a mobile Web browser. If
* {@code undefined}, defaults to {@code true}.
*
* @type {boolean}
*/

View File

@ -19,18 +19,11 @@ import Filmstrip from "./videolayout/Filmstrip";
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
import Profile from "./side_pannels/profile/Profile";
import Settings from "./../settings/Settings";
import RingOverlay from "./ring_overlay/RingOverlay";
import UIErrors from './UIErrors';
import { debounce } from "../util/helpers";
import {
updateDeviceList
} from '../../react/features/base/devices';
import {
setAudioMuted,
setVideoMuted
} from '../../react/features/base/media';
import { updateDeviceList } from '../../react/features/base/devices';
import { setAudioMuted, setVideoMuted } from '../../react/features/base/media';
import {
openDeviceSelectionDialog
} from '../../react/features/device-selection';
@ -368,10 +361,6 @@ UI.start = function () {
"closeHtml": '<button type="button" tabIndex="-1">&times;</button>'
};
}
const { callee } = APP.store.getState()['features/jwt'];
callee && UI.showRingOverlay();
};
/**
@ -492,7 +481,7 @@ UI.getSharedDocumentManager = () => etherpadManager;
UI.addUser = function (user) {
var id = user.getId();
var displayName = user.getDisplayName();
UI.hideRingOverlay();
if (UI.ContactList)
UI.ContactList.addContact(id);
@ -1371,17 +1360,6 @@ UI.setCameraButtonEnabled
UI.setMicrophoneButtonEnabled
= enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
UI.showRingOverlay = function () {
const { callee } = APP.store.getState()['features/jwt'];
callee && RingOverlay.show(callee, interfaceConfig.DISABLE_RINGING);
Filmstrip.toggleFilmstrip(false, false);
};
UI.hideRingOverlay
= () => RingOverlay.hide() && Filmstrip.toggleFilmstrip(true, false);
/**
* Indicates if any the "top" overlays are currently visible. The check includes
* the call/ring overlay, the suspended overlay, the GUM permissions overlay,
@ -1390,16 +1368,11 @@ UI.hideRingOverlay
* @returns {*|boolean} {true} if an overlay is visible; {false}, otherwise
*/
UI.isOverlayVisible = function () {
return this.isRingOverlayVisible() || this.overlayVisible;
return (
this.overlayVisible
|| APP.store.getState()['features/jwt'].callOverlayVisible);
};
/**
* Indicates if the ring overlay is currently visible.
*
* @returns {*|boolean} {true} if the ring overlay is visible, {false} otherwise
*/
UI.isRingOverlayVisible = () => RingOverlay.isVisible();
/**
* Handles user's features changes.
*/

View File

@ -1,178 +0,0 @@
/* global $, APP */
import UIEvents from "../../../service/UI/UIEvents";
/**
* Store the current ring overlay instance.
* Note: We want to have only 1 instance at a time.
*/
let overlay = null;
/**
* Handler for UIEvents.LARGE_VIDEO_AVATAR_VISIBLE event.
* @param {boolean} shown indicates whether the avatar on the large video is
* currently displayed or not.
*/
function onAvatarVisible(shown) {
overlay._changeBackground(shown);
}
/**
* Shows ring overlay
*/
class RingOverlay {
/**
*
* @param callee The callee (Object) as defined by the JWT support.
* @param {boolean} disableRinging if true the ringing sound wont be played.
*/
constructor(callee, disableRinging) {
this._containerId = 'ringOverlay';
this._audioContainerId = 'ringOverlayRinging';
this.isRinging = true;
this.callee = callee;
this.disableRinging = disableRinging;
this.render();
if (!disableRinging)
this._initAudio();
this._timeout
= setTimeout(
() => {
this.destroy();
this.render();
},
30000);
}
/**
* Initializes the audio element and setups the interval for playing it.
*/
_initAudio() {
this.audio = document.getElementById(this._audioContainerId);
this.audio.play();
this.interval = setInterval(() => this.audio.play(), 5000);
}
/**
* Chagnes the background of the ring overlay.
* @param {boolean} solid - if true the new background will be the solid
* one, otherwise the background will be default one.
* NOTE: The method just toggles solidBG css class.
*/
_changeBackground(solid) {
const container = $("#" + this._containerId);
if (solid) {
container.addClass("solidBG");
} else {
container.removeClass("solidBG");
}
}
/**
* Builds and appends the ring overlay to the html document
*/
_getHtmlStr(callee) {
let callingLabel = this.isRinging ? "<p>Calling...</p>" : "";
let callerStateLabel = this.isRinging ? "" : " isn't available";
let audioHTML = this.disableRinging ? ""
: "<audio id=\"" + this._audioContainerId
+ "\" src=\"./sounds/ring.ogg\" />";
return `
<div id="${this._containerId}" class='ringing' >
<div class='ringing__content'>
${callingLabel}
<img class='ringing__avatar' src="${callee.avatarUrl}" />
<div class="ringing__caller-info">
<p>${callee.name}${callerStateLabel}</p>
</div>
</div>
${audioHTML}
</div>`;
}
/**
*
*/
render() {
this.htmlStr = this._getHtmlStr(this.callee);
this._attach();
}
/**
* Destroys and clears all the objects (html elements and audio interval)
* related to the ring overlay.
*/
destroy() {
this.isRinging = false;
this._stopAudio();
this._detach();
}
_attach() {
$("body").append(this.htmlStr);
}
_detach() {
$(`#${this._containerId}`).remove();
}
/**
* Stops the ringing and clears related timers.
*/
_stopAudio() {
if (this.interval) {
clearInterval(this.interval);
}
if (this._timeout) {
clearTimeout(this._timeout);
}
}
}
export default {
/**
* Shows the ring overlay for the passed callee.
*
* @param {Object} callee - The callee. Object containing data about
* callee.
* @param {boolean} disableRinging - If true the ringing sound won't be
* played.
* @returns {void}
*/
show(callee, disableRinging = false) {
if (overlay) {
this.hide();
}
overlay = new RingOverlay(callee, disableRinging);
APP.UI.addListener(UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
onAvatarVisible);
},
/**
* Hides the ring overlay. Destroys all the elements related to the ring
* overlay.
*/
hide() {
if (!overlay) {
return false;
}
overlay.destroy();
overlay = null;
APP.UI.removeListener(UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
onAvatarVisible);
return true;
},
/**
* Checks whether or not the ring overlay is currently displayed.
*
* @returns {boolean} true if the ring overlay is currently displayed or
* false otherwise.
*/
isVisible() {
return overlay !== null;
}
};

View File

@ -77,7 +77,6 @@ export function connect() {
}
})
.catch(error => {
APP.UI.hideRingOverlay();
APP.API.notifyConferenceLeft(APP.conference.roomName);
logger.error(error);

View File

@ -1,6 +1,10 @@
import UIEvents from '../../../service/UI/UIEvents';
/* @flow */
import { MiddlewareRegistry } from '../base/redux';
import { SET_CALL_OVERLAY_VISIBLE } from '../jwt';
import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
import UIEvents from '../../../service/UI/UIEvents';
import {
SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
@ -10,19 +14,33 @@ import {
declare var APP: Object;
// eslint-disable-next-line no-unused-vars
MiddlewareRegistry.register(store => next => action => {
const result = next(action);
MiddlewareRegistry.register(({ getState }) => next => action => {
switch (action.type) {
case SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY:
case SET_FILMSTRIP_VISIBILITY: {
if (typeof APP !== 'undefined') {
APP.UI.emitEvent(UIEvents.UPDATED_FILMSTRIP_DISPLAY);
case SET_CALL_OVERLAY_VISIBLE:
if (typeof APP === 'undefined') {
const oldValue
= Boolean(getState()['features/jwt'].callOverlayVisible);
const result = next(action);
const newValue
= Boolean(getState()['features/jwt'].callOverlayVisible);
oldValue === newValue
|| Filmstrip.toggleFilmstrip(!newValue, false);
return result;
}
break;
case SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY:
case SET_FILMSTRIP_VISIBILITY: {
const result = next(action);
typeof APP === 'undefined'
|| APP.UI.emitEvent(UIEvents.UPDATED_FILMSTRIP_DISPLAY);
return result;
}
}
return result;
return next(action);
});

View File

@ -1,3 +1,13 @@
/**
* The type of redux action which sets the visibility of {@code CallOverlay}.
*
* {
* type: SET_CALL_OVERLAY_VISIBLE,
* callOverlayVisible: boolean
* }
*/
export const SET_CALL_OVERLAY_VISIBLE = Symbol('SET_CALL_OVERLAY_VISIBLE');
/**
* The type of redux action which stores a specific JSON Web Token (JWT) into
* the redux store.

View File

@ -1,6 +1,27 @@
/* @flow */
import { SET_JWT } from './actionTypes';
import { SET_CALL_OVERLAY_VISIBLE, SET_JWT } from './actionTypes';
/**
* Sets the visibility of {@code CallOverlay}.
*
* @param {boolean|undefined} callOverlayVisible - If {@code CallOverlay} is to
* be displayed/visible, then {@code true}; otherwise, {@code false} or
* {@code undefined}.
* @returns {{
* type: SET_CALL_OVERLAY_VISIBLE,
* callOverlayVisible: (boolean|undefined)
* }}
*/
export function setCallOverlayVisible(callOverlayVisible: boolean) {
return (dispatch: Dispatch<*>, getState: Function) => {
getState()['features/jwt'].callOverlayVisible === callOverlayVisible
|| dispatch({
type: SET_CALL_OVERLAY_VISIBLE,
callOverlayVisible
});
};
}
/**
* Stores a specific JSON Web Token (JWT) into the redux store.

View File

@ -0,0 +1,288 @@
/* @flow */
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Audio } from '../../base/media';
import { Avatar } from '../../base/participants';
import { Container, Text } from '../../base/react';
import UIEvents from '../../../../service/UI/UIEvents';
declare var $: Object;
declare var APP: Object;
declare var interfaceConfig: Object;
/**
* Implements a React {@link Component} which depicts the establishment of a
* call with a specific remote callee.
*
* @extends Component
*/
class CallOverlay extends Component {
/**
* The (reference to the) {@link Audio} which plays/renders the audio
* depicting the ringing phase of the call establishment represented by this
* {@code CallOverlay}.
*/
_audio: ?Audio
_onLargeVideoAvatarVisible: Function
_playAudioInterval: ?number
_ringingTimeout: ?number
_setAudio: Function
state: {
/**
* The CSS class (name), if any, to add to this {@code CallOverlay}.
*
* @type {string}
*/
className: ?string,
/**
* The indicator which determines whether this {@code CallOverlay}
* should play/render audio to indicate the ringing phase of the
* call establishment between the local participant and the
* associated remote callee.
*
* @type {boolean}
*/
renderAudio: boolean,
/**
* The indicator which determines whether this {@code CallOverlay}
* is depicting the ringing phase of the call establishment between
* the local participant and the associated remote callee or the
* phase afterwards when the callee has not answered the call for a
* period of time and, consequently, is considered unavailable.
*
* @type {boolean}
*/
ringing: boolean
}
/**
* {@code CallOverlay} component's property types.
*
* @static
*/
static propTypes = {
_callee: React.PropTypes.object
};
/**
* Initializes a new {@code CallOverlay} instance.
*
* @param {Object} props - The read-only React {@link Component} props with
* which the new instance is to be initialized.
*/
constructor(props) {
super(props);
this.state = {
className: undefined,
renderAudio:
typeof interfaceConfig !== 'object'
|| !interfaceConfig.DISABLE_RINGING,
ringing: true
};
this._onLargeVideoAvatarVisible
= this._onLargeVideoAvatarVisible.bind(this);
this._setAudio = this._setAudio.bind(this);
if (typeof APP === 'object') {
APP.UI.addListener(
UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
this._onLargeVideoAvatarVisible);
}
}
/**
* Sets up timeouts such as the timeout to end the ringing phase of the call
* establishment depicted by this {@code CallOverlay}.
*
* @inheritdoc
*/
componentDidMount() {
// Set up the timeout to end the ringing phase of the call establishment
// depicted by this CallOverlay.
if (this.state.ringing && !this._ringingTimeout) {
this._ringingTimeout
= setTimeout(
() => {
this._pauseAudio();
this._ringingTimeout = undefined;
this.setState({
ringing: false
});
},
30000);
}
this._playAudio();
}
/**
* Cleans up before this {@code Calleverlay} is unmounted and destroyed.
*
* @inheritdoc
*/
componentWillUnmount() {
this._pauseAudio();
if (this._ringingTimeout) {
clearTimeout(this._ringingTimeout);
this._ringingTimeout = undefined;
}
if (typeof APP === 'object') {
APP.UI.removeListener(
UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
this._onLargeVideoAvatarVisible);
}
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { className, ringing } = this.state;
const { avatarUrl, name } = this.props._callee;
return (
<Container
className = { `ringing ${className || ''}` }
id = 'ringOverlay'>
<Container className = 'ringing__content'>
{ ringing ? <Text>Calling...</Text> : null }
<Avatar
className = 'ringing__avatar'
uri = { avatarUrl } />
<Container className = 'ringing__caller-info'>
<Text>
{ name }
{ ringing ? null : ' isn\'t available' }
</Text>
</Container>
</Container>
{ this._renderAudio() }
</Container>
);
}
/**
* Notifies this {@code CallOverlay} that the visibility of the
* participant's avatar in the large video has changed.
*
* @param {boolean} largeVideoAvatarVisible - If the avatar in the large
* video (i.e. of the participant on the stage) is visible, then
* {@code true}; otherwise, {@code false}.
* @private
* @returns {void}
*/
_onLargeVideoAvatarVisible(largeVideoAvatarVisible: boolean) {
this.setState({
className: largeVideoAvatarVisible ? 'solidBG' : undefined
});
}
/**
* Stops the playback of the audio which represents the ringing phase of the
* call establishment depicted by this {@code CallOverlay}.
*
* @private
* @returns {void}
*/
_pauseAudio() {
const audio = this._audio;
if (audio) {
audio.pause();
}
if (this._playAudioInterval) {
clearInterval(this._playAudioInterval);
this._playAudioInterval = undefined;
}
}
/**
* Starts the playback of the audio which represents the ringing phase of
* the call establishment depicted by this {@code CallOverlay}.
*
* @private
* @returns {void}
*/
_playAudio() {
if (this._audio) {
this._audio.play();
if (!this._playAudioInterval) {
this._playAudioInterval
= setInterval(() => this._playAudio(), 5000);
}
}
}
/**
* Renders an audio element to represent the ringing phase of the call
* establishment represented by this {@code CallOverlay}.
*
* @private
* @returns {ReactElement}
*/
_renderAudio() {
if (this.state.renderAudio && this.state.ringing) {
return (
<Audio
ref = { this._setAudio }
src = './sounds/ring.ogg' />
);
}
return null;
}
/**
* Sets the (reference to the) {@link Audio} which renders the ringing phase
* of the call establishment represented by this {@code CallOverlay}.
*
* @param {Audio} audio - The (reference to the) {@code Audio} which
* plays/renders the audio depicting the ringing phase of the call
* establishment represented by this {@code CallOverlay}.
* @private
* @returns {void}
*/
_setAudio(audio) {
this._audio = audio;
}
}
/**
* Maps (parts of) the redux state to {@code CallOverlay}'s props.
*
* @param {Object} state - The redux state.
* @private
* @returns {{
* _callee: Object
* }}
*/
function _mapStateToProps(state) {
return {
/**
*
* @private
* @type {Object}
*/
_callee: state['features/jwt'].callee
};
}
export default connect(_mapStateToProps)(CallOverlay);

View File

@ -0,0 +1 @@
export { default as CallOverlay } from './CallOverlay';

View File

@ -1,4 +1,5 @@
export * from './actions';
export * from './components';
export * from './functions';
import './middleware';

View File

@ -1,22 +1,38 @@
import jwtDecode from 'jwt-decode';
import {
CONFERENCE_FAILED,
CONFERENCE_LEFT,
CONFERENCE_WILL_LEAVE,
SET_ROOM
} from '../base/conference';
import { SET_CONFIG } from '../base/config';
import { SET_LOCATION_URL } from '../base/connection';
import { LIB_INIT_ERROR } from '../base/lib-jitsi-meet';
import { PARTICIPANT_JOINED } from '../base/participants';
import { MiddlewareRegistry } from '../base/redux';
import { setJWT } from './actions';
import { setCallOverlayVisible, setJWT } from './actions';
import { SET_JWT } from './actionTypes';
import { parseJWTFromURLParams } from './functions';
/**
* Middleware to parse token data upon setting a new room URL.
*
* @param {Store} store - The Redux store.
* @param {Store} store - The redux store.
* @private
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_FAILED:
case CONFERENCE_LEFT:
case CONFERENCE_WILL_LEAVE:
case LIB_INIT_ERROR:
case PARTICIPANT_JOINED:
case SET_ROOM:
return _maybeSetCallOverlayVisible(store, next, action);
case SET_CONFIG:
case SET_LOCATION_URL:
// XXX The JSON Web Token (JWT) is not the only piece of state that we
@ -33,16 +49,87 @@ MiddlewareRegistry.register(store => next => action => {
return next(action);
});
/**
* Notifies the feature jwt that a specific {@code action} is being dispatched
* within a specific redux {@code store} which may have an effect on the
* visiblity of (the) {@code CallOverlay}.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action which is being dispatched in the
* specified {@code store}.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified {@code action}.
*/
function _maybeSetCallOverlayVisible({ dispatch, getState }, next, action) {
const result = next(action);
const state = getState();
const stateFeaturesJWT = state['features/jwt'];
let callOverlayVisible;
if (stateFeaturesJWT.callee) {
const { conference, leaving, room } = state['features/base/conference'];
// XXX The CallOverlay is to be displayed/visible as soon as
// possible including even before the conference is joined.
if (room && (!conference || conference !== leaving)) {
switch (action.type) {
case CONFERENCE_FAILED:
case CONFERENCE_LEFT:
case CONFERENCE_WILL_LEAVE:
case LIB_INIT_ERROR:
// Because the CallOverlay is to be displayed/visible as soon as
// possible even before the connection is established and/or the
// conference is joined, it is very complicated to figure out
// based on the current state alone. In order to reduce the
// risks of displaying the CallOverly at inappropirate times, do
// not even attempt to figure out based on the current state.
// The (redux) actions listed above are also the equivalents of
// the execution ponints at which APP.UI.hideRingOverlay() used
// to be invoked.
break;
default: {
// The CallOverlay it to no longer be displayed/visible as soon
// as another participant joins.
const participants = state['features/base/participants'];
callOverlayVisible
= Boolean(
participants
&& participants.length === 1
&& participants[0].local);
// However, the CallDialog is not to be displayed/visible again
// after all remote participants leave.
if (callOverlayVisible
&& stateFeaturesJWT.callOverlayVisible === false) {
callOverlayVisible = false;
}
break;
}
}
}
}
dispatch(setCallOverlayVisible(callOverlayVisible));
return result;
}
/**
* Notifies the feature jwt that the action {@link SET_CONFIG} or
* {@link SET_LOCATION_URL} is being dispatched within a specific Redux
* {@link SET_LOCATION_URL} is being dispatched within a specific redux
* {@code store}.
*
* @param {Store} store - The Redux store in which the specified {@code action}
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The Redux dispatch function to dispatch the
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The Redux action {@code SET_CONFIG} or
* @param {Action} action - The redux action {@code SET_CONFIG} or
* {@code SET_LOCATION_URL} which is being dispatched in the specified
* {@code store}.
* @private
@ -65,26 +152,26 @@ function _setConfigOrLocationURL({ dispatch, getState }, next, action) {
/**
* Notifies the feature jwt that the action {@link SET_JWT} is being dispatched
* within a specific Redux {@code store}.
* within a specific redux {@code store}.
*
* @param {Store} store - The Redux store in which the specified {@code action}
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The Redux dispatch function to dispatch the
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The Redux action {@code SET_JWT} which is being
* @param {Action} action - The redux action {@code SET_JWT} which is being
* dispatched in the specified {@code store}.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified {@code action}.
*/
function _setJWT({ getState }, next, action) {
function _setJWT(store, next, action) {
// eslint-disable-next-line no-unused-vars
const { jwt, type, ...actionPayload } = action;
if (jwt && !Object.keys(actionPayload).length) {
const {
enableUserRolesBasedOnToken
} = getState()['features/base/config'];
} = store.getState()['features/base/config'];
action.isGuest = !enableUserRolesBasedOnToken;
@ -103,5 +190,5 @@ function _setJWT({ getState }, next, action) {
}
}
return next(action);
return _maybeSetCallOverlayVisible(store, next, action);
}

View File

@ -1,6 +1,6 @@
import { equals, ReducerRegistry } from '../base/redux';
import { equals, set, ReducerRegistry } from '../base/redux';
import { SET_JWT } from './actionTypes';
import { SET_CALL_OVERLAY_VISIBLE, SET_JWT } from './actionTypes';
/**
* The initial redux state of the feature jwt.
@ -11,6 +11,14 @@ import { SET_JWT } from './actionTypes';
* }}
*/
const _INITIAL_STATE = {
/**
* The indicator which determines whether (the) {@code CallOverlay} is
* visible.
*
* @type {boolean|undefined}
*/
callOverlayVisible: undefined,
/**
* The indicator which determines whether the local participant is a guest
* in the conference.
@ -31,6 +39,9 @@ const _INITIAL_STATE = {
*/
ReducerRegistry.register('features/jwt', (state = _INITIAL_STATE, action) => {
switch (action.type) {
case SET_CALL_OVERLAY_VISIBLE:
return set(state, 'callOverlayVisible', action.callOverlayVisible);
case SET_JWT: {
// eslint-disable-next-line no-unused-vars
const { type, ...payload } = action;

View File

@ -1,6 +1,8 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { CallOverlay } from '../../jwt';
import PageReloadFilmstripOnlyOverlay from './PageReloadFilmstripOnlyOverlay';
import PageReloadOverlay from './PageReloadOverlay';
import SuspendedFilmstripOnlyOverlay from './SuspendedFilmstripOnlyOverlay';
@ -33,6 +35,15 @@ class OverlayContainer extends Component {
*/
_browser: React.PropTypes.string,
/**
* The indicator which determines whether the {@link CallOverlay} is
* displayed/visible.
*
* @private
* @type {boolean}
*/
_callOverlayVisible: React.PropTypes.bool,
/**
* The indicator which determines whether the status of the
* JitsiConnection object has been "established" or not.
@ -172,6 +183,8 @@ class OverlayContainer extends Component {
props = {
browser: this.props._browser
};
} else if (this.props._callOverlayVisible) {
overlayComponent = CallOverlay;
}
return (
@ -182,17 +195,18 @@ class OverlayContainer extends Component {
}
/**
* Maps (parts of) the Redux state to the associated OverlayContainer's props.
* Maps (parts of) the redux state to the associated OverlayContainer's props.
*
* @param {Object} state - The Redux state.
* @param {Object} state - The redux state.
* @returns {{
* _browser: string,
* _connectionEstablished: bool,
* _haveToReload: bool,
* _isNetworkFailure: bool,
* _isMediaPermissionPromptVisible: bool,
* _callOverlayVisible: boolean,
* _connectionEstablished: boolean,
* _haveToReload: boolean,
* _isNetworkFailure: boolean,
* _isMediaPermissionPromptVisible: boolean,
* _reason: string,
* _suspendDetected: bool
* _suspendDetected: boolean
* }}
* @private
*/
@ -203,18 +217,27 @@ function _mapStateToProps(state) {
/**
* The browser which is used currently.
*
* NOTE: Used by UserMediaPermissionsOverlay only.
* NOTE: Used by {@link UserMediaPermissionsOverlay} only.
*
* @private
* @type {string}
*/
_browser: stateFeaturesOverlay.browser,
/**
* The indicator which determines whether the {@link CallOverlay} is
* displayed/visible.
*
* @private
* @type {boolean}
*/
_callOverlayVisible: Boolean(state['features/jwt'].callOverlayVisible),
/**
* The indicator which determines whether the status of the
* JitsiConnection object has been "established" or not.
*
* NOTE: Used by PageReloadOverlay only.
* NOTE: Used by {@link PageReloadOverlay} only.
*
* @private
* @type {boolean}
@ -225,7 +248,7 @@ function _mapStateToProps(state) {
* The indicator which determines whether a critical error for reload
* has been received.
*
* NOTE: Used by PageReloadOverlay only.
* NOTE: Used by {@link PageReloadOverlay} only.
*
* @private
* @type {boolean}
@ -236,7 +259,7 @@ function _mapStateToProps(state) {
* The indicator which determines whether the GUM permissions prompt is
* displayed or not.
*
* NOTE: Used by UserMediaPermissionsOverlay only.
* NOTE: Used by {@link UserMediaPermissionsOverlay} only.
*
* @private
* @type {boolean}
@ -248,7 +271,7 @@ function _mapStateToProps(state) {
* The indicator which determines whether the reload was caused by
* network failure.
*
* NOTE: Used by PageReloadOverlay only.
* NOTE: Used by {@link PageReloadOverlay} only.
*
* @private
* @type {boolean}
@ -258,7 +281,7 @@ function _mapStateToProps(state) {
/**
* The reason for the error that will cause the reload.
*
* NOTE: Used by PageReloadOverlay only.
* NOTE: Used by {@link PageReloadOverlay} only.
*
* @private
* @type {string}
@ -269,7 +292,7 @@ function _mapStateToProps(state) {
* The indicator which determines whether the GUM permissions prompt is
* displayed or not.
*
* NOTE: Used by SuspendedOverlay only.
* NOTE: Used by {@link SuspendedOverlay} only.
*
* @private
* @type {string}

View File

@ -178,7 +178,7 @@ export function hideToolbox(force: boolean = false): Function {
if (!force
&& (hovered
|| APP.UI.isRingOverlayVisible()
|| state['features/jwt'].callOverlayVisible
|| SideContainerToggler.isVisible())) {
dispatch(
setToolboxTimeout(