Create pointer event handler for pencil, shape and text.
This commit is contained in:
parent
52637c59e0
commit
a19451a2c4
@ -0,0 +1,377 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Logger from '/imports/startup/client/logger';
|
||||
|
||||
const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations;
|
||||
const DRAW_START = ANNOTATION_CONFIG.status.start;
|
||||
const DRAW_UPDATE = ANNOTATION_CONFIG.status.update;
|
||||
const DRAW_END = ANNOTATION_CONFIG.status.end;
|
||||
|
||||
// maximum value of z-index to prevent other things from overlapping
|
||||
const MAX_Z_INDEX = (2 ** 31) - 1;
|
||||
const POINTS_TO_BUFFER = 2;
|
||||
|
||||
export default class PencilPointerListener extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// to track the status of drawing
|
||||
this.isDrawing = false;
|
||||
this.points = [];
|
||||
|
||||
this.handlePointerDown = this.handlePointerDown.bind(this);
|
||||
this.handlePointerUp = this.handlePointerUp.bind(this);
|
||||
this.handlePointerMove = this.handlePointerMove.bind(this);
|
||||
this.handlePointerCancle = this.handlePointerCancle.bind(this);
|
||||
|
||||
this.resetState = this.resetState.bind(this);
|
||||
this.sendLastMessage = this.sendLastMessage.bind(this);
|
||||
this.sendCoordinates = this.sendCoordinates.bind(this);
|
||||
this.discardAnnotation = this.discardAnnotation.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// to send the last DRAW_END message in case if a user reloads the page while drawing
|
||||
window.addEventListener('beforeunload', this.sendLastMessage);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('beforeunload', this.sendLastMessage);
|
||||
|
||||
// sending the last message on componentDidUnmount
|
||||
this.sendLastMessage();
|
||||
}
|
||||
|
||||
commonDrawStartHandler(clientX, clientY) {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
getTransformedSvgPoint,
|
||||
generateNewShapeId,
|
||||
svgCoordinateToPercentages,
|
||||
} = actions;
|
||||
|
||||
// changing isDrawing to true
|
||||
this.isDrawing = true;
|
||||
|
||||
// sending the first message
|
||||
let transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// transforming svg coordinate to percentages relative to the slide width/height
|
||||
transformedSvgPoint = svgCoordinateToPercentages(transformedSvgPoint);
|
||||
|
||||
// sending the first message
|
||||
this.points = [transformedSvgPoint.x, transformedSvgPoint.y];
|
||||
this.handleDrawPencil(this.points, DRAW_START, generateNewShapeId());
|
||||
}
|
||||
|
||||
commonDrawMoveHandler(clientX, clientY) {
|
||||
if (this.isDrawing) {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
checkIfOutOfBounds,
|
||||
getTransformedSvgPoint,
|
||||
svgCoordinateToPercentages,
|
||||
} = actions;
|
||||
|
||||
// get the transformed svg coordinate
|
||||
let transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// check if it's out of bounds
|
||||
transformedSvgPoint = checkIfOutOfBounds(transformedSvgPoint);
|
||||
|
||||
// transforming svg coordinate to percentages relative to the slide width/height
|
||||
transformedSvgPoint = svgCoordinateToPercentages(transformedSvgPoint);
|
||||
|
||||
// saving the coordinate to the array
|
||||
this.points.push(transformedSvgPoint.x);
|
||||
this.points.push(transformedSvgPoint.y);
|
||||
|
||||
if (this.points.length > POINTS_TO_BUFFER) {
|
||||
this.sendCoordinates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendCoordinates() {
|
||||
if (this.isDrawing && this.points.length > 0) {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const { getCurrentShapeId } = actions;
|
||||
this.handleDrawPencil(this.points, DRAW_UPDATE, getCurrentShapeId());
|
||||
}
|
||||
}
|
||||
|
||||
handleDrawPencil(points, status, id, dimensions) {
|
||||
const {
|
||||
whiteboardId,
|
||||
userId,
|
||||
actions,
|
||||
drawSettings,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
normalizeThickness,
|
||||
sendAnnotation,
|
||||
} = actions;
|
||||
|
||||
const {
|
||||
thickness,
|
||||
color,
|
||||
} = drawSettings;
|
||||
|
||||
const annotation = {
|
||||
id,
|
||||
status,
|
||||
annotationType: 'pencil',
|
||||
annotationInfo: {
|
||||
color,
|
||||
thickness: normalizeThickness(thickness),
|
||||
points,
|
||||
id,
|
||||
whiteboardId,
|
||||
status,
|
||||
type: 'pencil',
|
||||
},
|
||||
wbId: whiteboardId,
|
||||
userId,
|
||||
position: 0,
|
||||
};
|
||||
|
||||
// dimensions are added to the 'DRAW_END', last message
|
||||
if (dimensions) {
|
||||
annotation.annotationInfo.dimensions = dimensions;
|
||||
}
|
||||
|
||||
sendAnnotation(annotation, whiteboardId);
|
||||
}
|
||||
|
||||
sendLastMessage() {
|
||||
if (this.isDrawing) {
|
||||
const {
|
||||
physicalSlideWidth,
|
||||
physicalSlideHeight,
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const { getCurrentShapeId } = actions;
|
||||
|
||||
this.handleDrawPencil(
|
||||
this.points,
|
||||
DRAW_END,
|
||||
getCurrentShapeId(),
|
||||
[Math.round(physicalSlideWidth), Math.round(physicalSlideHeight)],
|
||||
);
|
||||
this.resetState();
|
||||
}
|
||||
}
|
||||
|
||||
resetState() {
|
||||
// resetting the current info
|
||||
this.points = [];
|
||||
this.isDrawing = false;
|
||||
|
||||
window.removeEventListener('pointerup', this.handlePointerUp);
|
||||
window.removeEventListener('pointermove', this.handlePointerMove);
|
||||
window.removeEventListener('pointercancle', this.handlePointerCancle);
|
||||
}
|
||||
|
||||
discardAnnotation() {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
getCurrentShapeId,
|
||||
clearPreview,
|
||||
} = actions;
|
||||
|
||||
this.resetState();
|
||||
clearPreview(getCurrentShapeId());
|
||||
}
|
||||
|
||||
handlePointerDown(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'mouse': {
|
||||
const isLeftClick = event.button === 0;
|
||||
const isRightClick = event.button === 2;
|
||||
|
||||
if (!this.isDrawing) {
|
||||
if (isLeftClick) {
|
||||
window.addEventListener('pointerup', this.handlePointerUp);
|
||||
window.addEventListener('pointermove', this.handlePointerMove);
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
}
|
||||
|
||||
// if you switch to a different window using Alt+Tab while mouse is down and release it
|
||||
// it wont catch mouseUp and will keep tracking the movements. Thus we need this check.
|
||||
} else if (isRightClick) {
|
||||
this.discardAnnotation();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'pen': {
|
||||
this.touchPenDownHandler(event);
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
this.touchPenDownHandler(event);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'pencil_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
touchPenDownHandler(event) {
|
||||
event.preventDefault();
|
||||
if (!this.isDrawing) {
|
||||
window.addEventListener('pointerup', this.handlePointerUp);
|
||||
window.addEventListener('pointermove', this.handlePointerMove);
|
||||
window.addEventListener('pointercancle', this.handlePointerCancle, true);
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
|
||||
// if you switch to a different window using Alt+Tab while mouse is down and release it
|
||||
// it wont catch mouseUp and will keep tracking the movements. Thus we need this check.
|
||||
} else {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerUp(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'mouse': {
|
||||
this.sendLastMessage();
|
||||
break;
|
||||
}
|
||||
case 'pen': {
|
||||
this.sendLastMessage();
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
this.sendLastMessage();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'pencil_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerMove(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'mouse': {
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
break;
|
||||
}
|
||||
case 'pen': {
|
||||
event.preventDefault();
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
event.preventDefault();
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'pencil_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerCancle(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'pen': {
|
||||
this.sendLastMessage();
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
this.sendLastMessage();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'pencil_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const { contextMenuHandler } = actions;
|
||||
|
||||
const baseName = Meteor.settings.public.app.cdn + Meteor.settings.public.app.basename;
|
||||
const pencilDrawStyle = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
touchAction: 'none',
|
||||
zIndex: MAX_Z_INDEX,
|
||||
cursor: `url('${baseName}/resources/images/whiteboard-cursor/pencil.png') 2 22, default`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onPointerDown={this.handlePointerDown}
|
||||
role="presentation"
|
||||
style={pencilDrawStyle}
|
||||
onContextMenu={contextMenuHandler}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PencilPointerListener.propTypes = {
|
||||
// Defines a whiteboard id, which needed to publish an annotation message
|
||||
whiteboardId: PropTypes.string.isRequired,
|
||||
// Defines a user id, which needed to publish an annotation message
|
||||
userId: PropTypes.string.isRequired,
|
||||
// Defines the physical widith of the slide
|
||||
physicalSlideWidth: PropTypes.number.isRequired,
|
||||
// Defines the physical height of the slide
|
||||
physicalSlideHeight: PropTypes.number.isRequired,
|
||||
// Defines an object containing all available actions
|
||||
actions: PropTypes.shape({
|
||||
// Defines a function which transforms a coordinate from the window to svg coordinate system
|
||||
getTransformedSvgPoint: PropTypes.func.isRequired,
|
||||
// Defines a function which checks if the shape is out of bounds and returns
|
||||
// appropriate coordinates
|
||||
checkIfOutOfBounds: PropTypes.func.isRequired,
|
||||
// Defines a function which receives an svg point and transforms it into
|
||||
// percentage-based coordinates
|
||||
svgCoordinateToPercentages: PropTypes.func.isRequired,
|
||||
// Defines a function which returns a current shape id
|
||||
getCurrentShapeId: PropTypes.func.isRequired,
|
||||
// Defines a function which generates a new shape id
|
||||
generateNewShapeId: PropTypes.func.isRequired,
|
||||
// Defines a function which receives a thickness num and normalizes it before we send a message
|
||||
normalizeThickness: PropTypes.func.isRequired,
|
||||
// Defines a function which we use to publish a message to the server
|
||||
sendAnnotation: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
drawSettings: PropTypes.shape({
|
||||
// Annotation color
|
||||
color: PropTypes.number.isRequired,
|
||||
// Annotation thickness (not normalized)
|
||||
thickness: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
// Defines if palm rejection is active or not
|
||||
// palmRejection: PropTypes.bool.isRequired,
|
||||
};
|
@ -0,0 +1,449 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Logger from '/imports/startup/client/logger';
|
||||
|
||||
const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations;
|
||||
const DRAW_START = ANNOTATION_CONFIG.status.start;
|
||||
const DRAW_UPDATE = ANNOTATION_CONFIG.status.update;
|
||||
const DRAW_END = ANNOTATION_CONFIG.status.end;
|
||||
|
||||
// maximum value of z-index to prevent other things from overlapping
|
||||
const MAX_Z_INDEX = (2 ** 31) - 1;
|
||||
|
||||
export default class ShapePointerListener extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// there is no valid defaults for the coordinates, and we wouldn't want them anyway
|
||||
this.initialCoordinate = {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
};
|
||||
this.lastSentCoordinate = {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
};
|
||||
this.currentCoordinate = {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
};
|
||||
|
||||
// to track the status of drawing
|
||||
this.isDrawing = false;
|
||||
|
||||
this.currentStatus = undefined;
|
||||
|
||||
this.handlePointerDown = this.handlePointerDown.bind(this);
|
||||
this.handlePointerUp = this.handlePointerUp.bind(this);
|
||||
this.handlePointerMove = this.handlePointerMove.bind(this);
|
||||
this.handlePointerCancle = this.handlePointerCancle.bind(this);
|
||||
|
||||
this.resetState = this.resetState.bind(this);
|
||||
this.sendLastMessage = this.sendLastMessage.bind(this);
|
||||
this.sendCoordinates = this.sendCoordinates.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// to send the last message if the user refreshes the page while drawing
|
||||
window.addEventListener('beforeunload', this.sendLastMessage);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('beforeunload', this.sendLastMessage);
|
||||
|
||||
// sending the last message on componentDidUnmount
|
||||
this.sendLastMessage();
|
||||
}
|
||||
|
||||
commonDrawStartHandler(clientX, clientY) {
|
||||
this.isDrawing = true;
|
||||
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
getTransformedSvgPoint,
|
||||
generateNewShapeId,
|
||||
svgCoordinateToPercentages,
|
||||
} = actions;
|
||||
|
||||
// sending the first message
|
||||
let transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// transforming svg coordinate to percentages relative to the slide width/height
|
||||
transformedSvgPoint = svgCoordinateToPercentages(transformedSvgPoint);
|
||||
|
||||
// generating new shape id
|
||||
generateNewShapeId();
|
||||
|
||||
// setting the initial current status
|
||||
this.currentStatus = DRAW_START;
|
||||
|
||||
// saving the coordinates for future references
|
||||
this.initialCoordinate = {
|
||||
x: transformedSvgPoint.x,
|
||||
y: transformedSvgPoint.y,
|
||||
};
|
||||
|
||||
this.currentCoordinate = {
|
||||
x: transformedSvgPoint.x,
|
||||
y: transformedSvgPoint.y,
|
||||
};
|
||||
|
||||
// All the messages will be send on timer by sendCoordinates func
|
||||
this.sendCoordinates();
|
||||
}
|
||||
|
||||
commonDrawMoveHandler(clientX, clientY) {
|
||||
if (!this.isDrawing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
checkIfOutOfBounds,
|
||||
getTransformedSvgPoint,
|
||||
svgCoordinateToPercentages,
|
||||
} = actions;
|
||||
|
||||
// get the transformed svg coordinate
|
||||
let transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// check if it's out of bounds
|
||||
transformedSvgPoint = checkIfOutOfBounds(transformedSvgPoint);
|
||||
|
||||
// transforming svg coordinate to percentages relative to the slide width/height
|
||||
transformedSvgPoint = svgCoordinateToPercentages(transformedSvgPoint);
|
||||
|
||||
// saving the last sent coordinate
|
||||
this.currentCoordinate = transformedSvgPoint;
|
||||
this.sendCoordinates();
|
||||
}
|
||||
|
||||
sendCoordinates() {
|
||||
const {
|
||||
actions,
|
||||
drawSettings,
|
||||
} = this.props;
|
||||
|
||||
// check the current drawing status
|
||||
if (!this.isDrawing) {
|
||||
return;
|
||||
}
|
||||
// check if a current coordinate is not the same as an initial one
|
||||
// it prevents us from drawing dots on random clicks
|
||||
if (this.currentCoordinate.x === this.initialCoordinate.x
|
||||
&& this.currentCoordinate.y === this.initialCoordinate.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if previously sent coordinate is not equal to a current one
|
||||
if (this.currentCoordinate.x === this.lastSentCoordinate.x
|
||||
&& this.currentCoordinate.y === this.lastSentCoordinate.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { getCurrentShapeId } = actions;
|
||||
this.handleDrawCommonAnnotation(
|
||||
this.initialCoordinate,
|
||||
this.currentCoordinate,
|
||||
this.currentStatus,
|
||||
getCurrentShapeId(),
|
||||
drawSettings.tool,
|
||||
);
|
||||
this.lastSentCoordinate = this.currentCoordinate;
|
||||
|
||||
if (this.currentStatus === DRAW_START) {
|
||||
this.currentStatus = DRAW_UPDATE;
|
||||
}
|
||||
}
|
||||
|
||||
sendLastMessage() {
|
||||
const {
|
||||
actions,
|
||||
drawSettings,
|
||||
} = this.props;
|
||||
|
||||
if (this.isDrawing) {
|
||||
// make sure we are drawing and we have some coordinates sent for this shape before
|
||||
// to prevent sending DRAW_END on a random mouse click
|
||||
if (this.lastSentCoordinate.x && this.lastSentCoordinate.y) {
|
||||
const { getCurrentShapeId } = actions;
|
||||
this.handleDrawCommonAnnotation(
|
||||
this.initialCoordinate,
|
||||
this.currentCoordinate,
|
||||
DRAW_END,
|
||||
getCurrentShapeId(),
|
||||
drawSettings.tool,
|
||||
);
|
||||
}
|
||||
this.resetState();
|
||||
}
|
||||
}
|
||||
|
||||
resetState() {
|
||||
// resetting the current drawing state
|
||||
this.isDrawing = false;
|
||||
this.currentStatus = undefined;
|
||||
this.initialCoordinate = {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
};
|
||||
this.lastSentCoordinate = {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
};
|
||||
this.currentCoordinate = {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
};
|
||||
|
||||
window.removeEventListener('pointerup', this.handlePointerUp);
|
||||
window.removeEventListener('pointermove', this.handlePointerMove);
|
||||
window.removeEventListener('pointercancle', this.handlePointerCancle);
|
||||
}
|
||||
|
||||
// since Rectangle / Triangle / Ellipse / Line have the same coordinate structure
|
||||
// we use the same function for all of them
|
||||
handleDrawCommonAnnotation(startPoint, endPoint, status, id, shapeType) {
|
||||
const {
|
||||
whiteboardId,
|
||||
userId,
|
||||
actions,
|
||||
drawSettings,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
normalizeThickness,
|
||||
sendAnnotation,
|
||||
} = actions;
|
||||
|
||||
const {
|
||||
color,
|
||||
thickness,
|
||||
} = drawSettings;
|
||||
|
||||
const annotation = {
|
||||
id,
|
||||
status,
|
||||
annotationType: shapeType,
|
||||
annotationInfo: {
|
||||
color,
|
||||
thickness: normalizeThickness(thickness),
|
||||
points: [
|
||||
startPoint.x,
|
||||
startPoint.y,
|
||||
endPoint.x,
|
||||
endPoint.y,
|
||||
],
|
||||
id,
|
||||
whiteboardId,
|
||||
status,
|
||||
type: shapeType,
|
||||
},
|
||||
wbId: whiteboardId,
|
||||
userId,
|
||||
position: 0,
|
||||
};
|
||||
|
||||
sendAnnotation(annotation, whiteboardId);
|
||||
}
|
||||
|
||||
discardAnnotation() {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
getCurrentShapeId,
|
||||
clearPreview,
|
||||
} = actions;
|
||||
|
||||
this.resetState();
|
||||
clearPreview(getCurrentShapeId());
|
||||
}
|
||||
|
||||
handlePointerDown(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'mouse': {
|
||||
const isLeftClick = event.button === 0;
|
||||
const isRightClick = event.button === 2;
|
||||
|
||||
if (!this.isDrawing) {
|
||||
if (isLeftClick) {
|
||||
window.addEventListener('pointerup', this.handlePointerUp);
|
||||
window.addEventListener('pointermove', this.handlePointerMove);
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
}
|
||||
|
||||
// if you switch to a different window using Alt+Tab while mouse is down and release it
|
||||
// it wont catch mouseUp and will keep tracking the movements. Thus we need this check.
|
||||
} else if (isRightClick) {
|
||||
this.discardAnnotation();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'pen': {
|
||||
this.touchPenDownHandler(event);
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
this.touchPenDownHandler(event);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'shape_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
touchPenDownHandler(event) {
|
||||
event.preventDefault();
|
||||
if (!this.isDrawing) {
|
||||
window.addEventListener('pointerup', this.handlePointerUp);
|
||||
window.addEventListener('pointermove', this.handlePointerMove);
|
||||
window.addEventListener('pointercancle', this.handlePointerCancle, true);
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
|
||||
// if you switch to a different window using Alt+Tab while mouse is down and release it
|
||||
// it wont catch mouseUp and will keep tracking the movements. Thus we need this check.
|
||||
} else {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerUp(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'mouse': {
|
||||
this.sendLastMessage();
|
||||
break;
|
||||
}
|
||||
case 'pen': {
|
||||
this.sendLastMessage();
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
this.sendLastMessage();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'shape_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerMove(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'mouse': {
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
break;
|
||||
}
|
||||
case 'pen': {
|
||||
event.preventDefault();
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
event.preventDefault();
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'shape_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerCancle(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'pen': {
|
||||
this.sendLastMessage();
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
this.sendLastMessage();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'shape_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
actions,
|
||||
drawSettings,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
contextMenuHandler,
|
||||
} = actions;
|
||||
|
||||
const {
|
||||
tool,
|
||||
} = drawSettings;
|
||||
|
||||
const baseName = Meteor.settings.public.app.cdn + Meteor.settings.public.app.basename;
|
||||
const shapeDrawStyle = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
touchAction: 'none',
|
||||
zIndex: MAX_Z_INDEX,
|
||||
cursor: `url('${baseName}/resources/images/whiteboard-cursor/${tool !== 'rectangle' ? tool : 'square'}.png'), default`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onPointerDown={this.handlePointerDown}
|
||||
role="presentation"
|
||||
style={shapeDrawStyle}
|
||||
onContextMenu={contextMenuHandler}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShapePointerListener.propTypes = {
|
||||
// Defines a whiteboard id, which needed to publish an annotation message
|
||||
whiteboardId: PropTypes.string.isRequired,
|
||||
// Defines a user id, which needed to publish an annotation message
|
||||
userId: PropTypes.string.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
// Defines a function which transforms a coordinate from the window to svg coordinate system
|
||||
getTransformedSvgPoint: PropTypes.func.isRequired,
|
||||
// Defines a function which checks if the shape is out of bounds and returns
|
||||
// appropriate coordinates
|
||||
checkIfOutOfBounds: PropTypes.func.isRequired,
|
||||
// Defines a function which receives an svg point and transforms it into
|
||||
// percentage-based coordinates
|
||||
svgCoordinateToPercentages: PropTypes.func.isRequired,
|
||||
// Defines a function which returns a current shape id
|
||||
getCurrentShapeId: PropTypes.func.isRequired,
|
||||
// Defines a function which generates a new shape id
|
||||
generateNewShapeId: PropTypes.func.isRequired,
|
||||
// Defines a function which receives a thickness num and normalizes it before we send a message
|
||||
normalizeThickness: PropTypes.func.isRequired,
|
||||
// Defines a function which we use to publish a message to the server
|
||||
sendAnnotation: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
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,
|
||||
}).isRequired,
|
||||
};
|
@ -0,0 +1,619 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Logger from '/imports/startup/client/logger';
|
||||
|
||||
const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations;
|
||||
const DRAW_START = ANNOTATION_CONFIG.status.start;
|
||||
const DRAW_UPDATE = ANNOTATION_CONFIG.status.update;
|
||||
const DRAW_END = ANNOTATION_CONFIG.status.end;
|
||||
|
||||
// maximum value of z-index to prevent other things from overlapping
|
||||
const MAX_Z_INDEX = (2 ** 31) - 1;
|
||||
|
||||
export default class TextPointerListener extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
// text shape state properties
|
||||
textBoxX: undefined,
|
||||
textBoxY: undefined,
|
||||
textBoxWidth: 0,
|
||||
textBoxHeight: 0,
|
||||
|
||||
// to track the status of drawing
|
||||
isDrawing: false,
|
||||
|
||||
// to track the status of writing a text shape after the textarea has been drawn
|
||||
isWritingText: false,
|
||||
};
|
||||
|
||||
// initial mousedown coordinates
|
||||
this.initialX = undefined;
|
||||
this.initialY = undefined;
|
||||
|
||||
// current X, Y, width and height in percentages of the current slide
|
||||
// saving them so that we won't have to recalculate these values on each update
|
||||
this.currentX = undefined;
|
||||
this.currentY = undefined;
|
||||
this.currentWidth = undefined;
|
||||
this.currentHeight = undefined;
|
||||
|
||||
// current text shape status, it may change between DRAW_START, DRAW_UPDATE, DRAW_END
|
||||
this.currentStatus = '';
|
||||
|
||||
// Mobile Firefox has a bug where e.preventDefault on touchstart doesn't prevent
|
||||
// onmousedown from triggering right after. Thus we have to track it manually.
|
||||
// In case if it's fixed one day - there is another issue, React one.
|
||||
// https://github.com/facebook/react/issues/9809
|
||||
// Check it to figure if you can add onTouchStart in render(), or should use raw DOM api
|
||||
this.hasBeenTouchedRecently = false;
|
||||
|
||||
this.handlePointerDown = this.handlePointerDown.bind(this);
|
||||
this.handlePointerUp = this.handlePointerUp.bind(this);
|
||||
this.handlePointerMove = this.handlePointerMove.bind(this);
|
||||
this.handlePointerCancle = this.handlePointerCancle.bind(this);
|
||||
|
||||
this.resetState = this.resetState.bind(this);
|
||||
this.sendLastMessage = this.sendLastMessage.bind(this);
|
||||
this.checkTextAreaFocus = this.checkTextAreaFocus.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('beforeunload', this.sendLastMessage);
|
||||
}
|
||||
|
||||
|
||||
// If the activeId suddenly became empty - this means the shape was deleted
|
||||
// While the user was drawing it. So we are resetting the state.
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { drawSettings } = this.props;
|
||||
const nextDrawsettings = nextProps.drawSettings;
|
||||
|
||||
if (drawSettings.textShapeActiveId !== '' && nextDrawsettings.textShapeActiveId === '') {
|
||||
this.resetState();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
drawSettings,
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const prevDrawsettings = prevProps.drawSettings;
|
||||
const prevTextShapeValue = prevProps.drawSettings.textShapeValue;
|
||||
|
||||
// Updating the component in cases when:
|
||||
// Either color / font-size or text value has changed
|
||||
// and excluding the case when the textShapeActiveId changed to ''
|
||||
const fontSizeChanged = drawSettings.textFontSize !== prevDrawsettings.textFontSize;
|
||||
const colorChanged = drawSettings.color !== prevDrawsettings.color;
|
||||
const textShapeValueChanged = drawSettings.textShapeValue !== prevTextShapeValue;
|
||||
const textShapeIdNotEmpty = drawSettings.textShapeActiveId !== '';
|
||||
|
||||
if ((fontSizeChanged || colorChanged || textShapeValueChanged) && textShapeIdNotEmpty) {
|
||||
const { getCurrentShapeId } = actions;
|
||||
this.currentStatus = DRAW_UPDATE;
|
||||
|
||||
this.handleDrawText(
|
||||
{ x: this.currentX, y: this.currentY },
|
||||
this.currentWidth,
|
||||
this.currentHeight,
|
||||
this.currentStatus,
|
||||
getCurrentShapeId(),
|
||||
drawSettings.textShapeValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('beforeunload', this.sendLastMessage);
|
||||
// sending the last message on componentDidUnmount
|
||||
// for example in case when you switched a tool while drawing text shape
|
||||
this.sendLastMessage();
|
||||
}
|
||||
|
||||
// checks if the input textarea is focused or not, and if not - moves focus there
|
||||
// returns false if text area wasn't focused
|
||||
// returns true if textarea was focused
|
||||
// currently used only with iOS devices
|
||||
checkTextAreaFocus() {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const { getCurrentShapeId } = actions;
|
||||
|
||||
const textarea = document.getElementById(getCurrentShapeId());
|
||||
|
||||
if (textarea) {
|
||||
if (document.activeElement === textarea) {
|
||||
return true;
|
||||
}
|
||||
textarea.focus();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
commonDrawStartHandler(clientX, clientY) {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
getTransformedSvgPoint,
|
||||
} = actions;
|
||||
|
||||
const transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// saving initial X and Y coordinates for further displaying of the textarea
|
||||
this.initialX = transformedSvgPoint.x;
|
||||
this.initialY = transformedSvgPoint.y;
|
||||
|
||||
this.setState({
|
||||
textBoxX: transformedSvgPoint.x,
|
||||
textBoxY: transformedSvgPoint.y,
|
||||
isDrawing: true,
|
||||
});
|
||||
}
|
||||
|
||||
sendLastMessage() {
|
||||
const {
|
||||
drawSettings,
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isWritingText,
|
||||
} = this.state;
|
||||
|
||||
if (!isWritingText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
getCurrentShapeId,
|
||||
} = actions;
|
||||
|
||||
this.currentStatus = DRAW_END;
|
||||
|
||||
this.handleDrawText(
|
||||
{ x: this.currentX, y: this.currentY },
|
||||
this.currentWidth,
|
||||
this.currentHeight,
|
||||
this.currentStatus,
|
||||
getCurrentShapeId(),
|
||||
drawSettings.textShapeValue,
|
||||
);
|
||||
|
||||
this.resetState();
|
||||
}
|
||||
|
||||
resetState() {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
// resetting the text shape session values
|
||||
actions.resetTextShapeSession();
|
||||
// resetting the current state
|
||||
this.currentX = undefined;
|
||||
this.currentY = undefined;
|
||||
this.currentWidth = undefined;
|
||||
this.currentHeight = undefined;
|
||||
this.currentStatus = '';
|
||||
this.initialX = undefined;
|
||||
this.initialY = undefined;
|
||||
|
||||
this.setState({
|
||||
isDrawing: false,
|
||||
isWritingText: false,
|
||||
textBoxX: undefined,
|
||||
textBoxY: undefined,
|
||||
textBoxWidth: 0,
|
||||
textBoxHeight: 0,
|
||||
});
|
||||
|
||||
window.removeEventListener('pointerup', this.handlePointerUp);
|
||||
window.removeEventListener('pointermove', this.handlePointerMove);
|
||||
window.removeEventListener('pointercancle', this.handlePointerCancle);
|
||||
}
|
||||
|
||||
commonDrawMoveHandler(clientX, clientY) {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
const {
|
||||
checkIfOutOfBounds,
|
||||
getTransformedSvgPoint,
|
||||
} = actions;
|
||||
|
||||
// get the transformed svg coordinate
|
||||
let transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// check if it's out of bounds
|
||||
transformedSvgPoint = checkIfOutOfBounds(transformedSvgPoint);
|
||||
|
||||
// check if we need to use initial or new coordinates for the top left corner of the rectangle
|
||||
const x = transformedSvgPoint.x < this.initialX ? transformedSvgPoint.x : this.initialX;
|
||||
const y = transformedSvgPoint.y < this.initialY ? transformedSvgPoint.y : this.initialY;
|
||||
|
||||
// calculating the width and height of the displayed text box
|
||||
const width = transformedSvgPoint.x > this.initialX
|
||||
? transformedSvgPoint.x - this.initialX : this.initialX - transformedSvgPoint.x;
|
||||
const height = transformedSvgPoint.y > this.initialY
|
||||
? transformedSvgPoint.y - this.initialY : this.initialY - transformedSvgPoint.y;
|
||||
|
||||
this.setState({
|
||||
textBoxWidth: width,
|
||||
textBoxHeight: height,
|
||||
textBoxX: x,
|
||||
textBoxY: y,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
commonDrawEndHandler() {
|
||||
const {
|
||||
actions,
|
||||
slideWidth,
|
||||
slideHeight,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isDrawing,
|
||||
isWritingText,
|
||||
textBoxX,
|
||||
textBoxY,
|
||||
textBoxWidth,
|
||||
textBoxHeight,
|
||||
} = this.state;
|
||||
|
||||
// TODO - find if the size is large enough to display the text area
|
||||
if (!isDrawing && isWritingText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
generateNewShapeId,
|
||||
getCurrentShapeId,
|
||||
setTextShapeActiveId,
|
||||
} = actions;
|
||||
|
||||
// coordinates and width/height of the textarea in percentages of the current slide
|
||||
// saving them in the class since they will be used during all updates
|
||||
this.currentX = (textBoxX / slideWidth) * 100;
|
||||
this.currentY = (textBoxY / slideHeight) * 100;
|
||||
this.currentWidth = (textBoxWidth / slideWidth) * 100;
|
||||
this.currentHeight = (textBoxHeight / slideHeight) * 100;
|
||||
this.currentStatus = DRAW_START;
|
||||
this.handleDrawText(
|
||||
{ x: this.currentX, y: this.currentY },
|
||||
this.currentWidth,
|
||||
this.currentHeight,
|
||||
this.currentStatus,
|
||||
generateNewShapeId(),
|
||||
'',
|
||||
);
|
||||
|
||||
setTextShapeActiveId(getCurrentShapeId());
|
||||
|
||||
this.setState({
|
||||
isWritingText: true,
|
||||
isDrawing: false,
|
||||
textBoxX: undefined,
|
||||
textBoxY: undefined,
|
||||
textBoxWidth: 0,
|
||||
textBoxHeight: 0,
|
||||
});
|
||||
}
|
||||
|
||||
handleDrawText(startPoint, width, height, status, id, text) {
|
||||
const {
|
||||
whiteboardId,
|
||||
userId,
|
||||
actions,
|
||||
drawSettings,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
normalizeFont,
|
||||
sendAnnotation,
|
||||
} = actions;
|
||||
|
||||
const {
|
||||
color,
|
||||
textFontSize,
|
||||
} = drawSettings;
|
||||
|
||||
const annotation = {
|
||||
id,
|
||||
status,
|
||||
annotationType: 'text',
|
||||
annotationInfo: {
|
||||
x: startPoint.x, // left corner
|
||||
y: startPoint.y, // left corner
|
||||
fontColor: color,
|
||||
calcedFontSize: normalizeFont(textFontSize), // fontsize
|
||||
textBoxWidth: width, // width
|
||||
text,
|
||||
textBoxHeight: height, // height
|
||||
id,
|
||||
whiteboardId,
|
||||
status,
|
||||
fontSize: textFontSize,
|
||||
dataPoints: `${startPoint.x},${startPoint.y}`,
|
||||
type: 'text',
|
||||
},
|
||||
wbId: whiteboardId,
|
||||
userId,
|
||||
position: 0,
|
||||
};
|
||||
|
||||
sendAnnotation(annotation, whiteboardId);
|
||||
}
|
||||
|
||||
discardAnnotation() {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
getCurrentShapeId,
|
||||
clearPreview,
|
||||
} = actions;
|
||||
|
||||
this.resetState();
|
||||
clearPreview(getCurrentShapeId());
|
||||
}
|
||||
|
||||
handlePointerDown(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'mouse': {
|
||||
const {
|
||||
isDrawing,
|
||||
isWritingText,
|
||||
} = this.state;
|
||||
|
||||
const isLeftClick = event.button === 0;
|
||||
const isRightClick = event.button === 2;
|
||||
|
||||
if (this.hasBeenTouchedRecently) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if our current drawing state is not drawing the box and not writing the text
|
||||
if (!isDrawing && !isWritingText) {
|
||||
if (isLeftClick) {
|
||||
window.addEventListener('pointerup', this.handlePointerUp);
|
||||
window.addEventListener('pointermove', this.handlePointerMove);
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
}
|
||||
|
||||
// second case is when a user finished writing the text and publishes the final result
|
||||
} else if (isRightClick) {
|
||||
this.discardAnnotation();
|
||||
} else {
|
||||
// publishing the final shape and resetting the state
|
||||
this.sendLastMessage();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'pen': {
|
||||
this.touchPenDownHandler(event);
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
this.touchPenDownHandler(event);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'text_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
touchPenDownHandler(event) {
|
||||
const {
|
||||
isDrawing,
|
||||
isWritingText,
|
||||
} = this.state;
|
||||
|
||||
this.hasBeenTouchedRecently = true;
|
||||
setTimeout(() => { this.hasBeenTouchedRecently = false; }, 500);
|
||||
// to prevent default behavior (scrolling) on devices (in Safari), when you draw a text box
|
||||
event.preventDefault();
|
||||
|
||||
if (!isDrawing && !isWritingText) {
|
||||
window.addEventListener('pointerup', this.handlePointerUp);
|
||||
window.addEventListener('pointermove', this.handlePointerMove);
|
||||
window.addEventListener('pointercancle', this.handlePointerCancle, true);
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
|
||||
// this case is specifically for iOS, since text shape is working in 3 steps there:
|
||||
// touch to draw a box -> tap to focus -> tap to publish
|
||||
} else if (!isDrawing && isWritingText && !this.checkTextAreaFocus()) {
|
||||
|
||||
// if you switch to a different window using Alt+Tab while mouse is down and release it
|
||||
// it wont catch mouseUp and will keep tracking the movements. Thus we need this check.
|
||||
} else {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerUp(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'mouse': {
|
||||
window.removeEventListener('pointerup', this.handlePointerUp);
|
||||
window.removeEventListener('pointermove', this.handlePointerMove);
|
||||
this.commonDrawEndHandler();
|
||||
break;
|
||||
}
|
||||
case 'pen': {
|
||||
this.touchPenEndHandler();
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
this.touchPenEndHandler();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'text_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
touchPenEndHandler() {
|
||||
window.removeEventListener('pointerup', this.handlePointerUp);
|
||||
window.removeEventListener('pointermove', this.handlePointerMove);
|
||||
window.removeEventListener('pointercancle', this.handlePointerCancle);
|
||||
this.commonDrawEndHandler();
|
||||
}
|
||||
|
||||
handlePointerMove(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'mouse': {
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
break;
|
||||
}
|
||||
case 'pen': {
|
||||
event.preventDefault();
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
event.preventDefault();
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'text_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerCancle(event) {
|
||||
switch (event.pointerType) {
|
||||
case 'pen': {
|
||||
this.touchPenEndHandler();
|
||||
break;
|
||||
}
|
||||
case 'touch': {
|
||||
this.touchPenEndHandler();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Logger.error({ logCode: 'text_pointer_listener_unkownPointerTypeError' }, 'PointerType is unknown or could not be detected!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
textBoxX,
|
||||
textBoxY,
|
||||
textBoxWidth,
|
||||
textBoxHeight,
|
||||
isWritingText,
|
||||
isDrawing,
|
||||
} = this.state;
|
||||
|
||||
const { contextMenuHandler } = actions;
|
||||
|
||||
const baseName = Meteor.settings.public.app.cdn + Meteor.settings.public.app.basename;
|
||||
const textDrawStyle = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
touchAction: 'none',
|
||||
zIndex: MAX_Z_INDEX,
|
||||
cursor: `url('${baseName}/resources/images/whiteboard-cursor/text.png'), default`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
role="presentation"
|
||||
style={textDrawStyle}
|
||||
onPointerDown={this.handlePointerDown}
|
||||
onContextMenu={contextMenuHandler}
|
||||
>
|
||||
{isDrawing
|
||||
? (
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
{!isWritingText
|
||||
? (
|
||||
<rect
|
||||
x={textBoxX}
|
||||
y={textBoxY}
|
||||
fill="none"
|
||||
stroke="black"
|
||||
strokeWidth="1"
|
||||
width={textBoxWidth}
|
||||
height={textBoxHeight}
|
||||
/>
|
||||
)
|
||||
: null }
|
||||
</svg>
|
||||
)
|
||||
: null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TextPointerListener.propTypes = {
|
||||
// Defines a whiteboard id, which needed to publish an annotation message
|
||||
whiteboardId: PropTypes.string.isRequired,
|
||||
// Defines a user id, which needed to publish an annotation message
|
||||
userId: PropTypes.string.isRequired,
|
||||
// Width of the slide (svg coordinate system)
|
||||
slideWidth: PropTypes.number.isRequired,
|
||||
// Height of the slide (svg coordinate system)
|
||||
slideHeight: PropTypes.number.isRequired,
|
||||
// Current draw settings passed from the toolbar and text shape (text value)
|
||||
drawSettings: PropTypes.shape({
|
||||
// Annotation color
|
||||
color: PropTypes.number.isRequired,
|
||||
// Font size for the text shape
|
||||
textFontSize: PropTypes.number.isRequired,
|
||||
// Current active text shape value
|
||||
textShapeValue: PropTypes.string.isRequired,
|
||||
// Text active text shape id
|
||||
textShapeActiveId: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
actions: PropTypes.shape({
|
||||
// Defines a function which transforms a coordinate from the window to svg coordinate system
|
||||
getTransformedSvgPoint: PropTypes.func.isRequired,
|
||||
// Defines a function which checks if the shape is out of bounds and returns
|
||||
// appropriate coordinates
|
||||
checkIfOutOfBounds: PropTypes.func.isRequired,
|
||||
// Defines a function which returns a current shape id
|
||||
getCurrentShapeId: PropTypes.func.isRequired,
|
||||
// Defines a function which generates a new shape id
|
||||
generateNewShapeId: PropTypes.func.isRequired,
|
||||
// Defines a function which receives a thickness num and normalizes it before we send a message
|
||||
normalizeFont: PropTypes.func.isRequired,
|
||||
// Defines a function which we use to publish a message to the server
|
||||
sendAnnotation: PropTypes.func.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,
|
||||
}).isRequired,
|
||||
};
|
Loading…
Reference in New Issue
Block a user