Parcourir la source

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
j8
virtuacoplenny il y a 8 ans
Parent
révision
bf03e73876

+ 12
- 1
react/features/filmstrip/actionTypes.js Voir le fichier

@@ -1,5 +1,16 @@
1 1
 /**
2
- * The type of action sets the visibility of the entire filmstrip;
2
+ * The type of the action which sets whether or not the filmstrip is being
3
+ * hovered with the cursor.
4
+ *
5
+ * {
6
+ *     type: SET_FILMSTRIP_HOVERED,
7
+ *     hovered: boolean
8
+ * }
9
+ */
10
+export const SET_FILMSTRIP_HOVERED = Symbol('SET_FILMSTRIP_HOVERED');
11
+
12
+/**
13
+ * The type of action sets the visibility of the entire filmstrip.
3 14
  *
4 15
  * {
5 16
  *     type: SET_FILMSTRIP_VISIBILITY,

+ 18
- 0
react/features/filmstrip/actions.js Voir le fichier

@@ -1,7 +1,25 @@
1 1
 import {
2
+    SET_FILMSTRIP_HOVERED,
2 3
     SET_FILMSTRIP_VISIBILITY
3 4
 } from './actionTypes';
4 5
 
6
+/**
7
+ * Sets if the filmstrip is currently being hovered over.
8
+ *
9
+ * @param {boolean} hovered - Whether or not the filmstrip is currently being
10
+ * hovered over.
11
+ * @returns {{
12
+ *     type: SET_FILMSTRIP_HOVERED,
13
+ *     hovered: boolean
14
+ * }}
15
+ */
16
+export function setFilmstripHovered(hovered) {
17
+    return {
18
+        type: SET_FILMSTRIP_HOVERED,
19
+        hovered
20
+    };
21
+}
22
+
5 23
 /**
6 24
  * Sets if the entire filmstrip should be visible.
7 25
  *

+ 96
- 2
react/features/filmstrip/components/Filmstrip.web.js Voir le fichier

@@ -1,10 +1,12 @@
1 1
 /* @flow */
2 2
 
3
+import _ from 'lodash';
3 4
 import React, { Component } from 'react';
4 5
 import { connect } from 'react-redux';
5 6
 
6 7
 import { Toolbox } from '../../toolbox';
7 8
 
9
+import { setFilmstripHovered } from '../actions';
8 10
 import { shouldRemoteVideosBeVisible } from '../functions';
9 11
 
10 12
 /**
@@ -14,19 +16,68 @@ import { shouldRemoteVideosBeVisible } from '../functions';
14 16
  * @extends Component
15 17
  */
16 18
 class Filmstrip extends Component {
19
+    _isHovered: boolean;
20
+
21
+    _notifyOfHoveredStateUpdate: Function;
22
+
23
+    _onMouseOut: Function;
24
+
25
+    _onMouseOver: Function;
26
+
27
+    /**
28
+     * {@code Filmstrip} component's property types.
29
+     *
30
+     * @static
31
+     */
17 32
     static propTypes = {
33
+        /**
34
+         * Whether or not remote videos are currently being hovered over.
35
+         */
36
+        _hovered: React.PropTypes.bool,
37
+
18 38
         /**
19 39
          * Whether or not the remote videos should be visible. Will toggle
20 40
          * a class for hiding the videos.
21 41
          */
22 42
         _remoteVideosVisible: React.PropTypes.bool,
23 43
 
44
+        /**
45
+         * Updates the redux store with filmstrip hover changes.
46
+         */
47
+        dispatch: React.PropTypes.func,
48
+
24 49
         /**
25 50
          * Whether or not the toolbox should be displayed within the filmstrip.
26 51
          */
27 52
         displayToolbox: React.PropTypes.bool
28 53
     };
29 54
 
55
+    /**
56
+     * Initializes a new {@code Filmstrip} instance.
57
+     *
58
+     * @param {Object} props - The read-only properties with which the new
59
+     * instance is to be initialized.
60
+     */
61
+    constructor(props) {
62
+        super(props);
63
+
64
+        // Debounce the method for dispatching the new filmstrip handled state
65
+        // so that it does not get called with each mouse movement event. This
66
+        // also works around an issue where mouseout and then a mouseover event
67
+        // is fired when hovering over remote thumbnails, which are not yet in
68
+        // react.
69
+        this._notifyOfHoveredStateUpdate
70
+            = _.debounce(this._notifyOfHoveredStateUpdate, 100);
71
+
72
+        // Cache the current hovered state for _updateHoveredState to always
73
+        // send the last known hovered state.
74
+        this._isHovered = false;
75
+
76
+        // Bind event handlers so they are only bound once for every instance.
77
+        this._onMouseOver = this._onMouseOver.bind(this);
78
+        this._onMouseOut = this._onMouseOut.bind(this);
79
+    }
80
+
30 81
     /**
31 82
      * Implements React's {@link Component#render()}.
32 83
      *
@@ -54,7 +105,9 @@ class Filmstrip extends Component {
54 105
                     id = 'remoteVideos'>
55 106
                     <div
56 107
                         className = 'filmstrip__videos'
57
-                        id = 'filmstripLocalVideo' />
108
+                        id = 'filmstripLocalVideo'
109
+                        onMouseOut = { this._onMouseOut }
110
+                        onMouseOver = { this._onMouseOver } />
58 111
                     <div
59 112
                         className = 'filmstrip__videos'
60 113
                         id = 'filmstripRemoteVideos'>
@@ -65,7 +118,9 @@ class Filmstrip extends Component {
65 118
                           */}
66 119
                         <div
67 120
                             className = 'remote-videos-container'
68
-                            id = 'filmstripRemoteVideosContainer' />
121
+                            id = 'filmstripRemoteVideosContainer'
122
+                            onMouseOut = { this._onMouseOut }
123
+                            onMouseOver = { this._onMouseOver } />
69 124
                     </div>
70 125
                     <audio
71 126
                         id = 'userJoined'
@@ -79,6 +134,43 @@ class Filmstrip extends Component {
79 134
             </div>
80 135
         );
81 136
     }
137
+
138
+    /**
139
+     * If the current hover state does not match the known hover state in redux,
140
+     * dispatch an action to update the known hover state in redux.
141
+     *
142
+     * @private
143
+     * @returns {void}
144
+     */
145
+    _notifyOfHoveredStateUpdate() {
146
+        if (this.props._hovered !== this._isHovered) {
147
+            this.props.dispatch(setFilmstripHovered(this._isHovered));
148
+        }
149
+    }
150
+
151
+    /**
152
+     * Updates the currently known mouseover state and attempt to dispatch an
153
+     * update of the known hover state in redux.
154
+     *
155
+     * @private
156
+     * @returns {void}
157
+     */
158
+    _onMouseOut() {
159
+        this._isHovered = false;
160
+        this._notifyOfHoveredStateUpdate();
161
+    }
162
+
163
+    /**
164
+     * Updates the currently known mouseover state and attempt to dispatch an
165
+     * update of the known hover state in redux.
166
+     *
167
+     * @private
168
+     * @returns {void}
169
+     */
170
+    _onMouseOver() {
171
+        this._isHovered = true;
172
+        this._notifyOfHoveredStateUpdate();
173
+    }
82 174
 }
83 175
 
84 176
 /**
@@ -87,11 +179,13 @@ class Filmstrip extends Component {
87 179
  * @param {Object} state - The Redux state.
88 180
  * @private
89 181
  * @returns {{
182
+ *     _hovered: boolean,
90 183
  *     _remoteVideosVisible: boolean
91 184
  * }}
92 185
  */
93 186
 function _mapStateToProps(state) {
94 187
     return {
188
+        _hovered: state['features/filmstrip'].hovered,
95 189
         _remoteVideosVisible: shouldRemoteVideosBeVisible(state)
96 190
     };
97 191
 }

+ 11
- 10
react/features/filmstrip/functions.js Voir le fichier

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

+ 7
- 0
react/features/filmstrip/reducer.js Voir le fichier

@@ -1,5 +1,6 @@
1 1
 import { ReducerRegistry } from '../base/redux';
2 2
 import {
3
+    SET_FILMSTRIP_HOVERED,
3 4
     SET_FILMSTRIP_VISIBILITY
4 5
 } from './actionTypes';
5 6
 
@@ -11,6 +12,12 @@ ReducerRegistry.register(
11 12
     'features/filmstrip',
12 13
     (state = DEFAULT_STATE, action) => {
13 14
         switch (action.type) {
15
+        case SET_FILMSTRIP_HOVERED:
16
+            return {
17
+                ...state,
18
+                hovered: action.hovered
19
+            };
20
+
14 21
         case SET_FILMSTRIP_VISIBILITY:
15 22
             return {
16 23
                 ...state,

Chargement…
Annuler
Enregistrer