From 152427e01b1f79494838ed3950d0d1316d2f28ea Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 19 Jun 2017 11:01:44 -0500 Subject: [PATCH] feat(small-video): convert the "toolbar" to react Move display of audio muted, video muted, and moderator icons, which make up the elements of the small video toolbar, into React Components. --- css/_vertical_filmstrip_overrides.scss | 1 + modules/UI/videolayout/RemoteVideo.js | 8 ++ modules/UI/videolayout/SmallVideo.js | 125 +++++------------- react/features/filmstrip/components/_.web.js | 1 + react/features/filmstrip/components/index.js | 2 + .../components/web/AudioMutedIndicator.js | 24 ++++ .../filmstrip/components/web/BaseIndicator.js | 110 +++++++++++++++ .../components/web/ModeratorIndicator.js | 24 ++++ .../components/web/VideoMutedIndicator.js | 24 ++++ .../filmstrip/components/web/index.js | 3 + 10 files changed, 227 insertions(+), 95 deletions(-) create mode 100644 react/features/filmstrip/components/web/AudioMutedIndicator.js create mode 100644 react/features/filmstrip/components/web/BaseIndicator.js create mode 100644 react/features/filmstrip/components/web/ModeratorIndicator.js create mode 100644 react/features/filmstrip/components/web/VideoMutedIndicator.js create mode 100644 react/features/filmstrip/components/web/index.js diff --git a/css/_vertical_filmstrip_overrides.scss b/css/_vertical_filmstrip_overrides.scss index 49c8a683f..d3ac48dd7 100644 --- a/css/_vertical_filmstrip_overrides.scss +++ b/css/_vertical_filmstrip_overrides.scss @@ -82,6 +82,7 @@ .toolbar-icon { float: none; + margin: auto; } } } diff --git a/modules/UI/videolayout/RemoteVideo.js b/modules/UI/videolayout/RemoteVideo.js index 3d31f05bc..7ada86ea0 100644 --- a/modules/UI/videolayout/RemoteVideo.js +++ b/modules/UI/videolayout/RemoteVideo.js @@ -2,6 +2,7 @@ /* eslint-disable no-unused-vars */ import React from 'react'; +import ReactDOM from 'react-dom'; import { MuteButton, @@ -515,6 +516,13 @@ RemoteVideo.prototype.remove = function () { this.removeAudioLevelIndicator(); + const toolbarContainer + = this.container.querySelector('.videocontainer__toolbar'); + + if (toolbarContainer) { + ReactDOM.unmountComponentAtNode(toolbarContainer); + } + this.removeConnectionIndicator(); this.removeDisplayName(); diff --git a/modules/UI/videolayout/SmallVideo.js b/modules/UI/videolayout/SmallVideo.js index 2c4dfb95d..b07d65540 100644 --- a/modules/UI/videolayout/SmallVideo.js +++ b/modules/UI/videolayout/SmallVideo.js @@ -16,7 +16,12 @@ import { ConnectionIndicator } from '../../../react/features/connection-indicator'; import { DisplayName } from '../../../react/features/display-name'; -/* eslint-disable no-unused-vars */ +import { + AudioMutedIndicator, + ModeratorIndicator, + VideoMutedIndicator +} from '../../../react/features/filmstrip'; +/* eslint-enable no-unused-vars */ const logger = require("jitsi-meet-logger").getLogger(__filename); @@ -64,6 +69,7 @@ const DISPLAY_VIDEO_WITH_NAME = 3; const DISPLAY_AVATAR_WITH_NAME = 4; function SmallVideo(VideoLayout) { + this._isModerator = false; this.isAudioMuted = false; this.hasAvatar = false; this.isVideoMuted = false; @@ -270,43 +276,8 @@ SmallVideo.prototype.updateConnectionStatus = function (connectionStatus) { * or hidden */ SmallVideo.prototype.showAudioIndicator = function (isMuted) { - let mutedIndicator = this.getAudioMutedIndicator(); - - UIUtil.setVisible(mutedIndicator, isMuted); - this.isAudioMuted = isMuted; -}; - -/** - * Returns the audio muted indicator jquery object. If it doesn't exists - - * creates it. - * - * @returns {HTMLElement} the audio muted indicator - */ -SmallVideo.prototype.getAudioMutedIndicator = function () { - let selector = '#' + this.videoSpanId + ' .audioMuted'; - let audioMutedSpan = document.querySelector(selector); - - if (audioMutedSpan) { - return audioMutedSpan; - } - - audioMutedSpan = document.createElement('span'); - audioMutedSpan.className = 'audioMuted toolbar-icon'; - - UIUtil.setTooltip(audioMutedSpan, - "videothumbnail.mute", - "top"); - - let mutedIndicator = document.createElement('i'); - mutedIndicator.className = 'icon-mic-disabled'; - audioMutedSpan.appendChild(mutedIndicator); - - this.container - .querySelector('.videocontainer__toolbar') - .appendChild(audioMutedSpan); - - return audioMutedSpan; + this.updateStatusBar(); }; /** @@ -320,75 +291,38 @@ SmallVideo.prototype.setVideoMutedView = function(isMuted) { this.isVideoMuted = isMuted; this.updateView(); - let element = this.getVideoMutedIndicator(); - - UIUtil.setVisible(element, isMuted); + this.updateStatusBar(); }; /** - * Returns the video muted indicator jquery object. If it doesn't exists - - * creates it. + * Create or updates the ReactElement for displaying status indicators about + * audio mute, video mute, and moderator status. * - * @returns {jQuery|HTMLElement} the video muted indicator + * @returns {void} */ -SmallVideo.prototype.getVideoMutedIndicator = function () { - var selector = '#' + this.videoSpanId + ' .videoMuted'; - var videoMutedSpan = document.querySelector(selector); +SmallVideo.prototype.updateStatusBar = function () { + const statusBarContainer + = this.container.querySelector('.videocontainer__toolbar'); - if (videoMutedSpan) { - return videoMutedSpan; - } - - videoMutedSpan = document.createElement('span'); - videoMutedSpan.className = 'videoMuted toolbar-icon'; - - this.container - .querySelector('.videocontainer__toolbar') - .appendChild(videoMutedSpan); - - var mutedIndicator = document.createElement('i'); - mutedIndicator.className = 'icon-camera-disabled'; - - UIUtil.setTooltip(mutedIndicator, - "videothumbnail.videomute", - "top"); - - videoMutedSpan.appendChild(mutedIndicator); - - return videoMutedSpan; + /* jshint ignore:start */ + ReactDOM.render( +
+ { this.isAudioMuted ? : null } + { this.isVideoMuted ? : null } + { this._isModerator + && !interfaceConfig.DISABLE_FOCUS_INDICATOR + ? : null } +
, + statusBarContainer); + /* jshint ignore:end */ }; /** * Adds the element indicating the moderator(owner) of the conference. */ SmallVideo.prototype.addModeratorIndicator = function () { - - // Don't create moderator indicator if DISABLE_FOCUS_INDICATOR is true - if (interfaceConfig.DISABLE_FOCUS_INDICATOR) - return false; - - // Show moderator indicator - var indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator'); - - if (indicatorSpan.length) { - return; - } - - indicatorSpan = document.createElement('span'); - indicatorSpan.className = 'focusindicator toolbar-icon right'; - - this.container - .querySelector('.videocontainer__toolbar') - .appendChild(indicatorSpan); - - var moderatorIndicator = document.createElement('i'); - moderatorIndicator.className = 'icon-star'; - - UIUtil.setTooltip(moderatorIndicator, - "videothumbnail.moderator", - "top-left"); - - indicatorSpan.appendChild(moderatorIndicator); + this._isModerator = true; + this.updateStatusBar(); }; /** @@ -456,7 +390,8 @@ SmallVideo.prototype._getAudioLevelContainer = function () { * Removes the element indicating the moderator(owner) of the conference. */ SmallVideo.prototype.removeModeratorIndicator = function () { - $('#' + this.videoSpanId + ' .focusindicator').remove(); + this._isModerator = false; + this.updateStatusBar(); }; /** diff --git a/react/features/filmstrip/components/_.web.js b/react/features/filmstrip/components/_.web.js index e69de29bb..b80c83af3 100644 --- a/react/features/filmstrip/components/_.web.js +++ b/react/features/filmstrip/components/_.web.js @@ -0,0 +1 @@ +export * from './web'; diff --git a/react/features/filmstrip/components/index.js b/react/features/filmstrip/components/index.js index ea1fd9926..0205f7fc6 100644 --- a/react/features/filmstrip/components/index.js +++ b/react/features/filmstrip/components/index.js @@ -1 +1,3 @@ +export * from './_'; + export { default as Filmstrip } from './Filmstrip'; diff --git a/react/features/filmstrip/components/web/AudioMutedIndicator.js b/react/features/filmstrip/components/web/AudioMutedIndicator.js new file mode 100644 index 000000000..c49d11f1b --- /dev/null +++ b/react/features/filmstrip/components/web/AudioMutedIndicator.js @@ -0,0 +1,24 @@ +import BaseIndicator from './BaseIndicator'; + +/** + * React {@code Component} for showing an audio muted icon with a tooltip. + * + * @extends BaseIndicator + */ +class AudioMutedIndicator extends BaseIndicator { + /** + * Initializes a new AudioMutedIcon instance. + * + * @param {Object} props - The read-only React Component props with which + * the new instance is to be initialized. + */ + constructor(props) { + super(props); + + this._classNames = 'audioMuted toolbar-icon'; + this._iconClass = 'icon-mic-disabled'; + this._tooltipKey = 'videothumbnail.mute'; + } +} + +export default AudioMutedIndicator; diff --git a/react/features/filmstrip/components/web/BaseIndicator.js b/react/features/filmstrip/components/web/BaseIndicator.js new file mode 100644 index 000000000..30f21142a --- /dev/null +++ b/react/features/filmstrip/components/web/BaseIndicator.js @@ -0,0 +1,110 @@ +import React, { Component } from 'react'; + +import UIUtil from '../../../../../modules/UI/util/UIUtil'; + +/** + * React {@code Component} for showing an icon with a tooltip. + * + * @extends Component + */ +class BaseIndicator extends Component { + /** + * Initializes a new {@code BaseIndicator} instance. + * + * @param {Object} props - The read-only properties with which the new + * instance is to be initialized. + */ + constructor(props) { + super(props); + + /** + * The CSS classes to apply to the root HTML element of the component. + * + * @type {string} + */ + this._classNames = ''; + + /** + * The CSS class which will display an icon. + * + * @type {string} + */ + this._iconClass = ''; + + /** + * An internal reference to the HTML element at the top of the + * component's DOM hierarchy. The reference is needed for attaching a + * tooltip. + * + * @type {HTMLElement} + */ + this._rootElement = null; + + /** + * The translation key for the text to display in the tooltip. + * + * @type {string} + */ + this._tooltipKey = ''; + + // Bind event handler so it is only bound once for every instance. + this._setRootElementRef = this._setRootElementRef.bind(this); + } + + /** + * Sets a tooltip which will display when hovering over the component. + * + * @inheritdoc + * @returns {void} + */ + componentDidMount() { + this._setTooltip(); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return ( + + + + ); + } + + /** + * Sets the internal reference to the root HTML element for the component. + * + * @param {HTMLIconElement} element - The root HTML element of the + * component. + * @private + * @returns {void} + */ + _setRootElementRef(element) { + this._rootElement = element; + } + + /** + * Associate the component as a tooltip trigger so a tooltip may display on + * hover. + * + * @private + * @returns {void} + */ + _setTooltip() { + // TODO Replace UIUtil with an AtlasKit component when a suitable one + // becomes available for tooltips. + UIUtil.setTooltip( + this._rootElement, + this._tooltipKey, + 'top' + ); + } +} + +export default BaseIndicator; diff --git a/react/features/filmstrip/components/web/ModeratorIndicator.js b/react/features/filmstrip/components/web/ModeratorIndicator.js new file mode 100644 index 000000000..7fc552aaf --- /dev/null +++ b/react/features/filmstrip/components/web/ModeratorIndicator.js @@ -0,0 +1,24 @@ +import BaseIndicator from './BaseIndicator'; + +/** + * React {@code Component} for showing a moderator icon with a tooltip. + * + * @extends BaseIndicator + */ +class ModeratorIndicator extends BaseIndicator { + /** + * Initializes a new ModeratorIndicator instance. + * + * @param {Object} props - The read-only React Component props with which + * the new instance is to be initialized. + */ + constructor(props) { + super(props); + + this._classNames = 'focusindicator toolbar-icon right'; + this._iconClass = 'icon-star'; + this._tooltipKey = 'videothumbnail.moderator'; + } +} + +export default ModeratorIndicator; diff --git a/react/features/filmstrip/components/web/VideoMutedIndicator.js b/react/features/filmstrip/components/web/VideoMutedIndicator.js new file mode 100644 index 000000000..f5dcd10d3 --- /dev/null +++ b/react/features/filmstrip/components/web/VideoMutedIndicator.js @@ -0,0 +1,24 @@ +import BaseIndicator from './BaseIndicator'; + +/** + * React {@code Component} for showing a video muted icon with a tooltip. + * + * @extends BaseIndicator + */ +class VideoMutedIndicator extends BaseIndicator { + /** + * Initializes a new VideoMutedIndicator instance. + * + * @param {Object} props - The read-only React Component props with which + * the new instance is to be initialized. + */ + constructor(props) { + super(props); + + this._classNames = 'videoMuted toolbar-icon'; + this._iconClass = 'icon-camera-disabled'; + this._tooltipKey = 'videothumbnail.videomute'; + } +} + +export default VideoMutedIndicator; diff --git a/react/features/filmstrip/components/web/index.js b/react/features/filmstrip/components/web/index.js new file mode 100644 index 000000000..e47ee6edd --- /dev/null +++ b/react/features/filmstrip/components/web/index.js @@ -0,0 +1,3 @@ +export { default as AudioMutedIndicator } from './AudioMutedIndicator'; +export { default as ModeratorIndicator } from './ModeratorIndicator'; +export { default as VideoMutedIndicator } from './VideoMutedIndicator';