import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { TransitionGroup, CSSTransition } from 'react-transition-group'; import WhiteboardOverlayContainer from '/imports/ui/components/whiteboard/whiteboard-overlay/container'; import WhiteboardToolbarContainer from '/imports/ui/components/whiteboard/whiteboard-toolbar/container'; import { HUNDRED_PERCENT, MAX_PERCENT } from '/imports/utils/slideCalcUtils'; import PresentationToolbarContainer from './presentation-toolbar/container'; import CursorWrapperContainer from './cursor/cursor-wrapper-container/container'; import AnnotationGroupContainer from '../whiteboard/annotation-group/container'; import PresentationOverlayContainer from './presentation-overlay/container'; import Slide from './slide/component'; import { styles } from './styles.scss'; import MediaService, { shouldEnableSwapLayout } from '../media/service'; import PresentationCloseButton from './presentation-close-button/component'; class PresentationArea extends Component { constructor() { super(); this.state = { presentationWidth: 0, presentationHeight: 0, showSlide: false, zoom: 100, touchZoom: false, delta: { x: 0, y: 0, }, fitToWidth: false, }; this.getSvgRef = this.getSvgRef.bind(this); this.zoomChanger = this.zoomChanger.bind(this); this.touchUpdate = this.touchUpdate.bind(this); this.pointUpdate = this.pointUpdate.bind(this); this.fitToWidthHandler = this.fitToWidthHandler.bind(this); } componentDidMount() { // adding an event listener to scale the whiteboard on 'resize' events sent by chat/userlist etc window.addEventListener('resize', () => { setTimeout(this.handleResize.bind(this), 0); }); this.getInitialPresentationSizes(); } componentWillUnmount() { window.removeEventListener('resize', () => { setTimeout(this.handleResize.bind(this), 0); }); } // returns a ref to the svg element, which is required by a WhiteboardOverlay // to transform screen coordinates to svg coordinate system getSvgRef() { return this.svggroup; } getToolbarHeight() { const { refPresentationToolbar } = this; let height = 0; if (refPresentationToolbar) { const { clientHeight } = refPresentationToolbar; height = clientHeight; } return height; } getPresentationSizesAvailable() { const { userIsPresenter, multiUser } = this.props; const { refPresentationArea, refWhiteboardArea } = this; const presentationSizes = {}; if (refPresentationArea && refWhiteboardArea) { // By default presentation sizes are equal to the sizes of the refPresentationArea // direct parent of the svg wrapper let { clientWidth, clientHeight } = refPresentationArea; // if a user is a presenter - this means there is a whiteboard toolbar on the right // and we have to get the width/height of the refWhiteboardArea // (inner hidden div with absolute position) if (userIsPresenter || multiUser) { ({ clientWidth, clientHeight } = refWhiteboardArea); } presentationSizes.presentationHeight = clientHeight - this.getToolbarHeight(); presentationSizes.presentationWidth = clientWidth; } return presentationSizes; } getInitialPresentationSizes() { // determining the presentationWidth and presentationHeight (available space for the svg) // on the initial load const presentationSizes = this.getPresentationSizesAvailable(); if (Object.keys(presentationSizes).length > 0) { // setting the state of the available space for the svg // and set the showSlide to true to start rendering the slide this.setState({ presentationHeight: presentationSizes.presentationHeight, presentationWidth: presentationSizes.presentationWidth, showSlide: true, }); } } handleResize() { const presentationSizes = this.getPresentationSizesAvailable(); if (Object.keys(presentationSizes).length > 0) { // updating the size of the space available for the slide this.setState(presentationSizes); } } calculateSize() { const { presentationHeight, presentationWidth } = this.state; const { currentSlide } = this.props; const originalWidth = currentSlide.calculatedData.width; const originalHeight = currentSlide.calculatedData.height; let adjustedWidth; let adjustedHeight; // Slide has a portrait orientation if (originalWidth <= originalHeight) { adjustedWidth = (presentationHeight * originalWidth) / originalHeight; if (presentationWidth < adjustedWidth) { adjustedHeight = (presentationHeight * presentationWidth) / adjustedWidth; adjustedWidth = presentationWidth; } else { adjustedHeight = presentationHeight; } // Slide has a landscape orientation } else { adjustedHeight = (presentationWidth * originalHeight) / originalWidth; if (presentationHeight < adjustedHeight) { adjustedWidth = (presentationWidth * presentationHeight) / adjustedHeight; adjustedHeight = presentationHeight; } else { adjustedWidth = presentationWidth; } } return { width: adjustedWidth, height: adjustedHeight + this.getToolbarHeight(), }; } zoomChanger(incomingZoom) { const { zoom } = this.state; let newZoom = incomingZoom; const isDifferent = newZoom !== zoom; if (newZoom <= HUNDRED_PERCENT) { newZoom = HUNDRED_PERCENT; } else if (incomingZoom >= MAX_PERCENT) { newZoom = MAX_PERCENT; } if (isDifferent) this.setState({ zoom: newZoom }); } pointUpdate(pointX, pointY) { this.setState({ delta: { x: pointX, y: pointY, }, }); } touchUpdate(bool) { this.setState({ touchZoom: bool, }); } fitToWidthHandler() { const { fitToWidth } = this.state; this.setState({ fitToWidth: !fitToWidth, }); } isPresentationAccessible() { const { currentSlide } = this.props; // sometimes tomcat publishes the slide url, but the actual file is not accessible (why?) return currentSlide && currentSlide.calculatedData; } renderPresentationClose() { const { isFullscreen } = this.props; if (!shouldEnableSwapLayout() || isFullscreen) { return null; } return ; } renderOverlays(slideObj, adjustedSizes) { const { userIsPresenter, multiUser, podId, currentSlide, } = this.props; const { delta, zoom, touchZoom, fitToWidth, } = this.state; if (!userIsPresenter && !multiUser) { return null; } // retrieving the pre-calculated data from the slide object const { x, y, width, height, viewBoxWidth, viewBoxHeight, } = slideObj.calculatedData; return ( ); } // renders the whole presentation area renderPresentationArea() { const { fitToWidth } = this.state; const { podId, currentSlide } = this.props; if (!this.isPresentationAccessible()) return null; // to control the size of the svg wrapper manually // and adjust cursor's thickness, so that svg didn't scale it automatically const adjustedSizes = this.calculateSize(); // a reference to the slide object const slideObj = currentSlide; const presentationCloseButton = this.renderPresentationClose(); // retrieving the pre-calculated data from the slide object const { x, y, width, height, viewBoxWidth, viewBoxHeight, imageUri, } = slideObj.calculatedData; const svgDimensions = fitToWidth ? { position: 'absolute', width: 'inherit', } : { position: 'absolute', width: adjustedSizes.width, height: adjustedSizes.height, }; return (
{presentationCloseButton} { if (ref != null) { this.svggroup = ref; } }} viewBox={`${x} ${y} ${viewBoxWidth} ${viewBoxHeight}`} version="1.1" xmlns="http://www.w3.org/2000/svg" className={styles.svgStyles} > {this.renderOverlays(slideObj, adjustedSizes)}
{ this.refPresentationToolbar = ref; }} > {this.renderPresentationToolbar()}
); } renderPresentationToolbar() { const { currentSlide, podId, isFullscreen: propIsFullscreen, } = this.props; const { zoom } = this.state; const fullRef = () => this.refPresentationContainer.requestFullscreen(); if (!currentSlide) { return null; } return ( ); } renderWhiteboardToolbar() { const { currentSlide } = this.props; if (!this.isPresentationAccessible()) return null; const adjustedSizes = this.calculateSize(); return ( ); } render() { const { userIsPresenter, multiUser } = this.props; const { showSlide } = this.state; return (
{ this.refPresentationContainer = ref; }} className={styles.presentationContainer} >
{ this.refPresentationArea = ref; }} className={styles.presentationArea} >
{ this.refWhiteboardArea = ref; }} className={styles.whiteboardSizeAvailable} /> {showSlide ? this.renderPresentationArea() : null} {userIsPresenter || multiUser ? this.renderWhiteboardToolbar() : null}
); } } export default PresentationArea; PresentationArea.propTypes = { podId: PropTypes.string.isRequired, // Defines a boolean value to detect whether a current user is a presenter userIsPresenter: PropTypes.bool.isRequired, currentSlide: PropTypes.shape({ presentationId: PropTypes.string.isRequired, current: PropTypes.bool.isRequired, heightRatio: PropTypes.number.isRequired, widthRatio: PropTypes.number.isRequired, xOffset: PropTypes.number.isRequired, yOffset: PropTypes.number.isRequired, num: PropTypes.number.isRequired, id: PropTypes.string.isRequired, calculatedData: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, height: PropTypes.number.isRequired, width: PropTypes.number.isRequired, viewBoxWidth: PropTypes.number.isRequired, viewBoxHeight: PropTypes.number.isRequired, imageUri: PropTypes.string.isRequired, }), }), // current multi-user status multiUser: PropTypes.bool.isRequired, }; PresentationArea.defaultProps = { currentSlide: undefined, };