bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/component.jsx
Jonathan Schilling 7313916940 Add browser sniffing.
If a browser version can not handle the pointer events, it becomes detected and that browser uses the mouse event etc.
For these browser is no palm rejection provided. All other browser use the pointer events.
2021-03-19 12:35:29 +00:00

331 lines
9.4 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 TextPointerListener from './text-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') {
if (window.PointerEvent) {
return (
<TextPointerListener
userId={userId}
whiteboardId={whiteboardId}
drawSettings={drawSettings}
actions={actions}
slideWidth={slideWidth}
slideHeight={slideHeight}
/>
);
}
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,
}).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,
};