Merge pull request #1587 from virtuacoplenny/lenny/vertical-filmstrip
vertical filmstrip and 1-on-1 mode
This commit is contained in:
commit
1ff89c5a1c
|
@ -1286,6 +1286,8 @@ export default {
|
||||||
|
|
||||||
// check the roles for the new user and reflect them
|
// check the roles for the new user and reflect them
|
||||||
APP.UI.updateUserRole(user);
|
APP.UI.updateUserRole(user);
|
||||||
|
|
||||||
|
updateRemoteThumbnailsVisibility();
|
||||||
});
|
});
|
||||||
room.on(ConferenceEvents.USER_LEFT, (id, user) => {
|
room.on(ConferenceEvents.USER_LEFT, (id, user) => {
|
||||||
APP.store.dispatch(participantLeft(id, user));
|
APP.store.dispatch(participantLeft(id, user));
|
||||||
|
@ -1293,6 +1295,8 @@ export default {
|
||||||
APP.API.notifyUserLeft(id);
|
APP.API.notifyUserLeft(id);
|
||||||
APP.UI.removeUser(id, user.getDisplayName());
|
APP.UI.removeUser(id, user.getDisplayName());
|
||||||
APP.UI.onSharedVideoStop(id);
|
APP.UI.onSharedVideoStop(id);
|
||||||
|
|
||||||
|
updateRemoteThumbnailsVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
room.on(ConferenceEvents.USER_STATUS_CHANGED, (id, status) => {
|
room.on(ConferenceEvents.USER_STATUS_CHANGED, (id, status) => {
|
||||||
|
@ -1475,6 +1479,8 @@ export default {
|
||||||
reportError(e);
|
reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateRemoteThumbnailsVisibility();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1811,6 +1817,8 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateRemoteThumbnailsVisibility();
|
||||||
});
|
});
|
||||||
room.addCommandListener(
|
room.addCommandListener(
|
||||||
this.commands.defaults.SHARED_VIDEO, ({value, attributes}, id) => {
|
this.commands.defaults.SHARED_VIDEO, ({value, attributes}, id) => {
|
||||||
|
@ -1826,6 +1834,21 @@ export default {
|
||||||
APP.UI.onSharedVideoUpdate(id, value, attributes);
|
APP.UI.onSharedVideoUpdate(id, value, attributes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function updateRemoteThumbnailsVisibility() {
|
||||||
|
const localUserId = APP.conference.getMyUserId();
|
||||||
|
const remoteParticipantsCount = room.getParticipantCount() - 1;
|
||||||
|
|
||||||
|
// Get the remote thumbnail count for cases where there are
|
||||||
|
// non-participants displaying video, such as with video sharing.
|
||||||
|
const remoteVideosCount = APP.UI.getRemoteVideosCount();
|
||||||
|
|
||||||
|
const shouldShowRemoteThumbnails = APP.UI.isPinned(localUserId)
|
||||||
|
|| remoteVideosCount > 1
|
||||||
|
|| remoteParticipantsCount !== remoteVideosCount;
|
||||||
|
|
||||||
|
APP.UI.setRemoteThumbnailsVisibility(shouldShowRemoteThumbnails);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Adds any room listener.
|
* Adds any room listener.
|
||||||
|
|
|
@ -57,6 +57,9 @@ var config = { // eslint-disable-line no-unused-vars
|
||||||
webrtcIceTcpDisable: false,
|
webrtcIceTcpDisable: false,
|
||||||
|
|
||||||
openSctp: true, // Toggle to enable/disable SCTP channels
|
openSctp: true, // Toggle to enable/disable SCTP channels
|
||||||
|
|
||||||
|
// Disable hiding of remote thumbnails when in a 1-on-1 conference call.
|
||||||
|
disable1On1Mode: false,
|
||||||
disableStats: false,
|
disableStats: false,
|
||||||
disableAudioLevels: false,
|
disableAudioLevels: false,
|
||||||
channelLastN: -1, // The default value of the channel attribute last-n.
|
channelLastN: -1, // The default value of the channel attribute last-n.
|
||||||
|
|
|
@ -62,10 +62,18 @@
|
||||||
videos. */
|
videos. */
|
||||||
font-size: 0pt;
|
font-size: 0pt;
|
||||||
|
|
||||||
|
#filmstripLocalVideo {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
bottom: -196px;
|
bottom: -196px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remote-videos-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.videocontainer {
|
.videocontainer {
|
||||||
display: none;
|
display: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -130,4 +138,13 @@
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
padding-right: $defaultToolbarSize;
|
padding-right: $defaultToolbarSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remote-videos-container {
|
||||||
|
transition: opacity 1s;
|
||||||
|
|
||||||
|
&.hide-videos {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,4 +44,28 @@
|
||||||
border-width: 5px;
|
border-width: 5px;
|
||||||
border-bottom-width: 0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For video labels that display on the top right to adjust its position as
|
||||||
|
* the filmstrip itself or filmstrip remote videos appear and disappear.
|
||||||
|
*/
|
||||||
|
.video-state-indicator {
|
||||||
|
transition: right 2s;
|
||||||
|
|
||||||
|
&.with-filmstrip {
|
||||||
|
&#recordingLabel {
|
||||||
|
right: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&#videoResolutionLabel {
|
||||||
|
right: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -73,5 +73,6 @@
|
||||||
@import 'policy';
|
@import 'policy';
|
||||||
@import 'filmstrip';
|
@import 'filmstrip';
|
||||||
@import 'unsupported-browser/main';
|
@import 'unsupported-browser/main';
|
||||||
|
@import 'vertical_filmstrip_overrides';
|
||||||
|
|
||||||
/* Modules END */
|
/* Modules END */
|
||||||
|
|
|
@ -55,6 +55,12 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
|
||||||
* Whether to only show the filmstrip (and hide the toolbar).
|
* Whether to only show the filmstrip (and hide the toolbar).
|
||||||
*/
|
*/
|
||||||
filmStripOnly: false,
|
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
|
//A html text to be shown to guests on the close page, false disables it
|
||||||
CLOSE_PAGE_GUEST_HINT: false,
|
CLOSE_PAGE_GUEST_HINT: false,
|
||||||
RANDOM_AVATAR_URL_PREFIX: false,
|
RANDOM_AVATAR_URL_PREFIX: false,
|
||||||
|
|
|
@ -309,6 +309,10 @@ UI.start = function () {
|
||||||
SideContainerToggler.init(eventEmitter);
|
SideContainerToggler.init(eventEmitter);
|
||||||
Filmstrip.init(eventEmitter);
|
Filmstrip.init(eventEmitter);
|
||||||
|
|
||||||
|
// By default start with remote videos hidden and rely on other logic to
|
||||||
|
// make them visible.
|
||||||
|
UI.setRemoteThumbnailsVisibility(false);
|
||||||
|
|
||||||
VideoLayout.init(eventEmitter);
|
VideoLayout.init(eventEmitter);
|
||||||
if (!interfaceConfig.filmStripOnly) {
|
if (!interfaceConfig.filmStripOnly) {
|
||||||
VideoLayout.initLargeVideo();
|
VideoLayout.initLargeVideo();
|
||||||
|
@ -339,6 +343,10 @@ UI.start = function () {
|
||||||
JitsiPopover.enabled = false;
|
JitsiPopover.enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||||
|
$("body").addClass("vertical-filmstrip");
|
||||||
|
}
|
||||||
|
|
||||||
document.title = interfaceConfig.APP_NAME;
|
document.title = interfaceConfig.APP_NAME;
|
||||||
|
|
||||||
if (!interfaceConfig.filmStripOnly) {
|
if (!interfaceConfig.filmStripOnly) {
|
||||||
|
@ -1142,6 +1150,15 @@ UI.getLargeVideo = function () {
|
||||||
return VideoLayout.getLargeVideo();
|
return VideoLayout.getLargeVideo();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the passed in user id is currently pinned to the large
|
||||||
|
* video.
|
||||||
|
*
|
||||||
|
* @param {string} userId - The id of the user to check is pinned or not.
|
||||||
|
* @returns {boolean} True if the user is currently pinned to the large video.
|
||||||
|
*/
|
||||||
|
UI.isPinned = userId => VideoLayout.getPinnedId() === userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows dialog with a link to FF extension.
|
* Shows dialog with a link to FF extension.
|
||||||
*/
|
*/
|
||||||
|
@ -1392,6 +1409,23 @@ UI.isRingOverlayVisible = () => RingOverlay.isVisible();
|
||||||
*/
|
*/
|
||||||
UI.onUserFeaturesChanged = user => VideoLayout.onUserFeaturesChanged(user);
|
UI.onUserFeaturesChanged = user => VideoLayout.onUserFeaturesChanged(user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of known remote videos.
|
||||||
|
*
|
||||||
|
* @returns {number} The number of remote videos.
|
||||||
|
*/
|
||||||
|
UI.getRemoteVideosCount = () => VideoLayout.getRemoteVideosCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes remote thumbnail videos visible or not visible.
|
||||||
|
*
|
||||||
|
* @param {boolean} shouldHide - True if remote thumbnails should be hidden,
|
||||||
|
* false f they should be visible.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
UI.setRemoteThumbnailsVisibility
|
||||||
|
= shouldHide => Filmstrip.setRemoteVideoVisibility(shouldHide);
|
||||||
|
|
||||||
const UIListeners = new Map([
|
const UIListeners = new Map([
|
||||||
[
|
[
|
||||||
UIEvents.ETHERPAD_CLICKED,
|
UIEvents.ETHERPAD_CLICKED,
|
||||||
|
|
|
@ -201,6 +201,15 @@ function _showStopRecordingPrompt(recordingType) {
|
||||||
* position
|
* position
|
||||||
*/
|
*/
|
||||||
function moveToCorner(selector, move) {
|
function moveToCorner(selector, move) {
|
||||||
|
const {
|
||||||
|
remoteVideosCount,
|
||||||
|
remoteVideosVisible,
|
||||||
|
visible
|
||||||
|
} = APP.store.getState()['features/filmstrip'];
|
||||||
|
selector.toggleClass(
|
||||||
|
'with-filmstrip',
|
||||||
|
Boolean(remoteVideosCount && remoteVideosVisible && visible));
|
||||||
|
|
||||||
let moveToCornerClass = "moveToCorner";
|
let moveToCornerClass = "moveToCorner";
|
||||||
let containsClass = selector.hasClass(moveToCornerClass);
|
let containsClass = selector.hasClass(moveToCornerClass);
|
||||||
|
|
||||||
|
@ -295,6 +304,10 @@ var Recording = {
|
||||||
APP.UI.messageHandler.enableNotifications(false);
|
APP.UI.messageHandler.enableNotifications(false);
|
||||||
APP.UI.messageHandler.enablePopups(false);
|
APP.UI.messageHandler.enablePopups(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.eventEmitter.addListener(UIEvents.UPDATED_FILMSTRIP_DISPLAY, () =>{
|
||||||
|
this._updateStatusLabel();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -456,6 +456,9 @@ export default class SharedVideoManager {
|
||||||
// revert to original behavior (prevents pausing
|
// revert to original behavior (prevents pausing
|
||||||
// for participants not sharing the video to pause it)
|
// for participants not sharing the video to pause it)
|
||||||
$("#sharedVideo").css("pointer-events","auto");
|
$("#sharedVideo").css("pointer-events","auto");
|
||||||
|
|
||||||
|
this.emitter.emit(
|
||||||
|
UIEvents.UPDATE_SHARED_VIDEO, null, 'removed');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.url = null;
|
this.url = null;
|
||||||
|
@ -656,7 +659,7 @@ SharedVideoThumb.prototype.createContainer = function (spanId) {
|
||||||
avatar.src = "https://img.youtube.com/vi/" + this.url + "/0.jpg";
|
avatar.src = "https://img.youtube.com/vi/" + this.url + "/0.jpg";
|
||||||
container.appendChild(avatar);
|
container.appendChild(avatar);
|
||||||
|
|
||||||
var remotes = document.getElementById('remoteVideos');
|
var remotes = document.getElementById('filmstripRemoteVideosContainer');
|
||||||
return remotes.appendChild(container);
|
return remotes.appendChild(container);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,74 @@
|
||||||
/* global $ */
|
/* 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 () {
|
var JitsiPopover = (function () {
|
||||||
/**
|
/**
|
||||||
* The default options
|
* The default options
|
||||||
|
@ -7,7 +77,8 @@ var JitsiPopover = (function () {
|
||||||
skin: 'white',
|
skin: 'white',
|
||||||
content: '',
|
content: '',
|
||||||
hasArrow: true,
|
hasArrow: true,
|
||||||
onBeforePosition: undefined
|
onBeforePosition: undefined,
|
||||||
|
position: 'top'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,7 +92,6 @@ var JitsiPopover = (function () {
|
||||||
function JitsiPopover(element, options)
|
function JitsiPopover(element, options)
|
||||||
{
|
{
|
||||||
this.options = Object.assign({}, defaultOptions, options);
|
this.options = Object.assign({}, defaultOptions, options);
|
||||||
|
|
||||||
this.elementIsHovered = false;
|
this.elementIsHovered = false;
|
||||||
this.popoverIsHovered = false;
|
this.popoverIsHovered = false;
|
||||||
this.popoverShown = false;
|
this.popoverShown = false;
|
||||||
|
@ -45,12 +115,15 @@ var JitsiPopover = (function () {
|
||||||
* Returns template for popover
|
* Returns template for popover
|
||||||
*/
|
*/
|
||||||
JitsiPopover.prototype.getTemplate = function () {
|
JitsiPopover.prototype.getTemplate = function () {
|
||||||
|
const { hasArrow, position, skin } = this.options;
|
||||||
|
|
||||||
let arrow = '';
|
let arrow = '';
|
||||||
if (this.options.hasArrow) {
|
if (hasArrow) {
|
||||||
arrow = '<div class="arrow"></div>';
|
arrow = '<div class="arrow"></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`<div class="jitsipopover ${this.options.skin}">
|
`<div class="jitsipopover ${skin} ${position}">
|
||||||
${arrow}
|
${arrow}
|
||||||
<div class="jitsipopover__content"></div>
|
<div class="jitsipopover__content"></div>
|
||||||
<div class="jitsipopover__menu-padding"></div>
|
<div class="jitsipopover__menu-padding"></div>
|
||||||
|
@ -129,21 +202,14 @@ var JitsiPopover = (function () {
|
||||||
* Refreshes the position of the popover.
|
* Refreshes the position of the popover.
|
||||||
*/
|
*/
|
||||||
JitsiPopover.prototype.refreshPosition = function () {
|
JitsiPopover.prototype.refreshPosition = function () {
|
||||||
$(".jitsipopover").position({
|
const positionOptions = Object.assign(
|
||||||
my: "bottom",
|
{},
|
||||||
at: "top",
|
positionConfigurations[this.options.position],
|
||||||
collision: "fit",
|
{
|
||||||
of: this.element,
|
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});
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
$(".jitsipopover").position(positionOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global $, APP */
|
/* global $, APP, interfaceConfig */
|
||||||
/* jshint -W101 */
|
/* jshint -W101 */
|
||||||
|
|
||||||
import JitsiPopover from "../util/JitsiPopover";
|
import JitsiPopover from "../util/JitsiPopover";
|
||||||
|
@ -309,7 +309,8 @@ ConnectionIndicator.prototype.create = function () {
|
||||||
this.popover = new JitsiPopover($(element), {
|
this.popover = new JitsiPopover($(element), {
|
||||||
content: popoverContent,
|
content: popoverContent,
|
||||||
skin: "black",
|
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
|
// override popover show method to make sure we will update the content
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
/* global $, APP, JitsiMeetJS, interfaceConfig */
|
/* global $, APP, config, JitsiMeetJS, interfaceConfig */
|
||||||
|
|
||||||
|
import {
|
||||||
|
setFilmstripRemoteVideosVisibility,
|
||||||
|
setFilmstripVisibility
|
||||||
|
} from '../../../react/features/filmstrip';
|
||||||
|
|
||||||
import UIEvents from "../../../service/UI/UIEvents";
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
import UIUtil from "../util/UIUtil";
|
import UIUtil from "../util/UIUtil";
|
||||||
|
@ -14,6 +19,7 @@ const Filmstrip = {
|
||||||
this.iconMenuUpClassName = 'icon-menu-up';
|
this.iconMenuUpClassName = 'icon-menu-up';
|
||||||
this.filmstripContainerClassName = 'filmstrip';
|
this.filmstripContainerClassName = 'filmstrip';
|
||||||
this.filmstrip = $('#remoteVideos');
|
this.filmstrip = $('#remoteVideos');
|
||||||
|
this.filmstripRemoteVideos = $('#filmstripRemoteVideosContainer');
|
||||||
this.eventEmitter = eventEmitter;
|
this.eventEmitter = eventEmitter;
|
||||||
|
|
||||||
// Show the toggle button and add event listeners only when out of
|
// Show the toggle button and add event listeners only when out of
|
||||||
|
@ -24,6 +30,28 @@ const Filmstrip = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a class on the remote videos container for CSS to adjust visibility
|
||||||
|
* of the remote videos. Will no-op if config.debug is truthy, as should be
|
||||||
|
* the case with torture tests.
|
||||||
|
*
|
||||||
|
* @param {boolean} shouldHide - True if remote videos should be hidden,
|
||||||
|
* false if they should be visible.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
setRemoteVideoVisibility(shouldShow) {
|
||||||
|
// FIXME Checking config.debug is a grand hack to avoid fixing the
|
||||||
|
// torture tests after the 1-on-1 UI was implemented, which hides remote
|
||||||
|
// videos on 1-on-1 calls. If this check is to be kept, at least create
|
||||||
|
// new torture tests to verify 1-on-1 mode.
|
||||||
|
if (config.debug || config.disable1On1Mode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP.store.dispatch(setFilmstripRemoteVideosVisibility(shouldShow));
|
||||||
|
this.filmstripRemoteVideos.toggleClass('hide-videos', !shouldShow);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the filmstrip toolbar.
|
* Initializes the filmstrip toolbar.
|
||||||
*/
|
*/
|
||||||
|
@ -150,11 +178,14 @@ const Filmstrip = {
|
||||||
|
|
||||||
// Emit/fire UIEvents.TOGGLED_FILMSTRIP.
|
// Emit/fire UIEvents.TOGGLED_FILMSTRIP.
|
||||||
const eventEmitter = this.eventEmitter;
|
const eventEmitter = this.eventEmitter;
|
||||||
|
const isFilmstripVisible = this.isFilmstripVisible();
|
||||||
|
|
||||||
if (eventEmitter) {
|
if (eventEmitter) {
|
||||||
eventEmitter.emit(
|
eventEmitter.emit(
|
||||||
UIEvents.TOGGLED_FILMSTRIP,
|
UIEvents.TOGGLED_FILMSTRIP,
|
||||||
this.isFilmstripVisible());
|
this.isFilmstripVisible());
|
||||||
}
|
}
|
||||||
|
APP.store.dispatch(setFilmstripVisibility(isFilmstripVisible));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -177,7 +208,10 @@ const Filmstrip = {
|
||||||
* @returns {number} height
|
* @returns {number} height
|
||||||
*/
|
*/
|
||||||
getFilmstripHeight() {
|
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();
|
return $(`.${this.filmstripContainerClassName}`).outerHeight();
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -364,13 +398,27 @@ const Filmstrip = {
|
||||||
(remoteLocalWidthRatio * numberRemoteThumbs + 1), availableHeight *
|
(remoteLocalWidthRatio * numberRemoteThumbs + 1), availableHeight *
|
||||||
interfaceConfig.LOCAL_THUMBNAIL_RATIO);
|
interfaceConfig.LOCAL_THUMBNAIL_RATIO);
|
||||||
const h = lW / 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 {
|
return {
|
||||||
localVideo:{
|
localVideo,
|
||||||
thumbWidth: lW,
|
|
||||||
thumbHeight: h
|
|
||||||
},
|
|
||||||
remoteVideo: {
|
remoteVideo: {
|
||||||
thumbWidth: lW * remoteLocalWidthRatio,
|
thumbWidth: removeVideoWidth,
|
||||||
thumbHeight: h
|
thumbHeight: h
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -406,10 +454,15 @@ const Filmstrip = {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
promises.push(new Promise((resolve) => {
|
promises.push(new Promise((resolve) => {
|
||||||
this.filmstrip.animate({
|
// Let CSS take care of height in vertical filmstrip mode.
|
||||||
// adds 2 px because of small video 1px border
|
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||||
height: remote.thumbHeight + 2
|
resolve();
|
||||||
}, this._getAnimateOptions(animate, 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(() => {
|
promises.push(new Promise(() => {
|
||||||
|
@ -456,8 +509,7 @@ const Filmstrip = {
|
||||||
}
|
}
|
||||||
|
|
||||||
let localThumb = $("#localVideoContainer");
|
let localThumb = $("#localVideoContainer");
|
||||||
let remoteThumbs = this.filmstrip.children(selector)
|
let remoteThumbs = this.filmstripRemoteVideos.children(selector);
|
||||||
.not("#localVideoContainer");
|
|
||||||
|
|
||||||
// Exclude the local video container if it has been hidden.
|
// Exclude the local video container if it has been hidden.
|
||||||
if (localThumb.hasClass("hidden")) {
|
if (localThumb.hasClass("hidden")) {
|
||||||
|
|
|
@ -91,7 +91,8 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
|
||||||
content: popupMenuElement.outerHTML,
|
content: popupMenuElement.outerHTML,
|
||||||
skin: "black",
|
skin: "black",
|
||||||
hasArrow: false,
|
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");
|
let element = $("#" + this.videoSpanId + " .remotevideomenu");
|
||||||
this.popover = new JitsiPopover(element, options);
|
this.popover = new JitsiPopover(element, options);
|
||||||
|
@ -800,7 +801,7 @@ RemoteVideo.createContainer = function (spanId) {
|
||||||
overlay.className = "videocontainer__hoverOverlay";
|
overlay.className = "videocontainer__hoverOverlay";
|
||||||
container.appendChild(overlay);
|
container.appendChild(overlay);
|
||||||
|
|
||||||
var remotes = document.getElementById('remoteVideos');
|
var remotes = document.getElementById('filmstripRemoteVideosContainer');
|
||||||
return remotes.appendChild(container);
|
return remotes.appendChild(container);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
/* global APP, $, interfaceConfig */
|
/* global APP, $, interfaceConfig */
|
||||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||||
|
|
||||||
|
import {
|
||||||
|
setFilmstripRemoteVideosCount
|
||||||
|
} from '../../../react/features/filmstrip';
|
||||||
|
|
||||||
import Filmstrip from "./Filmstrip";
|
import Filmstrip from "./Filmstrip";
|
||||||
import UIEvents from "../../../service/UI/UIEvents";
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
import UIUtil from "../util/UIUtil";
|
import UIUtil from "../util/UIUtil";
|
||||||
|
@ -550,6 +554,9 @@ var VideoLayout = {
|
||||||
if (onComplete && typeof onComplete === "function")
|
if (onComplete && typeof onComplete === "function")
|
||||||
onComplete();
|
onComplete();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
APP.store.dispatch(
|
||||||
|
setFilmstripRemoteVideosCount(this.getRemoteVideosCount()));
|
||||||
return { localVideo, remoteVideo };
|
return { localVideo, remoteVideo };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1133,6 +1140,15 @@ var VideoLayout = {
|
||||||
*/
|
*/
|
||||||
getLargeVideoWrapper() {
|
getLargeVideoWrapper() {
|
||||||
return this.getCurrentlyOnLargeContainer().$wrapper;
|
return this.getCurrentlyOnLargeContainer().$wrapper;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of remove video ids.
|
||||||
|
*
|
||||||
|
* @returns {number} The number of remote videos.
|
||||||
|
*/
|
||||||
|
getRemoteVideosCount() {
|
||||||
|
return Object.keys(remoteVideos).length;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { OverlayContainer } from '../../overlay';
|
||||||
import { Toolbox } from '../../toolbox';
|
import { Toolbox } from '../../toolbox';
|
||||||
import { HideNotificationBarStyle } from '../../unsupported-browser';
|
import { HideNotificationBarStyle } from '../../unsupported-browser';
|
||||||
import { VideoStatusLabel } from '../../video-status-label';
|
import { VideoStatusLabel } from '../../video-status-label';
|
||||||
|
import '../../filmstrip';
|
||||||
|
|
||||||
declare var $: Function;
|
declare var $: Function;
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
@ -106,35 +107,7 @@ class Conference extends Component {
|
||||||
src = 'images/spin.svg' />
|
src = 'images/spin.svg' />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className = 'filmstrip'>
|
{ this._renderFilmstrip() }
|
||||||
<div
|
|
||||||
className = 'filmstrip__videos'
|
|
||||||
id = 'remoteVideos'>
|
|
||||||
<span
|
|
||||||
className = 'videocontainer'
|
|
||||||
id = 'localVideoContainer'>
|
|
||||||
<div className = 'videocontainer__background' />
|
|
||||||
<span id = 'localVideoWrapper' />
|
|
||||||
<audio
|
|
||||||
autoPlay = { true }
|
|
||||||
id = 'localAudio'
|
|
||||||
muted = { true } />
|
|
||||||
<div className = 'videocontainer__toolbar' />
|
|
||||||
<div className = 'videocontainer__toptoolbar' />
|
|
||||||
<div
|
|
||||||
className
|
|
||||||
= 'videocontainer__hoverOverlay' />
|
|
||||||
</span>
|
|
||||||
<audio
|
|
||||||
id = 'userJoined'
|
|
||||||
preload = 'auto'
|
|
||||||
src = 'sounds/joined.wav' />
|
|
||||||
<audio
|
|
||||||
id = 'userLeft'
|
|
||||||
preload = 'auto'
|
|
||||||
src = 'sounds/left.wav' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogContainer />
|
<DialogContainer />
|
||||||
|
@ -143,6 +116,66 @@ class Conference extends Component {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a React Element for displaying filmstrip videos.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
_renderFilmstrip() {
|
||||||
|
return (
|
||||||
|
<div className = 'filmstrip'>
|
||||||
|
<div
|
||||||
|
className = 'filmstrip__videos'
|
||||||
|
id = 'remoteVideos'>
|
||||||
|
<div
|
||||||
|
className = 'filmstrip__videos'
|
||||||
|
id = 'filmstripLocalVideo'>
|
||||||
|
<span
|
||||||
|
className = 'videocontainer'
|
||||||
|
id = 'localVideoContainer'>
|
||||||
|
<div className = 'videocontainer__background' />
|
||||||
|
<span id = 'localVideoWrapper' />
|
||||||
|
<audio
|
||||||
|
autoPlay = { true }
|
||||||
|
id = 'localAudio'
|
||||||
|
muted = { true } />
|
||||||
|
<div className = 'videocontainer__toolbar' />
|
||||||
|
<div className = 'videocontainer__toptoolbar' />
|
||||||
|
<div
|
||||||
|
className
|
||||||
|
= 'videocontainer__hoverOverlay' />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className = 'filmstrip__videos'
|
||||||
|
id = 'filmstripRemoteVideos'>
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
This extra video container is needed for
|
||||||
|
scrolling thumbnails in firefox, otherwise the
|
||||||
|
flex thumbnails resize instead of causing
|
||||||
|
overflow.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
className = 'remote-videos-container'
|
||||||
|
id = 'filmstripRemoteVideosContainer' />
|
||||||
|
</div>
|
||||||
|
<audio
|
||||||
|
id = 'userJoined'
|
||||||
|
preload = 'auto'
|
||||||
|
src = 'sounds/joined.wav' />
|
||||||
|
<audio
|
||||||
|
id = 'userLeft'
|
||||||
|
preload = 'auto'
|
||||||
|
src = 'sounds/left.wav' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default reactReduxConnect()(Conference);
|
export default reactReduxConnect()(Conference);
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Symbol } from '../base/react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of action which signals to change the count of known remote videos
|
||||||
|
* displayed in the filmstrip.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
|
||||||
|
* remoteVideosCount: number
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_FILMSTRIP_REMOTE_VIDEOS_COUNT
|
||||||
|
= Symbol('SET_FILMSTRIP_REMOTE_VIDEOS_COUNT');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of action which signals to change the visibility of remote videos in
|
||||||
|
* the filmstrip.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
|
||||||
|
* removeVideosVisible: boolean
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY
|
||||||
|
= Symbol('SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of action sets the visibility of the entire filmstrip;
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_FILMSTRIP_VISIBILITY,
|
||||||
|
* visible: boolean
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_FILMSTRIP_VISIBILITY = Symbol('SET_FILMSTRIP_VISIBILITY');
|
|
@ -0,0 +1,54 @@
|
||||||
|
import {
|
||||||
|
SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
|
||||||
|
SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
|
||||||
|
SET_FILMSTRIP_VISIBILITY
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the visibility of remote videos in the filmstrip.
|
||||||
|
*
|
||||||
|
* @param {boolean} remoteVideosVisible - Whether or not remote videos in the
|
||||||
|
* filmstrip should be visible.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
|
||||||
|
* remoteVideosVisible: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setFilmstripRemoteVideosVisibility(remoteVideosVisible) {
|
||||||
|
return {
|
||||||
|
type: SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
|
||||||
|
remoteVideosVisible
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets how many remote videos are currently in the filmstrip.
|
||||||
|
*
|
||||||
|
* @param {number} remoteVideosCount - The number of remote videos.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
|
||||||
|
* remoteVideosCount: number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setFilmstripRemoteVideosCount(remoteVideosCount) {
|
||||||
|
return {
|
||||||
|
type: SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
|
||||||
|
remoteVideosCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets if the entire filmstrip should be visible.
|
||||||
|
*
|
||||||
|
* @param {boolean} visible - Whether not the filmstrip is visible.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_FILMSTRIP_VISIBILITY,
|
||||||
|
* visible: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setFilmstripVisibility(visible) {
|
||||||
|
return {
|
||||||
|
type: SET_FILMSTRIP_VISIBILITY,
|
||||||
|
visible
|
||||||
|
};
|
||||||
|
}
|
|
@ -1 +1,6 @@
|
||||||
|
export * from './actions';
|
||||||
|
export * from './actionTypes';
|
||||||
export * from './components';
|
export * from './components';
|
||||||
|
|
||||||
|
import './middleware';
|
||||||
|
import './reducer';
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import UIEvents from '../../../service/UI/UIEvents';
|
||||||
|
|
||||||
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
|
||||||
|
SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
|
||||||
|
SET_FILMSTRIP_VISIBILITY
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
declare var APP: Object;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
|
const result = next(action);
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case SET_FILMSTRIP_REMOTE_VIDEOS_COUNT:
|
||||||
|
case SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY:
|
||||||
|
case SET_FILMSTRIP_VISIBILITY: {
|
||||||
|
if (typeof APP !== 'undefined') {
|
||||||
|
APP.UI.emitEvent(UIEvents.UPDATED_FILMSTRIP_DISPLAY);
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { ReducerRegistry } from '../base/redux';
|
||||||
|
import {
|
||||||
|
SET_FILMSTRIP_REMOTE_VIDEOS_COUNT,
|
||||||
|
SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
|
||||||
|
SET_FILMSTRIP_VISIBILITY
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
const DEFAULT_STATE = {
|
||||||
|
remoteVideosCount: 0,
|
||||||
|
remoteVideosVisible: true,
|
||||||
|
visible: true
|
||||||
|
};
|
||||||
|
|
||||||
|
ReducerRegistry.register(
|
||||||
|
'features/filmstrip',
|
||||||
|
(state = DEFAULT_STATE, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case SET_FILMSTRIP_REMOTE_VIDEOS_COUNT:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
remoteVideosCount: action.remoteVideosCount
|
||||||
|
};
|
||||||
|
case SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
remoteVideosVisible: action.remoteVideosVisible
|
||||||
|
};
|
||||||
|
case SET_FILMSTRIP_VISIBILITY:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
visible: action.visible
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
});
|
|
@ -28,6 +28,11 @@ export class VideoStatusLabel extends Component {
|
||||||
*/
|
*/
|
||||||
_conferenceStarted: React.PropTypes.bool,
|
_conferenceStarted: React.PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the filmstrip is displayed with remote videos.
|
||||||
|
*/
|
||||||
|
_filmstripVisible: React.PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not a high-definition large video is displayed.
|
* Whether or not a high-definition large video is displayed.
|
||||||
*/
|
*/
|
||||||
|
@ -64,7 +69,13 @@ export class VideoStatusLabel extends Component {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { _audioOnly, _conferenceStarted, _largeVideoHD, t } = this.props;
|
const {
|
||||||
|
_audioOnly,
|
||||||
|
_conferenceStarted,
|
||||||
|
_filmstripVisible,
|
||||||
|
_largeVideoHD,
|
||||||
|
t
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
// FIXME The _conferenceStarted check is used to be defensive against
|
// FIXME The _conferenceStarted check is used to be defensive against
|
||||||
// toggling audio only mode while there is no conference and hides the
|
// toggling audio only mode while there is no conference and hides the
|
||||||
|
@ -82,9 +93,14 @@ export class VideoStatusLabel extends Component {
|
||||||
? t('videoStatus.hd') : t('videoStatus.sd');
|
? t('videoStatus.hd') : t('videoStatus.sd');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filmstripClassName
|
||||||
|
= _filmstripVisible ? 'with-filmstrip' : 'without-filmstrip';
|
||||||
|
const classNames
|
||||||
|
= `video-state-indicator moveToCorner ${filmstripClassName}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className = 'video-state-indicator moveToCorner'
|
className = { classNames }
|
||||||
id = 'videoResolutionLabel' >
|
id = 'videoResolutionLabel' >
|
||||||
{ displayedLabel }
|
{ displayedLabel }
|
||||||
{ this._renderVideonMenu() }
|
{ this._renderVideonMenu() }
|
||||||
|
@ -152,10 +168,17 @@ function _mapStateToProps(state) {
|
||||||
conference,
|
conference,
|
||||||
isLargeVideoHD
|
isLargeVideoHD
|
||||||
} = state['features/base/conference'];
|
} = state['features/base/conference'];
|
||||||
|
const {
|
||||||
|
remoteVideosCount,
|
||||||
|
remoteVideosVisible,
|
||||||
|
visible
|
||||||
|
} = state['features/filmstrip'];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_audioOnly: audioOnly,
|
_audioOnly: audioOnly,
|
||||||
_conferenceStarted: Boolean(conference),
|
_conferenceStarted: Boolean(conference),
|
||||||
|
_filmstripVisible:
|
||||||
|
Boolean(remoteVideosCount && remoteVideosVisible && visible),
|
||||||
_largeVideoHD: isLargeVideoHD
|
_largeVideoHD: isLargeVideoHD
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,12 @@ export default {
|
||||||
* @see {TOGGLE_FILMSTRIP}
|
* @see {TOGGLE_FILMSTRIP}
|
||||||
*/
|
*/
|
||||||
TOGGLED_FILMSTRIP: "UI.toggled_filmstrip",
|
TOGGLED_FILMSTRIP: "UI.toggled_filmstrip",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that the filmstrip has updated its appearance, such as by
|
||||||
|
* toggling or removing videos or adding videos.
|
||||||
|
*/
|
||||||
|
UPDATED_FILMSTRIP_DISPLAY: "UI.updated_filmstrip_display",
|
||||||
TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
|
TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
|
||||||
TOGGLED_SHARED_DOCUMENT: "UI.toggled_shared_document",
|
TOGGLED_SHARED_DOCUMENT: "UI.toggled_shared_document",
|
||||||
CONTACT_CLICKED: "UI.contact_clicked",
|
CONTACT_CLICKED: "UI.contact_clicked",
|
||||||
|
|
Loading…
Reference in New Issue