瀏覽代碼

feat(vertical-filmstrip): Initial implementation

- Add a class to the body when in vertical filmstrip mode
- Override styles as necessary to support the mode
- Add an option to make tooltips display from the left
- Move the HD Label to the bottom left
- Move the remote video menu to the bottom left, move the mute
  icons to the bottom right
- Scale the local video's height and width to fit the filmstrip
master
Leonard Kim 8 年之前
父節點
當前提交
aabe641047

+ 24
- 0
css/_jitsi_popover.scss 查看文件

@@ -44,4 +44,28 @@
44 44
         border-width: 5px;
45 45
         border-bottom-width: 0;
46 46
     }
47
+
48
+    /**
49
+     * Override default "top" styles to support popovers appearing from the
50
+     * left of the popover trigger element.
51
+     */
52
+    &.left {
53
+        margin-left: -$popoverMenuPadding;
54
+        margin-top: 0;
55
+
56
+        .arrow {
57
+            border-color: transparent transparent transparent $popoverBg;
58
+            border-width: 5px 0px 5px 5px;
59
+            margin-left: 0;
60
+            margin-top: -5px;
61
+        }
62
+
63
+        .jitsipopover {
64
+            &__menu-padding {
65
+                bottom: 0;
66
+                height: 100%;
67
+                width: $popoverMenuPadding;
68
+            }
69
+        }
70
+    }
47 71
 }

+ 150
- 0
css/_vertical_filmstrip_overrides.scss 查看文件

@@ -0,0 +1,150 @@
1
+/**
2
+ * Override other styles to support vertical filmstrip mode.
3
+ */
4
+.vertical-filmstrip {
5
+    .filmstrip {
6
+        align-items: flex-end;
7
+        box-sizing: border-box;
8
+        display: flex;
9
+        flex-direction: column-reverse;
10
+        height: 100%;
11
+
12
+        /**
13
+         * Hide videos by making them slight to the right.
14
+         */
15
+        .filmstrip__videos {
16
+            right: 0;
17
+            transition: right 2s;
18
+
19
+            &.hidden {
20
+                bottom: auto;
21
+                right: -196px;
22
+            }
23
+        }
24
+
25
+        #filmstripLocalVideo {
26
+            height: auto;
27
+            justify-content: flex-end;
28
+        }
29
+
30
+        /**
31
+         * Remove unnecssary padding that is normally used to prevent horizontal
32
+         * filmstrip from overlapping the left edge of the screen.
33
+         */
34
+        #filmstripLocalVideo,
35
+        #filmstripRemoteVideos {
36
+            padding: 0;
37
+        }
38
+
39
+        #filmstripRemoteVideos {
40
+            display: flex;
41
+            flex: 1;
42
+            flex-direction: column;
43
+            height: auto;
44
+            overflow-x: hidden !important;
45
+
46
+            .remote-videos-container {
47
+                flex-direction: column;
48
+            }
49
+        }
50
+
51
+        /**
52
+         * Rotate the hide filmstrip icon so it points towards the right edge
53
+         * of the screen.
54
+         */
55
+        &__toolbar {
56
+            transform: rotate(-90deg);
57
+        }
58
+
59
+        /**
60
+         * Move the remote video menu trigger to the bottom left of the
61
+         * video thumbnail.
62
+         */
63
+        .remotevideomenu {
64
+            bottom: 0;
65
+            left: 0;
66
+            top: auto;
67
+            right: auto;
68
+            transform: rotate(-90deg);
69
+        }
70
+
71
+        #remoteVideos {
72
+            flex-direction: column-reverse;
73
+            height: 100%;
74
+        }
75
+
76
+        .videocontainer {
77
+            /**
78
+             * Move status icons to the bottom right of the thumbnail.
79
+             */
80
+            &__toolbar {
81
+                text-align: right;
82
+
83
+                .toolbar-icon {
84
+                    float: none;
85
+                }
86
+            }
87
+        }
88
+    }
89
+
90
+    .video-state-indicator {
91
+        bottom: 30px;
92
+        left: 30px;
93
+        right: auto;
94
+        top: auto;
95
+
96
+        /**
97
+         * Move the label to the bottom left of the screen
98
+         */
99
+        &#videoResolutionLabel {
100
+            left: 60px;
101
+            z-index: $poweredByZ;
102
+
103
+            /**
104
+             * Open the menu above the label.
105
+             */
106
+            .video-state-indicator-menu {
107
+                bottom: calc(100% - 15px);
108
+                left: -10px;
109
+
110
+                /**
111
+                 * Create padding for mouse travel on hover.
112
+                 */
113
+                padding-bottom: 20px;
114
+                right: auto;
115
+                top: auto;
116
+
117
+                .video-state-indicator-menu-options {
118
+                    margin: 0;
119
+
120
+                    /**
121
+                     * The menu arrow should point down
122
+                     */
123
+                    &::after {
124
+                        border-color: $popoverBg transparent transparent;
125
+                        bottom: -10px;
126
+                        left: 15px;
127
+                        right: auto;
128
+                        top: auto;
129
+                    }
130
+                }
131
+            }
132
+        }
133
+
134
+        &#recordingLabel {
135
+            left: 110px;
136
+            z-index: $poweredByZ;
137
+        }
138
+    }
139
+
140
+    /**
141
+     * Move toastr closer to the bottom of the screen and move left to avoid
142
+     * overlapping of videos when they are configured at default height.
143
+     */
144
+    #toast-container {
145
+        &.notification-bottom-right {
146
+            bottom: 25px;
147
+            right: 130 + 2 * ($thumbnailVideoMargin + $thumbnailsBorder) + $thumbnailVideoBorder;
148
+        }
149
+    }
150
+}

+ 1
- 0
css/main.scss 查看文件

@@ -73,5 +73,6 @@
73 73
 @import 'policy';
74 74
 @import 'filmstrip';
75 75
 @import 'unsupported-browser/main';
76
+@import 'vertical_filmstrip_overrides';
76 77
 
77 78
 /* Modules END */

+ 6
- 0
interface_config.js 查看文件

@@ -55,6 +55,12 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
55 55
      * Whether to only show the filmstrip (and hide the toolbar).
56 56
      */
57 57
     filmStripOnly: false,
58
+
59
+    /**
60
+     * Whether to show thumbnails in filmstrip as a column instead of as a row.
61
+     */
62
+    VERTICAL_FILMSTRIP: false,
63
+
58 64
     //A html text to be shown to guests on the close page, false disables it
59 65
     CLOSE_PAGE_GUEST_HINT: false,
60 66
     RANDOM_AVATAR_URL_PREFIX: false,

+ 4
- 0
modules/UI/UI.js 查看文件

@@ -339,6 +339,10 @@ UI.start = function () {
339 339
         JitsiPopover.enabled = false;
340 340
     }
341 341
 
342
+    if (interfaceConfig.VERTICAL_FILMSTRIP) {
343
+        $("body").addClass("vertical-filmstrip");
344
+    }
345
+
342 346
     document.title = interfaceConfig.APP_NAME;
343 347
 
344 348
     if (!interfaceConfig.filmStripOnly) {

+ 84
- 18
modules/UI/util/JitsiPopover.js 查看文件

@@ -1,4 +1,74 @@
1 1
 /* global $ */
2
+
3
+const positionConfigurations = {
4
+    left: {
5
+
6
+        // Align the popover's right side to the target element.
7
+        my: 'right',
8
+
9
+        // Align the popover to the left side of the target element.
10
+        at: 'left',
11
+
12
+        // Force the popover to fit within the viewport.
13
+        collision: 'fit',
14
+
15
+        /**
16
+         * Callback invoked by jQuery UI tooltip.
17
+         *
18
+         * @param {Object} position - The top and bottom position the popover
19
+         * element should be set at.
20
+         * @param {Object} element. - Additional size and position information
21
+         * about the popover element and target.
22
+         * @param {Object} elements.element - Has position and size related data
23
+         * for the popover element itself.
24
+         * @param {Object} elements.target - Has position and size related data
25
+         * for the target element the popover displays from.
26
+         */
27
+        using: function setPositionLeft(position, elements) {
28
+            const { element, target } = elements;
29
+
30
+            $('.jitsipopover').css({
31
+                display: 'table',
32
+                left: position.left,
33
+                top: position.top
34
+            });
35
+
36
+            // Move additional padding to the right edge of the popover and
37
+            // allow css to take care of width. The padding is used to maintain
38
+            // a hover state between the target and the popover.
39
+            $('.jitsipopover > .jitsipopover__menu-padding').css({
40
+                left: element.width
41
+            });
42
+
43
+            // Find the distance from the top of the popover to the center of
44
+            // the target and use that value to position the arrow to point to
45
+            // it.
46
+            const verticalCenterOfTarget = target.height / 2;
47
+            const verticalDistanceFromTops = target.top - element.top;
48
+            const verticalPositionOfTargetCenter
49
+                = verticalDistanceFromTops + verticalCenterOfTarget;
50
+
51
+            $('.jitsipopover > .arrow').css({
52
+                left: element.width,
53
+                top: verticalPositionOfTargetCenter
54
+            });
55
+        }
56
+    },
57
+    top: {
58
+        my: "bottom",
59
+        at: "top",
60
+        collision: "fit",
61
+        using: function setPositionTop(position, elements) {
62
+            var calcLeft = elements.target.left - elements.element.left +
63
+                elements.target.width/2;
64
+            $(".jitsipopover").css(
65
+                {top: position.top, left: position.left, display: "table"});
66
+            $(".jitsipopover > .arrow").css({left: calcLeft});
67
+            $(".jitsipopover > .jitsipopover__menu-padding").css(
68
+                {left: calcLeft - 50});
69
+        }
70
+    }
71
+};
2 72
 var JitsiPopover = (function () {
3 73
     /**
4 74
      * The default options
@@ -7,7 +77,8 @@ var JitsiPopover = (function () {
7 77
         skin: 'white',
8 78
         content: '',
9 79
         hasArrow: true,
10
-        onBeforePosition: undefined
80
+        onBeforePosition: undefined,
81
+        position: 'top'
11 82
     };
12 83
 
13 84
     /**
@@ -21,7 +92,6 @@ var JitsiPopover = (function () {
21 92
     function JitsiPopover(element, options)
22 93
     {
23 94
         this.options = Object.assign({}, defaultOptions, options);
24
-
25 95
         this.elementIsHovered = false;
26 96
         this.popoverIsHovered = false;
27 97
         this.popoverShown = false;
@@ -45,12 +115,15 @@ var JitsiPopover = (function () {
45 115
      * Returns template for popover
46 116
      */
47 117
     JitsiPopover.prototype.getTemplate = function () {
118
+        const { hasArrow, position, skin } = this.options;
119
+
48 120
         let arrow = '';
49
-        if (this.options.hasArrow) {
121
+        if (hasArrow) {
50 122
             arrow = '<div class="arrow"></div>';
51 123
         }
124
+
52 125
         return  (
53
-            `<div class="jitsipopover ${this.options.skin}">
126
+            `<div class="jitsipopover ${skin} ${position}">
54 127
                 ${arrow}
55 128
                 <div class="jitsipopover__content"></div>
56 129
                 <div class="jitsipopover__menu-padding"></div>
@@ -129,21 +202,14 @@ var JitsiPopover = (function () {
129 202
      * Refreshes the position of the popover.
130 203
      */
131 204
     JitsiPopover.prototype.refreshPosition = function () {
132
-        $(".jitsipopover").position({
133
-            my: "bottom",
134
-            at: "top",
135
-            collision: "fit",
136
-            of: this.element,
137
-            using: function (position, elements) {
138
-                var calcLeft = elements.target.left - elements.element.left +
139
-                    elements.target.width/2;
140
-                $(".jitsipopover").css(
141
-                    {top: position.top, left: position.left, display: "table"});
142
-                $(".jitsipopover > .arrow").css({left: calcLeft});
143
-                $(".jitsipopover > .jitsipopover__menu-padding").css(
144
-                    {left: calcLeft - 50});
205
+        const positionOptions = Object.assign(
206
+            {},
207
+            positionConfigurations[this.options.position],
208
+            {
209
+                of: this.element
145 210
             }
146
-        });
211
+        );
212
+        $(".jitsipopover").position(positionOptions);
147 213
     };
148 214
 
149 215
     /**

+ 3
- 2
modules/UI/videolayout/ConnectionIndicator.js 查看文件

@@ -1,4 +1,4 @@
1
-/* global $, APP */
1
+/* global $, APP, interfaceConfig */
2 2
 /* jshint -W101 */
3 3
 
4 4
 import JitsiPopover from "../util/JitsiPopover";
@@ -309,7 +309,8 @@ ConnectionIndicator.prototype.create = function () {
309 309
     this.popover = new JitsiPopover($(element), {
310 310
         content: popoverContent,
311 311
         skin: "black",
312
-        onBeforePosition: el => APP.translation.translateElement(el)
312
+        onBeforePosition: el => APP.translation.translateElement(el),
313
+        position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'
313 314
     });
314 315
 
315 316
     // override popover show method to make sure we will update the content

+ 32
- 10
modules/UI/videolayout/Filmstrip.js 查看文件

@@ -178,7 +178,10 @@ const Filmstrip = {
178 178
      * @returns {number} height
179 179
      */
180 180
     getFilmstripHeight() {
181
-        if (this.isFilmstripVisible()) {
181
+        // FIXME Make it more clear the getFilmstripHeight check is used in
182
+        // horizontal film strip mode for calculating how tall large video
183
+        // display should be.
184
+        if (this.isFilmstripVisible() && !interfaceConfig.VERTICAL_FILMSTRIP) {
182 185
             return $(`.${this.filmstripContainerClassName}`).outerHeight();
183 186
         } else {
184 187
             return 0;
@@ -365,13 +368,27 @@ const Filmstrip = {
365 368
             (remoteLocalWidthRatio * numberRemoteThumbs + 1), availableHeight *
366 369
             interfaceConfig.LOCAL_THUMBNAIL_RATIO);
367 370
         const h = lW / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
371
+
372
+        const removeVideoWidth = lW * remoteLocalWidthRatio;
373
+
374
+        let localVideo;
375
+        if (interfaceConfig.VERTICAL_FILMSTRIP) {
376
+            // scale both width and height
377
+            localVideo = {
378
+                thumbWidth: removeVideoWidth,
379
+                thumbHeight: h * remoteLocalWidthRatio
380
+            };
381
+        } else {
382
+            localVideo = {
383
+                thumbWidth: lW,
384
+                thumbHeight: h
385
+            };
386
+        }
387
+
368 388
         return {
369
-                    localVideo:{
370
-                        thumbWidth: lW,
371
-                        thumbHeight: h
372
-                    },
389
+                    localVideo,
373 390
                     remoteVideo: {
374
-                        thumbWidth: lW * remoteLocalWidthRatio,
391
+                        thumbWidth: removeVideoWidth,
375 392
                         thumbHeight: h
376 393
                     }
377 394
                 };
@@ -407,10 +424,15 @@ const Filmstrip = {
407 424
                 }));
408 425
             }
409 426
             promises.push(new Promise((resolve) => {
410
-                this.filmstrip.animate({
411
-                    // adds 2 px because of small video 1px border
412
-                    height: remote.thumbHeight + 2
413
-                }, this._getAnimateOptions(animate, resolve));
427
+                // Let CSS take care of height in vertical filmstrip mode.
428
+                if (interfaceConfig.VERTICAL_FILMSTRIP) {
429
+                    resolve();
430
+                } else {
431
+                    this.filmstrip.animate({
432
+                        // adds 2 px because of small video 1px border
433
+                        height: remote.thumbHeight + 2
434
+                    }, this._getAnimateOptions(animate, resolve));
435
+                }
414 436
             }));
415 437
 
416 438
             promises.push(new Promise(() => {

+ 2
- 1
modules/UI/videolayout/RemoteVideo.js 查看文件

@@ -91,7 +91,8 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
91 91
         content: popupMenuElement.outerHTML,
92 92
         skin: "black",
93 93
         hasArrow: false,
94
-        onBeforePosition: el => APP.translation.translateElement(el)
94
+        onBeforePosition: el => APP.translation.translateElement(el),
95
+        position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'
95 96
     };
96 97
     let element = $("#" + this.videoSpanId + " .remotevideomenu");
97 98
     this.popover = new JitsiPopover(element, options);

Loading…
取消
儲存