From 0fca0f392d8483f8c8ad9c9c6641ad5389881e00 Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Mon, 10 Sep 2018 15:10:45 -0700 Subject: [PATCH] feat(filmstrip): reactify the filmstrip toggle button --- modules/UI/UI.js | 15 +- modules/UI/videolayout/Filmstrip.js | 186 +----------------- .../filmstrip/components/web/Filmstrip.js | 134 ++++++++++++- react/features/video-layout/middleware.web.js | 6 + 4 files changed, 142 insertions(+), 199 deletions(-) diff --git a/modules/UI/UI.js b/modules/UI/UI.js index dbd5d1a99..d8c14d515 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -24,6 +24,7 @@ import { import { destroyLocalTracks } from '../../react/features/base/tracks'; import { openDisplayNamePrompt } from '../../react/features/display-name'; import { setEtherpadHasInitialzied } from '../../react/features/etherpad'; +import { setFilmstripVisible } from '../../react/features/filmstrip'; import { setNotificationsEnabled, showWarningNotification @@ -93,7 +94,7 @@ const UIListeners = new Map([ () => UI.toggleChat() ], [ UIEvents.TOGGLE_FILMSTRIP, - () => UI.handleToggleFilmstrip() + () => UI.toggleFilmstrip() ], [ UIEvents.FOLLOW_ME_ENABLED, enabled => followMeHandler && followMeHandler.enableFollowMe(enabled) @@ -255,11 +256,6 @@ UI.initConference = function() { followMeHandler = new FollowMe(APP.conference, UI); }; -/** * - * Handler for toggling filmstrip - */ -UI.handleToggleFilmstrip = () => UI.toggleFilmstrip(); - /** * Returns the shared document manager object. * @return {EtherpadManager} the shared document manager object @@ -298,7 +294,6 @@ UI.start = function() { if (interfaceConfig.filmStripOnly) { $('body').addClass('filmstrip-only'); - Filmstrip.setFilmstripOnly(); APP.store.dispatch(setNotificationsEnabled(false)); } else { // Initialize recording mode UI. @@ -512,9 +507,9 @@ UI.toggleSmileys = () => Chat.toggleSmileys(); * Toggles filmstrip. */ UI.toggleFilmstrip = function() { - // eslint-disable-next-line prefer-rest-params - Filmstrip.toggleFilmstrip(...arguments); - VideoLayout.resizeVideoArea(true, false); + const { visible } = APP.store.getState()['features/filmstrip']; + + APP.store.dispatch(setFilmstripVisible(!visible)); }; /** diff --git a/modules/UI/videolayout/Filmstrip.js b/modules/UI/videolayout/Filmstrip.js index 0c81939a3..c7d310407 100644 --- a/modules/UI/videolayout/Filmstrip.js +++ b/modules/UI/videolayout/Filmstrip.js @@ -1,6 +1,5 @@ /* global $, APP, interfaceConfig */ -import { setFilmstripVisible } from '../../../react/features/filmstrip'; import { LAYOUTS, getCurrentLayout, @@ -9,188 +8,16 @@ import { shouldDisplayTileView } from '../../../react/features/video-layout'; -import UIEvents from '../../../service/UI/UIEvents'; import UIUtil from '../util/UIUtil'; -import { - createShortcutEvent, - createToolbarEvent, - sendAnalytics -} from '../../../react/features/analytics'; - const Filmstrip = { /** - * - * @param eventEmitter the {EventEmitter} through which {Filmstrip} is to - * emit/fire {UIEvents} (such as {UIEvents.TOGGLED_FILMSTRIP}). + * Caches jquery lookups of the filmstrip for future use. */ - init(eventEmitter) { - this.iconMenuDownClassName = 'icon-menu-down'; - this.iconMenuUpClassName = 'icon-menu-up'; + init() { this.filmstripContainerClassName = 'filmstrip'; this.filmstrip = $('#remoteVideos'); this.filmstripRemoteVideos = $('#filmstripRemoteVideosContainer'); - this.eventEmitter = eventEmitter; - - // Show the toggle button and add event listeners only when out of - // filmstrip only mode. - if (!interfaceConfig.filmStripOnly) { - this._initFilmstripToolbar(); - this.registerListeners(); - } - }, - - /** - * Initializes the filmstrip toolbar. - */ - _initFilmstripToolbar() { - const toolbarContainerHTML = this._generateToolbarHTML(); - const className = this.filmstripContainerClassName; - const container = document.querySelector(`.${className}`); - - UIUtil.prependChild(container, toolbarContainerHTML); - - const iconSelector = '#toggleFilmstripButton i'; - - this.toggleFilmstripIcon = document.querySelector(iconSelector); - }, - - /** - * Generates HTML layout for filmstrip toggle button and wrapping container. - * @returns {HTMLElement} - * @private - */ - _generateToolbarHTML() { - const container = document.createElement('div'); - const isVisible = this.isFilmstripVisible(); - - container.className = 'filmstrip__toolbar'; - container.innerHTML = ` - - `; - - return container; - }, - - /** - * Attach 'click' listener to "hide filmstrip" button - */ - registerListeners() { - // Important: - // Firing the event instead of executing toggleFilmstrip method because - // it's important to hide the filmstrip by UI.toggleFilmstrip in order - // to correctly resize the video area. - $('#toggleFilmstripButton').on( - 'click', - () => { - // The 'enable' parameter is set to true if the action results - // in the filmstrip being hidden. - sendAnalytics(createToolbarEvent( - 'toggle.filmstrip.button', - { - enable: this.isFilmstripVisible() - })); - this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP); - }); - - this._registerToggleFilmstripShortcut(); - }, - - /** - * Registering toggle filmstrip shortcut - * @private - */ - _registerToggleFilmstripShortcut() { - const shortcut = 'F'; - const shortcutAttr = 'filmstripPopover'; - const description = 'keyboardShortcuts.toggleFilmstrip'; - - // Important: - // Firing the event instead of executing toggleFilmstrip method because - // it's important to hide the filmstrip by UI.toggleFilmstrip in order - // to correctly resize the video area. - const handler = () => { - sendAnalytics(createShortcutEvent( - 'toggle.filmstrip', - { - enable: this.isFilmstripVisible() - })); - this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP); - }; - - APP.keyboardshortcut.registerShortcut( - shortcut, - shortcutAttr, - handler, - description - ); - }, - - /** - * Changes classes of icon for showing down state - */ - showMenuDownIcon() { - const icon = this.toggleFilmstripIcon; - - if (icon) { - icon.classList.add(this.iconMenuDownClassName); - icon.classList.remove(this.iconMenuUpClassName); - } - }, - - /** - * Changes classes of icon for showing up state - */ - showMenuUpIcon() { - const icon = this.toggleFilmstripIcon; - - if (icon) { - icon.classList.add(this.iconMenuUpClassName); - icon.classList.remove(this.iconMenuDownClassName); - } - }, - - /** - * Toggles the visibility of the filmstrip, or sets it to a specific value - * if the 'visible' parameter is specified. - * - * @param visible optional {Boolean} which specifies the desired visibility - * of the filmstrip. If not specified, the visibility will be flipped - * (i.e. toggled); otherwise, the visibility will be set to the specified - * value. - * - * Note: - * This method shouldn't be executed directly to hide the filmstrip. - * It's important to hide the filmstrip with UI.toggleFilmstrip in order - * to correctly resize the video area. - */ - toggleFilmstrip(visible) { - const wasFilmstripVisible = this.isFilmstripVisible(); - - // If 'visible' is defined and matches the current state, we have - // nothing to do. Otherwise (regardless of whether 'visible' is defined) - // we need to toggle the state. - if (visible === wasFilmstripVisible) { - return; - } - - this.filmstrip.toggleClass('hidden'); - - if (wasFilmstripVisible) { - this.showMenuUpIcon(); - } else { - this.showMenuDownIcon(); - } - - if (this.eventEmitter) { - this.eventEmitter.emit( - UIEvents.TOGGLED_FILMSTRIP, - !wasFilmstripVisible); - } - APP.store.dispatch(setFilmstripVisible(!wasFilmstripVisible)); }, /** @@ -198,14 +25,7 @@ const Filmstrip = { * @returns {boolean} */ isFilmstripVisible() { - return !this.filmstrip.hasClass('hidden'); - }, - - /** - * Adjusts styles for filmstrip-only mode. - */ - setFilmstripOnly() { - this.filmstrip.addClass('filmstrip__videos-filmstripOnly'); + return APP.store.getState()['features/filmstrip'].visible; }, /** diff --git a/react/features/filmstrip/components/web/Filmstrip.js b/react/features/filmstrip/components/web/Filmstrip.js index b53c696db..02fb12a21 100644 --- a/react/features/filmstrip/components/web/Filmstrip.js +++ b/react/features/filmstrip/components/web/Filmstrip.js @@ -4,13 +4,19 @@ import _ from 'lodash'; import React, { Component } from 'react'; import { connect } from 'react-redux'; +import { + createShortcutEvent, + createToolbarEvent, + sendAnalytics +} from '../../../analytics'; import { dockToolbox } from '../../../toolbox'; -import { setFilmstripHovered } from '../../actions'; +import { setFilmstripHovered, setFilmstripVisible } from '../../actions'; import { shouldRemoteVideosBeVisible } from '../../functions'; import Toolbar from './Toolbar'; +declare var APP: Object; declare var interfaceConfig: Object; /** @@ -34,6 +40,16 @@ type Props = { */ _hovered: boolean, + /** + * Additional CSS class names to add to the container of all the thumbnails. + */ + _videosClassName: string, + + /** + * Whether or not the filmstrip videos should currently be displayed. + */ + _visible: boolean, + /** * The redux {@code dispatch} function. */ @@ -79,6 +95,35 @@ class Filmstrip extends Component { // Bind event handlers so they are only bound once for every instance. this._onMouseOut = this._onMouseOut.bind(this); this._onMouseOver = this._onMouseOver.bind(this); + this._onShortcutToggleFilmstrip + = this._onShortcutToggleFilmstrip.bind(this); + this._onToolbarToggleFilmstrip + = this._onToolbarToggleFilmstrip.bind(this); + } + + /** + * Implements React's {@link Component#componentDidMount}. + * + * @inheritdoc + */ + componentDidMount() { + if (!this.props._filmstripOnly) { + APP.keyboardshortcut.registerShortcut( + 'F', + 'filmstripPopover', + this._onShortcutToggleFilmstrip, + 'keyboardShortcuts.toggleFilmstrip' + ); + } + } + + /** + * Implements React's {@link Component#componentDidUpdate}. + * + * @inheritdoc + */ + componentWillUnmount() { + APP.keyboardshortcut.unregisterShortcut('F'); } /** @@ -97,9 +142,10 @@ class Filmstrip extends Component { return (
- { this.props._filmstripOnly && } + { this.props._filmstripOnly + ? : this._renderToggleButton() }
{ ); } + /** + * Dispatches an action to change the visibility of the filmstrip. + * + * @private + * @returns {void} + */ + _doToggleFilmstrip() { + this.props.dispatch(setFilmstripVisible(!this.props._visible)); + } + /** * If the current hover state does not match the known hover state in redux, * dispatch an action to update the known hover state in redux. @@ -166,6 +222,65 @@ class Filmstrip extends Component { this._isHovered = true; this._notifyOfHoveredStateUpdate(); } + + _onShortcutToggleFilmstrip: () => void; + + /** + * Creates an analytics keyboard shortcut event and dispatches an action for + * toggling filmstrip visibility. + * + * @private + * @returns {void} + */ + _onShortcutToggleFilmstrip() { + sendAnalytics(createShortcutEvent( + 'toggle.filmstrip', + { + enable: this.props._visible + })); + + this._doToggleFilmstrip(); + } + + _onToolbarToggleFilmstrip: () => void; + + /** + * Creates an analytics toolbar event and dispatches an action for opening + * the speaker stats modal. + * + * @private + * @returns {void} + */ + _onToolbarToggleFilmstrip() { + sendAnalytics(createToolbarEvent( + 'toggle.filmstrip.button', + { + enable: this.props._visible + })); + + this._doToggleFilmstrip(); + } + + /** + * Creates a React Element for changing the visibility of the filmstrip when + * clicked. + * + * @private + * @returns {ReactElement} + */ + _renderToggleButton() { + const icon = this.props._visible ? 'icon-menu-down' : 'icon-menu-up'; + + return ( +
+ +
+ ); + } } /** @@ -175,12 +290,14 @@ class Filmstrip extends Component { * @private * @returns {{ * _className: string, + * _filmstripOnly: boolean, * _hovered: boolean, - * _filmstripOnly: boolean + * _videosClassName: string, + * _visible: boolean * }} */ function _mapStateToProps(state) { - const { hovered } = state['features/filmstrip']; + const { hovered, visible } = state['features/filmstrip']; const isFilmstripOnly = Boolean(interfaceConfig.filmStripOnly); const reduceHeight = !isFilmstripOnly && state['features/toolbox'].visible @@ -188,11 +305,16 @@ function _mapStateToProps(state) { const remoteVideosVisible = shouldRemoteVideosBeVisible(state); const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${ reduceHeight ? 'reduce-height' : ''}`.trim(); + const videosClassName = `filmstrip__videos ${ + isFilmstripOnly ? 'filmstrip__videos-filmstripOnly' : ''} ${ + visible ? '' : 'hidden'}`; return { _className: className, _filmstripOnly: isFilmstripOnly, - _hovered: hovered + _hovered: hovered, + _videosClassName: videosClassName, + _visible: visible }; } diff --git a/react/features/video-layout/middleware.web.js b/react/features/video-layout/middleware.web.js index a3cce4527..a6002fab3 100644 --- a/react/features/video-layout/middleware.web.js +++ b/react/features/video-layout/middleware.web.js @@ -14,6 +14,7 @@ import { } from '../base/participants'; import { MiddlewareRegistry } from '../base/redux'; import { TRACK_ADDED } from '../base/tracks'; +import { SET_FILMSTRIP_VISIBLE } from '../filmstrip'; import { SET_TILE_VIEW } from './actionTypes'; @@ -73,6 +74,11 @@ MiddlewareRegistry.register(store => next => action => { Boolean(action.participant.id)); break; + case SET_FILMSTRIP_VISIBLE: + VideoLayout.resizeVideoArea(true, false); + APP.UI.emitEvent(UIEvents.TOGGLED_FILMSTRIP, action.visible); + break; + case SET_TILE_VIEW: APP.UI.emitEvent(UIEvents.TOGGLED_TILE_VIEW, action.enabled); break;