Merge pull request #7651 from vitormateusalmeida/issue-7598

Draggable webcams grid improvements
This commit is contained in:
Anton Georgiev 2019-06-26 11:59:41 -04:00 committed by GitHub
commit 34b727542a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 262 additions and 312 deletions

View File

@ -52,6 +52,7 @@ export default class Media extends Component {
disableVideo,
children,
audioModalIsOpen,
usersVideo,
} = this.props;
const contentClassName = cx({
@ -70,7 +71,12 @@ export default class Media extends Component {
className={cx(styles.container)}
ref={this.refContainer}
>
<div className={!swapLayout ? contentClassName : overlayClassName}>
<div
className={!swapLayout ? contentClassName : overlayClassName}
style={{
maxHeight: usersVideo.length < 1 || floatingOverlay ? '100%' : '80%',
}}
>
{children}
</div>
<WebcamDraggableOverlay
@ -80,6 +86,7 @@ export default class Media extends Component {
hideOverlay={hideOverlay}
disableVideo={disableVideo}
audioModalIsOpen={audioModalIsOpen}
usersVideo={usersVideo}
/>
</div>
);

View File

@ -123,6 +123,7 @@ export default withModalMounter(withTracker(() => {
}
const usersVideo = VideoService.getAllUsersVideo();
data.usersVideo = usersVideo;
if (MediaService.shouldShowOverlay() && usersVideo.length && viewParticipantsWebcams) {
data.floatingOverlay = usersVideo.length < 2;
data.hideOverlay = usersVideo.length === 0;

View File

@ -24,27 +24,28 @@
.overlay {
display: flex;
width: 100%;
border: 0;
margin-top: 10px;
margin-bottom: 10px;
z-index: 2;
align-items: center;
min-height: calc(var(--video-width) / var(--video-ratio));
max-height: var(--video-height);
min-height: var(--video-height);
overflow: hidden;
}
.overlayRelative{
position: relative;
width: 100%;
}
.overlayAbsoluteSingle{
position: absolute;
height: calc(var(--video-width) / var(--video-ratio));
width: fit-content;
}
.overlayAbsoluteMult{
position: absolute;
width: 100%;
max-width: 100%;
}
.overlayToTop {
@ -65,18 +66,19 @@
}
.floatingOverlay {
margin: 10px;
margin-top: 10px;
margin-bottom: 10px;
@include mq($medium-up) {
z-index: 999;
position: absolute;
bottom: 0;
right: 0;
width: var(--video-width);
min-width: var(--video-width);
max-width: var(--video-max-width);
height: calc(var(--video-width) / var(--video-ratio));
min-height: calc(var(--video-width) / var(--video-ratio));
width: fit-content;
min-width: calc(var(--video-height) * var(--video-ratio));
max-width: fit-content;
height: fit-content;
min-height: var(--video-height);
}
}
@ -121,4 +123,4 @@
z-index: 99;
width: 100%;
background-color: rgba(255, 255, 255, .3);
}
}

View File

@ -7,7 +7,7 @@ import browser from 'browser-detect';
import Draggable from 'react-draggable';
import { styles } from '../styles';
import { styles } from '../styles.scss';
const propTypes = {
floatingOverlay: PropTypes.bool,
@ -29,49 +29,18 @@ const fullscreenChangedEvents = [
const BROWSER_ISMOBILE = browser().mobile;
export default class WebcamDraggableOverlay extends Component {
static getWebcamBySelector() {
return document.querySelector('video[class^="media"]');
static getWebcamGridBySelector() {
return document.querySelector('div[class*="videoList"]');
}
static getWebcamBySelectorCount() {
return document.querySelectorAll('video[class^="media"]').length;
}
static getWebcamListBySelector() {
return document.querySelector('div[class^="videoList"]');
}
static getVideoCanvasBySelector() {
return document.querySelector('div[class^="videoCanvas"]');
static getVideoCountBySelector() {
return document.querySelectorAll('video[class*="media"]').length;
}
static getOverlayBySelector() {
return document.querySelector('div[class*="overlay"]');
}
static isOverlayAbsolute() {
return !!(document.querySelector('div[class*="overlayAbsolute"]'));
}
static getIsOverlayChanged() {
const overlayToTop = document.querySelector('div[class*="overlayToTop"]');
const overlayToBottom = document.querySelector('div[class*="overlayToBottom"]');
return !!(overlayToTop || overlayToBottom);
}
static getGridLineNum(numCams, camWidth, containerWidth) {
let used = (camWidth + 10) * numCams;
let countLines = 0;
while (used > containerWidth) {
used -= containerWidth;
countLines += 1;
}
return countLines + 1;
}
static waitFor(condition, callback) {
const cond = condition();
if (!cond) {
@ -90,19 +59,28 @@ export default class WebcamDraggableOverlay extends Component {
showDropZones: false,
showBgDropZoneTop: false,
showBgDropZoneBottom: false,
dropOnTop: BROWSER_ISMOBILE,
dropOnBottom: !BROWSER_ISMOBILE,
initialPosition: { x: 0, y: 0 },
initialRectPosition: { x: 0, y: 0 },
lastPosition: { x: 0, y: 0 },
dropOnTop: true,
dropOnBottom: false,
initialPosition: {
x: 0,
y: 0,
},
initialRectPosition: {
x: 0,
y: 0,
},
lastPosition: {
x: 0,
y: 0,
},
resetPosition: false,
isFullScreen: false,
isVideoLoaded: false,
isMinWidth: false,
userLength: 0,
shouldUpdatePosition: true,
};
this.shouldUpdatePosition = true;
this.updateWebcamPositionByResize = this.updateWebcamPositionByResize.bind(this);
this.eventVideoFocusChangeListener = this.eventVideoFocusChangeListener.bind(this);
@ -122,15 +100,13 @@ export default class WebcamDraggableOverlay extends Component {
this.handleFullscreenChange = this.handleFullscreenChange.bind(this);
this.fullscreenButtonChange = this.fullscreenButtonChange.bind(this);
this.getVideoListUsersChange = this.getVideoListUsersChange.bind(this);
this.setIsFullScreen = this.setIsFullScreen.bind(this);
this.setResetPosition = this.setResetPosition.bind(this);
this.setInitialReferencePoint = this.setInitialReferencePoint.bind(this);
this.setLastPosition = this.setLastPosition.bind(this);
this.setShouldUpdatePosition = this.setShouldUpdatePosition.bind(this);
this.setLastWebcamPosition = this.setLastWebcamPosition.bind(this);
this.setisMinWidth = this.setisMinWidth.bind(this);
this.setDropOnBottom = this.setDropOnBottom.bind(this);
this.setDropOnTop = this.setDropOnTop.bind(this);
this.dropZoneTopEnterHandler = this.dropZoneTopEnterHandler.bind(this);
this.dropZoneTopLeaveHandler = this.dropZoneTopLeaveHandler.bind(this);
@ -147,7 +123,9 @@ export default class WebcamDraggableOverlay extends Component {
const { resetPosition } = this.state;
if (!floatingOverlay
&& !resetPosition) this.setResetPosition(true);
&& !resetPosition) {
this.setResetPosition(true);
}
window.addEventListener('resize', this.eventResizeListener);
window.addEventListener('videoFocusChange', this.eventVideoFocusChangeListener);
@ -158,41 +136,61 @@ export default class WebcamDraggableOverlay extends Component {
// Ensures that the event will be called before the resize
document.addEventListener('webcamFullscreenButtonChange', this.fullscreenButtonChange);
window.addEventListener('videoListUsersChange', this.getVideoListUsersChange);
}
componentDidUpdate(prevProps, prevState) {
const { swapLayout } = this.props;
const { userLength, lastPosition } = this.state;
componentDidUpdate(prevProps) {
const { swapLayout, usersVideo, mediaContainer } = this.props;
const { lastPosition } = this.state;
const { y } = lastPosition;
// if (prevProps.swapLayout && !swapLayout && userLength === 1) {
// this.setShouldUpdatePosition(false);
// }
const userLength = usersVideo.length;
const prevUserLength = prevProps.usersVideo.length;
if (prevProps.mediaContainer && mediaContainer) {
const mediaContainerRect = mediaContainer.getBoundingClientRect();
const {
left: mediaLeft,
top: mediaTop,
} = mediaContainerRect;
const prevMediaContainerRect = prevProps.mediaContainer.getBoundingClientRect();
const {
left: prevMediaLeft,
top: prevMediaTop,
} = prevMediaContainerRect;
if (mediaLeft !== prevMediaLeft || mediaTop !== prevMediaTop) {
this.shouldUpdatePosition = false;
} else if (this.shouldUpdatePosition === false) {
this.shouldUpdatePosition = true;
}
}
if (prevProps.swapLayout && !swapLayout && userLength === 1) {
this.shouldUpdatePosition = false;
}
if (prevProps.swapLayout && !swapLayout && userLength > 1) {
this.setLastPosition(0, y);
}
if (prevState.userLength === 1 && userLength > 1) {
this.setDropOnBottom(true);
if (prevUserLength === 1 && userLength > 1) {
this.setResetPosition(true);
this.setDropOnTop(true);
}
if (prevUserLength !== userLength) {
WebcamDraggableOverlay.waitFor(
() => WebcamDraggableOverlay.getVideoCountBySelector() === userLength,
this.updateWebcamPositionByResize,
);
}
}
componentWillUnmount() {
fullscreenChangedEvents.forEach((event) => {
document.removeEventListener(event, this.fullScreenToggleCallback);
document.removeEventListener(event, this.handleFullscreenChange);
});
document.removeEventListener('webcamFullscreenButtonChange', this.fullscreenButtonChange);
document.removeEventListener('videoListUsersChange', this.getVideoListUsersChange);
document.removeEventListener('videoFocusChange', this.eventVideoFocusChangeListener);
}
getVideoListUsersChange() {
const userLength = WebcamDraggableOverlay.getWebcamBySelectorCount();
this.setState({ userLength });
}
setIsFullScreen(isFullScreen) {
this.setState({ isFullScreen });
}
@ -202,30 +200,26 @@ export default class WebcamDraggableOverlay extends Component {
}
setLastPosition(x, y) {
this.setState({ lastPosition: { x, y } });
this.setState({
lastPosition: {
x,
y,
},
});
}
setShouldUpdatePosition(shouldUpdatePosition) {
this.setState({ shouldUpdatePosition });
}
setDropOnBottom(dropOnBottom) {
this.setState({ dropOnBottom });
setDropOnTop(dropOnTop) {
this.setState({ dropOnTop });
}
setInitialReferencePoint() {
const { refMediaContainer } = this.props;
const { userLength, shouldUpdatePosition } = this.state;
const { refMediaContainer, usersVideo } = this.props;
const { current: mediaContainer } = refMediaContainer;
const userLength = usersVideo.length;
const webcamBySelector = WebcamDraggableOverlay.getWebcamBySelector();
if (webcamBySelector && mediaContainer && shouldUpdatePosition) {
if (userLength === 0) this.getVideoListUsersChange();
let x = 0;
let y = 0;
const webcamBySelector = WebcamDraggableOverlay.getWebcamGridBySelector();
if (webcamBySelector && mediaContainer && this.shouldUpdatePosition) {
const webcamBySelectorRect = webcamBySelector.getBoundingClientRect();
const {
width: webcamWidth,
@ -238,29 +232,37 @@ export default class WebcamDraggableOverlay extends Component {
height: mediaHeight,
} = mediaContainerRect;
const lineNum = WebcamDraggableOverlay
.getGridLineNum(userLength, webcamWidth, mediaWidth);
x = mediaWidth - ((webcamWidth + 10) * userLength); // 10 is margin
y = mediaHeight - ((webcamHeight + 10) * lineNum); // 10 is margin
const x = mediaWidth - ((webcamWidth + 10) * userLength); // 10 is margin
const y = mediaHeight - ((webcamHeight + 10)); // 10 is margin
if (x === 0 && y === 0) return false;
this.setState({ initialRectPosition: { x, y } });
this.setState({
initialRectPosition: {
x,
y,
},
});
return true;
}
return false;
}
setLastWebcamPosition() {
const { refMediaContainer } = this.props;
const { refMediaContainer, usersVideo, floatingOverlay } = this.props;
const { current: mediaContainer } = refMediaContainer;
const { initialRectPosition, userLength, shouldUpdatePosition } = this.state;
const {
initialRectPosition,
dragging,
dropOnTop,
dropOnBottom,
} = this.state;
const userLength = usersVideo.length;
const { x: initX, y: initY } = initialRectPosition;
const webcamBySelector = WebcamDraggableOverlay.getWebcamBySelector();
const webcamBySelector = WebcamDraggableOverlay.getWebcamGridBySelector();
if (webcamBySelector && mediaContainer && shouldUpdatePosition) {
if (webcamBySelector && mediaContainer && this.shouldUpdatePosition) {
const webcamBySelectorRect = webcamBySelector.getBoundingClientRect();
const {
left: webcamLeft,
@ -273,28 +275,23 @@ export default class WebcamDraggableOverlay extends Component {
top: mediaTop,
} = mediaContainerRect;
const webcamXByMedia = userLength > 1 ? 0 : webcamLeft - mediaLeft;
const webcamXByMedia = webcamLeft - mediaLeft;
const webcamYByMedia = webcamTop - mediaTop;
let x = 0;
let y = 0;
let x = -(initX - webcamXByMedia);
x = floatingOverlay ? -((initX - webcamXByMedia) + 10) : x;
x = userLength > 1 ? 0 : x;
if (webcamXByMedia > 0) {
x = webcamXByMedia - initX;
} else {
x = 0 - initX;
}
if (userLength > 1) x = 0;
x = !dragging && webcamXByMedia < 0 ? -initX : x;
if (webcamYByMedia > 0) {
y = webcamYByMedia - initY;
} else {
y = 0 - initY;
}
let y = -(initY - webcamYByMedia);
y = webcamYByMedia < 0 ? -initY : y;
if (webcamYByMedia > initY) {
y = -10;
}
y = userLength > 1 && dropOnTop ? -initY : y;
y = userLength > 1 && dropOnBottom ? 0 : y;
y = y < -initY ? -initY : y;
y = y > 0 ? 0 : y;
this.setLastPosition(x, y);
}
@ -327,7 +324,7 @@ export default class WebcamDraggableOverlay extends Component {
if (window.innerWidth < 641) {
this.setisMinWidth(true);
this.setState({ dropOnBottom: true });
this.setState({ dropOnTop: true });
this.setResetPosition(true);
} else if (isMinWidth) {
this.setisMinWidth(false);
@ -364,14 +361,14 @@ export default class WebcamDraggableOverlay extends Component {
resetPosition,
} = this.state;
if (!floatingOverlay) WebcamDraggableOverlay.getOverlayBySelector().style.bottom = 0;
if (!floatingOverlay && dropOnTop) WebcamDraggableOverlay.getOverlayBySelector().style.top = 0;
if (!dragging) this.setState({ dragging: true });
if (!showDropZones) this.setState({ showDropZones: true });
if (dropOnTop) this.setState({ dropOnTop: false });
if (dropOnBottom) this.setState({ dropOnBottom: false });
if (!showDropZones) this.setState({ showDropZones: true });
if (resetPosition) this.setState({ resetPosition: false });
window.dispatchEvent(new Event('resize'));
}
handleWebcamDragStop(e, position) {
@ -386,6 +383,7 @@ export default class WebcamDraggableOverlay extends Component {
if (showDropZones) this.setState({ showDropZones: false });
this.setLastPosition(x, y);
window.dispatchEvent(new Event('resize'));
}
dropZoneTopEnterHandler() {
@ -454,7 +452,11 @@ export default class WebcamDraggableOverlay extends Component {
disableVideo,
audioModalIsOpen,
refMediaContainer,
usersVideo,
} = this.props;
const userLength = usersVideo.length;
const { current: mediaContainer } = refMediaContainer;
let mediaContainerRect;
@ -482,8 +484,6 @@ export default class WebcamDraggableOverlay extends Component {
isMinWidth,
} = this.state;
const webcamBySelectorCount = WebcamDraggableOverlay.getWebcamBySelectorCount();
const contentClassName = cx({
[styles.content]: true,
});
@ -491,8 +491,7 @@ export default class WebcamDraggableOverlay extends Component {
const overlayClassName = cx({
[styles.overlay]: true,
[styles.overlayRelative]: (dropOnTop || dropOnBottom),
[styles.overlayAbsoluteSingle]: (!dropOnTop && !dropOnBottom && webcamBySelectorCount <= 1),
[styles.overlayAbsoluteMult]: (!dropOnTop && !dropOnBottom && webcamBySelectorCount > 1),
[styles.overlayAbsoluteMult]: (!dropOnTop && !dropOnBottom) && userLength > 1,
[styles.hideOverlay]: hideOverlay,
[styles.floatingOverlay]: floatingOverlay && (!dropOnTop && !dropOnBottom),
[styles.overlayToTop]: dropOnTop,
@ -539,12 +538,13 @@ export default class WebcamDraggableOverlay extends Component {
onMouseEnter={this.dropZoneTopEnterHandler}
onMouseLeave={this.dropZoneTopLeaveHandler}
onMouseUp={this.dropZoneTopMouseUpHandler}
data-dropzone="dropZoneTop"
role="presentation"
style={{ height: '100px' }}
style={{ height: userLength > 1 ? '50%' : '20%' }}
/>
<div
className={dropZoneBgTopClassName}
style={{ height: '100px' }}
style={{ height: userLength > 1 ? '50%' : '20%' }}
/>
<Draggable
@ -558,6 +558,9 @@ export default class WebcamDraggableOverlay extends Component {
>
<div
className={!swapLayout ? overlayClassName : contentClassName}
style={{
maxHeight: mediaHeight,
}}
>
{
!disableVideo && !audioModalIsOpen
@ -565,9 +568,7 @@ export default class WebcamDraggableOverlay extends Component {
<VideoProviderContainer
cursor={cursor()}
swapLayout={swapLayout}
mediaHeight={mediaHeight}
onMount={this.videoMounted}
onUpdate={this.videoUpdated}
/>
) : null}
</div>
@ -578,12 +579,13 @@ export default class WebcamDraggableOverlay extends Component {
onMouseEnter={this.dropZoneBottomEnterHandler}
onMouseLeave={this.dropZoneBottomLeaveHandler}
onMouseUp={this.dropZoneBottomMouseUpHandler}
data-dropzone="dropZoneBottom"
role="presentation"
style={{ height: '100px' }}
style={{ height: userLength > 1 ? '50%' : '20%' }}
/>
<div
className={dropZoneBgBottomClassName}
style={{ height: '100px' }}
style={{ height: userLength > 1 ? '50%' : '20%' }}
/>
</Fragment>
);

View File

@ -12,7 +12,6 @@ const VideoProviderContainer = ({ children, ...props }) => {
export default withTracker(props => ({
cursor: props.cursor,
swapLayout: props.swapLayout,
mediaHeight: props.mediaHeight,
meetingId: VideoService.meetingId(),
users: VideoService.getAllUsersVideo(),
userId: VideoService.userId(),

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import cx from 'classnames';
import _ from 'lodash';
import { styles } from './styles';
import VideoListItem from './video-list-item/component';
@ -28,18 +29,91 @@ const intlMessages = defineMessages({
},
});
// See: https://stackoverflow.com/a/3513565
const findOptimalGrid = (canvasWidth, canvasHeight, gutter, aspectRatio, numItems, columns = 1) => {
const rows = Math.ceil(numItems / columns);
const gutterTotalWidth = (columns - 1) * gutter;
const gutterTotalHeight = (rows - 1) * gutter;
const usableWidth = canvasWidth - gutterTotalWidth;
const usableHeight = canvasHeight - gutterTotalHeight;
let cellWidth = Math.floor(usableWidth / columns);
let cellHeight = Math.ceil(cellWidth / aspectRatio);
if ((cellHeight * rows) > usableHeight) {
cellHeight = Math.floor(usableHeight / rows);
cellWidth = Math.ceil(cellHeight * aspectRatio);
}
return {
columns,
rows,
width: (cellWidth * columns) + gutterTotalWidth,
height: (cellHeight * rows) + gutterTotalHeight,
filledArea: (cellWidth * cellHeight) * numItems,
};
};
const ASPECT_RATIO = 4 / 3;
class VideoList extends Component {
constructor(props) {
super(props);
this.state = {
focusedId: false,
optimalGrid: {
cols: 1,
rows: 1,
filledArea: 0,
},
};
this.ticking = false;
this.grid = null;
this.canvas = null;
this.handleCanvasResize = _.throttle(this.handleCanvasResize.bind(this), 66,
{
leading: true,
trailing: true,
});
this.setOptimalGrid = this.setOptimalGrid.bind(this);
}
componentDidMount() {
this.handleCanvasResize();
window.addEventListener('resize', this.handleCanvasResize, false);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleCanvasResize, false);
}
setOptimalGrid() {
const { users } = this.props;
let numItems = users.length;
if (numItems < 1 || !this.canvas || !this.grid) {
return;
}
const { focusedId } = this.state;
const { width: canvasWidth, height: canvasHeight } = this.canvas.getBoundingClientRect();
const gridGutter = parseInt(window.getComputedStyle(this.grid)
.getPropertyValue('grid-row-gap'), 10);
const hasFocusedItem = numItems > 2 && focusedId;
// Has a focused item so we need +3 cells
if (hasFocusedItem) {
numItems += 3;
}
const optimalGrid = _.range(1, numItems + 1)
.reduce((currentGrid, col) => {
const testGrid = findOptimalGrid(
canvasWidth, canvasHeight, gridGutter,
ASPECT_RATIO, numItems, col,
);
// We need a minimun of 2 rows and columns for the focused
const focusedConstraint = hasFocusedItem ? testGrid.rows > 1 && testGrid.columns > 1 : true;
const betterThanCurrent = testGrid.filledArea > currentGrid.filledArea;
return focusedConstraint && betterThanCurrent ? testGrid : currentGrid;
}, { filledArea: 0 });
this.setState({
optimalGrid,
});
}
handleVideoFocus(id) {
@ -50,6 +124,16 @@ class VideoList extends Component {
window.dispatchEvent(new Event('videoFocusChange'));
}
handleCanvasResize() {
if (!this.ticking) {
window.requestAnimationFrame(() => {
this.ticking = false;
this.setOptimalGrid();
});
}
this.ticking = true;
}
renderVideoList() {
const {
intl,
@ -60,7 +144,6 @@ class VideoList extends Component {
enableVideoStats,
cursor,
swapLayout,
mediaHeight,
} = this.props;
const { focusedId } = this.state;
@ -81,8 +164,7 @@ class VideoList extends Component {
<div
key={user.id}
className={cx({
[styles.videoListItem]: !swapLayout,
[styles.videoListItemSwapLayout]: swapLayout,
[styles.videoListItem]: true,
[styles.focused]: focusedId === user.id && users.length > 2,
})}
style={{
@ -93,12 +175,14 @@ class VideoList extends Component {
numOfUsers={users.length}
user={user}
actions={actions}
onMount={(videoRef) => { onMount(user.id, videoRef); }}
onMount={(videoRef) => {
this.handleCanvasResize();
onMount(user.id, videoRef);
}}
getStats={(videoRef, callback) => getStats(user.id, videoRef, callback)}
stopGettingStats={() => stopGettingStats(user.id)}
enableVideoStats={enableVideoStats}
swapLayout={swapLayout}
mediaHeight={mediaHeight}
/>
</div>
);
@ -106,27 +190,36 @@ class VideoList extends Component {
}
render() {
const { users, swapLayout } = this.props;
const { users } = this.props;
const { optimalGrid } = this.state;
const canvasClassName = cx({
[styles.videoCanvas]: !swapLayout,
[styles.videoCanvasSwapLayout]: swapLayout,
[styles.videoCanvas]: true,
});
const videoListClassName = cx({
[styles.videoList]: !swapLayout,
[styles.videoListSwapLayout]: swapLayout,
[styles.videoList]: true,
});
return (
<div
ref={(ref) => { this.canvas = ref; }}
ref={(ref) => {
this.canvas = ref;
}}
className={canvasClassName}
>
{!users.length ? null : (
<div
ref={(ref) => { this.grid = ref; }}
ref={(ref) => {
this.grid = ref;
}}
className={videoListClassName}
style={{
width: `${optimalGrid.width}px`,
height: `${optimalGrid.height}px`,
gridTemplateColumns: `repeat(${optimalGrid.columns}, 1fr)`,
gridTemplateRows: `repeat(${optimalGrid.rows}, 1fr)`,
}}
>
{this.renderVideoList()}
</div>

View File

@ -9,29 +9,15 @@
--cam-dropdown-width: 70%;
--audio-indicator-width: 1.12rem;
--audio-indicator-fs: 75%;
position: relative;
position: absolute;
width: 100%;
min-height: calc(var(--video-width) / var(--video-ratio));
min-height: var(--video-height);
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
align-items: center;
justify-content: center;
}
.videoCanvasSwapLayout {
--cam-dropdown-width: 70%;
--audio-indicator-width: 1.12rem;
--audio-indicator-fs: 75%;
position: relative;
width: 100%;
min-height: calc(var(--video-width) / var(--video-ratio));
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
}
@ -40,12 +26,7 @@
display: grid;
border-radius: 5px;
min-height: calc(var(--video-width) / var(--video-ratio));
grid-template-columns: repeat(auto-fit, var(--video-width));
grid-auto-columns: var(--video-width);
grid-auto-rows: calc(var(--video-width) / var(--video-ratio));
grid-auto-flow: dense;
grid-gap: 5px;
align-items: center;
@ -56,78 +37,22 @@
}
}
.videoListSwapLayout {
display: grid;
padding: 10px;
border-radius: 5px;
min-height: calc(var(--video-width) / var(--video-ratio));
grid-template-columns: repeat(auto-fit, minmax(var(--video-width), 1fr));
grid-auto-columns: minmax(var(--video-width), 1fr);
grid-auto-rows: 1fr;
grid-gap: 5px;
align-items: center;
justify-content: center;
grid-auto-flow: row;
@include mq($medium-up) {
grid-gap: 10px;
}
}
.videoListItem {
display: flex;
max-width: fit-content;
&.focused {
grid-column: 1 / span 2;
grid-row: 1 / span 2;
width: 100%;
min-width: 100%;
max-width: 100%;
height: 100%;
min-height: 100%;
max-height: 100%;
}
width: var(--video-width);
min-width: var(--video-width);
height: calc(var(--video-width) / var(--video-ratio));
min-height: calc(var(--video-width) / var(--video-ratio));
}
.videoListItemSwapLayout {
display: flex;
max-width: -moz-fit-content;
max-height: -moz-fit-content;
width: 100%;
height: 100%;
&.focused {
grid-column: 1 / span 2;
grid-row: 1 / span 2;
width: 100%;
min-width: 100%;
max-width: 100%;
height: 100%;
min-height: 100%;
max-height: 100%;
}
}
.content {
position: relative;
display: flex;
min-width: 100%;
height: 100%;
min-height: 100%;
border-radius: 5px;
background-color: var(--color-gray);
width: var(--video-width);
min-width: var(--video-width);
height: calc(var(--video-width) / var(--video-ratio));
min-height: calc(var(--video-width) / var(--video-ratio));
&::after {
content: "";
position: absolute;
@ -148,69 +73,6 @@
&.talking::after {
opacity: 1;
}
.focused & {
width: 100%;
min-width: 100%;
max-width: 100%;
height: 100%;
min-height: 100%;
max-height: 100%;
}
}
.contentSwapLayout {
position: relative;
min-width: 100%;
height: 100%;
min-height: 100%;
border-radius: 5px;
background-color: var(--color-gray);
width: 100%;
&::after {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border: 5px solid var(--color-white-with-transparency);
border-radius: 5px;
opacity: 0;
pointer-events: none;
:global(.animationsEnabled) & {
transition: opacity .1s;
}
}
&.talking::after {
opacity: 1;
}
.focused & {
width: 100%;
min-width: 100%;
max-width: 100%;
height: 100%;
min-height: 100%;
max-height: 100%;
}
}
.contentLoading {
width: var(--video-width);
min-width: var(--video-width);
height: calc(var(--video-width) / var(--video-ratio));
min-height: calc(var(--video-width) / var(--video-ratio));
}
.contentLoadingSwapLayout {
width: 100%;
height: 100%;
}
%media-area {
@ -239,8 +101,6 @@
font-size: 2.5rem;
text-align: center;
white-space: nowrap;
width: 100%;
height: 100%;
&::after {
@ -394,4 +254,4 @@
.voice {
background-color: var(--color-success);
}
}

View File

@ -130,7 +130,7 @@ class VideoListItem extends Component {
render() {
const { showStats, stats, videoIsReady } = this.state;
const {
user, numOfUsers, swapLayout, mediaHeight,
user, numOfUsers,
} = this.props;
const availableActions = this.getAvailableActions();
const enableVideoMenu = Meteor.settings.public.kurento.enableVideoMenu || false;
@ -140,22 +140,15 @@ class VideoListItem extends Component {
return (
<div className={cx({
[styles.content]: !swapLayout,
[styles.contentSwapLayout]: swapLayout,
[styles.content]: true,
[styles.talking]: user.isTalking,
[styles.contentLoading]: !videoIsReady && !swapLayout,
[styles.contentLoadingSwapLayout]: !videoIsReady && swapLayout,
})}
>
{!videoIsReady && <div className={styles.connecting} />}
<video
style={{
maxHeight: mediaHeight - 20, // 20 is margin
}}
muted
className={cx({
[styles.media]: true,
[styles.contentLoading]: !videoIsReady,
})}
ref={(ref) => { this.videoTag = ref; }}
autoPlay

View File

@ -1,13 +1,6 @@
@import "../../stylesheets/variables/_all";
:root {
--video-width: 10vw;
--video-height: calc((100vh - calc(var(--navbar-height) + var(--actionsbar-height))) * 0.2);
--video-ratio: calc(4 / 3);
}
@include mq($small-only) {
:root {
--video-width: 20vw;
}
}