瀏覽代碼

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
master
virtuacoplenny 8 年之前
父節點
當前提交
bf03e73876

+ 12
- 1
react/features/filmstrip/actionTypes.js 查看文件

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
  *     type: SET_FILMSTRIP_VISIBILITY,
16
  *     type: SET_FILMSTRIP_VISIBILITY,

+ 18
- 0
react/features/filmstrip/actions.js 查看文件

1
 import {
1
 import {
2
+    SET_FILMSTRIP_HOVERED,
2
     SET_FILMSTRIP_VISIBILITY
3
     SET_FILMSTRIP_VISIBILITY
3
 } from './actionTypes';
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
  * Sets if the entire filmstrip should be visible.
24
  * Sets if the entire filmstrip should be visible.
7
  *
25
  *

+ 96
- 2
react/features/filmstrip/components/Filmstrip.web.js 查看文件

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
+import _ from 'lodash';
3
 import React, { Component } from 'react';
4
 import React, { Component } from 'react';
4
 import { connect } from 'react-redux';
5
 import { connect } from 'react-redux';
5
 
6
 
6
 import { Toolbox } from '../../toolbox';
7
 import { Toolbox } from '../../toolbox';
7
 
8
 
9
+import { setFilmstripHovered } from '../actions';
8
 import { shouldRemoteVideosBeVisible } from '../functions';
10
 import { shouldRemoteVideosBeVisible } from '../functions';
9
 
11
 
10
 /**
12
 /**
14
  * @extends Component
16
  * @extends Component
15
  */
17
  */
16
 class Filmstrip extends Component {
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
     static propTypes = {
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
          * Whether or not the remote videos should be visible. Will toggle
39
          * Whether or not the remote videos should be visible. Will toggle
20
          * a class for hiding the videos.
40
          * a class for hiding the videos.
21
          */
41
          */
22
         _remoteVideosVisible: React.PropTypes.bool,
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
          * Whether or not the toolbox should be displayed within the filmstrip.
50
          * Whether or not the toolbox should be displayed within the filmstrip.
26
          */
51
          */
27
         displayToolbox: React.PropTypes.bool
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
      * Implements React's {@link Component#render()}.
82
      * Implements React's {@link Component#render()}.
32
      *
83
      *
54
                     id = 'remoteVideos'>
105
                     id = 'remoteVideos'>
55
                     <div
106
                     <div
56
                         className = 'filmstrip__videos'
107
                         className = 'filmstrip__videos'
57
-                        id = 'filmstripLocalVideo' />
108
+                        id = 'filmstripLocalVideo'
109
+                        onMouseOut = { this._onMouseOut }
110
+                        onMouseOver = { this._onMouseOver } />
58
                     <div
111
                     <div
59
                         className = 'filmstrip__videos'
112
                         className = 'filmstrip__videos'
60
                         id = 'filmstripRemoteVideos'>
113
                         id = 'filmstripRemoteVideos'>
65
                           */}
118
                           */}
66
                         <div
119
                         <div
67
                             className = 'remote-videos-container'
120
                             className = 'remote-videos-container'
68
-                            id = 'filmstripRemoteVideosContainer' />
121
+                            id = 'filmstripRemoteVideosContainer'
122
+                            onMouseOut = { this._onMouseOut }
123
+                            onMouseOver = { this._onMouseOver } />
69
                     </div>
124
                     </div>
70
                     <audio
125
                     <audio
71
                         id = 'userJoined'
126
                         id = 'userJoined'
79
             </div>
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
  * @param {Object} state - The Redux state.
179
  * @param {Object} state - The Redux state.
88
  * @private
180
  * @private
89
  * @returns {{
181
  * @returns {{
182
+ *     _hovered: boolean,
90
  *     _remoteVideosVisible: boolean
183
  *     _remoteVideosVisible: boolean
91
  * }}
184
  * }}
92
  */
185
  */
93
 function _mapStateToProps(state) {
186
 function _mapStateToProps(state) {
94
     return {
187
     return {
188
+        _hovered: state['features/filmstrip'].hovered,
95
         _remoteVideosVisible: shouldRemoteVideosBeVisible(state)
189
         _remoteVideosVisible: shouldRemoteVideosBeVisible(state)
96
     };
190
     };
97
 }
191
 }

+ 11
- 10
react/features/filmstrip/functions.js 查看文件

14
  */
14
  */
15
 export function shouldRemoteVideosBeVisible(state) {
15
 export function shouldRemoteVideosBeVisible(state) {
16
     const participants = state['features/base/participants'];
16
     const participants = state['features/base/participants'];
17
+    const participantsCount = participants.length;
17
 
18
 
18
     const shouldShowVideos
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
     return Boolean(shouldShowVideos);
34
     return Boolean(shouldShowVideos);
34
 }
35
 }

+ 7
- 0
react/features/filmstrip/reducer.js 查看文件

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

Loading…
取消
儲存