ref(overlay): The overlays to use React
This commit is contained in:
parent
f3269070b2
commit
92d0589a37
|
@ -20,6 +20,12 @@ import analytics from './modules/analytics/analytics';
|
|||
|
||||
import EventEmitter from "events";
|
||||
|
||||
import { conferenceFailed } from './react/features/base/conference';
|
||||
import {
|
||||
mediaPermissionPromptVisibilityChanged,
|
||||
suspendDetected
|
||||
} from './react/features/overlay';
|
||||
|
||||
const ConnectionEvents = JitsiMeetJS.events.connection;
|
||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
|
||||
|
@ -91,7 +97,10 @@ function createInitialLocalTracksAndConnect(roomName) {
|
|||
|
||||
JitsiMeetJS.mediaDevices.addEventListener(
|
||||
JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
|
||||
browser => APP.UI.showUserMediaPermissionsGuidanceOverlay(browser));
|
||||
browser =>
|
||||
APP.store.dispatch(
|
||||
mediaPermissionPromptVisibilityChanged(true, browser))
|
||||
);
|
||||
|
||||
// First try to retrieve both audio and video.
|
||||
let tryCreateLocalTracks = createLocalTracks(
|
||||
|
@ -109,8 +118,7 @@ function createInitialLocalTracksAndConnect(roomName) {
|
|||
|
||||
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
|
||||
.then(([tracks, con]) => {
|
||||
APP.UI.hideUserMediaPermissionsGuidanceOverlay();
|
||||
|
||||
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
|
||||
if (audioAndVideoError) {
|
||||
if (audioOnlyError) {
|
||||
// If both requests for 'audio' + 'video' and 'audio' only
|
||||
|
@ -334,6 +342,7 @@ class ConferenceConnector {
|
|||
this._reject(err);
|
||||
}
|
||||
_onConferenceFailed(err, ...params) {
|
||||
APP.store.dispatch(conferenceFailed(room, err, ...params));
|
||||
logger.error('CONFERENCE FAILED:', err, ...params);
|
||||
APP.UI.hideRingOverLay();
|
||||
switch (err) {
|
||||
|
@ -408,8 +417,6 @@ class ConferenceConnector {
|
|||
// the app. Both the errors above are unrecoverable from the library
|
||||
// perspective.
|
||||
room.leave().then(() => connection.disconnect());
|
||||
APP.UI.showPageReloadOverlay(
|
||||
false /* not a network type of failure */, err);
|
||||
break;
|
||||
|
||||
case ConferenceErrors.CONFERENCE_MAX_USERS:
|
||||
|
@ -466,6 +473,26 @@ function disconnect() {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles CONNECTION_FAILED events from lib-jitsi-meet.
|
||||
* @param {JitsiMeetJS.connection.error} error the error reported.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function _connectionFailedHandler (error) {
|
||||
switch (error) {
|
||||
case ConnectionErrors.CONNECTION_DROPPED_ERROR:
|
||||
case ConnectionErrors.OTHER_ERROR:
|
||||
case ConnectionErrors.SERVER_ERROR: {
|
||||
APP.connection.removeEventListener( ConnectionEvents.CONNECTION_FAILED,
|
||||
_connectionFailedHandler);
|
||||
if (room)
|
||||
room.leave();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
isModerator: false,
|
||||
audioMuted: false,
|
||||
|
@ -518,11 +545,13 @@ export default {
|
|||
return createInitialLocalTracksAndConnect(options.roomName);
|
||||
}).then(([tracks, con]) => {
|
||||
logger.log('initialized with %s local tracks', tracks.length);
|
||||
con.addEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED,
|
||||
_connectionFailedHandler);
|
||||
APP.connection = connection = con;
|
||||
this.isDesktopSharingEnabled =
|
||||
JitsiMeetJS.isDesktopSharingEnabled();
|
||||
APP.remoteControl.init();
|
||||
this._bindConnectionFailedHandler(con);
|
||||
this._createRoom(tracks);
|
||||
|
||||
if (UIUtil.isButtonEnabled('contacts')
|
||||
|
@ -561,47 +590,6 @@ export default {
|
|||
isLocalId (id) {
|
||||
return this.getMyUserId() === id;
|
||||
},
|
||||
/**
|
||||
* Binds a handler that will handle the case when the connection is dropped
|
||||
* in the middle of the conference.
|
||||
* @param {JitsiConnection} connection the connection to which the handler
|
||||
* will be bound to.
|
||||
* @private
|
||||
*/
|
||||
_bindConnectionFailedHandler (connection) {
|
||||
const handler = function (error, errMsg) {
|
||||
/* eslint-disable no-case-declarations */
|
||||
switch (error) {
|
||||
case ConnectionErrors.CONNECTION_DROPPED_ERROR:
|
||||
case ConnectionErrors.OTHER_ERROR:
|
||||
case ConnectionErrors.SERVER_ERROR:
|
||||
|
||||
logger.error("XMPP connection error: " + errMsg);
|
||||
|
||||
// From all of the cases above only CONNECTION_DROPPED_ERROR
|
||||
// is considered a network type of failure
|
||||
const isNetworkFailure
|
||||
= error === ConnectionErrors.CONNECTION_DROPPED_ERROR;
|
||||
|
||||
APP.UI.showPageReloadOverlay(
|
||||
isNetworkFailure,
|
||||
"xmpp-conn-dropped:" + errMsg);
|
||||
|
||||
connection.removeEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED, handler);
|
||||
|
||||
// FIXME it feels like the conference should be stopped
|
||||
// by lib-jitsi-meet
|
||||
if (room)
|
||||
room.leave();
|
||||
|
||||
break;
|
||||
}
|
||||
/* eslint-enable no-case-declarations */
|
||||
};
|
||||
connection.addEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED, handler);
|
||||
},
|
||||
/**
|
||||
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
|
||||
* @param mute true for mute and false for unmute.
|
||||
|
@ -1365,6 +1353,7 @@ export default {
|
|||
});
|
||||
|
||||
room.on(ConferenceEvents.SUSPEND_DETECTED, () => {
|
||||
APP.store.dispatch(suspendDetected());
|
||||
// After wake up, we will be in a state where conference is left
|
||||
// there will be dialog shown to user.
|
||||
// We do not want video/audio as we show an overlay and after it
|
||||
|
@ -1385,9 +1374,6 @@ export default {
|
|||
if (localAudio) {
|
||||
localAudio.dispose();
|
||||
}
|
||||
|
||||
// show overlay
|
||||
APP.UI.showSuspendedOverlay();
|
||||
});
|
||||
|
||||
room.on(ConferenceEvents.DTMF_SUPPORT_CHANGED, (isDTMFSupported) => {
|
||||
|
|
|
@ -4,6 +4,11 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
|
|||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||
import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
|
||||
|
||||
import {
|
||||
connectionEstablished,
|
||||
connectionFailed
|
||||
} from './react/features/base/connection';
|
||||
|
||||
const ConnectionEvents = JitsiMeetJS.events.connection;
|
||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
|
||||
|
@ -67,6 +72,23 @@ function connect(id, password, roomName) {
|
|||
connection.addEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
|
||||
);
|
||||
connection.addEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED, connectionFailedHandler);
|
||||
|
||||
function connectionFailedHandler (error, errMsg) {
|
||||
APP.store.dispatch(connectionFailed(connection, error, errMsg));
|
||||
|
||||
switch (error) {
|
||||
case ConnectionErrors.CONNECTION_DROPPED_ERROR:
|
||||
case ConnectionErrors.OTHER_ERROR:
|
||||
case ConnectionErrors.SERVER_ERROR: {
|
||||
connection.removeEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailedHandler);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unsubscribe() {
|
||||
connection.removeEventListener(
|
||||
|
@ -80,6 +102,7 @@ function connect(id, password, roomName) {
|
|||
}
|
||||
|
||||
function handleConnectionEstablished() {
|
||||
APP.store.dispatch(connectionEstablished(connection));
|
||||
unsubscribe();
|
||||
resolve(connection);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
line-height: 20px;
|
||||
}
|
||||
|
||||
.reload_overlay_msg {
|
||||
.reload_overlay_text {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 30px;
|
||||
|
|
|
@ -15,18 +15,13 @@ import UIEvents from "../../service/UI/UIEvents";
|
|||
import EtherpadManager from './etherpad/Etherpad';
|
||||
import SharedVideoManager from './shared_video/SharedVideo';
|
||||
import Recording from "./recording/Recording";
|
||||
import GumPermissionsOverlay
|
||||
from './gum_overlay/UserMediaPermissionsGuidanceOverlay';
|
||||
|
||||
import * as PageReloadOverlay from './reload_overlay/PageReloadOverlay';
|
||||
import SuspendedOverlay from './suspended_overlay/SuspendedOverlay';
|
||||
import VideoLayout from "./videolayout/VideoLayout";
|
||||
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 { randomInt } from "../../react/features/base/util/randomUtil";
|
||||
import UIErrors from './UIErrors';
|
||||
import { debounce } from "../util/helpers";
|
||||
|
||||
|
@ -40,6 +35,17 @@ import FollowMe from "../FollowMe";
|
|||
var eventEmitter = new EventEmitter();
|
||||
UI.eventEmitter = eventEmitter;
|
||||
|
||||
/**
|
||||
* Whether an overlay is visible or not.
|
||||
*
|
||||
* FIXME: This is temporary solution. Don't use this variable!
|
||||
* Should be removed when all the code is move to react.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @public
|
||||
*/
|
||||
UI.overlayVisible = false;
|
||||
|
||||
let etherpadManager;
|
||||
let sharedVideoManager;
|
||||
|
||||
|
@ -1087,20 +1093,6 @@ UI.notifyFocusDisconnected = function (focus, retrySec) {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify the user that the video conferencing service is badly broken and
|
||||
* the page should be reloaded.
|
||||
*
|
||||
* @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} a label string identifying the reason for the page reload
|
||||
* which will be included in details of the log event.
|
||||
*/
|
||||
UI.showPageReloadOverlay = function (isNetworkFailure, reason) {
|
||||
// Reload the page after 10 - 30 seconds
|
||||
PageReloadOverlay.show(10 + randomInt(0, 20), isNetworkFailure, reason);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates auth info on the UI.
|
||||
* @param {boolean} isAuthEnabled if authentication is enabled
|
||||
|
@ -1414,10 +1406,7 @@ UI.hideRingOverLay = function () {
|
|||
* @returns {*|boolean} {true} if the overlay is visible, {false} otherwise
|
||||
*/
|
||||
UI.isOverlayVisible = function () {
|
||||
return RingOverlay.isVisible()
|
||||
|| SuspendedOverlay.isVisible()
|
||||
|| PageReloadOverlay.isVisible()
|
||||
|| GumPermissionsOverlay.isVisible();
|
||||
return RingOverlay.isVisible() || this.overlayVisible;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1429,29 +1418,6 @@ UI.isRingOverlayVisible = function () {
|
|||
return RingOverlay.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.
|
||||
*/
|
||||
UI.showUserMediaPermissionsGuidanceOverlay = function (browser) {
|
||||
GumPermissionsOverlay.show(browser);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows suspended overlay with a button to rejoin conference.
|
||||
*/
|
||||
UI.showSuspendedOverlay = function () {
|
||||
SuspendedOverlay.show();
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides browser-specific overlay with guidance how to proceed with gUM prompt.
|
||||
*/
|
||||
UI.hideUserMediaPermissionsGuidanceOverlay = function () {
|
||||
GumPermissionsOverlay.hide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles user's features changes.
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -1,3 +1,5 @@
|
|||
/* global APP */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { compose, createStore } from 'redux';
|
||||
|
@ -300,6 +302,12 @@ export class AbstractApp extends Component {
|
|||
|
||||
if (typeof store === 'undefined') {
|
||||
store = this._createStore();
|
||||
|
||||
// This is temporary workaround to be able to dispatch actions from
|
||||
// non-reactified parts of the code (conference.js for example).
|
||||
// Don't use in the react code!!!
|
||||
// FIXME: remove when the reactification is finished!
|
||||
APP.store = store;
|
||||
}
|
||||
|
||||
return store;
|
||||
|
|
|
@ -34,7 +34,7 @@ function _addConferenceListeners(conference, dispatch) {
|
|||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_FAILED,
|
||||
(...args) => dispatch(_conferenceFailed(conference, ...args)));
|
||||
(...args) => dispatch(conferenceFailed(conference, ...args)));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_JOINED,
|
||||
(...args) => dispatch(_conferenceJoined(conference, ...args)));
|
||||
|
@ -87,8 +87,9 @@ function _addConferenceListeners(conference, dispatch) {
|
|||
* conference: JitsiConference,
|
||||
* error: string
|
||||
* }}
|
||||
* @public
|
||||
*/
|
||||
function _conferenceFailed(conference, error) {
|
||||
export function conferenceFailed(conference, error) {
|
||||
return {
|
||||
type: CONFERENCE_FAILED,
|
||||
conference,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* global APP */
|
||||
import { CONNECTION_ESTABLISHED } from '../connection';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
|
@ -53,7 +54,11 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
function _connectionEstablished(store, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
store.dispatch(createConference());
|
||||
// FIXME: workaround for the web version. Currently the creation of the
|
||||
// conference is handled by /conference.js
|
||||
if (!APP) {
|
||||
store.dispatch(createConference());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -43,13 +43,13 @@ export function connect() {
|
|||
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||
connectionDisconnected);
|
||||
_onConnectionDisconnected);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
connectionEstablished);
|
||||
_onConnectionEstablished);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailed);
|
||||
_onConnectionFailed);
|
||||
|
||||
connection.connect();
|
||||
|
||||
|
@ -59,11 +59,12 @@ export function connect() {
|
|||
*
|
||||
* @param {string} message - Disconnect reason.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function connectionDisconnected(message: string) {
|
||||
function _onConnectionDisconnected(message: string) {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||
connectionDisconnected);
|
||||
_onConnectionDisconnected);
|
||||
|
||||
dispatch(_connectionDisconnected(connection, message));
|
||||
}
|
||||
|
@ -72,10 +73,11 @@ export function connect() {
|
|||
* Resolves external promise when connection is established.
|
||||
*
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function connectionEstablished() {
|
||||
function _onConnectionEstablished() {
|
||||
unsubscribe();
|
||||
dispatch(_connectionEstablished(connection));
|
||||
dispatch(connectionEstablished(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,11 +85,12 @@ export function connect() {
|
|||
*
|
||||
* @param {JitsiConnectionErrors} err - Connection error.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function connectionFailed(err) {
|
||||
function _onConnectionFailed(err) {
|
||||
unsubscribe();
|
||||
console.error('CONNECTION FAILED:', err);
|
||||
dispatch(_connectionFailed(connection, err));
|
||||
dispatch(connectionFailed(connection, err, ''));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,10 +102,10 @@ export function connect() {
|
|||
function unsubscribe() {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
connectionEstablished);
|
||||
_onConnectionEstablished);
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailed);
|
||||
_onConnectionFailed);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -183,13 +186,13 @@ function _connectionDisconnected(connection, message: string) {
|
|||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which was
|
||||
* established.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_ESTABLISHED,
|
||||
* connection: JitsiConnection
|
||||
* }}
|
||||
* @public
|
||||
*/
|
||||
function _connectionEstablished(connection) {
|
||||
export function connectionEstablished(connection: Object) {
|
||||
return {
|
||||
type: CONNECTION_ESTABLISHED,
|
||||
connection
|
||||
|
@ -200,18 +203,22 @@ function _connectionEstablished(connection) {
|
|||
* Create an action for when the signaling connection could not be created.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which failed.
|
||||
* @param {string} error - Error message.
|
||||
* @private
|
||||
* @param {string} error - Error.
|
||||
* @param {string} errorMessage - Error message.
|
||||
* @returns {{
|
||||
* type: CONNECTION_FAILED,
|
||||
* connection: JitsiConnection,
|
||||
* error: string
|
||||
* error: string,
|
||||
* errorMessage: string
|
||||
* }}
|
||||
* @public
|
||||
*/
|
||||
function _connectionFailed(connection, error: string) {
|
||||
export function connectionFailed(
|
||||
connection: Object, error: string, errorMessage: string) {
|
||||
return {
|
||||
type: CONNECTION_FAILED,
|
||||
connection,
|
||||
error
|
||||
error,
|
||||
errorMessage
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,6 +12,11 @@ declare var JitsiMeetJS: Object;
|
|||
const JitsiConferenceEvents = JitsiMeetJS.events.conference;
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
export {
|
||||
connectionEstablished,
|
||||
connectionFailed
|
||||
} from './actions.native.js';
|
||||
|
||||
/**
|
||||
* Opens new connection.
|
||||
*
|
||||
|
|
|
@ -7,6 +7,8 @@ import { connect, disconnect } from '../../base/connection';
|
|||
import { Watermarks } from '../../base/react';
|
||||
import { FeedbackButton } from '../../feedback';
|
||||
|
||||
import { OverlayContainer } from '../../overlay';
|
||||
|
||||
/**
|
||||
* For legacy reasons, inline style for display none.
|
||||
*
|
||||
|
@ -162,6 +164,7 @@ class Conference extends Component {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OverlayContainer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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