ref(recording): convert recording label to react (#1915)
* ref(recording): convert recording label to react - Create a RecordingLabel component for displaying the current recording state, as reflected in the redux store. This is needed for 1-on-1 mode to be completely in redux. - Update the store with the recording state so RecordingLabel can update itself. - Remove previous logic for updating the non-react label, which includes event emitting for filmstrip visibility changes, as RecordingLabel is hooked into redux updates. * ref(recording): use status and type constants from lib * make label really dumb, move logic back to Recording
This commit is contained in:
parent
e04129bf4d
commit
735a596afe
|
@ -1,4 +1,3 @@
|
||||||
.recordingSpinner {
|
.recordingSpinner {
|
||||||
display: none;
|
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,12 +154,15 @@
|
||||||
bottom: 45%;
|
bottom: 45%;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
display: none;
|
display: none;
|
||||||
-webkit-transition: all 2s 2s linear;
|
padding: 10px;
|
||||||
transition: all 2s 2s linear;
|
transform: translate(-50%, 0);
|
||||||
z-index: $centeredVideoLabelZ;
|
z-index: $centeredVideoLabelZ;
|
||||||
|
|
||||||
&.moveToCorner {
|
&.moveToCorner {
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
|
transform: none;
|
||||||
|
-webkit-transition: all 2s 2s linear;
|
||||||
|
transition: all 2s 2s linear;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,50 @@ import VideoLayout from '../videolayout/VideoLayout';
|
||||||
|
|
||||||
import { setToolboxEnabled } from '../../../react/features/toolbox';
|
import { setToolboxEnabled } from '../../../react/features/toolbox';
|
||||||
import { setNotificationsEnabled } from '../../../react/features/notifications';
|
import { setNotificationsEnabled } from '../../../react/features/notifications';
|
||||||
|
import {
|
||||||
|
hideRecordingLabel,
|
||||||
|
updateRecordingState
|
||||||
|
} from '../../../react/features/recording';
|
||||||
|
|
||||||
|
const Status = JitsiMeetJS.constants.recordingStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translation keys to use for display in the UI when recording the conference
|
||||||
|
* but not streaming live.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
export const RECORDING_TRANSLATION_KEYS = {
|
||||||
|
failedToStartKey: 'recording.failedToStart',
|
||||||
|
recordingBusy: 'liveStreaming.busy',
|
||||||
|
recordingButtonTooltip: 'recording.buttonTooltip',
|
||||||
|
recordingErrorKey: 'recording.error',
|
||||||
|
recordingOffKey: 'recording.off',
|
||||||
|
recordingOnKey: 'recording.on',
|
||||||
|
recordingPendingKey: 'recording.pending',
|
||||||
|
recordingTitle: 'dialog.recording',
|
||||||
|
recordingUnavailable: 'recording.unavailable'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translation keys to use for display in the UI when the recording mode is
|
||||||
|
* currently streaming live.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
export const STREAMING_TRANSLATION_KEYS = {
|
||||||
|
failedToStartKey: 'liveStreaming.failedToStart',
|
||||||
|
recordingBusy: 'liveStreaming.busy',
|
||||||
|
recordingButtonTooltip: 'liveStreaming.buttonTooltip',
|
||||||
|
recordingErrorKey: 'liveStreaming.error',
|
||||||
|
recordingOffKey: 'liveStreaming.off',
|
||||||
|
recordingOnKey: 'liveStreaming.on',
|
||||||
|
recordingPendingKey: 'liveStreaming.pending',
|
||||||
|
recordingTitle: 'dialog.liveStreaming',
|
||||||
|
recordingUnavailable: 'liveStreaming.unavailable'
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The dialog for user input.
|
* The dialog for user input.
|
||||||
|
@ -194,57 +238,6 @@ function _showStopRecordingPrompt(recordingType) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves the element given by {selector} to the top right corner of the screen.
|
|
||||||
* Set additional classes that can be used to style the selector relative to the
|
|
||||||
* state of the filmstrip.
|
|
||||||
*
|
|
||||||
* @param selector the selector for the element to move
|
|
||||||
* @param move {true} to move the element, {false} to move it back to its intial
|
|
||||||
* position
|
|
||||||
*/
|
|
||||||
function moveToCorner(selector, move) {
|
|
||||||
let moveToCornerClass = "moveToCorner";
|
|
||||||
let containsClass = selector.hasClass(moveToCornerClass);
|
|
||||||
|
|
||||||
if (move && !containsClass)
|
|
||||||
selector.addClass(moveToCornerClass);
|
|
||||||
else if (!move && containsClass)
|
|
||||||
selector.removeClass(moveToCornerClass);
|
|
||||||
|
|
||||||
const {
|
|
||||||
remoteVideosVisible,
|
|
||||||
visible
|
|
||||||
} = APP.store.getState()['features/filmstrip'];
|
|
||||||
const filmstripWasHidden = selector.hasClass('without-filmstrip');
|
|
||||||
const filmstipIsOpening = filmstripWasHidden && visible;
|
|
||||||
selector.toggleClass('opening', filmstipIsOpening);
|
|
||||||
|
|
||||||
selector.toggleClass('with-filmstrip', visible);
|
|
||||||
selector.toggleClass('without-filmstrip', !visible);
|
|
||||||
|
|
||||||
selector.toggleClass('with-remote-videos', remoteVideosVisible);
|
|
||||||
selector.toggleClass('without-remote-videos', !remoteVideosVisible);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The status of the recorder.
|
|
||||||
* FIXME: Those constants should come from the library.
|
|
||||||
* @type {{ON: string, OFF: string, AVAILABLE: string,
|
|
||||||
* UNAVAILABLE: string, PENDING: string}}
|
|
||||||
*/
|
|
||||||
var Status = {
|
|
||||||
ON: "on",
|
|
||||||
OFF: "off",
|
|
||||||
AVAILABLE: "available",
|
|
||||||
UNAVAILABLE: "unavailable",
|
|
||||||
PENDING: "pending",
|
|
||||||
RETRYING: "retrying",
|
|
||||||
ERROR: "error",
|
|
||||||
FAILED: "failed",
|
|
||||||
BUSY: "busy"
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether if the given status is either PENDING or RETRYING
|
* Checks whether if the given status is either PENDING or RETRYING
|
||||||
* @param status {Status} Jibri status to be checked
|
* @param status {Status} Jibri status to be checked
|
||||||
|
@ -271,27 +264,11 @@ var Recording = {
|
||||||
|
|
||||||
if (recordingType === 'jibri') {
|
if (recordingType === 'jibri') {
|
||||||
this.baseClass = "fa fa-play-circle";
|
this.baseClass = "fa fa-play-circle";
|
||||||
this.recordingTitle = "dialog.liveStreaming";
|
Object.assign(this, STREAMING_TRANSLATION_KEYS);
|
||||||
this.recordingOnKey = "liveStreaming.on";
|
|
||||||
this.recordingOffKey = "liveStreaming.off";
|
|
||||||
this.recordingPendingKey = "liveStreaming.pending";
|
|
||||||
this.failedToStartKey = "liveStreaming.failedToStart";
|
|
||||||
this.recordingErrorKey = "liveStreaming.error";
|
|
||||||
this.recordingButtonTooltip = "liveStreaming.buttonTooltip";
|
|
||||||
this.recordingUnavailable = "liveStreaming.unavailable";
|
|
||||||
this.recordingBusy = "liveStreaming.busy";
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.baseClass = "icon-recEnable";
|
this.baseClass = "icon-recEnable";
|
||||||
this.recordingTitle = "dialog.recording";
|
Object.assign(this, RECORDING_TRANSLATION_KEYS);
|
||||||
this.recordingOnKey = "recording.on";
|
|
||||||
this.recordingOffKey = "recording.off";
|
|
||||||
this.recordingPendingKey = "recording.pending";
|
|
||||||
this.failedToStartKey = "recording.failedToStart";
|
|
||||||
this.recordingErrorKey = "recording.error";
|
|
||||||
this.recordingButtonTooltip = "recording.buttonTooltip";
|
|
||||||
this.recordingUnavailable = "recording.unavailable";
|
|
||||||
this.recordingBusy = "liveStreaming.busy";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX Due to the React-ification of Toolbox, the HTMLElement with id
|
// XXX Due to the React-ification of Toolbox, the HTMLElement with id
|
||||||
|
@ -311,10 +288,6 @@ var Recording = {
|
||||||
APP.store.dispatch(setNotificationsEnabled(false));
|
APP.store.dispatch(setNotificationsEnabled(false));
|
||||||
APP.UI.messageHandler.enablePopups(false);
|
APP.UI.messageHandler.enablePopups(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventEmitter.addListener(UIEvents.UPDATED_FILMSTRIP_DISPLAY, () =>{
|
|
||||||
this._updateStatusLabel();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -364,65 +337,85 @@ var Recording = {
|
||||||
let oldState = this.currentState;
|
let oldState = this.currentState;
|
||||||
this.currentState = recordingState;
|
this.currentState = recordingState;
|
||||||
|
|
||||||
// TODO: handle recording state=available
|
let labelDisplayConfiguration;
|
||||||
if (recordingState === Status.ON ||
|
|
||||||
recordingState === Status.RETRYING) {
|
switch (recordingState) {
|
||||||
|
case Status.ON:
|
||||||
|
case Status.RETRYING: {
|
||||||
|
labelDisplayConfiguration = {
|
||||||
|
centered: false,
|
||||||
|
key: this.recordingOnKey,
|
||||||
|
showSpinner: recordingState === Status.RETRYING
|
||||||
|
};
|
||||||
|
|
||||||
this._setToolbarButtonToggled(true);
|
this._setToolbarButtonToggled(true);
|
||||||
|
|
||||||
this._updateStatusLabel(this.recordingOnKey, false);
|
break;
|
||||||
}
|
}
|
||||||
else if (recordingState === Status.OFF
|
|
||||||
|| recordingState === Status.UNAVAILABLE
|
|
||||||
|| recordingState === Status.BUSY
|
|
||||||
|| recordingState === Status.FAILED) {
|
|
||||||
|
|
||||||
// We don't want to do any changes if this is
|
case Status.OFF:
|
||||||
// an availability change.
|
case Status.BUSY:
|
||||||
if (oldState !== Status.ON
|
case Status.FAILED:
|
||||||
&& !isStartingStatus(oldState))
|
case Status.UNAVAILABLE: {
|
||||||
|
const wasInStartingStatus = isStartingStatus(oldState);
|
||||||
|
|
||||||
|
// We don't want UI changes if this is an availability change.
|
||||||
|
if (oldState !== Status.ON && !wasInStartingStatus) {
|
||||||
|
APP.store.dispatch(updateRecordingState({ recordingState }));
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
labelDisplayConfiguration = {
|
||||||
|
centered: true,
|
||||||
|
key: wasInStartingStatus
|
||||||
|
? this.failedToStartKey
|
||||||
|
: this.recordingOffKey
|
||||||
|
};
|
||||||
|
|
||||||
this._setToolbarButtonToggled(false);
|
this._setToolbarButtonToggled(false);
|
||||||
|
|
||||||
let messageKey;
|
|
||||||
if (isStartingStatus(oldState))
|
|
||||||
messageKey = this.failedToStartKey;
|
|
||||||
else
|
|
||||||
messageKey = this.recordingOffKey;
|
|
||||||
|
|
||||||
this._updateStatusLabel(messageKey, true);
|
|
||||||
|
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
$('#recordingLabel').css({display: "none"});
|
APP.store.dispatch(hideRecordingLabel());
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (recordingState === Status.PENDING) {
|
|
||||||
|
case Status.PENDING: {
|
||||||
|
labelDisplayConfiguration = {
|
||||||
|
centered: true,
|
||||||
|
key: this.recordingPendingKey
|
||||||
|
};
|
||||||
|
|
||||||
this._setToolbarButtonToggled(false);
|
this._setToolbarButtonToggled(false);
|
||||||
|
|
||||||
this._updateStatusLabel(this.recordingPendingKey, true);
|
break;
|
||||||
}
|
}
|
||||||
else if (recordingState === Status.ERROR
|
|
||||||
|| recordingState === Status.FAILED) {
|
case Status.ERROR: {
|
||||||
|
labelDisplayConfiguration = {
|
||||||
|
centered: true,
|
||||||
|
key: this.recordingErrorKey
|
||||||
|
};
|
||||||
|
|
||||||
this._setToolbarButtonToggled(false);
|
this._setToolbarButtonToggled(false);
|
||||||
|
|
||||||
this._updateStatusLabel(this.recordingErrorKey, true);
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let labelSelector = $('#recordingLabel');
|
// Return an empty label display configuration to indicate no label
|
||||||
|
// should be displayed. The Status.AVAIABLE case is handled here.
|
||||||
|
default: {
|
||||||
|
labelDisplayConfiguration = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We don't show the label for available state.
|
APP.store.dispatch(updateRecordingState({
|
||||||
if (recordingState !== Status.AVAILABLE
|
labelDisplayConfiguration,
|
||||||
&& !labelSelector.is(":visible"))
|
recordingState
|
||||||
labelSelector.css({display: "inline-block"});
|
}));
|
||||||
|
|
||||||
// Recording spinner
|
|
||||||
let spinnerId = 'recordingSpinner';
|
|
||||||
UIUtil.setVisible(
|
|
||||||
spinnerId, recordingState === Status.RETRYING);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// checks whether recording is enabled and whether we have params
|
// checks whether recording is enabled and whether we have params
|
||||||
// to start automatically recording
|
// to start automatically recording
|
||||||
checkAutoRecord() {
|
checkAutoRecord() {
|
||||||
|
@ -432,21 +425,6 @@ var Recording = {
|
||||||
this.predefinedToken);
|
this.predefinedToken);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* Updates the status label.
|
|
||||||
* @param textKey the text to show
|
|
||||||
* @param isCentered indicates if the label should be centered on the window
|
|
||||||
* or moved to the top right corner.
|
|
||||||
*/
|
|
||||||
_updateStatusLabel(textKey, isCentered) {
|
|
||||||
let labelSelector = $('#recordingLabel');
|
|
||||||
let labelTextSelector = $('#recordingLabelText');
|
|
||||||
|
|
||||||
moveToCorner(labelSelector, !isCentered);
|
|
||||||
|
|
||||||
labelTextSelector.attr("data-i18n", textKey);
|
|
||||||
APP.translation.translateElement(labelSelector);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles {@code click} on {@code toolbar_button_record}.
|
* Handles {@code click} on {@code toolbar_button_record}.
|
||||||
|
|
|
@ -4,12 +4,6 @@ import { MiddlewareRegistry } from '../base/redux';
|
||||||
import { SET_CALL_OVERLAY_VISIBLE } from '../jwt';
|
import { SET_CALL_OVERLAY_VISIBLE } from '../jwt';
|
||||||
|
|
||||||
import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
|
import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
|
||||||
import UIEvents from '../../../service/UI/UIEvents';
|
|
||||||
|
|
||||||
import {
|
|
||||||
SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
|
|
||||||
SET_FILMSTRIP_VISIBILITY
|
|
||||||
} from './actionTypes';
|
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
|
||||||
|
@ -37,16 +31,6 @@ MiddlewareRegistry.register(({ getState }) => next => action => {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
break;
|
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 next(action);
|
return next(action);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import React, { Component } from 'react';
|
||||||
|
|
||||||
import { Watermarks } from '../../base/react';
|
import { Watermarks } from '../../base/react';
|
||||||
import { VideoQualityLabel } from '../../video-quality';
|
import { VideoQualityLabel } from '../../video-quality';
|
||||||
|
import { RecordingLabel } from '../../recording';
|
||||||
|
|
||||||
declare var interfaceConfig: Object;
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
|
@ -67,18 +68,8 @@ export default class LargeVideo extends Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span id = 'localConnectionMessage' />
|
<span id = 'localConnectionMessage' />
|
||||||
|
|
||||||
{ interfaceConfig.filmStripOnly ? null : <VideoQualityLabel /> }
|
{ interfaceConfig.filmStripOnly ? null : <VideoQualityLabel /> }
|
||||||
|
<RecordingLabel />
|
||||||
<span
|
|
||||||
className = 'video-state-indicator centeredVideoLabel'
|
|
||||||
id = 'recordingLabel'>
|
|
||||||
<span id = 'recordingLabelText' />
|
|
||||||
<img
|
|
||||||
className = 'recordingSpinner'
|
|
||||||
id = 'recordingSpinner'
|
|
||||||
src = 'images/spin.svg' />
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* The type of Redux action which signals for the label indicating current
|
||||||
|
* recording state to stop displaying.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: HIDE_RECORDING_LABEL
|
||||||
|
* }
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const HIDE_RECORDING_LABEL = Symbol('HIDE_RECORDING_LABEL');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of Redux action which updates the current known state of the
|
||||||
|
* recording feature.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: RECORDING_STATE_UPDATED,
|
||||||
|
* recordingState: string
|
||||||
|
* }
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const RECORDING_STATE_UPDATED = Symbol('RECORDING_STATE_UPDATED');
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { HIDE_RECORDING_LABEL, RECORDING_STATE_UPDATED } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides any displayed recording label, regardless of current recording state.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: HIDE_RECORDING_LABEL
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function hideRecordingLabel() {
|
||||||
|
return {
|
||||||
|
type: HIDE_RECORDING_LABEL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the redux state for the recording feature.
|
||||||
|
*
|
||||||
|
* @param {Object} recordingState - The new state to merge with the existing
|
||||||
|
* state in redux.
|
||||||
|
* @returns {{
|
||||||
|
* type: RECORDING_STATE_UPDATED,
|
||||||
|
* recordingState: Object
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function updateRecordingState(recordingState = {}) {
|
||||||
|
return {
|
||||||
|
type: RECORDING_STATE_UPDATED,
|
||||||
|
recordingState
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { translate } from '../../base/i18n';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React {@link Component} which displays the current state of
|
||||||
|
* conference recording. Currently it uses CSS to display itself automatically
|
||||||
|
* when there is a recording state update.
|
||||||
|
*
|
||||||
|
* @extends {Component}
|
||||||
|
*/
|
||||||
|
class RecordingLabel extends Component {
|
||||||
|
/**
|
||||||
|
* {@code RecordingLabel} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* Whether or not the filmstrip is currently visible or toggled to
|
||||||
|
* hidden. Depending on the filmstrip state, different CSS classes will
|
||||||
|
* be set to allow for adjusting of {@code RecordingLabel} positioning.
|
||||||
|
*/
|
||||||
|
_filmstripVisible: React.PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object to describe the {@code RecordingLabel} content. If no
|
||||||
|
* translation key to display is specified, the label will apply CSS to
|
||||||
|
* itself so it can be made invisible.
|
||||||
|
* {{
|
||||||
|
* centered: boolean,
|
||||||
|
* key: string,
|
||||||
|
* showSpinner: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
_labelDisplayConfiguration: React.PropTypes.object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not remote videos within the filmstrip are currently
|
||||||
|
* visible. Depending on the visibility state, coupled with filmstrip
|
||||||
|
* visibility, CSS classes will be set to allow for adjusting of
|
||||||
|
* {@code RecordingLabel} positioning.
|
||||||
|
*/
|
||||||
|
_remoteVideosVisible: React.PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to obtain translated string.
|
||||||
|
*/
|
||||||
|
t: React.PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code RecordingLabel} instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
/**
|
||||||
|
* Whether or not the filmstrip was not visible but has transitioned
|
||||||
|
* in the latest component update to visible. This boolean is used
|
||||||
|
* to set a class for position animations.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
filmstripBecomingVisible: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the state for whether or not the filmstrip is being toggled to
|
||||||
|
* display after having being hidden.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @param {Object} nextProps - The read-only props which this Component will
|
||||||
|
* receive.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.setState({
|
||||||
|
filmstripBecomingVisible: nextProps._filmstripVisible
|
||||||
|
&& !this.props._filmstripVisible
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { _labelDisplayConfiguration } = this.props;
|
||||||
|
const { centered, key, showSpinner } = _labelDisplayConfiguration || {};
|
||||||
|
|
||||||
|
const isVisible = Boolean(key);
|
||||||
|
const rootClassName = [
|
||||||
|
'video-state-indicator centeredVideoLabel',
|
||||||
|
isVisible ? 'show-inline' : '',
|
||||||
|
centered ? '' : 'moveToCorner',
|
||||||
|
this.state.filmstripBecomingVisible ? 'opening' : '',
|
||||||
|
this.props._filmstripVisible
|
||||||
|
? 'with-filmstrip' : 'without-filmstrip',
|
||||||
|
this.props._remoteVideosVisible
|
||||||
|
? 'with-remote-videos' : 'without-remote-videos'
|
||||||
|
].join(' ');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className = { rootClassName }
|
||||||
|
id = 'recordingLabel'>
|
||||||
|
<span id = 'recordingLabelText'>
|
||||||
|
{ this.props.t(key) }
|
||||||
|
</span>
|
||||||
|
{ showSpinner
|
||||||
|
? <img
|
||||||
|
className = 'recordingSpinner'
|
||||||
|
id = 'recordingSpinner'
|
||||||
|
src = 'images/spin.svg' />
|
||||||
|
: null }
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated {@code RecordingLabel}
|
||||||
|
* component's props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _filmstripVisible: boolean,
|
||||||
|
* _labelDisplayConfiguration: Object,
|
||||||
|
* _remoteVideosVisible: boolean,
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
const { remoteVideosVisible, visible } = state['features/filmstrip'];
|
||||||
|
const { labelDisplayConfiguration } = state['features/recording'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Whether or not the filmstrip is currently set to be displayed.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_filmstripVisible: visible,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object describing how {@code RecordingLabel} should display its
|
||||||
|
* contents.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
_labelDisplayConfiguration: labelDisplayConfiguration,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not remote videos are displayed in the filmstrip.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_remoteVideosVisible: remoteVideosVisible
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect(_mapStateToProps)(RecordingLabel));
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as RecordingLabel } from './RecordingLabel';
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './actions';
|
||||||
|
export * from './components';
|
||||||
|
|
||||||
|
import './reducer';
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { ReducerRegistry } from '../base/redux';
|
||||||
|
import { HIDE_RECORDING_LABEL, RECORDING_STATE_UPDATED } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces the Redux actions of the feature features/recording.
|
||||||
|
*/
|
||||||
|
ReducerRegistry.register('features/recording', (state = {}, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case HIDE_RECORDING_LABEL:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
labelDisplayConfiguration: null
|
||||||
|
};
|
||||||
|
|
||||||
|
case RECORDING_STATE_UPDATED:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.recordingState
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
});
|
|
@ -66,11 +66,6 @@ export default {
|
||||||
*/
|
*/
|
||||||
TOGGLED_FILMSTRIP: "UI.toggled_filmstrip",
|
TOGGLED_FILMSTRIP: "UI.toggled_filmstrip",
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies that the filmstrip has updated its appearance, such as by
|
|
||||||
* toggling or removing videos or adding videos.
|
|
||||||
*/
|
|
||||||
UPDATED_FILMSTRIP_DISPLAY: "UI.updated_filmstrip_display",
|
|
||||||
TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
|
TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
|
||||||
TOGGLED_SHARED_DOCUMENT: "UI.toggled_shared_document",
|
TOGGLED_SHARED_DOCUMENT: "UI.toggled_shared_document",
|
||||||
CONTACT_CLICKED: "UI.contact_clicked",
|
CONTACT_CLICKED: "UI.contact_clicked",
|
||||||
|
|
Loading…
Reference in New Issue