Procházet zdrojové kódy

feat(filmstrip) Make filmstrip user resizable (#10884)

Make conference info and toolbar appear on top of the filmstrip
After a breakpoint, filmstrip pushes over the stage view instead of appearing on top
On user resize make tiles wider; after a breakpoint show grid view in the filmstrip
On filmstrip visibility toggle animate stage view resize
Added config for filmstrip with disableResizableFilmstrip
master
Robert Pintilii před 3 roky
rodič
revize
2dda749b1f
Žádný účet není propojen s e-mailovou adresou tvůrce revize

+ 7
- 0
config.js Zobrazit soubor

@@ -1256,6 +1256,13 @@ var config = {
1256 1256
     // Prevent the filmstrip from autohiding when screen width is under a certain threshold
1257 1257
     // disableFilmstripAutohiding: false,
1258 1258
 
1259
+    // filmstrip: {
1260
+    //     // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
1261
+    //     // (width, tiles aspect ratios) through the interfaceConfig options.
1262
+    //     disableResizable: false,
1263
+    // }
1264
+
1265
+
1259 1266
     // Specifies whether the chat emoticons are disabled or not
1260 1267
     // disableChatSmileys: false,
1261 1268
 

+ 1
- 1
css/_subject.scss Zobrazit soubor

@@ -1,7 +1,7 @@
1 1
 .subject {
2 2
     color: #fff;
3 3
     transition: opacity .6s ease-in-out;
4
-    z-index: $zindex3;
4
+    z-index: $toolbarZ + 2;
5 5
     margin-top: 20px;
6 6
     opacity: 0;
7 7
 

+ 4
- 0
css/_videolayout_default.scss Zobrazit soubor

@@ -78,6 +78,10 @@
78 78
 #largeVideoContainer {
79 79
     overflow: hidden;
80 80
     text-align: center;
81
+
82
+    &.transition {
83
+        transition: width 1s, height 1s, top 1s;
84
+    }
81 85
 }
82 86
 
83 87
 #largeVideoContainer {

+ 23
- 3
css/filmstrip/_vertical_filmstrip.scss Zobrazit soubor

@@ -28,7 +28,7 @@
28 28
     flex-direction: column-reverse;
29 29
     height: 100%;
30 30
     width: 100%;
31
-    padding: ($desktopAppDragBarHeight - 5px) 5px calc(env(safe-area-inset-bottom, 0) + 10px);
31
+    padding: 0;
32 32
     /**
33 33
      * fixed positioning is necessary for remote menus and tooltips to pop
34 34
      * out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
@@ -40,6 +40,10 @@
40 40
     right: 0;
41 41
     z-index: $filmstripVideosZ;
42 42
 
43
+    &.no-vertical-padding {
44
+        padding: 0;
45
+    }
46
+
43 47
     /**
44 48
      * Hide videos by making them slight to the right.
45 49
      */
@@ -58,7 +62,10 @@
58 62
         &#remoteVideos {
59 63
             border: $thumbnailsBorder solid transparent;
60 64
             padding-left: 0;
65
+            border-left: 0;
61 66
             width: 100%;
67
+            height: 100%;
68
+            justify-content: center;
62 69
         }
63 70
     }
64 71
 
@@ -67,11 +74,12 @@
67 74
      */
68 75
     #filmstripLocalVideo {
69 76
         align-self: initial;
70
-        bottom: 5px;
77
+        margin-bottom: 5px;
71 78
         display: flex;
72 79
         flex-direction: column-reverse;
73 80
         height: auto;
74 81
         justify-content: flex-start;
82
+        width: 100%;
75 83
 
76 84
         #filmstripLocalVideoThumbnail {
77 85
             width: calc(100% - 15px);
@@ -100,15 +108,27 @@
100 108
         flex-grow: 1;
101 109
     }
102 110
 
111
+    .resizable-filmstrip #remoteVideos .videocontainer {
112
+        border-left: 0;
113
+        margin: 0;
114
+    }
115
+
103 116
     &.reduce-height {
104 117
         height: calc(100% - calc(#{$newToolbarSizeWithPadding} + #{$scrollHeight}));
105 118
     }
106 119
 
107 120
     .remote-videos {
108 121
         display: flex;
109
-        transition: height .3s ease-in;
110 122
         overscroll-behavior: contain;
111 123
 
124
+        &.height-transition {
125
+            transition: height .3s ease-in;
126
+        }
127
+
128
+        &.vertical-grid-margin > div {
129
+            margin-right: $scrollHeight;
130
+        }
131
+
112 132
         & > div {
113 133
             position: absolute;
114 134
             transition: opacity 1s;

+ 1
- 1
css/premeeting/_premeeting-screens.scss Zobrazit soubor

@@ -7,7 +7,7 @@
7 7
     position: absolute;
8 8
     right: 0;
9 9
     top: 0;
10
-    z-index: $toolbarZ + 1;
10
+    z-index: $toolbarZ + 2;
11 11
 
12 12
     .action-btn {
13 13
         border-radius: 6px;

+ 7
- 0
modules/UI/videolayout/LargeVideoManager.js Zobrazit soubor

@@ -26,6 +26,7 @@ import {
26 26
     isTrackStreamingStatusInactive,
27 27
     isTrackStreamingStatusInterrupted
28 28
 } from '../../../react/features/connection-indicator/functions';
29
+import { FILMSTRIP_BREAKPOINT, isFilmstripResizable } from '../../../react/features/filmstrip';
29 30
 import {
30 31
     updateKnownLargeVideoResolution
31 32
 } from '../../../react/features/large-video/actions';
@@ -401,7 +402,9 @@ export default class LargeVideoManager {
401 402
         let widthToUse = this.preferredWidth || window.innerWidth;
402 403
         const state = APP.store.getState();
403 404
         const { isOpen } = state['features/chat'];
405
+        const { width: filmstripWidth, visible } = state['features/filmstrip'];
404 406
         const isParticipantsPaneOpen = getParticipantsPaneOpen(state);
407
+        const resizableFilmstrip = isFilmstripResizable(state);
405 408
 
406 409
         if (isParticipantsPaneOpen) {
407 410
             widthToUse -= theme.participantsPaneWidth;
@@ -415,6 +418,10 @@ export default class LargeVideoManager {
415 418
             widthToUse -= CHAT_SIZE;
416 419
         }
417 420
 
421
+        if (resizableFilmstrip && visible && filmstripWidth.current >= FILMSTRIP_BREAKPOINT) {
422
+            widthToUse -= filmstripWidth.current;
423
+        }
424
+
418 425
         this.width = widthToUse;
419 426
         this.height = this.preferredHeight || window.innerHeight;
420 427
     }

+ 1
- 0
react/features/base/config/configWhitelist.js Zobrazit soubor

@@ -154,6 +154,7 @@ export default [
154 154
     'failICE',
155 155
     'feedbackPercentage',
156 156
     'fileRecordingsEnabled',
157
+    'filmstrip',
157 158
     'firefox_fake_device',
158 159
     'forceJVB121Ratio',
159 160
     'forceTurnRelay',

+ 18
- 0
react/features/filmstrip/actionTypes.js Zobrazit soubor

@@ -91,3 +91,21 @@ export const SET_VOLUME = 'SET_VOLUME';
91 91
  * }
92 92
  */
93 93
 export const SET_VISIBLE_REMOTE_PARTICIPANTS = 'SET_VISIBLE_REMOTE_PARTICIPANTS';
94
+
95
+/**
96
+ * The type of action which sets the width for the vertical filmstrip.
97
+ * {
98
+ *      type: SET_FILMSTRIP_WIDTH,
99
+ *      width: number
100
+ * }
101
+ */
102
+export const SET_FILMSTRIP_WIDTH = 'SET_FILMSTRIP_WIDTH';
103
+
104
+/**
105
+ * The type of action which sets the width for the vertical filmstrip (user resized).
106
+ * {
107
+ *      type: SET_USER_FILMSTRIP_WIDTH,
108
+ *      width: number
109
+ * }
110
+ */
111
+export const SET_USER_FILMSTRIP_WIDTH = 'SET_USER_FILMSTRIP_WIDTH';

+ 89
- 7
react/features/filmstrip/actions.web.js Zobrazit soubor

@@ -3,26 +3,32 @@ import type { Dispatch } from 'redux';
3 3
 
4 4
 import { getLocalParticipant, getParticipantById, pinParticipant } from '../base/participants';
5 5
 import { shouldHideSelfView } from '../base/settings/functions.any';
6
+import { getTileViewGridDimensions } from '../video-layout';
6 7
 
7 8
 import {
9
+    SET_FILMSTRIP_WIDTH,
8 10
     SET_HORIZONTAL_VIEW_DIMENSIONS,
9 11
     SET_TILE_VIEW_DIMENSIONS,
12
+    SET_USER_FILMSTRIP_WIDTH,
10 13
     SET_VERTICAL_VIEW_DIMENSIONS,
11 14
     SET_VOLUME
12 15
 } from './actionTypes';
13 16
 import {
14 17
     HORIZONTAL_FILMSTRIP_MARGIN,
15 18
     SCROLL_SIZE,
16
-    STAGE_VIEW_THUMBNAIL_HORIZONTAL_BORDER,
17 19
     STAGE_VIEW_THUMBNAIL_VERTICAL_BORDER,
18 20
     TILE_HORIZONTAL_MARGIN,
21
+    TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN,
19 22
     TILE_VERTICAL_MARGIN,
20 23
     VERTICAL_FILMSTRIP_VERTICAL_MARGIN
21 24
 } from './constants';
22 25
 import {
23 26
     calculateThumbnailSizeForHorizontalView,
24 27
     calculateThumbnailSizeForTileView,
25
-    calculateThumbnailSizeForVerticalView
28
+    calculateThumbnailSizeForVerticalView,
29
+    calculateThumbnailSizeForResizableVerticalView,
30
+    isFilmstripResizable,
31
+    showGridInVerticalView
26 32
 } from './functions';
27 33
 
28 34
 export * from './actions.any';
@@ -80,21 +86,65 @@ export function setVerticalViewDimensions() {
80 86
     return (dispatch: Dispatch<any>, getState: Function) => {
81 87
         const state = getState();
82 88
         const { clientHeight = 0, clientWidth = 0 } = state['features/base/responsive-ui'];
89
+        const { width: filmstripWidth } = state['features/filmstrip'];
83 90
         const disableSelfView = shouldHideSelfView(state);
84
-        const thumbnails = calculateThumbnailSizeForVerticalView(clientWidth);
91
+        const resizableFilmstrip = isFilmstripResizable(state);
92
+        const _verticalViewGrid = showGridInVerticalView(state);
93
+
94
+        let gridView = {};
95
+        let thumbnails = {};
96
+        let filmstripDimensions = {};
97
+
98
+        // grid view in the vertical filmstrip
99
+        if (_verticalViewGrid) {
100
+            const dimensions = getTileViewGridDimensions(state, filmstripWidth.current);
101
+            const {
102
+                height,
103
+                width
104
+            } = calculateThumbnailSizeForTileView({
105
+                ...dimensions,
106
+                clientWidth: filmstripWidth.current,
107
+                clientHeight,
108
+                disableResponsiveTiles: false,
109
+                disableTileEnlargement: false,
110
+                isVerticalFilmstrip: true
111
+            });
112
+            const { columns, rows } = dimensions;
113
+            const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height);
114
+            const hasScroll = clientHeight < thumbnailsTotalHeight;
115
+            const widthOfFilmstrip = (columns * (TILE_HORIZONTAL_MARGIN + width)) + (hasScroll ? SCROLL_SIZE : 0);
116
+            const filmstripHeight = Math.min(clientHeight, thumbnailsTotalHeight);
117
+
118
+            gridView = {
119
+                gridDimensions: dimensions,
120
+                thumbnailSize: {
121
+                    height,
122
+                    width
123
+                }
124
+            };
125
+
126
+            filmstripDimensions = {
127
+                height: filmstripHeight,
128
+                width: widthOfFilmstrip
129
+            };
130
+        } else {
131
+            thumbnails = resizableFilmstrip
132
+                ? calculateThumbnailSizeForResizableVerticalView(clientWidth, filmstripWidth.current)
133
+                : calculateThumbnailSizeForVerticalView(clientWidth);
134
+        }
85 135
 
86 136
         dispatch({
87 137
             type: SET_VERTICAL_VIEW_DIMENSIONS,
88 138
             dimensions: {
89 139
                 ...thumbnails,
90
-                remoteVideosContainer: {
140
+                remoteVideosContainer: _verticalViewGrid ? filmstripDimensions : {
91 141
                     width: thumbnails?.local?.width
92
-                        + TILE_HORIZONTAL_MARGIN + STAGE_VIEW_THUMBNAIL_HORIZONTAL_BORDER + SCROLL_SIZE,
142
+                        + TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN + SCROLL_SIZE,
93 143
                     height: clientHeight - (disableSelfView ? 0 : thumbnails?.local?.height)
94 144
                         - VERTICAL_FILMSTRIP_VERTICAL_MARGIN
95
-                }
145
+                },
146
+                gridView
96 147
             }
97
-
98 148
         });
99 149
     };
100 150
 }
@@ -163,3 +213,35 @@ export function setVolume(participantId: string, volume: number) {
163 213
         volume
164 214
     };
165 215
 }
216
+
217
+/**
218
+ * Sets the filmstrip's width.
219
+ *
220
+ * @param {number} width - The new width of the filmstrip.
221
+ * @returns {{
222
+ *      type: SET_FILMSTRIP_WIDTH,
223
+ *      width: number
224
+ * }}
225
+ */
226
+export function setFilmstripWidth(width: number) {
227
+    return {
228
+        type: SET_FILMSTRIP_WIDTH,
229
+        width
230
+    };
231
+}
232
+
233
+/**
234
+ * Sets the filmstrip's width and the user preferred width.
235
+ *
236
+ * @param {number} width - The new width of the filmstrip.
237
+ * @returns {{
238
+ *      type: SET_USER_FILMSTRIP_WIDTH,
239
+ *      width: number
240
+ * }}
241
+ */
242
+export function setUserFilmstripWidth(width: number) {
243
+    return {
244
+        type: SET_USER_FILMSTRIP_WIDTH,
245
+        width
246
+    };
247
+}

+ 215
- 116
react/features/filmstrip/components/web/Filmstrip.js Zobrazit soubor

@@ -2,6 +2,7 @@
2 2
 
3 3
 import { withStyles } from '@material-ui/styles';
4 4
 import clsx from 'clsx';
5
+import _ from 'lodash';
5 6
 import React, { PureComponent } from 'react';
6 7
 import { FixedSizeList, FixedSizeGrid } from 'react-window';
7 8
 import type { Dispatch } from 'redux';
@@ -20,19 +21,28 @@ import { shouldHideSelfView } from '../../../base/settings/functions.any';
20 21
 import { showToolbox } from '../../../toolbox/actions.web';
21 22
 import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.web';
22 23
 import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
23
-import { setFilmstripVisible, setVisibleRemoteParticipants } from '../../actions';
24
+import { setFilmstripVisible, setVisibleRemoteParticipants, setUserFilmstripWidth } from '../../actions';
24 25
 import {
25 26
     ASPECT_RATIO_BREAKPOINT,
27
+    DEFAULT_FILMSTRIP_WIDTH,
28
+    FILMSTRIP_BREAKPOINT,
29
+    FILMSTRIP_BREAKPOINT_OFFSET,
30
+    MIN_STAGE_VIEW_WIDTH,
26 31
     TILE_HORIZONTAL_MARGIN,
27 32
     TILE_VERTICAL_MARGIN,
28 33
     TOOLBAR_HEIGHT,
29 34
     TOOLBAR_HEIGHT_MOBILE
30 35
 } from '../../constants';
31
-import { shouldRemoteVideosBeVisible } from '../../functions';
36
+import {
37
+    isFilmstripResizable,
38
+    shouldRemoteVideosBeVisible,
39
+    showGridInVerticalView
40
+} from '../../functions';
32 41
 
33 42
 import AudioTracksContainer from './AudioTracksContainer';
34 43
 import Thumbnail from './Thumbnail';
35 44
 import ThumbnailWrapper from './ThumbnailWrapper';
45
+import { styles } from './styles';
36 46
 
37 47
 declare var APP: Object;
38 48
 declare var interfaceConfig: Object;
@@ -82,11 +92,21 @@ type Props = {
82 92
      */
83 93
     _isFilmstripButtonEnabled: boolean,
84 94
 
95
+    /**
96
+    * Whether or not the toolbox is displayed.
97
+    */
98
+    _isToolboxVisible: Boolean,
99
+
85 100
     /**
86 101
      * Whether or not the current layout is vertical filmstrip.
87 102
      */
88 103
     _isVerticalFilmstrip: boolean,
89 104
 
105
+    /**
106
+     * The maximum width of the vertical filmstrip.
107
+     */
108
+    _maxFilmstripWidth: number,
109
+
90 110
     /**
91 111
      * The participants in the call.
92 112
      */
@@ -97,6 +117,11 @@ type Props = {
97 117
      */
98 118
     _remoteParticipantsLength: number,
99 119
 
120
+    /**
121
+     * Whether or not the filmstrip should be user-resizable.
122
+     */
123
+    _resizableFilmstrip: boolean,
124
+
100 125
     /**
101 126
      * The number of rows in tile view.
102 127
      */
@@ -117,6 +142,16 @@ type Props = {
117 142
      */
118 143
     _thumbnailsReordered: Boolean,
119 144
 
145
+    /**
146
+     * The width of the vertical filmstrip (user resized).
147
+     */
148
+    _verticalFilmstripWidth: ?number,
149
+
150
+    /**
151
+     * Whether or not the vertical filmstrip should be displayed as grid.
152
+     */
153
+    _verticalViewGrid: boolean,
154
+
120 155
     /**
121 156
      * Additional CSS class names to add to the container of all the thumbnails.
122 157
      */
@@ -127,11 +162,6 @@ type Props = {
127 162
      */
128 163
     _visible: boolean,
129 164
 
130
-    /**
131
-     * Whether or not the toolbox is displayed.
132
-     */
133
-    _isToolboxVisible: Boolean,
134
-
135 165
     /**
136 166
      * An object containing the CSS classes.
137 167
      */
@@ -148,83 +178,23 @@ type Props = {
148 178
     t: Function
149 179
 };
150 180
 
151
-/**
152
- * Creates the styles for the component.
153
- *
154
- * @param {Object} theme - The current theme.
155
- * @returns {Object}
156
- */
157
-const styles = theme => {
158
-    return {
159
-        toggleFilmstripContainer: {
160
-            display: 'flex',
161
-            flexWrap: 'nowrap',
162
-            alignItems: 'center',
163
-            justifyContent: 'center',
164
-            backgroundColor: 'rgba(0, 0, 0, .6)',
165
-            width: '32px',
166
-            height: '24px',
167
-            position: 'absolute',
168
-            borderRadius: '4px',
169
-            top: 'calc(-24px - 2px)',
170
-            left: 'calc(50% - 16px)',
171
-            opacity: 0,
172
-            transition: 'opacity .3s'
173
-        },
174
-
175
-        toggleFilmstripButton: {
176
-            fontSize: '14px',
177
-            lineHeight: 1.2,
178
-            textAlign: 'center',
179
-            background: 'transparent',
180
-            height: 'auto',
181
-            width: '100%',
182
-            padding: 0,
183
-            margin: 0,
184
-            border: 'none',
185
-
186
-            '-webkit-appearance': 'none',
187
-
188
-            '& svg': {
189
-                fill: theme.palette.icon02
190
-            }
191
-        },
192
-
193
-        toggleVerticalFilmstripContainer: {
194
-            transform: 'rotate(-90deg)',
195
-            left: 'calc(-24px - 2px - 5px)',
196
-            top: 'calc(50% - 16px)'
197
-        },
198
-
199
-        filmstrip: {
200
-            transition: 'background .2s ease-in-out, right 1s, bottom 1s, height .3s ease-in',
201
-            right: 0,
202
-            bottom: 0,
203
-
204
-            '&:hover': {
205
-                backgroundColor: 'rgba(0, 0, 0, .6)',
206
-
207
-                '& .toggleFilmstripContainer': {
208
-                    opacity: 1
209
-                }
210
-            },
181
+type State = {
211 182
 
212
-            '.horizontal-filmstrip &.hidden': {
213
-                bottom: '-50px',
183
+    /**
184
+     * Whether or not the mouse is pressed.
185
+     */
186
+    isMouseDown: boolean,
214 187
 
215
-                '&:hover': {
216
-                    backgroundColor: 'transparent'
217
-                }
218
-            },
188
+    /**
189
+     * Initial mouse position on drag handle mouse down.
190
+     */
191
+    mousePosition: ?number,
219 192
 
220
-            '&.hidden': {
221
-                '& .toggleFilmstripContainer': {
222
-                    opacity: 1
223
-                }
224
-            }
225
-        }
226
-    };
227
-};
193
+    /**
194
+     * Initial filmstrip width on drag handle mouse down.
195
+     */
196
+    dragFilmstripWidth: ?number
197
+}
228 198
 
229 199
 /**
230 200
  * Implements a React {@link Component} which represents the filmstrip on
@@ -232,7 +202,9 @@ const styles = theme => {
232 202
  *
233 203
  * @augments Component
234 204
  */
235
-class Filmstrip extends PureComponent <Props> {
205
+class Filmstrip extends PureComponent <Props, State> {
206
+
207
+    _throttledResize: Function;
236 208
 
237 209
     /**
238 210
      * Initializes a new {@code Filmstrip} instance.
@@ -243,6 +215,12 @@ class Filmstrip extends PureComponent <Props> {
243 215
     constructor(props: Props) {
244 216
         super(props);
245 217
 
218
+        this.state = {
219
+            isMouseDown: false,
220
+            mousePosition: null,
221
+            dragFilmstripWidth: null
222
+        };
223
+
246 224
         // Bind event handlers so they are only bound once for every instance.
247 225
         this._onShortcutToggleFilmstrip = this._onShortcutToggleFilmstrip.bind(this);
248 226
         this._onToolbarToggleFilmstrip = this._onToolbarToggleFilmstrip.bind(this);
@@ -252,6 +230,17 @@ class Filmstrip extends PureComponent <Props> {
252 230
         this._onGridItemsRendered = this._onGridItemsRendered.bind(this);
253 231
         this._onListItemsRendered = this._onListItemsRendered.bind(this);
254 232
         this._onToggleButtonTouch = this._onToggleButtonTouch.bind(this);
233
+        this._onDragHandleMouseDown = this._onDragHandleMouseDown.bind(this);
234
+        this._onDragMouseUp = this._onDragMouseUp.bind(this);
235
+        this._onFilmstripResize = this._onFilmstripResize.bind(this);
236
+
237
+        this._throttledResize = _.throttle(
238
+            this._onFilmstripResize,
239
+            50,
240
+            {
241
+                leading: true,
242
+                trailing: false
243
+            });
255 244
     }
256 245
 
257 246
     /**
@@ -266,6 +255,8 @@ class Filmstrip extends PureComponent <Props> {
266 255
             this._onShortcutToggleFilmstrip,
267 256
             'keyboardShortcuts.toggleFilmstrip'
268 257
         );
258
+        document.addEventListener('mouseup', this._onDragMouseUp);
259
+        document.addEventListener('mousemove', this._throttledResize);
269 260
     }
270 261
 
271 262
     /**
@@ -275,6 +266,8 @@ class Filmstrip extends PureComponent <Props> {
275 266
      */
276 267
     componentWillUnmount() {
277 268
         APP.keyboardshortcut.unregisterShortcut('F');
269
+        document.removeEventListener('mouseup', this._onDragMouseUp);
270
+        document.removeEventListener('mousemove', this._throttledResize);
278 271
     }
279 272
 
280 273
     /**
@@ -285,17 +278,32 @@ class Filmstrip extends PureComponent <Props> {
285 278
      */
286 279
     render() {
287 280
         const filmstripStyle = { };
288
-        const { _currentLayout, _disableSelfView, classes, _visible } = this.props;
281
+        const {
282
+            _currentLayout,
283
+            _disableSelfView,
284
+            _resizableFilmstrip,
285
+            _verticalFilmstripWidth,
286
+            _visible,
287
+            _verticalViewGrid,
288
+            classes
289
+        } = this.props;
290
+        const { isMouseDown } = this.state;
289 291
         const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
292
+        let maxWidth;
290 293
 
291 294
         switch (_currentLayout) {
292 295
         case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
293
-            // Adding 18px for the 2px margins, 2px borders on the left and right and 5px padding on the left and right.
294
-            // Also adding 7px for the scrollbar.
295
-            filmstripStyle.maxWidth = (interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120) + 25;
296
+            maxWidth = _resizableFilmstrip
297
+                ? _verticalFilmstripWidth || DEFAULT_FILMSTRIP_WIDTH
298
+                : interfaceConfig.FILM_STRIP_MAX_HEIGHT || DEFAULT_FILMSTRIP_WIDTH;
299
+
300
+            // Adding 4px for the border-right and margin-right.
301
+            // On non-resizable filmstrip add 4px for the left margin and border.
302
+            // Also adding 7px for the scrollbar. Also adding 9px for the drag handle.
303
+            filmstripStyle.maxWidth = maxWidth + (_verticalViewGrid ? 0 : 11) + (_resizableFilmstrip ? 9 : 4);
296 304
 
297 305
             if (!_visible) {
298
-                filmstripStyle.right = `-${filmstripStyle.maxWidth + 2}px`;
306
+                filmstripStyle.right = `-${filmstripStyle.maxWidth}px`;
299 307
             }
300 308
             break;
301 309
         }
@@ -306,37 +314,113 @@ class Filmstrip extends PureComponent <Props> {
306 314
             toolbar = this._renderToggleButton();
307 315
         }
308 316
 
317
+        const filmstrip = (<>
318
+            <div
319
+                className = { clsx(this.props._videosClassName,
320
+                    !tileViewActive && !_resizableFilmstrip && 'filmstrip-hover') }
321
+                id = 'remoteVideos'>
322
+                {!_disableSelfView && !_verticalViewGrid && (
323
+                    <div
324
+                        className = 'filmstrip__videos'
325
+                        id = 'filmstripLocalVideo'>
326
+                        <div id = 'filmstripLocalVideoThumbnail'>
327
+                            {
328
+                                !tileViewActive && <Thumbnail
329
+                                    key = 'local' />
330
+                            }
331
+                        </div>
332
+                    </div>
333
+                )}
334
+                {
335
+                    this._renderRemoteParticipants()
336
+                }
337
+            </div>
338
+        </>);
339
+
309 340
         return (
310 341
             <div
311 342
                 className = { clsx('filmstrip',
312 343
                     this.props._className,
313
-                    classes.filmstrip) }
344
+                    classes.filmstrip,
345
+                    _verticalViewGrid && 'no-vertical-padding',
346
+                    _verticalFilmstripWidth + FILMSTRIP_BREAKPOINT_OFFSET >= FILMSTRIP_BREAKPOINT
347
+                        && classes.filmstripBackground) }
314 348
                 style = { filmstripStyle }>
315 349
                 { toolbar }
316
-                <div
317
-                    className = { this.props._videosClassName }
318
-                    id = 'remoteVideos'>
319
-                    {!_disableSelfView && (
350
+                {_resizableFilmstrip
351
+                    ? <div className = { clsx('resizable-filmstrip', classes.resizableFilmstripContainer) }>
320 352
                         <div
321
-                            className = 'filmstrip__videos'
322
-                            id = 'filmstripLocalVideo'>
323
-                            <div id = 'filmstripLocalVideoThumbnail'>
324
-                                {
325
-                                    !tileViewActive && <Thumbnail
326
-                                        key = 'local' />
327
-                                }
328
-                            </div>
353
+                            className = { clsx('dragHandleContainer',
354
+                                classes.dragHandleContainer,
355
+                                isMouseDown && 'visible')
356
+                            }
357
+                            onMouseDown = { this._onDragHandleMouseDown }>
358
+                            <div className = { clsx(classes.dragHandle, 'dragHandle') } />
329 359
                         </div>
330
-                    )}
331
-                    {
332
-                        this._renderRemoteParticipants()
333
-                    }
334
-                </div>
360
+                        {filmstrip}
361
+                    </div>
362
+                    : filmstrip
363
+                }
335 364
                 <AudioTracksContainer />
336 365
             </div>
337 366
         );
338 367
     }
339 368
 
369
+    _onDragHandleMouseDown: (MouseEvent) => void;
370
+
371
+    /**
372
+     * Handles mouse down on the drag handle.
373
+     *
374
+     * @param {MouseEvent} e - The mouse down event.
375
+     * @returns {void}
376
+     */
377
+    _onDragHandleMouseDown(e) {
378
+        this.setState({
379
+            isMouseDown: true,
380
+            mousePosition: e.clientX,
381
+            dragFilmstripWidth: this.props._verticalFilmstripWidth || DEFAULT_FILMSTRIP_WIDTH
382
+        });
383
+    }
384
+
385
+    _onDragMouseUp: () => void;
386
+
387
+    /**
388
+     * Drag handle mouse up handler.
389
+     *
390
+     * @returns {void}
391
+     */
392
+    _onDragMouseUp() {
393
+        if (this.state.isMouseDown) {
394
+            this.setState({
395
+                isMouseDown: false
396
+            });
397
+        }
398
+    }
399
+
400
+    _onFilmstripResize: (MouseEvent) => void;
401
+
402
+    /**
403
+     * Handles drag handle mouse move.
404
+     *
405
+     * @param {MouseEvent} e - The mousemove event.
406
+     * @returns {void}
407
+     */
408
+    _onFilmstripResize(e) {
409
+        if (this.state.isMouseDown) {
410
+            const { dispatch, _verticalFilmstripWidth, _maxFilmstripWidth } = this.props;
411
+            const { dragFilmstripWidth, mousePosition } = this.state;
412
+            const diff = mousePosition - e.clientX;
413
+            const width = Math.max(
414
+                Math.min(dragFilmstripWidth + diff, _maxFilmstripWidth),
415
+                DEFAULT_FILMSTRIP_WIDTH
416
+            );
417
+
418
+            if (width !== _verticalFilmstripWidth) {
419
+                dispatch(setUserFilmstripWidth(width));
420
+            }
421
+        }
422
+    }
423
+
340 424
     /**
341 425
      * Calculates the start and stop indices based on whether the thumbnails need to be reordered in the filmstrip.
342 426
      *
@@ -480,7 +564,8 @@ class Filmstrip extends PureComponent <Props> {
480 564
             _remoteParticipantsLength,
481 565
             _rows,
482 566
             _thumbnailHeight,
483
-            _thumbnailWidth
567
+            _thumbnailWidth,
568
+            _verticalViewGrid
484 569
         } = this.props;
485 570
 
486 571
         if (!_thumbnailWidth || isNaN(_thumbnailWidth) || !_thumbnailHeight
@@ -489,7 +574,7 @@ class Filmstrip extends PureComponent <Props> {
489 574
             return null;
490 575
         }
491 576
 
492
-        if (_currentLayout === LAYOUTS.TILE_VIEW) {
577
+        if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid) {
493 578
             return (
494 579
                 <FixedSizeGrid
495 580
                     className = 'filmstrip__videos remote-videos'
@@ -514,7 +599,7 @@ class Filmstrip extends PureComponent <Props> {
514 599
 
515 600
         const props = {
516 601
             itemCount: _remoteParticipantsLength,
517
-            className: 'filmstrip__videos remote-videos',
602
+            className: 'filmstrip__videos remote-videos height-transition',
518 603
             height: _filmstripHeight,
519 604
             itemKey: this._listItemKey,
520 605
             itemSize: 0,
@@ -668,18 +753,21 @@ function _mapStateToProps(state) {
668 753
     const toolbarButtons = getToolbarButtons(state);
669 754
     const { testing = {}, iAmRecorder } = state['features/base/config'];
670 755
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
671
-    const { visible, remoteParticipants } = state['features/filmstrip'];
756
+    const { visible, remoteParticipants, width: verticalFilmstripWidth } = state['features/filmstrip'];
672 757
     const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
673 758
     const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
674 759
     const { isOpen: shiftRight } = state['features/chat'];
675 760
     const {
676
-        gridDimensions = {},
761
+        gridDimensions: dimensions = {},
677 762
         filmstripHeight,
678 763
         filmstripWidth,
679 764
         thumbnailSize: tileViewThumbnailSize
680 765
     } = state['features/filmstrip'].tileViewDimensions;
681 766
     const _currentLayout = getCurrentLayout(state);
682 767
     const disableSelfView = shouldHideSelfView(state);
768
+    const _resizableFilmstrip = isFilmstripResizable(state);
769
+    const _verticalViewGrid = showGridInVerticalView(state);
770
+    let gridDimensions = dimensions;
683 771
 
684 772
     const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
685 773
     const availableSpace = clientHeight - filmstripHeight;
@@ -703,7 +791,7 @@ function _mapStateToProps(state) {
703 791
         isMobileBrowser() || _currentLayout !== LAYOUTS.VERTICAL_FILMSTRIP_VIEW);
704 792
 
705 793
     const videosClassName = `filmstrip__videos${visible ? '' : ' hidden'}`;
706
-    const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${
794
+    const className = `${remoteVideosVisible || _verticalViewGrid ? '' : 'hide-videos'} ${
707 795
         shouldReduceHeight ? 'reduce-height' : ''
708 796
     } ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${visible ? '' : 'hidden'}`.trim();
709 797
     let _thumbnailSize, remoteFilmstripHeight, remoteFilmstripWidth;
@@ -715,11 +803,18 @@ function _mapStateToProps(state) {
715 803
         remoteFilmstripWidth = filmstripWidth;
716 804
         break;
717 805
     case LAYOUTS.VERTICAL_FILMSTRIP_VIEW: {
718
-        const { remote, remoteVideosContainer } = state['features/filmstrip'].verticalViewDimensions;
806
+        const { remote, remoteVideosContainer, gridView } = state['features/filmstrip'].verticalViewDimensions;
719 807
 
720
-        _thumbnailSize = remote;
721
-        remoteFilmstripHeight = remoteVideosContainer?.height - (shouldReduceHeight ? TOOLBAR_HEIGHT : 0);
808
+        remoteFilmstripHeight = remoteVideosContainer?.height - (!_verticalViewGrid && shouldReduceHeight
809
+            ? TOOLBAR_HEIGHT : 0);
722 810
         remoteFilmstripWidth = remoteVideosContainer?.width;
811
+
812
+        if (_verticalViewGrid) {
813
+            gridDimensions = gridView.gridDimensions;
814
+            _thumbnailSize = gridView.thumbnailSize;
815
+        } else {
816
+            _thumbnailSize = remote;
817
+        }
723 818
         break;
724 819
     }
725 820
     case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW: {
@@ -741,16 +836,20 @@ function _mapStateToProps(state) {
741 836
         _filmstripWidth: remoteFilmstripWidth,
742 837
         _iAmRecorder: Boolean(iAmRecorder),
743 838
         _isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state),
839
+        _isToolboxVisible: isToolboxVisible(state),
840
+        _isVerticalFilmstrip: _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW,
841
+        _maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
744 842
         _remoteParticipantsLength: remoteParticipants.length,
745 843
         _remoteParticipants: remoteParticipants,
844
+        _resizableFilmstrip,
746 845
         _rows: gridDimensions.rows,
747 846
         _thumbnailWidth: _thumbnailSize?.width,
748 847
         _thumbnailHeight: _thumbnailSize?.height,
749 848
         _thumbnailsReordered: enableThumbnailReordering,
849
+        _verticalFilmstripWidth: verticalFilmstripWidth.current,
750 850
         _videosClassName: videosClassName,
751 851
         _visible: visible,
752
-        _isToolboxVisible: isToolboxVisible(state),
753
-        _isVerticalFilmstrip: _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
852
+        _verticalViewGrid
754 853
     };
755 854
 }
756 855
 

+ 21
- 6
react/features/filmstrip/components/web/Thumbnail.js Zobrazit soubor

@@ -28,10 +28,15 @@ import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
28 28
 import {
29 29
     DISPLAY_MODE_TO_CLASS_NAME,
30 30
     DISPLAY_VIDEO,
31
-    VIDEO_TEST_EVENTS,
32
-    SHOW_TOOLBAR_CONTEXT_MENU_AFTER
31
+    SHOW_TOOLBAR_CONTEXT_MENU_AFTER,
32
+    VIDEO_TEST_EVENTS
33 33
 } from '../../constants';
34
-import { isVideoPlayable, computeDisplayModeFromInput, getDisplayModeInput } from '../../functions';
34
+import {
35
+    computeDisplayModeFromInput,
36
+    getDisplayModeInput,
37
+    isVideoPlayable,
38
+    showGridInVerticalView
39
+} from '../../functions';
35 40
 
36 41
 import ThumbnailAudioIndicator from './ThumbnailAudioIndicator';
37 42
 import ThumbnailBottomIndicators from './ThumbnailBottomIndicators';
@@ -480,7 +485,6 @@ class Thumbnail extends Component<Props, State> {
480 485
             style
481 486
         } = this.props;
482 487
 
483
-
484 488
         const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
485 489
         const jitsiVideoTrack = _videoTrack?.jitsiTrack;
486 490
         const track = jitsiVideoTrack?.track;
@@ -949,19 +953,30 @@ function _mapStateToProps(state, ownProps): Object {
949 953
             },
950 954
             verticalViewDimensions = {
951 955
                 local: {},
952
-                remote: {}
956
+                remote: {},
957
+                gridView: {}
953 958
             }
954 959
         } = state['features/filmstrip'];
960
+        const _verticalViewGrid = showGridInVerticalView(state);
955 961
         const { local, remote }
956 962
             = _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
957 963
                 ? verticalViewDimensions : horizontalViewDimensions;
958
-        const { width, height } = isLocal ? local : remote;
964
+        const { width, height } = (isLocal ? local : remote) ?? {};
959 965
 
960 966
         size = {
961 967
             _width: width,
962 968
             _height: height
963 969
         };
964 970
 
971
+        if (_verticalViewGrid) {
972
+            const { width: _width, height: _height } = verticalViewDimensions.gridView.thumbnailSize;
973
+
974
+            size = {
975
+                _width,
976
+                _height
977
+            };
978
+        }
979
+
965 980
         _isMobilePortrait = _isMobile && state['features/base/responsive-ui'].aspectRatio === ASPECT_RATIO_NARROW;
966 981
 
967 982
         break;

+ 7
- 2
react/features/filmstrip/components/web/ThumbnailWrapper.js Zobrazit soubor

@@ -5,6 +5,7 @@ import { shouldComponentUpdate } from 'react-window';
5 5
 import { connect } from '../../../base/redux';
6 6
 import { shouldHideSelfView } from '../../../base/settings/functions.any';
7 7
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
8
+import { showGridInVerticalView } from '../../functions';
8 9
 
9 10
 import Thumbnail from './Thumbnail';
10 11
 
@@ -120,10 +121,14 @@ function _mapStateToProps(state, ownProps) {
120 121
     const { testing = {} } = state['features/base/config'];
121 122
     const disableSelfView = shouldHideSelfView(state);
122 123
     const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
124
+    const _verticalViewGrid = showGridInVerticalView(state);
123 125
 
124
-    if (_currentLayout === LAYOUTS.TILE_VIEW) {
126
+    if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid) {
125 127
         const { columnIndex, rowIndex } = ownProps;
126
-        const { gridDimensions = {}, thumbnailSize } = state['features/filmstrip'].tileViewDimensions;
128
+        const { gridDimensions: dimensions = {}, thumbnailSize: size } = state['features/filmstrip'].tileViewDimensions;
129
+        const { gridView } = state['features/filmstrip'].verticalViewDimensions;
130
+        const gridDimensions = _verticalViewGrid ? gridView.gridDimensions : dimensions;
131
+        const thumbnailSize = _verticalViewGrid ? gridView.thumbnailSize : size;
127 132
         const { columns, rows } = gridDimensions;
128 133
         const index = (rowIndex * columns) + columnIndex;
129 134
         let horizontalOffset;

+ 150
- 0
react/features/filmstrip/components/web/styles.js Zobrazit soubor

@@ -0,0 +1,150 @@
1
+
2
+const BACKGROUND_COLOR = 'rgba(51, 51, 51, .5)';
3
+
4
+/**
5
+ * Creates the styles for the component.
6
+ *
7
+ * @param {Object} theme - The current theme.
8
+ * @returns {Object}
9
+ */
10
+export const styles = theme => {
11
+    return {
12
+        toggleFilmstripContainer: {
13
+            display: 'flex',
14
+            flexWrap: 'nowrap',
15
+            alignItems: 'center',
16
+            justifyContent: 'center',
17
+            backgroundColor: BACKGROUND_COLOR,
18
+            width: '32px',
19
+            height: '24px',
20
+            position: 'absolute',
21
+            borderRadius: '4px',
22
+            top: 'calc(-24px - 3px)',
23
+            left: 'calc(50% - 16px)',
24
+            opacity: 0,
25
+            transition: 'opacity .3s',
26
+
27
+            '&:hover': {
28
+                backgroundColor: theme.palette.ui02
29
+            }
30
+        },
31
+
32
+        toggleFilmstripButton: {
33
+            fontSize: '14px',
34
+            lineHeight: 1.2,
35
+            textAlign: 'center',
36
+            background: 'transparent',
37
+            height: 'auto',
38
+            width: '100%',
39
+            padding: 0,
40
+            margin: 0,
41
+            border: 'none',
42
+
43
+            '-webkit-appearance': 'none',
44
+
45
+            '& svg': {
46
+                fill: theme.palette.icon01
47
+            }
48
+        },
49
+
50
+        toggleVerticalFilmstripContainer: {
51
+            transform: 'rotate(-90deg)',
52
+            left: 'calc(-24px - 3px - 4px)',
53
+            top: 'calc(50% - 12px)'
54
+        },
55
+
56
+        filmstrip: {
57
+            transition: 'background .2s ease-in-out, right 1s, bottom 1s, height .3s ease-in',
58
+            right: 0,
59
+            bottom: 0,
60
+
61
+            '&:hover': {
62
+                '& .resizable-filmstrip': {
63
+                    backgroundColor: BACKGROUND_COLOR
64
+                },
65
+
66
+                '& .filmstrip-hover': {
67
+                    backgroundColor: BACKGROUND_COLOR
68
+                },
69
+
70
+                '& .toggleFilmstripContainer': {
71
+                    opacity: 1
72
+                },
73
+
74
+                '& .dragHandleContainer': {
75
+                    visibility: 'visible'
76
+                }
77
+            },
78
+
79
+            '.horizontal-filmstrip &.hidden': {
80
+                bottom: '-50px',
81
+
82
+                '&:hover': {
83
+                    backgroundColor: 'transparent'
84
+                }
85
+            },
86
+
87
+            '&.hidden': {
88
+                '& .toggleFilmstripContainer': {
89
+                    opacity: 1
90
+                }
91
+            }
92
+        },
93
+
94
+        filmstripBackground: {
95
+            backgroundColor: theme.palette.uiBackground,
96
+
97
+            '&:hover': {
98
+                backgroundColor: theme.palette.uiBackground
99
+            }
100
+        },
101
+
102
+        resizableFilmstripContainer: {
103
+            display: 'flex',
104
+            position: 'relative',
105
+            flexDirection: 'row',
106
+            alignItems: 'center',
107
+            height: '100%',
108
+            width: '100%',
109
+            transition: 'background .2s ease-in-out',
110
+
111
+            '& .avatar-container': {
112
+                maxWidth: 'initial',
113
+                maxHeight: 'initial'
114
+            }
115
+        },
116
+
117
+        dragHandleContainer: {
118
+            height: '100%',
119
+            width: '9px',
120
+            backgroundColor: 'transparent',
121
+            position: 'relative',
122
+            cursor: 'col-resize',
123
+            display: 'flex',
124
+            alignItems: 'center',
125
+            justifyContent: 'center',
126
+            visibility: 'hidden',
127
+
128
+            '&:hover': {
129
+                '& .dragHandle': {
130
+                    backgroundColor: theme.palette.icon01
131
+                }
132
+            },
133
+
134
+            '&.visible': {
135
+                visibility: 'visible',
136
+
137
+                '& .dragHandle': {
138
+                    backgroundColor: theme.palette.icon01
139
+                }
140
+            }
141
+        },
142
+
143
+        dragHandle: {
144
+            backgroundColor: theme.palette.icon02,
145
+            height: '100px',
146
+            width: '3px',
147
+            borderRadius: '1px'
148
+        }
149
+    };
150
+};

+ 43
- 1
react/features/filmstrip/constants.js Zobrazit soubor

@@ -137,6 +137,14 @@ export const TILE_VERTICAL_MARGIN = 4;
137 137
  */
138 138
 export const TILE_HORIZONTAL_MARGIN = 4;
139 139
 
140
+/**
141
+ * The horizontal margin of a vertical filmstrip tile container.
142
+ *
143
+ * @type {number}
144
+ */
145
+export const TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN = 2;
146
+
147
+
140 148
 /**
141 149
  * The vertical margin of the tile grid container.
142 150
  *
@@ -189,7 +197,7 @@ export const SCROLL_SIZE = 7;
189 197
  *
190 198
  * @type {number}
191 199
  */
192
-export const VERTICAL_FILMSTRIP_VERTICAL_MARGIN = 60;
200
+export const VERTICAL_FILMSTRIP_VERTICAL_MARGIN = 26;
193 201
 
194 202
 /**
195 203
  * The min horizontal space between the thumbnails container and the edges of the window.
@@ -242,3 +250,37 @@ export const INDICATORS_TOOLTIP_POSITION = {
242 250
     [LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'left',
243 251
     [LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'top'
244 252
 };
253
+
254
+/**
255
+ * The default (and minimum) width for the vertical filmstrip (user resizable).
256
+ */
257
+export const DEFAULT_FILMSTRIP_WIDTH = 120;
258
+
259
+/**
260
+ * The width of the filmstrip at which it no longer goes above the stage view, but it pushes it.
261
+ */
262
+export const FILMSTRIP_BREAKPOINT = 180;
263
+
264
+/**
265
+ * The width of the filmstrip at which the display mode changes from column to grid.
266
+ */
267
+export const FILMSTRIP_GRID_BREAKPOINT = 300;
268
+
269
+/**
270
+ * How much before the breakpoint should we display the background.
271
+ * (We display the opaque background before we resize the stage view to make sure
272
+ * the resize is not visible behind the filmstrip).
273
+ */
274
+export const FILMSTRIP_BREAKPOINT_OFFSET = 5;
275
+
276
+/**
277
+ * The minimum width for the stage view
278
+ * (used to determine the maximum width of the user-resizable vertical filmstrip).
279
+ */
280
+export const MIN_STAGE_VIEW_WIDTH = 800;
281
+
282
+/**
283
+ * Horizontal margin used for the vertical filmstrip.
284
+ */
285
+export const VERTICAL_VIEW_HORIZONTAL_MARGIN = VERTICAL_FILMSTRIP_MIN_HORIZONTAL_MARGIN
286
+    + SCROLL_SIZE + TILE_HORIZONTAL_MARGIN + STAGE_VIEW_THUMBNAIL_HORIZONTAL_BORDER;

+ 69
- 15
react/features/filmstrip/functions.web.js Zobrazit soubor

@@ -1,6 +1,7 @@
1 1
 // @flow
2 2
 
3 3
 import { getSourceNameSignalingFeatureFlag } from '../base/config';
4
+import { isMobileBrowser } from '../base/environment/utils';
4 5
 import { MEDIA_TYPE } from '../base/media';
5 6
 import {
6 7
     getLocalParticipant,
@@ -16,25 +17,26 @@ import {
16 17
     isRemoteTrackMuted
17 18
 } from '../base/tracks/functions';
18 19
 import { isTrackStreamingStatusActive, isParticipantConnectionStatusActive } from '../connection-indicator/functions';
19
-import { LAYOUTS } from '../video-layout';
20
+import { getCurrentLayout, LAYOUTS } from '../video-layout';
20 21
 
21 22
 import {
22 23
     ASPECT_RATIO_BREAKPOINT,
24
+    DEFAULT_FILMSTRIP_WIDTH,
23 25
     DISPLAY_AVATAR,
24 26
     DISPLAY_VIDEO,
27
+    FILMSTRIP_GRID_BREAKPOINT,
25 28
     INDICATORS_TOOLTIP_POSITION,
26 29
     SCROLL_SIZE,
27 30
     SQUARE_TILE_ASPECT_RATIO,
28
-    STAGE_VIEW_THUMBNAIL_HORIZONTAL_BORDER,
29 31
     TILE_ASPECT_RATIO,
30 32
     TILE_HORIZONTAL_MARGIN,
33
+    TILE_MIN_HEIGHT_LARGE,
34
+    TILE_MIN_HEIGHT_SMALL,
35
+    TILE_PORTRAIT_ASPECT_RATIO,
31 36
     TILE_VERTICAL_MARGIN,
32 37
     TILE_VIEW_GRID_HORIZONTAL_MARGIN,
33 38
     TILE_VIEW_GRID_VERTICAL_MARGIN,
34
-    VERTICAL_FILMSTRIP_MIN_HORIZONTAL_MARGIN,
35
-    TILE_MIN_HEIGHT_LARGE,
36
-    TILE_MIN_HEIGHT_SMALL,
37
-    TILE_PORTRAIT_ASPECT_RATIO
39
+    VERTICAL_VIEW_HORIZONTAL_MARGIN
38 40
 } from './constants';
39 41
 
40 42
 export * from './functions.any';
@@ -139,7 +141,8 @@ export function isVideoPlayable(stateful: Object | Function, id: String) {
139 141
  */
140 142
 export function calculateThumbnailSizeForHorizontalView(clientHeight: number = 0) {
141 143
     const topBottomMargin = 15;
142
-    const availableHeight = Math.min(clientHeight, (interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120) + topBottomMargin);
144
+    const availableHeight = Math.min(clientHeight,
145
+        (interfaceConfig.FILM_STRIP_MAX_HEIGHT || DEFAULT_FILMSTRIP_WIDTH) + topBottomMargin);
143 146
     const height = availableHeight - topBottomMargin;
144 147
 
145 148
     return {
@@ -161,12 +164,9 @@ export function calculateThumbnailSizeForHorizontalView(clientHeight: number = 0
161 164
  * @returns {{local: {height, width}, remote: {height, width}}}
162 165
  */
163 166
 export function calculateThumbnailSizeForVerticalView(clientWidth: number = 0) {
164
-    const horizontalMargin
165
-        = VERTICAL_FILMSTRIP_MIN_HORIZONTAL_MARGIN + SCROLL_SIZE
166
-            + TILE_HORIZONTAL_MARGIN + STAGE_VIEW_THUMBNAIL_HORIZONTAL_BORDER;
167 167
     const availableWidth = Math.min(
168
-        Math.max(clientWidth - horizontalMargin, 0),
169
-        interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120);
168
+        Math.max(clientWidth - VERTICAL_VIEW_HORIZONTAL_MARGIN, 0),
169
+        interfaceConfig.FILM_STRIP_MAX_HEIGHT || DEFAULT_FILMSTRIP_WIDTH);
170 170
 
171 171
     return {
172 172
         local: {
@@ -180,6 +180,31 @@ export function calculateThumbnailSizeForVerticalView(clientWidth: number = 0) {
180 180
     };
181 181
 }
182 182
 
183
+/**
184
+ * Calculates the size for thumbnails when in vertical view layout
185
+ * and the filmstrip is resizable.
186
+ *
187
+ * @param {number} clientWidth - The height of the app window.
188
+ * @param {number} filmstripWidth - The width of the filmstrip.
189
+ * @returns {{local: {height, width}, remote: {height, width}}}
190
+ */
191
+export function calculateThumbnailSizeForResizableVerticalView(clientWidth: number = 0, filmstripWidth: number = 0) {
192
+    const availableWidth = Math.min(
193
+        Math.max(clientWidth - VERTICAL_VIEW_HORIZONTAL_MARGIN, 0),
194
+        filmstripWidth || DEFAULT_FILMSTRIP_WIDTH);
195
+
196
+    return {
197
+        local: {
198
+            height: DEFAULT_FILMSTRIP_WIDTH,
199
+            width: availableWidth
200
+        },
201
+        remote: {
202
+            height: DEFAULT_FILMSTRIP_WIDTH,
203
+            width: availableWidth
204
+        }
205
+    };
206
+}
207
+
183 208
 /**
184 209
  * Calculates the size for thumbnails when in tile view layout.
185 210
  *
@@ -193,7 +218,8 @@ export function calculateThumbnailSizeForTileView({
193 218
     clientWidth,
194 219
     clientHeight,
195 220
     disableResponsiveTiles,
196
-    disableTileEnlargement
221
+    disableTileEnlargement,
222
+    isVerticalFilmstrip = false
197 223
 }: Object) {
198 224
     let aspectRatio = TILE_ASPECT_RATIO;
199 225
 
@@ -202,7 +228,8 @@ export function calculateThumbnailSizeForTileView({
202 228
     }
203 229
 
204 230
     const minHeight = clientWidth < ASPECT_RATIO_BREAKPOINT ? TILE_MIN_HEIGHT_SMALL : TILE_MIN_HEIGHT_LARGE;
205
-    const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN) - TILE_VIEW_GRID_HORIZONTAL_MARGIN;
231
+    const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN)
232
+        - (isVerticalFilmstrip ? 0 : TILE_VIEW_GRID_HORIZONTAL_MARGIN);
206 233
     const viewHeight = clientHeight - (minVisibleRows * TILE_VERTICAL_MARGIN) - TILE_VIEW_GRID_VERTICAL_MARGIN;
207 234
     const initialWidth = viewWidth / columns;
208 235
     const initialHeight = viewHeight / minVisibleRows;
@@ -285,7 +312,7 @@ export function getVerticalFilmstripVisibleAreaWidth() {
285 312
     // TODO: Check if we can remove the left margins and paddings from the CSS.
286 313
     // FIXME: This function is used to calculate the size of the large video, etherpad or shared video. Once everything
287 314
     // is reactified this calculation will need to move to the corresponding components.
288
-    const filmstripMaxWidth = (interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120) + 18;
315
+    const filmstripMaxWidth = (interfaceConfig.FILM_STRIP_MAX_HEIGHT || DEFAULT_FILMSTRIP_WIDTH) + 18;
289 316
 
290 317
     return Math.min(filmstripMaxWidth, window.innerWidth);
291 318
 }
@@ -365,3 +392,30 @@ export function getDisplayModeInput(props: Object, state: Object) {
365 392
 export function getIndicatorsTooltipPosition(currentLayout: string) {
366 393
     return INDICATORS_TOOLTIP_POSITION[currentLayout] || 'top';
367 394
 }
395
+
396
+/**
397
+ * Returns whether or not the filmstrip is resizable.
398
+ *
399
+ * @param {Object} state - Redux state.
400
+ * @returns {boolean}
401
+ */
402
+export function isFilmstripResizable(state: Object) {
403
+    const { filmstrip } = state['features/base/config'];
404
+    const _currentLayout = getCurrentLayout(state);
405
+
406
+    return !filmstrip?.disableResizable && !isMobileBrowser()
407
+        && _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW;
408
+}
409
+
410
+/**
411
+ * Whether or not grid should be displayed in the vertical filmstrip.
412
+ *
413
+ * @param {Object} state - Redux state.
414
+ * @returns {boolean}
415
+ */
416
+export function showGridInVerticalView(state) {
417
+    const resizableFilmstrip = isFilmstripResizable(state);
418
+    const { width } = state['features/filmstrip'];
419
+
420
+    return resizableFilmstrip && ((width.current ?? 0) > FILMSTRIP_GRID_BREAKPOINT);
421
+}

+ 23
- 0
react/features/filmstrip/middleware.web.js Zobrazit soubor

@@ -10,12 +10,16 @@ import {
10 10
     LAYOUTS
11 11
 } from '../video-layout';
12 12
 
13
+import { SET_USER_FILMSTRIP_WIDTH } from './actionTypes';
13 14
 import {
15
+    setFilmstripWidth,
14 16
     setHorizontalViewDimensions,
15 17
     setTileViewDimensions,
16 18
     setVerticalViewDimensions
17 19
 } from './actions';
20
+import { DEFAULT_FILMSTRIP_WIDTH, MIN_STAGE_VIEW_WIDTH } from './constants';
18 21
 import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions';
22
+import { isFilmstripResizable } from './functions.web';
19 23
 import './subscriber';
20 24
 
21 25
 /**
@@ -53,6 +57,22 @@ MiddlewareRegistry.register(store => next => action => {
53 57
             store.dispatch(setVerticalViewDimensions());
54 58
             break;
55 59
         }
60
+
61
+        if (isFilmstripResizable(state)) {
62
+            const { width: filmstripWidth } = state['features/filmstrip'];
63
+            const { clientWidth } = action;
64
+            let width;
65
+
66
+            if (filmstripWidth.current > clientWidth - MIN_STAGE_VIEW_WIDTH) {
67
+                width = Math.max(clientWidth - MIN_STAGE_VIEW_WIDTH, DEFAULT_FILMSTRIP_WIDTH);
68
+            } else {
69
+                width = Math.min(clientWidth - MIN_STAGE_VIEW_WIDTH, filmstripWidth.userSet);
70
+            }
71
+
72
+            if (width !== filmstripWidth.current) {
73
+                store.dispatch(setFilmstripWidth(width));
74
+            }
75
+        }
56 76
         break;
57 77
     }
58 78
     case PARTICIPANT_JOINED: {
@@ -66,6 +86,9 @@ MiddlewareRegistry.register(store => next => action => {
66 86
         }
67 87
         break;
68 88
     }
89
+    case SET_USER_FILMSTRIP_WIDTH: {
90
+        VideoLayout.refreshLayout();
91
+    }
69 92
     }
70 93
 
71 94
     return result;

+ 42
- 1
react/features/filmstrip/reducer.js Zobrazit soubor

@@ -6,9 +6,11 @@ import { ReducerRegistry } from '../base/redux';
6 6
 import {
7 7
     SET_FILMSTRIP_ENABLED,
8 8
     SET_FILMSTRIP_VISIBLE,
9
+    SET_FILMSTRIP_WIDTH,
9 10
     SET_HORIZONTAL_VIEW_DIMENSIONS,
10 11
     SET_REMOTE_PARTICIPANTS,
11 12
     SET_TILE_VIEW_DIMENSIONS,
13
+    SET_USER_FILMSTRIP_WIDTH,
12 14
     SET_VERTICAL_VIEW_DIMENSIONS,
13 15
     SET_VISIBLE_REMOTE_PARTICIPANTS,
14 16
     SET_VOLUME
@@ -92,7 +94,26 @@ const DEFAULT_STATE = {
92 94
      * @public
93 95
      * @type {Set<string>}
94 96
      */
95
-    visibleRemoteParticipants: new Set()
97
+    visibleRemoteParticipants: new Set(),
98
+
99
+    /**
100
+     * The width of the resizable filmstrip.
101
+     *
102
+     * @public
103
+     * @type {Object}
104
+     */
105
+    width: {
106
+        /**
107
+         * Current width. Affected by: user filmstrip resize,
108
+         * window resize, panels open/ close.
109
+         */
110
+        current: null,
111
+
112
+        /**
113
+         * Width set by user resize. Used as the preferred width.
114
+         */
115
+        userSet: null
116
+    }
96 117
 };
97 118
 
98 119
 ReducerRegistry.register(
@@ -167,6 +188,26 @@ ReducerRegistry.register(
167 188
                 ...state
168 189
             };
169 190
         }
191
+        case SET_FILMSTRIP_WIDTH: {
192
+            return {
193
+                ...state,
194
+                width: {
195
+                    ...state.width,
196
+                    current: action.width
197
+                }
198
+            };
199
+        }
200
+        case SET_USER_FILMSTRIP_WIDTH: {
201
+            const { width } = action;
202
+
203
+            return {
204
+                ...state,
205
+                width: {
206
+                    current: width,
207
+                    userSet: width
208
+                }
209
+            };
210
+        }
170 211
         }
171 212
 
172 213
         return state;

+ 14
- 0
react/features/filmstrip/subscriber.web.js Zobrazit soubor

@@ -21,6 +21,7 @@ import {
21 21
     SINGLE_COLUMN_BREAKPOINT,
22 22
     TWO_COLUMN_BREAKPOINT
23 23
 } from './constants';
24
+import { isFilmstripResizable } from './functions.web';
24 25
 import './subscriber.any';
25 26
 
26 27
 
@@ -36,6 +37,7 @@ StateListenerRegistry.register(
36 37
     },
37 38
     /* listener */ (currentState, store) => {
38 39
         const state = store.getState();
40
+        const resizableFilmstrip = isFilmstripResizable(state);
39 41
 
40 42
         if (shouldDisplayTileView(state)) {
41 43
             const gridDimensions = getTileViewGridDimensions(state);
@@ -45,6 +47,9 @@ StateListenerRegistry.register(
45 47
                 store.dispatch(setTileViewDimensions(gridDimensions));
46 48
             }
47 49
         }
50
+        if (resizableFilmstrip) {
51
+            store.dispatch(setVerticalViewDimensions());
52
+        }
48 53
     }, {
49 54
         deepEquals: true
50 55
     });
@@ -170,3 +175,12 @@ StateListenerRegistry.register(
170 175
             store.dispatch(setTileViewDimensions(gridDimensions));
171 176
         }
172 177
     });
178
+
179
+/**
180
+ * Listens for changes in the filmstrip width to determine the size of the tiles.
181
+ */
182
+StateListenerRegistry.register(
183
+    /* selector */ state => state['features/filmstrip'].width?.current,
184
+    /* listener */(_, store) => {
185
+        store.dispatch(setVerticalViewDimensions());
186
+    });

+ 83
- 4
react/features/large-video/components/LargeVideo.web.js Zobrazit soubor

@@ -2,9 +2,11 @@
2 2
 
3 3
 import React, { Component } from 'react';
4 4
 
5
+import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
5 6
 import { Watermarks } from '../../base/react';
6 7
 import { connect } from '../../base/redux';
7 8
 import { setColorAlpha } from '../../base/util';
9
+import { FILMSTRIP_BREAKPOINT, isFilmstripResizable } from '../../filmstrip';
8 10
 import { SharedVideo } from '../../shared-video/components/web';
9 11
 import { Captions } from '../../subtitles/';
10 12
 import { setTileView } from '../../video-layout/actions';
@@ -21,12 +23,12 @@ type Props = {
21 23
     /**
22 24
      * The user selected background color.
23 25
      */
24
-     _customBackgroundColor: string,
26
+    _customBackgroundColor: string,
25 27
 
26 28
     /**
27 29
      * The user selected background image url.
28 30
      */
29
-     _customBackgroundImageUrl: string,
31
+    _customBackgroundImageUrl: string,
30 32
 
31 33
     /**
32 34
      * Prop that indicates whether the chat is open.
@@ -39,6 +41,21 @@ type Props = {
39 41
      */
40 42
     _noAutoPlayVideo: boolean,
41 43
 
44
+    /**
45
+     * Whether or not the filmstrip is resizable.
46
+     */
47
+    _resizableFilmstrip: boolean,
48
+
49
+    /**
50
+     * The width of the vertical filmstrip (user resized).
51
+     */
52
+    _verticalFilmstripWidth: ?number,
53
+
54
+    /**
55
+     * Whether or not the filmstrip is visible.
56
+     */
57
+    _visibleFilmstrip: boolean,
58
+
42 59
     /**
43 60
      * The Redux dispatch function.
44 61
      */
@@ -54,6 +71,10 @@ type Props = {
54 71
 class LargeVideo extends Component<Props> {
55 72
     _tappedTimeout: ?TimeoutID;
56 73
 
74
+    _containerRef: Object;
75
+
76
+    _wrapperRef: Object;
77
+
57 78
     /**
58 79
      * Constructor of the component.
59 80
      *
@@ -62,8 +83,25 @@ class LargeVideo extends Component<Props> {
62 83
     constructor(props) {
63 84
         super(props);
64 85
 
86
+        this._containerRef = React.createRef();
87
+        this._wrapperRef = React.createRef();
88
+
65 89
         this._clearTapTimeout = this._clearTapTimeout.bind(this);
66 90
         this._onDoubleTap = this._onDoubleTap.bind(this);
91
+        this._updateLayout = this._updateLayout.bind(this);
92
+    }
93
+
94
+    /**
95
+     * Implements {@code Component#componentDidUpdate}.
96
+     *
97
+     * @inheritdoc
98
+     */
99
+    componentDidUpdate(prevProps: Props) {
100
+        const { _visibleFilmstrip } = this.props;
101
+
102
+        if (prevProps._visibleFilmstrip !== _visibleFilmstrip) {
103
+            this._updateLayout();
104
+        }
67 105
     }
68 106
 
69 107
     /**
@@ -84,6 +122,7 @@ class LargeVideo extends Component<Props> {
84 122
             <div
85 123
                 className = { className }
86 124
                 id = 'largeVideoContainer'
125
+                ref = { this._containerRef }
87 126
                 style = { style }>
88 127
                 <SharedVideo />
89 128
                 <div id = 'etherpad' />
@@ -112,6 +151,7 @@ class LargeVideo extends Component<Props> {
112 151
                     <div
113 152
                         id = 'largeVideoWrapper'
114 153
                         onTouchEnd = { this._onDoubleTap }
154
+                        ref = { this._wrapperRef }
115 155
                         role = 'figure' >
116 156
                         <video
117 157
                             autoPlay = { !_noAutoPlayVideo }
@@ -126,6 +166,32 @@ class LargeVideo extends Component<Props> {
126 166
         );
127 167
     }
128 168
 
169
+    _updateLayout: () => void;
170
+
171
+    /**
172
+     * Refreshes the video layout to determine the dimensions of the stage view.
173
+     * If the filmstrip is toggled it adds CSS transition classes and removes them
174
+     * when the transition is done.
175
+     *
176
+     * @returns {void}
177
+     */
178
+    _updateLayout() {
179
+        const { _verticalFilmstripWidth, _resizableFilmstrip } = this.props;
180
+
181
+        if (_resizableFilmstrip && _verticalFilmstripWidth >= FILMSTRIP_BREAKPOINT) {
182
+            this._containerRef.current.classList.add('transition');
183
+            this._wrapperRef.current.classList.add('transition');
184
+            VideoLayout.refreshLayout();
185
+
186
+            setTimeout(() => {
187
+                this._containerRef.current && this._containerRef.current.classList.remove('transition');
188
+                this._wrapperRef.current && this._wrapperRef.current.classList.remove('transition');
189
+            }, 1000);
190
+        } else {
191
+            VideoLayout.refreshLayout();
192
+        }
193
+    }
194
+
129 195
     _clearTapTimeout: () => void;
130 196
 
131 197
     /**
@@ -147,7 +213,12 @@ class LargeVideo extends Component<Props> {
147 213
      */
148 214
     _getCustomSyles() {
149 215
         const styles = {};
150
-        const { _customBackgroundColor, _customBackgroundImageUrl } = this.props;
216
+        const {
217
+            _customBackgroundColor,
218
+            _customBackgroundImageUrl,
219
+            _verticalFilmstripWidth,
220
+            _visibleFilmstrip
221
+        } = this.props;
151 222
 
152 223
         styles.backgroundColor = _customBackgroundColor || interfaceConfig.DEFAULT_BACKGROUND;
153 224
 
@@ -162,6 +233,10 @@ class LargeVideo extends Component<Props> {
162 233
             styles.backgroundSize = 'cover';
163 234
         }
164 235
 
236
+        if (_visibleFilmstrip && _verticalFilmstripWidth >= FILMSTRIP_BREAKPOINT) {
237
+            styles.width = `calc(100% - ${_verticalFilmstripWidth || 0}px)`;
238
+        }
239
+
165 240
         return styles;
166 241
     }
167 242
 
@@ -199,13 +274,17 @@ function _mapStateToProps(state) {
199 274
     const testingConfig = state['features/base/config'].testing;
200 275
     const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding'];
201 276
     const { isOpen: isChatOpen } = state['features/chat'];
277
+    const { width: verticalFilmstripWidth, visible } = state['features/filmstrip'];
202 278
 
203 279
     return {
204 280
         _backgroundAlpha: state['features/base/config'].backgroundAlpha,
205 281
         _customBackgroundColor: backgroundColor,
206 282
         _customBackgroundImageUrl: backgroundImageUrl,
207 283
         _isChatOpen: isChatOpen,
208
-        _noAutoPlayVideo: testingConfig?.noAutoPlayVideo
284
+        _noAutoPlayVideo: testingConfig?.noAutoPlayVideo,
285
+        _resizableFilmstrip: isFilmstripResizable(state),
286
+        _verticalFilmstripWidth: verticalFilmstripWidth.current,
287
+        _visibleFilmstrip: visible
209 288
     };
210 289
 }
211 290
 

+ 9
- 6
react/features/video-layout/functions.js Zobrazit soubor

@@ -59,9 +59,10 @@ export function getCurrentLayout(state: Object) {
59 59
  * returned will be between 1 and 7, inclusive.
60 60
  *
61 61
  * @param {Object} state - The redux store state.
62
+ * @param {number} width - Custom width to use for calculation.
62 63
  * @returns {number}
63 64
  */
64
-export function getMaxColumnCount(state: Object) {
65
+export function getMaxColumnCount(state: Object, width: ?number) {
65 66
     const configuredMax = (typeof interfaceConfig === 'undefined'
66 67
         ? DEFAULT_MAX_COLUMNS
67 68
         : interfaceConfig.TILE_VIEW_MAX_COLUMNS) || DEFAULT_MAX_COLUMNS;
@@ -69,20 +70,21 @@ export function getMaxColumnCount(state: Object) {
69 70
 
70 71
     if (!disableResponsiveTiles) {
71 72
         const { clientWidth } = state['features/base/responsive-ui'];
73
+        const widthToUse = width || clientWidth;
72 74
         const participantCount = getParticipantCount(state);
73 75
 
74 76
         // If there are just two participants in a conference, enforce single-column view for mobile size.
75
-        if (participantCount === 2 && clientWidth < ASPECT_RATIO_BREAKPOINT) {
77
+        if (participantCount === 2 && widthToUse < ASPECT_RATIO_BREAKPOINT) {
76 78
             return Math.min(1, Math.max(configuredMax, 1));
77 79
         }
78 80
 
79 81
         // Enforce single column view at very small screen widths.
80
-        if (clientWidth < SINGLE_COLUMN_BREAKPOINT) {
82
+        if (widthToUse < SINGLE_COLUMN_BREAKPOINT) {
81 83
             return Math.min(1, Math.max(configuredMax, 1));
82 84
         }
83 85
 
84 86
         // Enforce two column view below breakpoint.
85
-        if (clientWidth < TWO_COLUMN_BREAKPOINT) {
87
+        if (widthToUse < TWO_COLUMN_BREAKPOINT) {
86 88
             return Math.min(2, Math.max(configuredMax, 1));
87 89
         }
88 90
     }
@@ -96,11 +98,12 @@ export function getMaxColumnCount(state: Object) {
96 98
  * which rows will be added but no more columns.
97 99
  *
98 100
  * @param {Object} state - The redux store state.
101
+ * @param {number} width - Custom width to use for calculation.
99 102
  * @returns {Object} An object is return with the desired number of columns,
100 103
  * rows, and visible rows (the rest should overflow) for the tile view layout.
101 104
  */
102
-export function getTileViewGridDimensions(state: Object) {
103
-    const maxColumns = getMaxColumnCount(state);
105
+export function getTileViewGridDimensions(state: Object, width: ?number) {
106
+    const maxColumns = getMaxColumnCount(state, width);
104 107
 
105 108
     // When in tile view mode, we must discount ourselves (the local participant) because our
106 109
     // tile is not visible.

+ 0
- 2
react/features/video-layout/middleware.web.js Zobrazit soubor

@@ -10,7 +10,6 @@ import {
10 10
 } from '../base/participants';
11 11
 import { MiddlewareRegistry } from '../base/redux';
12 12
 import { TRACK_ADDED, TRACK_REMOVED, TRACK_STOPPED } from '../base/tracks';
13
-import { SET_FILMSTRIP_VISIBLE } from '../filmstrip';
14 13
 import { PARTICIPANTS_PANE_CLOSE, PARTICIPANTS_PANE_OPEN } from '../participants-pane/actionTypes.js';
15 14
 
16 15
 import './middleware.any';
@@ -54,7 +53,6 @@ MiddlewareRegistry.register(store => next => action => {
54 53
 
55 54
     case PARTICIPANTS_PANE_CLOSE:
56 55
     case PARTICIPANTS_PANE_OPEN:
57
-    case SET_FILMSTRIP_VISIBLE:
58 56
         VideoLayout.resizeVideoArea();
59 57
         break;
60 58
 

Načítá se…
Zrušit
Uložit