319 lines
9.1 KiB
JavaScript
Executable File
319 lines
9.1 KiB
JavaScript
Executable File
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 (
|
|
<ShapePointerListener
|
|
userId={userId}
|
|
actions={actions}
|
|
drawSettings={drawSettings}
|
|
whiteboardId={whiteboardId}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ShapeDrawListener
|
|
userId={userId}
|
|
actions={actions}
|
|
drawSettings={drawSettings}
|
|
whiteboardId={whiteboardId}
|
|
/>
|
|
);
|
|
} if (tool === 'pencil') {
|
|
if (window.PointerEvent) {
|
|
return (
|
|
<PencilPointerListener
|
|
userId={userId}
|
|
whiteboardId={whiteboardId}
|
|
drawSettings={drawSettings}
|
|
actions={actions}
|
|
physicalSlideWidth={physicalSlideWidth}
|
|
physicalSlideHeight={physicalSlideHeight}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<PencilDrawListener
|
|
userId={userId}
|
|
whiteboardId={whiteboardId}
|
|
drawSettings={drawSettings}
|
|
actions={actions}
|
|
physicalSlideWidth={physicalSlideWidth}
|
|
physicalSlideHeight={physicalSlideHeight}
|
|
/>
|
|
);
|
|
} if (tool === 'text') {
|
|
return (
|
|
<TextDrawListener
|
|
userId={userId}
|
|
whiteboardId={whiteboardId}
|
|
drawSettings={drawSettings}
|
|
actions={actions}
|
|
slideWidth={slideWidth}
|
|
slideHeight={slideHeight}
|
|
/>
|
|
);
|
|
}
|
|
return (
|
|
<span />
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<CursorListener
|
|
whiteboardId={whiteboardId}
|
|
actions={actions}
|
|
updateCursor={updateCursor}
|
|
>
|
|
{this.renderDrawListener(actions)}
|
|
</CursorListener>
|
|
);
|
|
}
|
|
}
|
|
|
|
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,
|
|
};
|