Webcam draggable refactoring

This commit is contained in:
Vitor Mateus 2019-07-09 20:11:48 -03:00
parent f19ba4dda0
commit 68ec6411aa
10 changed files with 466 additions and 542 deletions

View File

@ -266,6 +266,9 @@ class App extends Component {
className={styles.media}
aria-label={intl.formatMessage(intlMessages.mediaLabel)}
aria-hidden={this.shouldAriaHide()}
style={{
overflow: 'hidden',
}}
>
{media}
{this.renderCaptions()}

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import WebcamDraggableOverlay from './webcam-draggable-overlay/component';
import WebcamDraggable from './webcam-draggable-overlay/component';
import { styles } from './styles';
@ -70,6 +70,9 @@ export default class Media extends Component {
id="container"
className={cx(styles.container)}
ref={this.refContainer}
style={{
overflow: 'hidden',
}}
>
<div
className={!swapLayout ? contentClassName : overlayClassName}
@ -79,10 +82,11 @@ export default class Media extends Component {
>
{children}
</div>
<WebcamDraggableOverlay
<WebcamDraggable
refMediaContainer={this.refContainer}
swapLayout={swapLayout}
floatingOverlay={floatingOverlay}
singleWebcam={floatingOverlay}
usersVideoLenght={usersVideo.length}
hideOverlay={hideOverlay}
disableVideo={disableVideo}
audioModalIsOpen={audioModalIsOpen}

View File

@ -1,6 +1,14 @@
@import "../../stylesheets/variables/_all";
@import "../../stylesheets/variables/video";
.cursorGrab{
cursor: grab;
}
.cursorGrabbing{
cursor: grabbing;
}
.container {
order: 1;
flex: 2;
@ -22,30 +30,21 @@
order: 2;
}
.overlay {
%overlay {
display: flex;
border: 0;
margin-top: 10px;
margin-bottom: 10px;
z-index: 2;
align-items: center;
max-height: var(--video-height);
min-height: var(--video-height);
overflow: hidden;
}
.overlayRelative{
position: relative;
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
}
.overlayAbsoluteSingle{
width: fit-content;
}
.overlayAbsoluteMult{
width: 100%;
max-width: 100%;
.overlay {
@extend %overlay;
}
.overlayToTop {
@ -61,27 +60,38 @@
position: absolute;
overflow: hidden;
clip: rect(0 0 0 0);
height: 1px; width: 1px;
margin: -1px; padding: 0; border: 0;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
}
.floatingOverlay {
margin-top: 10px;
margin-bottom: 10px;
@extend %overlay;
top: 0;
left: 0;
@include mq($medium-up) {
z-index: 999;
position: absolute;
bottom: 0;
right: 0;
width: fit-content;
min-width: calc(var(--video-height) * var(--video-ratio));
max-width: fit-content;
height: fit-content;
min-height: var(--video-height);
}
}
.fit {
width: fit-content;
height: fit-content;
max-width: fit-content;
}
.full {
min-width: 100%;
width: 100%;
max-width: 100%;
}
.hide {
display: none;
}
@ -90,14 +100,6 @@
display: block;
}
.top {
top: 0;
}
.bottom {
bottom: 0;
}
.dragging {
opacity: .5;
}
@ -110,17 +112,24 @@
z-index: 9999;
}
.dropZoneTop {
.dropZoneBgTop,
.dropZoneBgBottom {
z-index: 99;
width: 100%;
height: 100%;
}
.dropZoneTop,
.dropZoneBgTop {
top: 0;
}
.dropZoneBottom {
.dropZoneBottom,
.dropZoneBgBottom {
bottom: 0;
}
.dropZoneBg {
position: absolute;
z-index: 99;
width: 100%;
.dropZoneTop:hover .dropZoneBgTop,
.dropZoneBottom:hover .dropZoneBgBottom {
background-color: rgba(255, 255, 255, .3);
}
}

View File

@ -1,488 +1,212 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import VideoProviderContainer from '/imports/ui/components/video-provider/container';
import _ from 'lodash';
import browser from 'browser-detect';
import Draggable from 'react-draggable';
import cx from 'classnames';
import * as _ from 'lodash';
import browser from 'browser-detect';
import { withDraggableContext } from './context';
import VideoProviderContainer from '/imports/ui/components/video-provider/container';
import { styles } from '../styles.scss';
import Storage from '../../../services/storage/session';
const propTypes = {
floatingOverlay: PropTypes.bool,
hideOverlay: PropTypes.bool,
};
const defaultProps = {
floatingOverlay: false,
hideOverlay: true,
};
const fullscreenChangedEvents = [
'fullscreenchange',
'webkitfullscreenchange',
'mozfullscreenchange',
'MSFullscreenChange',
];
const { webcamsDefaultPlacement } = Meteor.settings.public.layout;
const BROWSER_ISMOBILE = browser().mobile;
export default class WebcamDraggableOverlay extends Component {
static getWebcamGridBySelector() {
return document.querySelector('div[class*="videoList"]');
}
static getVideoCountBySelector() {
return document.querySelectorAll('video[class*="media"]').length;
}
static getOverlayBySelector() {
return document.querySelector('div[class*="overlay"]');
}
static waitFor(condition, callback) {
const cond = condition();
if (!cond) {
setTimeout(WebcamDraggableOverlay.waitFor.bind(null, condition, callback), 500);
} else {
callback();
}
return false;
}
class WebcamDraggable extends Component {
constructor(props) {
super(props);
this.state = {
dragging: false,
showDropZones: false,
showBgDropZoneTop: false,
showBgDropZoneBottom: false,
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,
};
this.shouldUpdatePosition = true;
this.updateWebcamPositionByResize = this.updateWebcamPositionByResize.bind(this);
this.eventVideoFocusChangeListener = this.eventVideoFocusChangeListener.bind(this);
this.eventResizeListener = _.throttle(
this.updateWebcamPositionByResize,
500,
{
leading: true,
trailing: true,
},
);
this.videoMounted = this.videoMounted.bind(this);
this.handleWebcamDragStart = this.handleWebcamDragStart.bind(this);
this.handleWebcamDragStop = this.handleWebcamDragStop.bind(this);
this.handleFullscreenChange = this.handleFullscreenChange.bind(this);
this.fullscreenButtonChange = this.fullscreenButtonChange.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.setLastWebcamPosition = this.setLastWebcamPosition.bind(this);
this.setisMinWidth = this.setisMinWidth.bind(this);
this.setDropOnTop = this.setDropOnTop.bind(this);
this.dropZoneTopEnterHandler = this.dropZoneTopEnterHandler.bind(this);
this.dropZoneTopLeaveHandler = this.dropZoneTopLeaveHandler.bind(this);
this.dropZoneBottomEnterHandler = this.dropZoneBottomEnterHandler.bind(this);
this.dropZoneBottomLeaveHandler = this.dropZoneBottomLeaveHandler.bind(this);
this.dropZoneTopMouseUpHandler = this.dropZoneTopMouseUpHandler.bind(this);
this.dropZoneBottomMouseUpHandler = this.dropZoneBottomMouseUpHandler.bind(this);
}
componentDidMount() {
const { floatingOverlay } = this.props;
const { resetPosition } = this.state;
if (!floatingOverlay
&& !resetPosition) {
this.setResetPosition(true);
}
window.addEventListener('resize', this.eventResizeListener);
window.addEventListener('videoFocusChange', this.eventVideoFocusChangeListener);
fullscreenChangedEvents.forEach((event) => {
document.addEventListener(event, this.handleFullscreenChange);
});
// Ensures that the event will be called before the resize
document.addEventListener('webcamFullscreenButtonChange', this.fullscreenButtonChange);
window.addEventListener('resize', _.debounce(this.onResize.bind(this), 500));
}
componentDidUpdate(prevProps) {
const { swapLayout, usersVideo, mediaContainer } = this.props;
const { lastPosition } = this.state;
const { y } = lastPosition;
const userLength = usersVideo.length;
const prevUserLength = prevProps.usersVideo.length;
const { swapLayout } = this.props;
if (prevProps.swapLayout === true && swapLayout === false) {
setTimeout(() => this.forceUpdate(), 500);
}
}
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;
onResize() {
const { webcamDraggableState, webcamDraggableDispatch } = this.props;
const { mediaSize } = webcamDraggableState;
const { width: stateWidth, height: stateHeight } = mediaSize;
const { width, height } = this.getMediaBounds();
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 (prevUserLength === 1 && userLength > 1) {
this.setResetPosition(true);
this.setDropOnTop(true);
}
if (prevUserLength !== userLength) {
WebcamDraggableOverlay.waitFor(
() => WebcamDraggableOverlay.getVideoCountBySelector() === userLength,
this.updateWebcamPositionByResize,
if (stateWidth !== width || stateHeight !== height) {
webcamDraggableDispatch(
{
type: 'setMediaSize',
value: {
width,
height,
},
},
);
}
}
componentWillUnmount() {
fullscreenChangedEvents.forEach((event) => {
document.removeEventListener(event, this.handleFullscreenChange);
});
document.removeEventListener('webcamFullscreenButtonChange', this.fullscreenButtonChange);
document.removeEventListener('videoFocusChange', this.eventVideoFocusChangeListener);
}
setIsFullScreen(isFullScreen) {
this.setState({ isFullScreen });
}
setResetPosition(resetPosition) {
this.setState({ resetPosition });
}
setLastPosition(x, y) {
this.setState({
lastPosition: {
x,
y,
},
});
}
setDropOnTop(dropOnTop) {
this.setState({ dropOnTop });
}
setInitialReferencePoint() {
const { refMediaContainer, usersVideo } = this.props;
getMediaBounds() {
const { refMediaContainer, webcamDraggableState, webcamDraggableDispatch } = this.props;
const { mediaSize: mediaState } = webcamDraggableState;
const { current: mediaContainer } = refMediaContainer;
const userLength = usersVideo.length;
const webcamBySelector = WebcamDraggableOverlay.getWebcamGridBySelector();
if (webcamBySelector && mediaContainer && this.shouldUpdatePosition) {
const webcamBySelectorRect = webcamBySelector.getBoundingClientRect();
const {
width: webcamWidth,
height: webcamHeight,
} = webcamBySelectorRect;
if (mediaContainer) {
const mediaContainerRect = mediaContainer.getBoundingClientRect();
const {
width: mediaWidth,
height: mediaHeight,
top, left, width, height,
} = mediaContainerRect;
const x = mediaWidth - ((webcamWidth + 10) * userLength); // 10 is margin
const y = mediaHeight - ((webcamHeight + 10)); // 10 is margin
if (mediaState.width === 0 || mediaState.height === 0) {
webcamDraggableDispatch(
{
type: 'setMediaSize',
value: {
width,
height,
},
},
);
}
if (x === 0 && y === 0) return false;
this.setState({
initialRectPosition: {
x,
y,
},
});
return true;
return {
top,
left,
width,
height,
};
}
return false;
}
setLastWebcamPosition() {
const { refMediaContainer, usersVideo, floatingOverlay } = this.props;
const { current: mediaContainer } = refMediaContainer;
const {
initialRectPosition,
dragging,
dropOnTop,
dropOnBottom,
} = this.state;
const userLength = usersVideo.length;
const { x: initX, y: initY } = initialRectPosition;
const webcamBySelector = WebcamDraggableOverlay.getWebcamGridBySelector();
if (webcamBySelector && mediaContainer && this.shouldUpdatePosition) {
const webcamBySelectorRect = webcamBySelector.getBoundingClientRect();
getWebcamsListBounds() {
const { webcamDraggableState, singleWebcam } = this.props;
const { videoListRef } = webcamDraggableState;
if (videoListRef) {
const videoListRefRect = videoListRef.getBoundingClientRect();
const {
left: webcamLeft,
top: webcamTop,
} = webcamBySelectorRect;
const mediaContainerRect = mediaContainer.getBoundingClientRect();
const {
left: mediaLeft,
top: mediaTop,
} = mediaContainerRect;
const webcamXByMedia = webcamLeft - mediaLeft;
const webcamYByMedia = webcamTop - mediaTop;
let x = -(initX - webcamXByMedia);
x = floatingOverlay ? -((initX - webcamXByMedia) + 10) : x;
x = userLength > 1 ? 0 : x;
x = !dragging && webcamXByMedia < 0 ? -initX : x;
let y = -(initY - webcamYByMedia);
y = webcamYByMedia < 0 ? -initY : y;
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);
top, left, width, height,
} = videoListRefRect;
return {
top: top - 10, // 10 = margin
left: left - (singleWebcam ? 10 : 0), // 10 = margin
width: width + (singleWebcam ? 20 : 0), // 20 = margin
height: height + 20, // 20 = margin
};
}
return false;
}
setisMinWidth(isMinWidth) {
this.setState({ isMinWidth });
calculatePosition() {
const { top: mediaTop, left: mediaLeft } = this.getMediaBounds();
const { top: webcamsListTop, left: webcamsListLeft } = this.getWebcamsListBounds();
const x = webcamsListLeft - mediaLeft;
const y = webcamsListTop - mediaTop;
return {
x,
y,
};
}
videoMounted() {
this.setResetPosition(true);
WebcamDraggableOverlay.waitFor(this.setInitialReferencePoint, this.setLastWebcamPosition);
this.setState({ isVideoLoaded: true });
}
async handleWebcamDragStart() {
const { webcamDraggableDispatch, singleWebcam } = this.props;
const { x, y } = await this.calculatePosition();
fullscreenButtonChange() {
this.setIsFullScreen(true);
}
webcamDraggableDispatch({ type: 'dragStart' });
updateWebcamPositionByResize() {
const {
isVideoLoaded,
isMinWidth,
} = this.state;
if (isVideoLoaded) {
this.setInitialReferencePoint();
this.setLastWebcamPosition();
}
if (window.innerWidth < 641) {
this.setisMinWidth(true);
this.setState({ dropOnTop: true });
this.setResetPosition(true);
} else if (isMinWidth) {
this.setisMinWidth(false);
}
}
eventVideoFocusChangeListener() {
setTimeout(() => {
this.setInitialReferencePoint();
this.setLastWebcamPosition();
}, 500);
}
handleFullscreenChange() {
if (document.fullscreenElement
|| document.webkitFullscreenElement
|| document.mozFullScreenElement
|| document.msFullscreenElement) {
window.removeEventListener('resize', this.eventResizeListener);
this.setIsFullScreen(true);
} else {
this.setIsFullScreen(false);
window.addEventListener('resize', this.eventResizeListener);
}
}
handleWebcamDragStart() {
const { floatingOverlay } = this.props;
const {
dragging,
showDropZones,
dropOnTop,
dropOnBottom,
resetPosition,
} = this.state;
if (!floatingOverlay && dropOnTop) WebcamDraggableOverlay.getOverlayBySelector().style.top = 0;
if (!dragging) this.setState({ dragging: 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 });
webcamDraggableDispatch(
{
type: 'setTempPosition',
value: {
x: singleWebcam ? x : 0,
y,
},
},
);
}
handleWebcamDragStop(e, position) {
const {
dragging,
showDropZones,
} = this.state;
const { webcamDraggableDispatch, singleWebcam } = this.props;
const targetClassname = e.target.className;
const { x, y } = position;
if (dragging) this.setState({ dragging: false });
if (showDropZones) this.setState({ showDropZones: false });
this.setLastPosition(x, y);
window.dispatchEvent(new Event('resize'));
}
dropZoneTopEnterHandler() {
const {
showBgDropZoneTop,
} = this.state;
if (!showBgDropZoneTop) this.setState({ showBgDropZoneTop: true });
}
dropZoneBottomEnterHandler() {
const {
showBgDropZoneBottom,
} = this.state;
if (!showBgDropZoneBottom) this.setState({ showBgDropZoneBottom: true });
}
dropZoneTopLeaveHandler() {
const {
showBgDropZoneTop,
} = this.state;
if (showBgDropZoneTop) this.setState({ showBgDropZoneTop: false });
}
dropZoneBottomLeaveHandler() {
const {
showBgDropZoneBottom,
} = this.state;
if (showBgDropZoneBottom) this.setState({ showBgDropZoneBottom: false });
}
dropZoneTopMouseUpHandler() {
const { dropOnTop } = this.state;
if (!dropOnTop) {
this.setState({
dropOnTop: true,
dropOnBottom: false,
resetPosition: true,
});
if (targetClassname.includes('Top')) {
webcamDraggableDispatch({ type: 'setplacementToTop' });
} else if (targetClassname.includes('Bottom')) {
webcamDraggableDispatch({ type: 'setplacementToBottom' });
} else if (singleWebcam) {
webcamDraggableDispatch(
{
type: 'setLastPosition',
value: {
x,
y,
},
},
);
webcamDraggableDispatch({ type: 'setplacementToFloating' });
}
webcamDraggableDispatch({ type: 'dragEnd' });
window.dispatchEvent(new Event('resize'));
setTimeout(() => this.setLastWebcamPosition(), 500);
}
dropZoneBottomMouseUpHandler() {
const { dropOnBottom } = this.state;
if (!dropOnBottom) {
this.setState({
dropOnTop: false,
dropOnBottom: true,
resetPosition: true,
});
}
window.dispatchEvent(new Event('resize'));
setTimeout(() => this.setLastWebcamPosition(), 500);
}
render() {
const {
webcamDraggableState,
singleWebcam,
swapLayout,
floatingOverlay,
hideOverlay,
disableVideo,
audioModalIsOpen,
refMediaContainer,
usersVideo,
} = this.props;
const userLength = usersVideo.length;
const { current: mediaContainer } = refMediaContainer;
let mediaContainerRect;
let mediaHeight;
if (mediaContainer) {
mediaContainerRect = mediaContainer.getBoundingClientRect();
const {
height,
} = mediaContainerRect;
mediaHeight = height;
const { dragging, isFullscreen } = webcamDraggableState;
let placement = Storage.getItem('webcamPlacement');
const lastPosition = Storage.getItem('webcamLastPosition') || { x: 0, y: 0 };
let position = lastPosition;
if (!placement) {
placement = webcamsDefaultPlacement;
}
if (dragging) {
position = webcamDraggableState.tempPosition;
} else if (!dragging && placement === 'floating' && singleWebcam) {
position = webcamDraggableState.lastPosition;
} else {
position = {
x: 0,
y: 0,
};
}
if (swapLayout || isFullscreen || BROWSER_ISMOBILE) {
position = {
x: 0,
y: 0,
};
}
const {
dragging,
showDropZones,
showBgDropZoneTop,
showBgDropZoneBottom,
dropOnTop,
dropOnBottom,
initialPosition,
lastPosition,
resetPosition,
isFullScreen,
isMinWidth,
} = this.state;
width: mediaWidth,
height: mediaHeight,
} = this.getMediaBounds();
const {
width: webcamsWidth,
height: webcamsHeight,
} = this.getWebcamsListBounds();
const isOverflowWidth = (lastPosition.x + webcamsWidth) > mediaWidth;
const isOverflowHeight = (lastPosition.y + webcamsHeight) > mediaHeight;
position = {
x: isOverflowWidth
&& !dragging && !swapLayout && singleWebcam && placement === 'floating' ? mediaWidth - webcamsWidth : position.x,
y: isOverflowHeight
&& !dragging && !swapLayout && singleWebcam && placement === 'floating' ? mediaHeight - (webcamsHeight + 1) : position.y,
};
const contentClassName = cx({
[styles.content]: true,
@ -490,62 +214,50 @@ export default class WebcamDraggableOverlay extends Component {
const overlayClassName = cx({
[styles.overlay]: true,
[styles.overlayRelative]: (dropOnTop || dropOnBottom),
[styles.overlayAbsoluteMult]: (!dropOnTop && !dropOnBottom) && userLength > 1,
[styles.hideOverlay]: hideOverlay,
[styles.floatingOverlay]: floatingOverlay && (!dropOnTop && !dropOnBottom),
[styles.overlayToTop]: dropOnTop,
[styles.overlayToBottom]: dropOnBottom,
[styles.floatingOverlay]: (singleWebcam && placement === 'floating') || dragging,
[styles.fit]: singleWebcam && (placement === 'floating' || dragging),
[styles.full]: (singleWebcam && (placement === 'top' || placement === 'bottom')
&& !dragging)
|| !singleWebcam,
[styles.overlayToTop]: (placement === 'floating' && !singleWebcam)
|| (placement === 'top' && !dragging),
[styles.overlayToBottom]: placement === 'bottom' && !dragging,
[styles.dragging]: dragging,
});
const dropZoneTopClassName = cx({
[styles.dropZoneTop]: true,
[styles.show]: showDropZones,
[styles.hide]: !showDropZones,
[styles.show]: dragging,
[styles.hide]: !dragging,
[styles.cursorGrabbing]: dragging,
});
const dropZoneBottomClassName = cx({
[styles.dropZoneBottom]: true,
[styles.show]: showDropZones,
[styles.hide]: !showDropZones,
[styles.show]: dragging,
[styles.hide]: !dragging,
[styles.cursorGrabbing]: dragging,
});
const dropZoneBgTopClassName = cx({
[styles.dropZoneBg]: true,
[styles.top]: true,
[styles.show]: showBgDropZoneTop,
[styles.hide]: !showBgDropZoneTop,
[styles.dropZoneBgTop]: true,
});
const dropZoneBgBottomClassName = cx({
[styles.dropZoneBg]: true,
[styles.bottom]: true,
[styles.show]: showBgDropZoneBottom,
[styles.hide]: !showBgDropZoneBottom,
[styles.dropZoneBgBottom]: true,
});
const cursor = () => {
if ((!swapLayout || !isFullScreen || !BROWSER_ISMOBILE || !isMinWidth) && !dragging) return 'grab';
if (dragging) return 'grabbing';
return 'default';
};
return (
<Fragment>
<div
className={dropZoneTopClassName}
onMouseEnter={this.dropZoneTopEnterHandler}
onMouseLeave={this.dropZoneTopLeaveHandler}
onMouseUp={this.dropZoneTopMouseUpHandler}
data-dropzone="dropZoneTop"
role="presentation"
style={{ height: userLength > 1 ? '50%' : '20%' }}
/>
<div
className={dropZoneBgTopClassName}
style={{ height: userLength > 1 ? '50%' : '20%' }}
/>
style={{ height: !singleWebcam ? '50%' : '20%' }}
>
<div
className={dropZoneBgTopClassName}
/>
</div>
<Draggable
handle="video"
@ -553,44 +265,41 @@ export default class WebcamDraggableOverlay extends Component {
onStart={this.handleWebcamDragStart}
onStop={this.handleWebcamDragStop}
onMouseDown={e => e.preventDefault()}
disabled={swapLayout || isFullScreen || BROWSER_ISMOBILE || isMinWidth}
position={resetPosition || swapLayout ? initialPosition : lastPosition}
disabled={swapLayout || isFullscreen || BROWSER_ISMOBILE}
position={position}
>
<div
className={!swapLayout ? overlayClassName : contentClassName}
style={{
maxHeight: mediaHeight,
marginLeft: singleWebcam
&& !(placement === 'bottom' || placement === 'top')
? 10
: 0,
marginRight: singleWebcam
&& !(placement === 'bottom' || placement === 'top')
? 10
: 0,
}}
>
{
!disableVideo && !audioModalIsOpen
? (
<VideoProviderContainer
cursor={cursor()}
swapLayout={swapLayout}
onMount={this.videoMounted}
/>
) : null}
{!disableVideo && !audioModalIsOpen ? (
<VideoProviderContainer
swapLayout={swapLayout}
/>
) : null}
</div>
</Draggable>
<div
className={dropZoneBottomClassName}
onMouseEnter={this.dropZoneBottomEnterHandler}
onMouseLeave={this.dropZoneBottomLeaveHandler}
onMouseUp={this.dropZoneBottomMouseUpHandler}
data-dropzone="dropZoneBottom"
role="presentation"
style={{ height: userLength > 1 ? '50%' : '20%' }}
/>
<div
className={dropZoneBgBottomClassName}
style={{ height: userLength > 1 ? '50%' : '20%' }}
/>
style={{ height: !singleWebcam ? '50%' : '20%' }}
>
<div
className={dropZoneBgBottomClassName}
/>
</div>
</Fragment>
);
}
}
WebcamDraggableOverlay.propTypes = propTypes;
WebcamDraggableOverlay.defaultProps = defaultProps;
export default withDraggableContext(WebcamDraggable);

View File

@ -0,0 +1,178 @@
import React, { createContext, useReducer, useEffect } from 'react';
import Storage from '../../../services/storage/session';
export const WebcamDraggableContext = createContext();
const initialState = {
placement: 'top',
mediaSize: {
width: 0,
height: 0,
},
initialRef: {
x: 0,
y: 0,
},
tempPosition: {
x: 0,
y: 0,
},
lastPosition: {
x: 0,
y: 0,
},
dragging: false,
videoRef: null,
videoListRef: null,
isFullscreen: false,
};
const reducer = (state, action) => {
switch (action.type) {
case 'setplacementToTop': {
return {
...state,
placement: 'top',
};
}
case 'setplacementToBottom': {
return {
...state,
placement: 'bottom',
};
}
case 'setplacementToFloating': {
return {
...state,
placement: 'floating',
};
}
case 'setMediaSize': {
return {
...state,
mediaSize: {
width: action.value.width,
height: action.value.height,
},
};
}
case 'setWebcamRef': {
return {
...state,
webcamRef: action.value,
};
}
case 'setInitialRef': {
return {
...state,
initialRef: {
x: action.value.x,
y: action.value.y,
},
};
}
case 'setTempPosition': {
return {
...state,
tempPosition: {
x: action.value.x,
y: action.value.y,
},
};
}
case 'setLastPosition': {
return {
...state,
lastPosition: {
x: action.value.x,
y: action.value.y,
},
};
}
case 'setVideoRef': {
return {
...state,
videoRef: action.value,
};
}
case 'setVideoListRef': {
return {
...state,
videoListRef: action.value,
};
}
case 'dragStart': {
return {
...state,
dragging: true,
};
}
case 'dragEnd': {
return {
...state,
dragging: false,
};
}
case 'onFullscreen': {
return {
...state,
isFullscreen: true,
};
}
case 'offFullscreen': {
return {
...state,
isFullscreen: false,
};
}
default: {
throw new Error('Unexpected action');
}
}
};
const ContextConsumer = Component => props => (
<WebcamDraggableContext.Consumer>
{contexts => <Component {...props} {...contexts} />}
</WebcamDraggableContext.Consumer>
);
const ContextProvider = (props) => {
const [webcamDraggableState, webcamDraggableDispatch] = useReducer(reducer, initialState);
const { placement, lastPosition } = webcamDraggableState;
const { children } = props;
useEffect(() => {
Storage.setItem('webcamPlacement', placement);
Storage.setItem('webcamLastPosition', lastPosition);
}, [
placement,
lastPosition,
]);
return (
<WebcamDraggableContext.Provider value={{
webcamDraggableState,
webcamDraggableDispatch,
...props,
}}
>
{children}
</WebcamDraggableContext.Provider>
);
};
const withProvider = Component => props => (
<ContextProvider {...props}>
<Component />
</ContextProvider>
);
const withConsumer = Component => ContextConsumer(Component);
const withDraggableContext = Component => withProvider(withConsumer(Component));
export {
withProvider,
withConsumer,
withDraggableContext,
};

View File

@ -161,9 +161,6 @@ class VideoProvider extends Component {
}
componentDidMount() {
const { onMount } = this.props;
onMount();
this.checkIceConnectivity();
document.addEventListener('joinVideo', this.shareWebcam); // TODO find a better way to do this
document.addEventListener('exitVideo', this.unshareWebcam);
@ -1017,14 +1014,10 @@ class VideoProvider extends Component {
const {
users,
enableVideoStats,
cursor,
swapLayout,
mediaHeight,
} = this.props;
return (
<VideoList
cursor={cursor}
swapLayout={swapLayout}
mediaHeight={mediaHeight}
users={users}
onMount={this.createVideoTag}

View File

@ -12,6 +12,7 @@ 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(),
@ -19,5 +20,4 @@ export default withTracker(props => ({
userName: VideoService.userName(),
enableVideoStats: getFromUserSettings('enableVideoStats', Meteor.settings.public.kurento.enableVideoStats),
voiceBridge: VideoService.voiceBridge(),
onMount: props.onMount,
}))(VideoProviderContainer);

View File

@ -5,6 +5,7 @@ import cx from 'classnames';
import _ from 'lodash';
import { styles } from './styles';
import VideoListItem from './video-list-item/component';
import { withConsumer } from '../../media/webcam-draggable-overlay/context';
const propTypes = {
users: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -77,6 +78,14 @@ class VideoList extends Component {
}
componentDidMount() {
const { webcamDraggableDispatch } = this.props;
webcamDraggableDispatch(
{
type: 'setVideoListRef',
value: this.grid,
},
);
this.handleCanvasResize();
window.addEventListener('resize', this.handleCanvasResize, false);
}
@ -93,6 +102,7 @@ class VideoList extends Component {
}
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;
@ -142,8 +152,6 @@ class VideoList extends Component {
getStats,
stopGettingStats,
enableVideoStats,
cursor,
swapLayout,
} = this.props;
const { focusedId } = this.state;
@ -167,9 +175,6 @@ class VideoList extends Component {
[styles.videoListItem]: true,
[styles.focused]: focusedId === user.id && users.length > 2,
})}
style={{
cursor,
}}
>
<VideoListItem
numOfUsers={users.length}
@ -182,7 +187,6 @@ class VideoList extends Component {
getStats={(videoRef, callback) => getStats(user.id, videoRef, callback)}
stopGettingStats={() => stopGettingStats(user.id)}
enableVideoStats={enableVideoStats}
swapLayout={swapLayout}
/>
</div>
);
@ -231,4 +235,4 @@ class VideoList extends Component {
VideoList.propTypes = propTypes;
export default injectIntl(VideoList);
export default injectIntl(withConsumer(VideoList));

View File

@ -39,6 +39,9 @@
.videoListItem {
display: flex;
overflow: hidden;
width: 100%;
height: 100%;
&.focused {
grid-column: 1 / span 2;
@ -50,7 +53,6 @@
position: relative;
display: flex;
min-width: 100%;
height: 100%;
border-radius: 5px;
&::after {
@ -83,6 +85,14 @@
border-radius: 5px;
}
.cursorGrab{
cursor: grab;
}
.cursorGrabbing{
cursor: grabbing;
}
@keyframes spin {
from {
transform: rotate(0deg);
@ -129,6 +139,7 @@
.media {
@extend %media-area;
background-color: var(--color-gray);
}
.info {

View File

@ -16,6 +16,7 @@ import logger from '/imports/startup/client/logger';
import VideoListItemStats from './video-list-item-stats/component';
import FullscreenButtonContainer from '../../fullscreen-button/container';
import { styles } from '../styles';
import { withConsumer } from '../../../media/webcam-draggable-overlay/context';
const intlMessages = defineMessages({
connectionStatsLabel: {
@ -40,7 +41,15 @@ class VideoListItem extends Component {
}
componentDidMount() {
const { onMount } = this.props;
const { onMount, webcamDraggableDispatch } = this.props;
webcamDraggableDispatch(
{
type: 'setVideoRef',
value: this.videoTag,
},
);
onMount(this.videoTag);
this.videoTag.addEventListener('loadeddata', () => this.setVideoIsReady());
@ -130,7 +139,9 @@ class VideoListItem extends Component {
render() {
const { showStats, stats, videoIsReady } = this.state;
const {
user, numOfUsers,
user,
numOfUsers,
webcamDraggableState,
} = this.props;
const availableActions = this.getAvailableActions();
const enableVideoMenu = Meteor.settings.public.kurento.enableVideoMenu || false;
@ -149,6 +160,8 @@ class VideoListItem extends Component {
muted
className={cx({
[styles.media]: true,
[styles.cursorGrab]: !webcamDraggableState.dragging,
[styles.cursorGrabbing]: webcamDraggableState.dragging,
})}
ref={(ref) => { this.videoTag = ref; }}
autoPlay
@ -198,7 +211,7 @@ class VideoListItem extends Component {
}
}
export default injectIntl(VideoListItem);
export default injectIntl(withConsumer(VideoListItem));
VideoListItem.defaultProps = {
numOfUsers: 0,