import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ShapeDrawListener from './shape-draw-listener/component';
import TextDrawListener from './text-draw-listener/component';
import PencilDrawListener from './pencil-draw-listener/component';
import ShapePointerListener from './shape-pointer-listener/component';
import PencilPointerListener from './pencil-pointer-listener/component';
import CursorListener from './cursor-listener/component';
export default class WhiteboardOverlay extends Component {
// a function to transform a screen point to svg point
// accepts and returns a point of type SvgPoint and an svg object
// if unable to get the screen CTM, returns an out
// of bounds (-1, -1) svg point
static coordinateTransform(screenPoint, someSvgObject) {
const CTM = someSvgObject.getScreenCTM();
if (CTM !== null) {
return screenPoint.matrixTransform(CTM.inverse());
}
const outOfBounds = someSvgObject.createSVGPoint();
outOfBounds.x = -1;
outOfBounds.y = -1;
return outOfBounds;
}
// Removes selection from all selected elements
static unSelect() {
if (document.selection) {
document.selection.empty();
} else {
window.getSelection().removeAllRanges();
}
}
constructor(props) {
super(props);
// current shape id
this.currentShapeId = undefined;
// count, used to generate a new shape id
this.count = 0;
this.getCurrentShapeId = this.getCurrentShapeId.bind(this);
this.generateNewShapeId = this.generateNewShapeId.bind(this);
this.getTransformedSvgPoint = this.getTransformedSvgPoint.bind(this);
this.checkIfOutOfBounds = this.checkIfOutOfBounds.bind(this);
this.svgCoordinateToPercentages = this.svgCoordinateToPercentages.bind(this);
this.normalizeThickness = this.normalizeThickness.bind(this);
this.normalizeFont = this.normalizeFont.bind(this);
}
getCurrentShapeId() {
return this.currentShapeId;
}
// this function receives an event from the mouse event attached to the window
// it transforms the coordinate to the main svg coordinate system
getTransformedSvgPoint(clientX, clientY) {
const {
getSvgRef,
} = this.props;
const svgObject = getSvgRef();
const svgPoint = svgObject.createSVGPoint();
svgPoint.x = clientX;
svgPoint.y = clientY;
const transformedSvgPoint = WhiteboardOverlay.coordinateTransform(svgPoint, svgObject);
return transformedSvgPoint;
}
// receives an svg coordinate and changes the values to percentages of the slide's width/height
svgCoordinateToPercentages(svgPoint) {
const {
slideWidth,
slideHeight,
} = this.props;
const point = {
x: (svgPoint.x / slideWidth) * 100,
y: (svgPoint.y / slideHeight) * 100,
};
return point;
}
normalizeThickness(thickness) {
const {
physicalSlideWidth,
} = this.props;
return (thickness * 100) / physicalSlideWidth;
}
normalizeFont(fontSize) {
const {
physicalSlideHeight,
} = this.props;
return (fontSize * 100) / physicalSlideHeight;
}
generateNewShapeId() {
const {
userId,
} = this.props;
this.count = this.count + 1;
this.currentShapeId = `${userId}-${this.count}-${new Date().getTime()}`;
return this.currentShapeId;
}
// this function receives a transformed svg coordinate and checks if it's not out of bounds
checkIfOutOfBounds(point) {
const {
viewBoxX,
viewBoxY,
viewBoxWidth,
viewBoxHeight,
} = this.props;
let { x, y } = point;
// set this flag to true if either x or y are out of bounds
let shouldUnselect = false;
if (x < viewBoxX) {
x = viewBoxX;
shouldUnselect = true;
} else if (x > viewBoxX + viewBoxWidth) {
x = viewBoxX + viewBoxWidth;
shouldUnselect = true;
}
if (y < viewBoxY) {
y = viewBoxY;
shouldUnselect = true;
} else if (y > viewBoxY + viewBoxHeight) {
y = viewBoxY + viewBoxHeight;
shouldUnselect = true;
}
// if either x or y are out of bounds - remove selection from potentially selected elements
if (shouldUnselect) {
WhiteboardOverlay.unSelect();
}
return {
x,
y,
};
}
renderDrawListener(actions) {
const {
drawSettings,
userId,
whiteboardId,
physicalSlideWidth,
physicalSlideHeight,
slideWidth,
slideHeight,
} = this.props;
const { tool } = drawSettings;
if (tool === 'triangle' || tool === 'rectangle' || tool === 'ellipse' || tool === 'line') {
if (window.PointerEvent) {
return (
);
}
return (
);
} if (tool === 'pencil') {
if (window.PointerEvent) {
return (
);
}
return (
);
} if (tool === 'text') {
return (
);
}
return (
);
}
render() {
const {
whiteboardId,
sendAnnotation,
resetTextShapeSession,
setTextShapeActiveId,
contextMenuHandler,
clearPreview,
updateCursor,
} = this.props;
const actions = {
getTransformedSvgPoint: this.getTransformedSvgPoint,
checkIfOutOfBounds: this.checkIfOutOfBounds,
svgCoordinateToPercentages: this.svgCoordinateToPercentages,
getCurrentShapeId: this.getCurrentShapeId,
generateNewShapeId: this.generateNewShapeId,
normalizeThickness: this.normalizeThickness,
normalizeFont: this.normalizeFont,
sendAnnotation,
resetTextShapeSession,
setTextShapeActiveId,
contextMenuHandler,
clearPreview,
};
return (
{this.renderDrawListener(actions)}
);
}
}
WhiteboardOverlay.propTypes = {
// Defines a function which returns a reference to the main svg object
getSvgRef: PropTypes.func.isRequired,
// Defines the width of the slide (svg coordinate system)
slideWidth: PropTypes.number.isRequired,
// Defines the height of the slide (svg coordinate system)
slideHeight: PropTypes.number.isRequired,
// Defines the physical width of the slide, needed to calculate thickness, and pencil smoothing
physicalSlideWidth: PropTypes.number.isRequired,
// Defines the physical width of the slide, to calculate pencil smoothing
physicalSlideHeight: PropTypes.number.isRequired,
// Defines a current user's user id
userId: PropTypes.string.isRequired,
// Defines an X coordinate of the viewBox
viewBoxX: PropTypes.number.isRequired,
// Defines a Y coordinate of the viewBox
viewBoxY: PropTypes.number.isRequired,
// Defines a width of the viewBox
viewBoxWidth: PropTypes.number.isRequired,
// Defines a height of the viewBox
viewBoxHeight: PropTypes.number.isRequired,
// Defines a handler to publish an annotation to the server
sendAnnotation: PropTypes.func.isRequired,
// Defines a handler to clear a shape preview
clearPreview: PropTypes.func.isRequired,
// Defines a current whiteboard id
whiteboardId: PropTypes.string.isRequired,
// Defines an object containing current settings for drawing
drawSettings: PropTypes.shape({
// Annotation color
color: PropTypes.number.isRequired,
// Annotation thickness (not normalized)
thickness: PropTypes.number.isRequired,
// The name of the tool currently selected
tool: PropTypes.string,
// Font size for the text shape
textFontSize: PropTypes.number.isRequired,
// Text shape value
textShapeValue: PropTypes.string.isRequired,
// Fill shape
//fill: PropTypes.bool.isRequired,
}).isRequired,
// Defines a function which resets the current state of the text shape drawing
resetTextShapeSession: PropTypes.func.isRequired,
// Defines a function that sets a session value for the current active text shape
setTextShapeActiveId: PropTypes.func.isRequired,
// Defines a handler to publish cursor position to the server
updateCursor: PropTypes.func.isRequired,
};