bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx

558 lines
15 KiB
React
Raw Normal View History

2019-03-01 05:39:57 +08:00
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import VideoProviderContainer from '/imports/ui/components/video-provider/container';
2019-03-05 01:29:40 +08:00
import VideoService from '/imports/ui/components/video-provider/service';
2019-03-01 05:39:57 +08:00
import _ from 'lodash';
import Draggable from 'react-draggable';
import { styles } from '../styles';
const propTypes = {
floatingOverlay: PropTypes.bool,
hideOverlay: PropTypes.bool,
};
const defaultProps = {
floatingOverlay: false,
hideOverlay: true,
};
export default class WebcamDraggableOverlay extends Component {
static getWebcamBySelector() {
2019-03-05 01:29:40 +08:00
return document.querySelector('div[class^="videoList"]');
}
static getOverlayBySelector() {
return document.querySelector('div[class*="overlay"]');
2019-03-01 05:39:57 +08:00
}
static getIsOverlayChanged() {
const overlayToTop = document.querySelector('div[class*="overlayToTop"]');
const overlayToBottom = document.querySelector('div[class*="overlayToBottom"]');
return !!(overlayToTop || overlayToBottom);
}
constructor(props) {
super(props);
this.state = {
dragging: false,
showDropZones: false,
showBgDropZoneTop: false,
showBgDropZoneBottom: false,
dropOnTop: false,
dropOnBottom: true,
initialPosition: { x: 0, y: 0 },
initialRectPosition: { x: 0, y: 0 },
lastPosition: { x: 0, y: 0 },
lastPositionInitSet: false,
resetPosition: false,
isFullScreen: false,
2019-03-05 01:29:40 +08:00
isVideoLoaded: false,
numUsers: 0,
2019-03-01 05:39:57 +08:00
};
this.updateWebcamPositionByResize = this.updateWebcamPositionByResize.bind(this);
this.eventResizeListener = _.throttle(
this.updateWebcamPositionByResize,
500,
{
leading: true,
trailing: true,
},
);
2019-03-05 01:29:40 +08:00
this.videoMounted = this.videoMounted.bind(this);
this.videoUpdated = this.videoUpdated.bind(this);
2019-03-01 05:39:57 +08:00
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.setLastPositionInitSet = this.setLastPositionInitSet.bind(this);
this.setPositionAfterDropInEdge = this.setPositionAfterDropInEdge.bind(this);
this.getLastWebcamPosition = this.getLastWebcamPosition.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);
const fullscreenChangedEvents = [
'fullscreenchange',
'webkitfullscreenchange',
'mozfullscreenchange',
'MSFullscreenChange',
];
fullscreenChangedEvents.forEach((event) => {
document.addEventListener(event, this.handleFullscreenChange);
});
// Ensures that the event will be called before the resize
document.addEventListener('webcamFullscreenButtonChange', this.fullscreenButtonChange);
}
componentDidUpdate() {
const {
initialRectPosition,
lastPositionInitSet,
dropOnTop,
dropOnBottom,
} = this.state;
const { x: initX, y: initY } = initialRectPosition;
2019-03-05 01:29:40 +08:00
const { isVideoLoaded } = this.state;
if (isVideoLoaded) {
if (initX === 0 && initY === 0) {
this.setInitialReferencePoint();
}
2019-03-01 05:39:57 +08:00
2019-03-05 01:29:40 +08:00
if (initX !== 0
&& (dropOnTop || dropOnBottom)
&& !lastPositionInitSet) {
this.setPositionAfterDropInEdge();
this.setLastPositionInitSet(true);
}
2019-03-01 05:39:57 +08:00
}
}
componentWillUnmount() {
const fullscreenChangedEvents = [
'fullscreenchange',
'webkitfullscreenchange',
'mozfullscreenchange',
'MSFullscreenChange',
];
fullscreenChangedEvents.forEach((event) => {
document.removeEventListener(event, this.fullScreenToggleCallback);
});
document.removeEventListener('webcamFullscreenButtonChange', this.fullscreenButtonChange);
}
setIsFullScreen(isFullScreen) {
this.setState({ isFullScreen });
}
setResetPosition(resetPosition) {
this.setState({ resetPosition });
}
setLastPosition(x, y) {
2019-03-05 01:29:40 +08:00
this.setState({ lastPosition: { x, y } });
2019-03-01 05:39:57 +08:00
}
setLastPositionInitSet(lastPositionInitSet) {
this.setState({ lastPositionInitSet });
}
setInitialReferencePoint() {
2019-03-05 01:29:40 +08:00
const {
refMediaContainer,
} = this.props;
2019-03-01 05:39:57 +08:00
const webcamBySelector = WebcamDraggableOverlay.getWebcamBySelector();
if (webcamBySelector && refMediaContainer) {
const webcamBySelectorRect = webcamBySelector.getBoundingClientRect();
const {
width: webcamWidth,
height: webcamHeight,
} = webcamBySelectorRect;
const refMediaContainerRect = refMediaContainer.getBoundingClientRect();
const {
width: mediaWidth,
height: mediaHeight,
} = refMediaContainerRect;
const x = mediaWidth - (webcamWidth + 10); // 10 is margin
const y = mediaHeight - (webcamHeight + 10); // 10 is margin
this.setState({ initialRectPosition: { x, y } });
2019-03-05 01:29:40 +08:00
return { x, y };
2019-03-01 05:39:57 +08:00
}
2019-03-05 01:29:40 +08:00
return null;
2019-03-01 05:39:57 +08:00
}
setPositionAfterDropInEdge() {
const changed = setInterval(() => {
const lastWebcamPosition = this.getLastWebcamPosition();
const isOverlayChanged = WebcamDraggableOverlay.getIsOverlayChanged();
// Wait for the element render at relative position
if (lastWebcamPosition && isOverlayChanged) {
const { x, y } = lastWebcamPosition;
this.setLastPosition(x, y);
clearInterval(changed);
}
}, 500);
}
getLastWebcamPosition() {
const { refMediaContainer } = this.props;
const { initialRectPosition } = this.state;
const { x: initX, y: initY } = initialRectPosition;
const webcamBySelector = WebcamDraggableOverlay.getWebcamBySelector();
if (webcamBySelector && refMediaContainer) {
const webcamBySelectorRect = webcamBySelector.getBoundingClientRect();
const {
left: webcamLeft,
top: webcamTop,
} = webcamBySelectorRect;
const refMediaContainerRect = refMediaContainer.getBoundingClientRect();
const {
left: mediaLeft,
top: mediaTop,
} = refMediaContainerRect;
const webcamXByMedia = (webcamLeft) - mediaLeft;
const webcamYByMedia = (webcamTop) - mediaTop;
const newPosition = {
x: (webcamXByMedia - initX),
y: (webcamYByMedia - initY),
};
return newPosition;
}
return null;
}
2019-03-05 01:29:40 +08:00
videoMounted() {
const elementRendered = setInterval(() => {
const webcamBySelector = WebcamDraggableOverlay.getWebcamBySelector();
if (webcamBySelector) {
this.setState({ isVideoLoaded: true });
clearInterval(elementRendered);
}
}, 500);
}
videoUpdated() {
const { numUsers: numUsersState } = this.state;
const numUsers = VideoService.getAllUsersVideo().length;
if (numUsers !== numUsersState) {
this.setState({ numUsers });
this.setPositionAfterDropInEdge();
window.dispatchEvent(new Event('resize'));
}
}
2019-03-01 05:39:57 +08:00
fullscreenButtonChange() {
this.setIsFullScreen(true);
}
updateWebcamPositionByResize() {
2019-03-05 01:29:40 +08:00
const {
floatingOverlay,
refMediaContainer,
} = this.props;
2019-03-01 05:39:57 +08:00
const {
isFullScreen,
lastPosition,
2019-03-05 01:29:40 +08:00
dropOnTop,
dropOnBottom,
2019-03-01 05:39:57 +08:00
} = this.state;
2019-03-05 01:29:40 +08:00
const webcamBySelector = WebcamDraggableOverlay.getWebcamBySelector();
const initialRectPosition = this.setInitialReferencePoint();
2019-03-01 05:39:57 +08:00
const { x: lastX, y: lastY } = lastPosition;
if (isFullScreen) return;
2019-03-05 01:29:40 +08:00
if (webcamBySelector && refMediaContainer && initialRectPosition) {
const { x: initRectX, y: initRectY } = initialRectPosition;
2019-03-01 05:39:57 +08:00
const webcamBySelectorRect = webcamBySelector.getBoundingClientRect();
const {
left: webcamLeft,
top: webcamTop,
} = webcamBySelectorRect;
const refMediaContainerRect = refMediaContainer.getBoundingClientRect();
const {
left: mediaLeft,
top: mediaTop,
} = refMediaContainerRect;
2019-03-05 01:29:40 +08:00
const webcamXByMedia = webcamLeft - mediaLeft;
const webcamYByMedia = webcamTop - mediaTop;
2019-03-01 05:39:57 +08:00
const newX = (initRectX - 10) <= 0 ? 0 : -(initRectX - 10);
const newY = (initRectY - 10) <= 0 ? 0 : -(initRectY - 10);
2019-03-05 01:29:40 +08:00
let x = webcamXByMedia <= 0 ? newX : lastX;
let y = webcamYByMedia <= 0 ? newY : lastY;
x = !floatingOverlay ? 0 : x;
y = !floatingOverlay && dropOnBottom ? 0 : y;
y = !floatingOverlay && dropOnTop ? -(initRectY - 10) : y;
2019-03-01 05:39:57 +08:00
this.setLastPosition(
2019-03-05 01:29:40 +08:00
x,
y,
2019-03-01 05:39:57 +08:00
);
}
}
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() {
2019-03-05 01:29:40 +08:00
const { floatingOverlay } = this.props;
2019-03-01 05:39:57 +08:00
const {
dragging,
showDropZones,
dropOnTop,
dropOnBottom,
resetPosition,
} = this.state;
2019-03-05 01:29:40 +08:00
if (!floatingOverlay) WebcamDraggableOverlay.getOverlayBySelector().style.bottom = 0;
2019-03-01 05:39:57 +08:00
2019-03-05 01:29:40 +08:00
if (!dragging) this.setState({ dragging: true });
2019-03-01 05:39:57 +08:00
if (!showDropZones) this.setState({ showDropZones: true });
if (dropOnTop) this.setState({ dropOnTop: false });
if (dropOnBottom) this.setState({ dropOnBottom: false });
if (resetPosition) this.setState({ resetPosition: false });
window.dispatchEvent(new Event('resize'));
}
handleWebcamDragStop(e, position) {
const {
dragging,
showDropZones,
} = this.state;
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,
2019-03-05 01:29:40 +08:00
dropOnBottom: false,
2019-03-01 05:39:57 +08:00
resetPosition: true,
});
}
this.setPositionAfterDropInEdge();
}
dropZoneBottomMouseUpHandler() {
const { dropOnBottom } = this.state;
if (!dropOnBottom) {
this.setState({
2019-03-05 01:29:40 +08:00
dropOnTop: false,
2019-03-01 05:39:57 +08:00
dropOnBottom: true,
resetPosition: true,
});
}
this.setPositionAfterDropInEdge();
}
render() {
const {
swapLayout,
floatingOverlay,
hideOverlay,
disableVideo,
} = this.props;
const {
dragging,
showDropZones,
showBgDropZoneTop,
showBgDropZoneBottom,
dropOnTop,
dropOnBottom,
initialPosition,
lastPosition,
resetPosition,
isFullScreen,
} = this.state;
const contentClassName = cx({
[styles.content]: true,
});
const overlayClassName = cx({
[styles.overlay]: true,
2019-03-05 01:29:40 +08:00
[styles.overlayRelative]: (dropOnTop || dropOnBottom),
[styles.overlayAbsolute]: (!dropOnTop && !dropOnBottom),
2019-03-01 05:39:57 +08:00
[styles.hideOverlay]: hideOverlay,
[styles.floatingOverlay]: floatingOverlay && (!dropOnTop && !dropOnBottom),
[styles.overlayToTop]: dropOnTop,
[styles.overlayToBottom]: dropOnBottom,
[styles.dragging]: dragging,
});
const dropZoneTopClassName = cx({
[styles.dropZoneTop]: true,
[styles.show]: showDropZones,
[styles.hide]: !showDropZones,
});
const dropZoneBottomClassName = cx({
[styles.dropZoneBottom]: true,
[styles.show]: showDropZones,
[styles.hide]: !showDropZones,
});
const dropZoneBgTopClassName = cx({
[styles.dropZoneBg]: true,
[styles.top]: true,
[styles.show]: showBgDropZoneTop,
[styles.hide]: !showBgDropZoneTop,
});
const dropZoneBgBottomClassName = cx({
[styles.dropZoneBg]: true,
[styles.bottom]: true,
[styles.show]: showBgDropZoneBottom,
[styles.hide]: !showBgDropZoneBottom,
});
return (
<Fragment>
<div
className={dropZoneTopClassName}
onMouseEnter={this.dropZoneTopEnterHandler}
onMouseLeave={this.dropZoneTopLeaveHandler}
onMouseUp={this.dropZoneTopMouseUpHandler}
role="presentation"
style={{ height: '100px' }}
ref={(ref) => { this.refDropZone = ref; }}
/>
<div
className={dropZoneBgTopClassName}
style={{ height: '100px' }}
/>
<Draggable
handle="video"
2019-03-05 01:29:40 +08:00
bounds="#container"
2019-03-01 05:39:57 +08:00
onStart={this.handleWebcamDragStart}
onStop={this.handleWebcamDragStop}
disabled={swapLayout || isFullScreen}
position={resetPosition || swapLayout ? initialPosition : lastPosition}
>
<div
className={!swapLayout ? overlayClassName : contentClassName}
ref={(ref) => { this.refWebcamOverlay = ref; }}
>
2019-03-05 01:29:40 +08:00
{
!disableVideo
? (
<VideoProviderContainer
onMount={this.videoMounted}
onUpdate={this.videoUpdated}
/>
) : null}
2019-03-01 05:39:57 +08:00
</div>
</Draggable>
<div
className={dropZoneBottomClassName}
onMouseEnter={this.dropZoneBottomEnterHandler}
onMouseLeave={this.dropZoneBottomLeaveHandler}
onMouseUp={this.dropZoneBottomMouseUpHandler}
role="presentation"
style={{ height: '100px' }}
ref={(ref) => { this.refDropZone = ref; }}
/>
<div
className={dropZoneBgBottomClassName}
style={{ height: '100px' }}
/>
</Fragment>
);
}
}
WebcamDraggableOverlay.propTypes = propTypes;
WebcamDraggableOverlay.defaultProps = defaultProps;