feat(filmstrip): show thumbnails with toolbar and on hover (#1944)

* feat(filmstrip): show thumbnails with toolbar and on hover

* squash: reduce verbosity of logic for when to display

* squash: remove check for fake participant

Before fake participant (youtube video) would make the filmstrip
always displayed. However, youtube videos already dock the
toolbar, so filmstrip will remain displayed, so the check is
redundant.

* squash: change mouse hover listener targets
This commit is contained in:
virtuacoplenny 2017-09-01 14:40:05 -07:00 committed by yanas
parent 6682543691
commit bf03e73876
5 changed files with 144 additions and 13 deletions

View File

@ -1,5 +1,16 @@
/**
* The type of action sets the visibility of the entire filmstrip;
* The type of the action which sets whether or not the filmstrip is being
* hovered with the cursor.
*
* {
* type: SET_FILMSTRIP_HOVERED,
* hovered: boolean
* }
*/
export const SET_FILMSTRIP_HOVERED = Symbol('SET_FILMSTRIP_HOVERED');
/**
* The type of action sets the visibility of the entire filmstrip.
*
* {
* type: SET_FILMSTRIP_VISIBILITY,

View File

@ -1,7 +1,25 @@
import {
SET_FILMSTRIP_HOVERED,
SET_FILMSTRIP_VISIBILITY
} from './actionTypes';
/**
* Sets if the filmstrip is currently being hovered over.
*
* @param {boolean} hovered - Whether or not the filmstrip is currently being
* hovered over.
* @returns {{
* type: SET_FILMSTRIP_HOVERED,
* hovered: boolean
* }}
*/
export function setFilmstripHovered(hovered) {
return {
type: SET_FILMSTRIP_HOVERED,
hovered
};
}
/**
* Sets if the entire filmstrip should be visible.
*

View File

@ -1,10 +1,12 @@
/* @flow */
import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Toolbox } from '../../toolbox';
import { setFilmstripHovered } from '../actions';
import { shouldRemoteVideosBeVisible } from '../functions';
/**
@ -14,19 +16,68 @@ import { shouldRemoteVideosBeVisible } from '../functions';
* @extends Component
*/
class Filmstrip extends Component {
_isHovered: boolean;
_notifyOfHoveredStateUpdate: Function;
_onMouseOut: Function;
_onMouseOver: Function;
/**
* {@code Filmstrip} component's property types.
*
* @static
*/
static propTypes = {
/**
* Whether or not remote videos are currently being hovered over.
*/
_hovered: React.PropTypes.bool,
/**
* Whether or not the remote videos should be visible. Will toggle
* a class for hiding the videos.
*/
_remoteVideosVisible: React.PropTypes.bool,
/**
* Updates the redux store with filmstrip hover changes.
*/
dispatch: React.PropTypes.func,
/**
* Whether or not the toolbox should be displayed within the filmstrip.
*/
displayToolbox: React.PropTypes.bool
};
/**
* Initializes a new {@code Filmstrip} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
super(props);
// Debounce the method for dispatching the new filmstrip handled state
// so that it does not get called with each mouse movement event. This
// also works around an issue where mouseout and then a mouseover event
// is fired when hovering over remote thumbnails, which are not yet in
// react.
this._notifyOfHoveredStateUpdate
= _.debounce(this._notifyOfHoveredStateUpdate, 100);
// Cache the current hovered state for _updateHoveredState to always
// send the last known hovered state.
this._isHovered = false;
// Bind event handlers so they are only bound once for every instance.
this._onMouseOver = this._onMouseOver.bind(this);
this._onMouseOut = this._onMouseOut.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
@ -54,7 +105,9 @@ class Filmstrip extends Component {
id = 'remoteVideos'>
<div
className = 'filmstrip__videos'
id = 'filmstripLocalVideo' />
id = 'filmstripLocalVideo'
onMouseOut = { this._onMouseOut }
onMouseOver = { this._onMouseOver } />
<div
className = 'filmstrip__videos'
id = 'filmstripRemoteVideos'>
@ -65,7 +118,9 @@ class Filmstrip extends Component {
*/}
<div
className = 'remote-videos-container'
id = 'filmstripRemoteVideosContainer' />
id = 'filmstripRemoteVideosContainer'
onMouseOut = { this._onMouseOut }
onMouseOver = { this._onMouseOver } />
</div>
<audio
id = 'userJoined'
@ -79,6 +134,43 @@ class Filmstrip extends Component {
</div>
);
}
/**
* 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.
*
* @private
* @returns {void}
*/
_notifyOfHoveredStateUpdate() {
if (this.props._hovered !== this._isHovered) {
this.props.dispatch(setFilmstripHovered(this._isHovered));
}
}
/**
* Updates the currently known mouseover state and attempt to dispatch an
* update of the known hover state in redux.
*
* @private
* @returns {void}
*/
_onMouseOut() {
this._isHovered = false;
this._notifyOfHoveredStateUpdate();
}
/**
* Updates the currently known mouseover state and attempt to dispatch an
* update of the known hover state in redux.
*
* @private
* @returns {void}
*/
_onMouseOver() {
this._isHovered = true;
this._notifyOfHoveredStateUpdate();
}
}
/**
@ -87,11 +179,13 @@ class Filmstrip extends Component {
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _hovered: boolean,
* _remoteVideosVisible: boolean
* }}
*/
function _mapStateToProps(state) {
return {
_hovered: state['features/filmstrip'].hovered,
_remoteVideosVisible: shouldRemoteVideosBeVisible(state)
};
}

View File

@ -14,21 +14,22 @@ import {
*/
export function shouldRemoteVideosBeVisible(state) {
const participants = state['features/base/participants'];
const participantsCount = participants.length;
const shouldShowVideos
= state['features/base/config'].disable1On1Mode
= participantsCount > 2
// Always show the filmstrip when there is another participant to show
// and the filmstrip is hovered, or local video is pinned, or the
// toolbar is displayed.
|| (participantsCount > 1
&& (state['features/filmstrip'].hovered
|| state['features/toolbox'].visible
|| getLocalParticipant(state) === getPinnedParticipant(state)))
|| interfaceConfig.filmStripOnly
// This is not a 1-on-1 call.
|| participants.length > 2
// There is another participant and the local participant is pinned.
|| (participants.length > 1
&& getLocalParticipant(state) === getPinnedParticipant(state))
// There is any non-person participant, like a shared video.
|| participants.find(participant => participant.isBot);
|| state['features/base/config'].disable1On1Mode;
return Boolean(shouldShowVideos);
}

View File

@ -1,5 +1,6 @@
import { ReducerRegistry } from '../base/redux';
import {
SET_FILMSTRIP_HOVERED,
SET_FILMSTRIP_VISIBILITY
} from './actionTypes';
@ -11,6 +12,12 @@ ReducerRegistry.register(
'features/filmstrip',
(state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_FILMSTRIP_HOVERED:
return {
...state,
hovered: action.hovered
};
case SET_FILMSTRIP_VISIBILITY:
return {
...state,