From aabe641047c5d1cfbd04a12b2777b8df0e70465c Mon Sep 17 00:00:00 2001 From: Leonard Kim Date: Wed, 17 May 2017 15:40:41 -0700 Subject: [PATCH] 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 --- css/_jitsi_popover.scss | 24 +++ css/_vertical_filmstrip_overrides.scss | 150 ++++++++++++++++++ css/main.scss | 1 + interface_config.js | 6 + modules/UI/UI.js | 4 + modules/UI/util/JitsiPopover.js | 102 +++++++++--- modules/UI/videolayout/ConnectionIndicator.js | 5 +- modules/UI/videolayout/Filmstrip.js | 42 +++-- modules/UI/videolayout/RemoteVideo.js | 3 +- 9 files changed, 306 insertions(+), 31 deletions(-) create mode 100644 css/_vertical_filmstrip_overrides.scss diff --git a/css/_jitsi_popover.scss b/css/_jitsi_popover.scss index 77e983703..2397b295f 100644 --- a/css/_jitsi_popover.scss +++ b/css/_jitsi_popover.scss @@ -44,4 +44,28 @@ border-width: 5px; border-bottom-width: 0; } + + /** + * Override default "top" styles to support popovers appearing from the + * left of the popover trigger element. + */ + &.left { + margin-left: -$popoverMenuPadding; + margin-top: 0; + + .arrow { + border-color: transparent transparent transparent $popoverBg; + border-width: 5px 0px 5px 5px; + margin-left: 0; + margin-top: -5px; + } + + .jitsipopover { + &__menu-padding { + bottom: 0; + height: 100%; + width: $popoverMenuPadding; + } + } + } } diff --git a/css/_vertical_filmstrip_overrides.scss b/css/_vertical_filmstrip_overrides.scss new file mode 100644 index 000000000..c7269f3b2 --- /dev/null +++ b/css/_vertical_filmstrip_overrides.scss @@ -0,0 +1,150 @@ +/** + * Override other styles to support vertical filmstrip mode. + */ +.vertical-filmstrip { + .filmstrip { + align-items: flex-end; + box-sizing: border-box; + display: flex; + flex-direction: column-reverse; + height: 100%; + + /** + * Hide videos by making them slight to the right. + */ + .filmstrip__videos { + right: 0; + transition: right 2s; + + &.hidden { + bottom: auto; + right: -196px; + } + } + + #filmstripLocalVideo { + height: auto; + justify-content: flex-end; + } + + /** + * Remove unnecssary padding that is normally used to prevent horizontal + * filmstrip from overlapping the left edge of the screen. + */ + #filmstripLocalVideo, + #filmstripRemoteVideos { + padding: 0; + } + + #filmstripRemoteVideos { + display: flex; + flex: 1; + flex-direction: column; + height: auto; + overflow-x: hidden !important; + + .remote-videos-container { + flex-direction: column; + } + } + + /** + * Rotate the hide filmstrip icon so it points towards the right edge + * of the screen. + */ + &__toolbar { + transform: rotate(-90deg); + } + + /** + * Move the remote video menu trigger to the bottom left of the + * video thumbnail. + */ + .remotevideomenu { + bottom: 0; + left: 0; + top: auto; + right: auto; + transform: rotate(-90deg); + } + + #remoteVideos { + flex-direction: column-reverse; + height: 100%; + } + + .videocontainer { + /** + * Move status icons to the bottom right of the thumbnail. + */ + &__toolbar { + text-align: right; + + .toolbar-icon { + float: none; + } + } + } + } + + .video-state-indicator { + bottom: 30px; + left: 30px; + right: auto; + top: auto; + + /** + * Move the label to the bottom left of the screen + */ + &#videoResolutionLabel { + left: 60px; + z-index: $poweredByZ; + + /** + * Open the menu above the label. + */ + .video-state-indicator-menu { + bottom: calc(100% - 15px); + left: -10px; + + /** + * Create padding for mouse travel on hover. + */ + padding-bottom: 20px; + right: auto; + top: auto; + + .video-state-indicator-menu-options { + margin: 0; + + /** + * The menu arrow should point down + */ + &::after { + border-color: $popoverBg transparent transparent; + bottom: -10px; + left: 15px; + right: auto; + top: auto; + } + } + } + } + + &#recordingLabel { + left: 110px; + z-index: $poweredByZ; + } + } + + /** + * Move toastr closer to the bottom of the screen and move left to avoid + * overlapping of videos when they are configured at default height. + */ + #toast-container { + &.notification-bottom-right { + bottom: 25px; + right: 130 + 2 * ($thumbnailVideoMargin + $thumbnailsBorder) + $thumbnailVideoBorder; + } + } +} diff --git a/css/main.scss b/css/main.scss index 894620bb4..43630b571 100644 --- a/css/main.scss +++ b/css/main.scss @@ -73,5 +73,6 @@ @import 'policy'; @import 'filmstrip'; @import 'unsupported-browser/main'; +@import 'vertical_filmstrip_overrides'; /* Modules END */ diff --git a/interface_config.js b/interface_config.js index 56e44fd10..69f7a7d76 100644 --- a/interface_config.js +++ b/interface_config.js @@ -55,6 +55,12 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars * Whether to only show the filmstrip (and hide the toolbar). */ filmStripOnly: false, + + /** + * Whether to show thumbnails in filmstrip as a column instead of as a row. + */ + VERTICAL_FILMSTRIP: false, + //A html text to be shown to guests on the close page, false disables it CLOSE_PAGE_GUEST_HINT: false, RANDOM_AVATAR_URL_PREFIX: false, diff --git a/modules/UI/UI.js b/modules/UI/UI.js index c404e2813..b0893362e 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -339,6 +339,10 @@ UI.start = function () { JitsiPopover.enabled = false; } + if (interfaceConfig.VERTICAL_FILMSTRIP) { + $("body").addClass("vertical-filmstrip"); + } + document.title = interfaceConfig.APP_NAME; if (!interfaceConfig.filmStripOnly) { diff --git a/modules/UI/util/JitsiPopover.js b/modules/UI/util/JitsiPopover.js index da6dd4622..ce6686b39 100644 --- a/modules/UI/util/JitsiPopover.js +++ b/modules/UI/util/JitsiPopover.js @@ -1,4 +1,74 @@ /* global $ */ + +const positionConfigurations = { + left: { + + // Align the popover's right side to the target element. + my: 'right', + + // Align the popover to the left side of the target element. + at: 'left', + + // Force the popover to fit within the viewport. + collision: 'fit', + + /** + * Callback invoked by jQuery UI tooltip. + * + * @param {Object} position - The top and bottom position the popover + * element should be set at. + * @param {Object} element. - Additional size and position information + * about the popover element and target. + * @param {Object} elements.element - Has position and size related data + * for the popover element itself. + * @param {Object} elements.target - Has position and size related data + * for the target element the popover displays from. + */ + using: function setPositionLeft(position, elements) { + const { element, target } = elements; + + $('.jitsipopover').css({ + display: 'table', + left: position.left, + top: position.top + }); + + // Move additional padding to the right edge of the popover and + // allow css to take care of width. The padding is used to maintain + // a hover state between the target and the popover. + $('.jitsipopover > .jitsipopover__menu-padding').css({ + left: element.width + }); + + // Find the distance from the top of the popover to the center of + // the target and use that value to position the arrow to point to + // it. + const verticalCenterOfTarget = target.height / 2; + const verticalDistanceFromTops = target.top - element.top; + const verticalPositionOfTargetCenter + = verticalDistanceFromTops + verticalCenterOfTarget; + + $('.jitsipopover > .arrow').css({ + left: element.width, + top: verticalPositionOfTargetCenter + }); + } + }, + top: { + my: "bottom", + at: "top", + collision: "fit", + using: function setPositionTop(position, elements) { + var calcLeft = elements.target.left - elements.element.left + + elements.target.width/2; + $(".jitsipopover").css( + {top: position.top, left: position.left, display: "table"}); + $(".jitsipopover > .arrow").css({left: calcLeft}); + $(".jitsipopover > .jitsipopover__menu-padding").css( + {left: calcLeft - 50}); + } + } +}; var JitsiPopover = (function () { /** * The default options @@ -7,7 +77,8 @@ var JitsiPopover = (function () { skin: 'white', content: '', hasArrow: true, - onBeforePosition: undefined + onBeforePosition: undefined, + position: 'top' }; /** @@ -21,7 +92,6 @@ var JitsiPopover = (function () { function JitsiPopover(element, options) { this.options = Object.assign({}, defaultOptions, options); - this.elementIsHovered = false; this.popoverIsHovered = false; this.popoverShown = false; @@ -45,12 +115,15 @@ var JitsiPopover = (function () { * Returns template for popover */ JitsiPopover.prototype.getTemplate = function () { + const { hasArrow, position, skin } = this.options; + let arrow = ''; - if (this.options.hasArrow) { + if (hasArrow) { arrow = '
'; } + return ( - `
+ `
${arrow}
@@ -129,21 +202,14 @@ var JitsiPopover = (function () { * Refreshes the position of the popover. */ JitsiPopover.prototype.refreshPosition = function () { - $(".jitsipopover").position({ - my: "bottom", - at: "top", - collision: "fit", - of: this.element, - using: function (position, elements) { - var calcLeft = elements.target.left - elements.element.left + - elements.target.width/2; - $(".jitsipopover").css( - {top: position.top, left: position.left, display: "table"}); - $(".jitsipopover > .arrow").css({left: calcLeft}); - $(".jitsipopover > .jitsipopover__menu-padding").css( - {left: calcLeft - 50}); + const positionOptions = Object.assign( + {}, + positionConfigurations[this.options.position], + { + of: this.element } - }); + ); + $(".jitsipopover").position(positionOptions); }; /** diff --git a/modules/UI/videolayout/ConnectionIndicator.js b/modules/UI/videolayout/ConnectionIndicator.js index 2959d4b7b..470d0fc3b 100644 --- a/modules/UI/videolayout/ConnectionIndicator.js +++ b/modules/UI/videolayout/ConnectionIndicator.js @@ -1,4 +1,4 @@ -/* global $, APP */ +/* global $, APP, interfaceConfig */ /* jshint -W101 */ import JitsiPopover from "../util/JitsiPopover"; @@ -309,7 +309,8 @@ ConnectionIndicator.prototype.create = function () { this.popover = new JitsiPopover($(element), { content: popoverContent, skin: "black", - onBeforePosition: el => APP.translation.translateElement(el) + onBeforePosition: el => APP.translation.translateElement(el), + position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top' }); // override popover show method to make sure we will update the content diff --git a/modules/UI/videolayout/Filmstrip.js b/modules/UI/videolayout/Filmstrip.js index 7c9865878..1fc076f9f 100644 --- a/modules/UI/videolayout/Filmstrip.js +++ b/modules/UI/videolayout/Filmstrip.js @@ -178,7 +178,10 @@ const Filmstrip = { * @returns {number} height */ getFilmstripHeight() { - if (this.isFilmstripVisible()) { + // FIXME Make it more clear the getFilmstripHeight check is used in + // horizontal film strip mode for calculating how tall large video + // display should be. + if (this.isFilmstripVisible() && !interfaceConfig.VERTICAL_FILMSTRIP) { return $(`.${this.filmstripContainerClassName}`).outerHeight(); } else { return 0; @@ -365,13 +368,27 @@ const Filmstrip = { (remoteLocalWidthRatio * numberRemoteThumbs + 1), availableHeight * interfaceConfig.LOCAL_THUMBNAIL_RATIO); const h = lW / interfaceConfig.LOCAL_THUMBNAIL_RATIO; + + const removeVideoWidth = lW * remoteLocalWidthRatio; + + let localVideo; + if (interfaceConfig.VERTICAL_FILMSTRIP) { + // scale both width and height + localVideo = { + thumbWidth: removeVideoWidth, + thumbHeight: h * remoteLocalWidthRatio + }; + } else { + localVideo = { + thumbWidth: lW, + thumbHeight: h + }; + } + return { - localVideo:{ - thumbWidth: lW, - thumbHeight: h - }, + localVideo, remoteVideo: { - thumbWidth: lW * remoteLocalWidthRatio, + thumbWidth: removeVideoWidth, thumbHeight: h } }; @@ -407,10 +424,15 @@ const Filmstrip = { })); } promises.push(new Promise((resolve) => { - this.filmstrip.animate({ - // adds 2 px because of small video 1px border - height: remote.thumbHeight + 2 - }, this._getAnimateOptions(animate, resolve)); + // Let CSS take care of height in vertical filmstrip mode. + if (interfaceConfig.VERTICAL_FILMSTRIP) { + resolve(); + } else { + this.filmstrip.animate({ + // adds 2 px because of small video 1px border + height: remote.thumbHeight + 2 + }, this._getAnimateOptions(animate, resolve)); + } })); promises.push(new Promise(() => { diff --git a/modules/UI/videolayout/RemoteVideo.js b/modules/UI/videolayout/RemoteVideo.js index f06a74145..36da7bc98 100644 --- a/modules/UI/videolayout/RemoteVideo.js +++ b/modules/UI/videolayout/RemoteVideo.js @@ -91,7 +91,8 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) { content: popupMenuElement.outerHTML, skin: "black", hasArrow: false, - onBeforePosition: el => APP.translation.translateElement(el) + onBeforePosition: el => APP.translation.translateElement(el), + position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top' }; let element = $("#" + this.videoSpanId + " .remotevideomenu"); this.popover = new JitsiPopover(element, options);