whiteboard performance improvements

This commit is contained in:
Chad Pilkey 2020-04-06 20:34:08 +00:00
parent 65c3a8b185
commit 40b18b0662
21 changed files with 273 additions and 257 deletions

View File

@ -105,49 +105,65 @@ class WhiteboardModel extends SystemConfiguration {
//not empty and head id equals annotation id
//println("!usersAnnotations.isEmpty: " + (!usersAnnotations.isEmpty) + ", usersAnnotations.head.id == annotation.id: " + (usersAnnotations.head.id == annotation.id));
if (!usersAnnotations.isEmpty && usersAnnotations.head.id == annotation.id) {
var dimensions: List[Int] = List[Int]()
annotation.annotationInfo.get("dimensions").foreach(d => {
d match {
case d2: List[_] => dimensions = convertListNumbersToInt(d2)
}
})
//println("dimensions.size(): " + dimensions.size());
if (dimensions.length == 2) {
val oldAnnotation = usersAnnotations.head
var oldPoints: List[Float] = List[Float]()
oldAnnotation.annotationInfo.get("points").foreach(a => {
a match {
case a2: List[_] => oldPoints = a2.asInstanceOf[List[Float]]
}
})
var newPoints: List[Float] = List[Float]()
annotation.annotationInfo.get("points").foreach(a => {
a match {
case a2: List[_] => newPoints = convertListNumbersToFloat(a2)
}
}) //newPoints = a.asInstanceOf[ArrayList[Float]])
//println("oldPoints.size(): " + oldPoints.size());
//val oldPointsJava: java.util.List[java.lang.Float] = oldPoints.asJava.asInstanceOf[java.util.List[java.lang.Float]]
//println("****class = " + oldPointsJava.getClass())
val pathData = BezierWrapper.lineSimplifyAndCurve((oldPoints ::: newPoints).asJava.asInstanceOf[java.util.List[java.lang.Float]], dimensions(0), dimensions(1))
//println("Path data: pointssize " + pathData.points.size() + " commandssize " + pathData.commands.size())
val updatedAnnotationData = annotation.annotationInfo + ("points" -> pathData.points.asScala.toList) + ("commands" -> pathData.commands.asScala.toList)
val updatedAnnotation = annotation.copy(position = oldAnnotation.position, annotationInfo = updatedAnnotationData)
val newAnnotationsMap = wb.annotationsMap + (userId -> (updatedAnnotation :: usersAnnotations.tail))
//println("Annotation has position [" + usersAnnotations.head.position + "]")
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
//println("Updating annotation on page [" + wb.id + "]. After numAnnotations=[" + getAnnotationsByUserId(wb, userId).length + "].")
saveWhiteboard(newWb)
rtnAnnotation = updatedAnnotation
var dimensions: List[Int] = List[Int]()
annotation.annotationInfo.get("dimensions").foreach(d => {
d match {
case d2: List[_] => dimensions = convertListNumbersToInt(d2)
}
})
//println("dimensions.size(): " + dimensions.size());
if (dimensions.length == 2) {
var oldPoints: List[Float] = List[Float]()
val oldAnnotationOption: Option[AnnotationVO] = usersAnnotations.headOption
if (!oldAnnotationOption.isEmpty) {
val oldAnnotation = oldAnnotationOption.get
if (oldAnnotation.id == annotation.id) {
oldAnnotation.annotationInfo.get("points").foreach(a => {
a match {
case a2: List[_] => oldPoints = a2.asInstanceOf[List[Float]]
}
})
}
}
var newPoints: List[Float] = List[Float]()
annotation.annotationInfo.get("points").foreach(a => {
a match {
case a2: List[_] => newPoints = convertListNumbersToFloat(a2)
}
}) //newPoints = a.asInstanceOf[ArrayList[Float]])
//println("oldPoints.size(): " + oldPoints.size)
//val oldPointsJava: java.util.List[java.lang.Float] = oldPoints.asJava.asInstanceOf[java.util.List[java.lang.Float]]
//println("****class = " + oldPointsJava.getClass())
val pathData = BezierWrapper.lineSimplifyAndCurve((oldPoints ::: newPoints).asJava.asInstanceOf[java.util.List[java.lang.Float]], dimensions(0), dimensions(1))
//println("Path data: pointssize " + pathData.points.size() + " commandssize " + pathData.commands.size())
val updatedAnnotationData = annotation.annotationInfo + ("points" -> pathData.points.asScala.toList) + ("commands" -> pathData.commands.asScala.toList)
//println("oldAnnotation value = " + oldAnnotationOption.getOrElse("Empty"))
var newPosition: Int = oldAnnotationOption match {
case Some(annotation) => annotation.position
case None => wb.annotationCount
}
val updatedAnnotation = annotation.copy(position = newPosition, annotationInfo = updatedAnnotationData)
var newUsersAnnotations: List[AnnotationVO] = oldAnnotationOption match {
case Some(annotation) => usersAnnotations.tail
case None => usersAnnotations
}
val newAnnotationsMap = wb.annotationsMap + (userId -> (updatedAnnotation :: newUsersAnnotations))
//println("Annotation has position [" + usersAnnotations.head.position + "]")
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
//println("Updating annotation on page [" + wb.id + "]. After numAnnotations=[" + getAnnotationsByUserId(wb, userId).length + "].")
saveWhiteboard(newWb)
rtnAnnotation = updatedAnnotation
}
rtnAnnotation

View File

@ -481,6 +481,15 @@ class PresentationArea extends PureComponent {
width,
height,
}}
published
whiteboardId={currentSlide.id}
/>
<AnnotationGroupContainer
{...{
width,
height,
}}
published={false}
whiteboardId={currentSlide.id}
/>
<CursorWrapperContainer

View File

@ -6,7 +6,7 @@ import GroupChat from '/imports/api/group-chat';
import Users from '/imports/api/users';
import Annotations from '/imports/api/annotations';
import AnnotationsTextService from '/imports/ui/components/whiteboard/annotations/text/service';
import AnnotationsLocal from '/imports/ui/components/whiteboard/service';
import { Annotations as AnnotationsLocal } from '/imports/ui/components/whiteboard/service';
const CHAT_CONFIG = Meteor.settings.public.chat;

View File

@ -1,6 +1,6 @@
import Annotations from '/imports/ui/components/whiteboard/service';
import { UnsentAnnotations } from '/imports/ui/components/whiteboard/service';
const getAnnotationById = _id => Annotations.findOne({
const getAnnotationById = _id => UnsentAnnotations.findOne({
_id,
});

View File

@ -1,4 +1,4 @@
import Annotations from '/imports/ui/components/whiteboard/service';
import { Annotations } from '/imports/ui/components/whiteboard/service';
const getAnnotationById = _id => Annotations.findOne({
_id,

View File

@ -4,18 +4,27 @@ import { withTracker } from 'meteor/react-meteor-data';
import AnnotationGroupService from './service';
import AnnotationGroup from './component';
const AnnotationGroupContainer = props => (
const AnnotationGroupContainer = ({
annotationsInfo, width, height, whiteboardId,
}) => (
<AnnotationGroup
annotationsInfo={props.annotationsInfo}
slideWidth={props.width}
slideHeight={props.height}
whiteboardId={props.whiteboardId}
annotationsInfo={annotationsInfo}
slideWidth={width}
slideHeight={height}
whiteboardId={whiteboardId}
/>
);
export default withTracker((params) => {
const { whiteboardId } = params;
const annotationsInfo = AnnotationGroupService.getCurrentAnnotationsInfo(whiteboardId);
const {
whiteboardId,
published,
} = params;
const fetchFunc = published
? AnnotationGroupService.getCurrentAnnotationsInfo : AnnotationGroupService.getUnsetAnnotations;
const annotationsInfo = fetchFunc(whiteboardId);
return {
annotationsInfo,
};

View File

@ -1,4 +1,4 @@
import Annotations from '/imports/ui/components/whiteboard/service';
import { Annotations, UnsentAnnotations } from '/imports/ui/components/whiteboard/service';
const getCurrentAnnotationsInfo = (whiteboardId) => {
if (!whiteboardId) {
@ -8,7 +8,22 @@ const getCurrentAnnotationsInfo = (whiteboardId) => {
return Annotations.find(
{
whiteboardId,
// annotationType: { $ne: 'pencil_base' },
},
{
sort: { position: 1 },
fields: { status: 1, _id: 1, annotationType: 1 },
},
).fetch();
};
const getUnsetAnnotations = (whiteboardId) => {
if (!whiteboardId) {
return null;
}
return UnsentAnnotations.find(
{
whiteboardId,
},
{
sort: { position: 1 },
@ -19,4 +34,5 @@ const getCurrentAnnotationsInfo = (whiteboardId) => {
export default {
getCurrentAnnotationsInfo,
getUnsetAnnotations,
};

View File

@ -1,16 +1,16 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AnnotationHelpers from '../helpers';
import { getFormattedColor, getStrokeWidth, denormalizeCoord } from '../helpers';
export default class EllipseDrawComponent extends Component {
shouldComponentUpdate(nextProps) {
return this.props.version !== nextProps.version;
const { version } = this.props;
return version !== nextProps.version;
}
getCoordinates() {
const { points } = this.props.annotation;
const { slideWidth, slideHeight } = this.props;
const { slideWidth, slideHeight, annotation } = this.props;
const { points } = annotation;
// x1 and y1 - coordinates of the ellipse's top left corner
// x2 and y2 - coordinates of the ellipse's bottom right corner
@ -24,10 +24,10 @@ export default class EllipseDrawComponent extends Component {
// cx and cy - coordinates of the ellipse's center
let rx = (x2 - x1) / 2;
let ry = (y2 - y1) / 2;
const cx = ((rx + x1) * slideWidth) / 100;
const cy = ((ry + y1) * slideHeight) / 100;
rx = Math.abs((rx / 100) * slideWidth);
ry = Math.abs((ry / 100) * slideHeight);
const cx = denormalizeCoord(rx + x1, slideWidth);
const cy = denormalizeCoord(ry + y1, slideHeight);
rx = denormalizeCoord(Math.abs(rx), slideWidth);
ry = denormalizeCoord(Math.abs(ry), slideHeight);
return {
cx,
@ -40,7 +40,9 @@ export default class EllipseDrawComponent extends Component {
render() {
const results = this.getCoordinates();
const { annotation, slideWidth } = this.props;
const { cx, cy, rx, ry } = results;
const {
cx, cy, rx, ry,
} = results;
return (
<ellipse
@ -49,8 +51,8 @@ export default class EllipseDrawComponent extends Component {
rx={rx}
ry={ry}
fill="none"
stroke={AnnotationHelpers.getFormattedColor(annotation.color)}
strokeWidth={AnnotationHelpers.getStrokeWidth(annotation.thickness, slideWidth)}
stroke={getFormattedColor(annotation.color)}
strokeWidth={getStrokeWidth(annotation.thickness, slideWidth)}
style={{ WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)' }}
/>
);

View File

@ -20,7 +20,10 @@ const getFormattedColor = (color) => {
const getStrokeWidth = (thickness, slideWidth) => (thickness * slideWidth) / 100;
export default {
const denormalizeCoord = (normCoord, sideLength) => ((normCoord / 100) * sideLength).toFixed(2);
export {
getFormattedColor,
getStrokeWidth,
denormalizeCoord,
};

View File

@ -1,21 +1,21 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AnnotationHelpers from '../helpers';
import { getFormattedColor, getStrokeWidth, denormalizeCoord } from '../helpers';
export default class LineDrawComponent extends Component {
shouldComponentUpdate(nextProps) {
return this.props.version !== nextProps.version;
const { version } = this.props;
return version !== nextProps.version;
}
getCoordinates() {
const { slideWidth, slideHeight } = this.props;
const { points } = this.props.annotation;
const { slideWidth, slideHeight, annotation } = this.props;
const { points } = annotation;
const x1 = (points[0] / 100) * slideWidth;
const y1 = (points[1] / 100) * slideHeight;
const x2 = (points[2] / 100) * slideWidth;
const y2 = (points[3] / 100) * slideHeight;
const x1 = denormalizeCoord(points[0], slideWidth);
const y1 = denormalizeCoord(points[1], slideHeight);
const x2 = denormalizeCoord(points[2], slideWidth);
const y2 = denormalizeCoord(points[3], slideHeight);
return {
x1,
@ -28,7 +28,9 @@ export default class LineDrawComponent extends Component {
render() {
const results = this.getCoordinates();
const { annotation, slideWidth } = this.props;
const { x1, y1, x2, y2 } = results;
const {
x1, y1, x2, y2,
} = results;
return (
<line
@ -36,9 +38,9 @@ export default class LineDrawComponent extends Component {
y1={y1}
x2={x2}
y2={y2}
stroke={AnnotationHelpers.getFormattedColor(annotation.color)}
stroke={getFormattedColor(annotation.color)}
strokeLinejoin="round"
strokeWidth={AnnotationHelpers.getStrokeWidth(annotation.thickness, slideWidth)}
strokeWidth={getStrokeWidth(annotation.thickness, slideWidth)}
style={{ WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)' }}
/>
);

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AnnotationHelpers from '../helpers';
import { getFormattedColor, getStrokeWidth, denormalizeCoord } from '../helpers';
export default class PencilDrawComponent extends Component {
static getInitialCoordinates(annotation, slideWidth, slideHeight) {
@ -8,11 +8,11 @@ export default class PencilDrawComponent extends Component {
let i = 2;
let path = '';
if (points && points.length >= 2) {
path = `M${(points[0] / 100) * slideWidth
}, ${(points[1] / 100) * slideHeight}`;
path = `M${denormalizeCoord(points[0], slideWidth)
}, ${denormalizeCoord(points[1], slideHeight)}`;
while (i < points.length) {
path = `${path} L${(points[i] / 100) * slideWidth
}, ${(points[i + 1] / 100) * slideHeight}`;
path = `${path} L${denormalizeCoord(points[i], slideWidth)
}, ${denormalizeCoord(points[i + 1], slideHeight)}`;
i += 2;
}
}
@ -30,32 +30,32 @@ export default class PencilDrawComponent extends Component {
switch (commands[i]) {
// MOVE_TO - consumes 1 pair of values
case 1:
path = `${path} M${(points[j] / 100) * slideWidth} ${(points[j + 1] / 100) * slideHeight}`;
path = `${path} M${denormalizeCoord(points[j], slideWidth)} ${denormalizeCoord(points[j + 1], slideHeight)}`;
j += 2;
break;
// LINE_TO - consumes 1 pair of values
case 2:
path = `${path} L${(points[j] / 100) * slideWidth} ${(points[j + 1] / 100) * slideHeight}`;
path = `${path} L${denormalizeCoord(points[j], slideWidth)} ${denormalizeCoord(points[j + 1], slideHeight)}`;
j += 2;
break;
// QUADRATIC_CURVE_TO - consumes 2 pairs of values
// 1st pair is a control point, second is a coordinate
case 3:
path = `${path} Q${(points[j] / 100) * slideWidth}, ${
(points[j + 1] / 100) * slideHeight}, ${(points[j + 2] / 100) * slideWidth}, ${
(points[j + 3] / 100) * slideHeight}`;
path = `${path} Q${denormalizeCoord(points[j], slideWidth)}, ${
denormalizeCoord(points[j + 1], slideHeight)}, ${denormalizeCoord(points[j + 2], slideWidth)}, ${
denormalizeCoord(points[j + 3], slideHeight)}`;
j += 4;
break;
// CUBIC_CURVE_TO - consumes 3 pairs of values
// 1st and 2nd are control points, 3rd is an end coordinate
case 4:
path = `${path} C${(points[j] / 100) * slideWidth}, ${
(points[j + 1] / 100) * slideHeight}, ${(points[j + 2] / 100) * slideWidth}, ${
(points[j + 3] / 100) * slideHeight}, ${(points[j + 4] / 100) * slideWidth}, ${
(points[j + 5] / 100) * slideHeight}`;
path = `${path} C${denormalizeCoord(points[j], slideWidth)}, ${
denormalizeCoord(points[j + 1], slideHeight)}, ${denormalizeCoord(points[j + 2], slideWidth)}, ${
denormalizeCoord(points[j + 3], slideHeight)}, ${denormalizeCoord(points[j + 4], slideWidth)}, ${
denormalizeCoord(points[j + 5], slideHeight)}`;
j += 6;
break;
@ -67,7 +67,7 @@ export default class PencilDrawComponent extends Component {
// If that's just one coordinate at the end (dot) - we want to display it.
// So adding L with the same X and Y values to the path
if (path && points.length === 2) {
path = `${path} L${(points[0] / 100) * slideWidth} ${(points[1] / 100) * slideHeight}`;
path = `${path} L${denormalizeCoord(points[0], slideWidth)} ${denormalizeCoord(points[1], slideHeight)}`;
}
return { path, points };
@ -85,13 +85,17 @@ export default class PencilDrawComponent extends Component {
}
shouldComponentUpdate(nextProps) {
return this.props.version !== nextProps.version;
const { version } = this.props;
return version !== nextProps.version;
}
componentWillUpdate(nextProps) {
const { annotation, slideWidth, slideHeight } = nextProps;
if (annotation.points.length !== this.props.annotation.points.length) {
this.path = this.getCoordinates(annotation, slideWidth, slideHeight);
const { annotation: nextAnnotation, slideWidth, slideHeight } = nextProps;
const { points: nextPoints } = nextAnnotation;
const { annotation } = this.props;
const { points } = annotation;
if (nextPoints.length !== points.length) {
this.path = this.getCoordinates(nextAnnotation, slideWidth, slideHeight);
}
}
@ -123,14 +127,15 @@ export default class PencilDrawComponent extends Component {
updateCoordinates(annotation, slideWidth, slideHeight) {
const { points } = annotation;
let i = this.points.length;
let path = '';
while (i < points.length) {
path = `${path} L${(points[i] / 100) * slideWidth
}, ${(points[i + 1] / 100) * slideHeight}`;
path = `${path} L${denormalizeCoord(points[i], slideWidth)
}, ${denormalizeCoord(points[i + 1], slideHeight)}`;
i += 2;
}
path = this.path + path;
return { path, points };
@ -141,9 +146,9 @@ export default class PencilDrawComponent extends Component {
return (
<path
fill="none"
stroke={AnnotationHelpers.getFormattedColor(annotation.color)}
stroke={getFormattedColor(annotation.color)}
d={this.getCurrentPath()}
strokeWidth={AnnotationHelpers.getStrokeWidth(annotation.thickness, slideWidth)}
strokeWidth={getStrokeWidth(annotation.thickness, slideWidth)}
strokeLinejoin="round"
strokeLinecap="round"
style={{ WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)' }}

View File

@ -1,16 +1,17 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AnnotationHelpers from '../helpers';
import { getFormattedColor, getStrokeWidth, denormalizeCoord } from '../helpers';
export default class RectangleDrawComponent extends Component {
shouldComponentUpdate(nextProps) {
return this.props.version !== nextProps.version;
const { version } = this.props;
return version !== nextProps.version;
}
getCoordinates() {
const { points } = this.props.annotation;
const { slideWidth, slideHeight } = this.props;
const { slideWidth, slideHeight, annotation } = this.props;
const { points } = annotation;
/* eslint-disable prefer-destructuring */
// x1 and y1 are the coordinates of the top left corner of the annotation
// x2 and y2 are the coordinates of the bottom right corner of the annotation
let x1 = points[0];
@ -29,11 +30,11 @@ export default class RectangleDrawComponent extends Component {
y1 = points[3];
y2 = points[1];
}
const x = (x1 / 100) * slideWidth;
const y = (y1 / 100) * slideHeight;
const width = ((x2 - x1) / 100) * slideWidth;
const height = ((y2 - y1) / 100) * slideHeight;
/* eslint-enable prefer-destructuring */
const x = denormalizeCoord(x1, slideWidth);
const y = denormalizeCoord(y1, slideHeight);
const width = denormalizeCoord((x2 - x1), slideWidth);
const height = denormalizeCoord((y2 - y1), slideHeight);
return {
x,
@ -54,8 +55,8 @@ export default class RectangleDrawComponent extends Component {
width={results.width}
height={results.height}
fill="none"
stroke={AnnotationHelpers.getFormattedColor(annotation.color)}
strokeWidth={AnnotationHelpers.getStrokeWidth(annotation.thickness, slideWidth)}
stroke={getFormattedColor(annotation.color)}
strokeWidth={getStrokeWidth(annotation.thickness, slideWidth)}
style={{ WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)' }}
/>
);

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import RenderInBrowser from 'react-render-in-browser';
import AnnotationHelpers from '../helpers';
import { getFormattedColor, denormalizeCoord } from '../helpers';
const DRAW_END = Meteor.settings.public.whiteboard.annotations.status.end;
@ -109,11 +109,11 @@ export default class TextDrawComponent extends Component {
text,
} = annotation;
const _x = (x / 100) * slideWidth;
const _y = (y / 100) * slideHeight;
const _width = (textBoxWidth / 100) * slideWidth;
const _height = (textBoxHeight / 100) * slideHeight;
const _fontColor = AnnotationHelpers.getFormattedColor(fontColor);
const _x = denormalizeCoord(x, slideWidth);
const _y = denormalizeCoord(y, slideHeight);
const _width = denormalizeCoord(textBoxWidth, slideWidth);
const _height = denormalizeCoord(textBoxHeight, slideHeight);
const _fontColor = getFormattedColor(fontColor);
const _fontSize = fontSize;
const _calcedFontSize = (calcedFontSize / 100) * slideHeight;
const _text = text;

View File

@ -1,16 +1,16 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AnnotationHelpers from '../helpers';
import { getFormattedColor, getStrokeWidth, denormalizeCoord } from '../helpers';
export default class TriangleDrawComponent extends Component {
shouldComponentUpdate(nextProps) {
return this.props.version !== nextProps.version;
const { version } = this.props;
return version !== nextProps.version;
}
getCoordinates() {
const { slideWidth, slideHeight } = this.props;
const { points } = this.props.annotation;
const { slideWidth, slideHeight, annotation } = this.props;
const { points } = annotation;
// points[0] and points[1] are x and y coordinates of the top left corner of the annotation
// points[2] and points[3] are x and y coordinates of the bottom right corner of the annotation
@ -21,13 +21,13 @@ export default class TriangleDrawComponent extends Component {
const xTop = ((xBottomRight - xBottomLeft) / 2) + xBottomLeft;
const yTop = points[1];
const path = `M${(xTop / 100) * slideWidth
},${(yTop / 100) * slideHeight
},${(xBottomLeft / 100) * slideWidth
},${(yBottomLeft / 100) * slideHeight
},${(xBottomRight / 100) * slideWidth
},${(yBottomRight / 100) * slideHeight
}Z`;
const path = `M${denormalizeCoord(xTop, slideWidth)
},${denormalizeCoord(yTop, slideHeight)
},${denormalizeCoord(xBottomLeft, slideWidth)
},${denormalizeCoord(yBottomLeft, slideHeight)
},${denormalizeCoord(xBottomRight, slideWidth)
},${denormalizeCoord(yBottomRight, slideHeight)
}Z`;
return path;
}
@ -39,9 +39,9 @@ export default class TriangleDrawComponent extends Component {
<path
style={{ WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)' }}
fill="none"
stroke={AnnotationHelpers.getFormattedColor(annotation.color)}
stroke={getFormattedColor(annotation.color)}
d={path}
strokeWidth={AnnotationHelpers.getStrokeWidth(annotation.thickness, slideWidth)}
strokeWidth={getStrokeWidth(annotation.thickness, slideWidth)}
strokeLinejoin="miter"
/>
);

View File

@ -2,25 +2,25 @@ import Users from '/imports/api/users';
import Auth from '/imports/ui/services/auth';
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
import addAnnotationQuery from '/imports/api/annotations/addAnnotation';
import logger from '/imports/startup/client/logger';
import { makeCall } from '/imports/ui/services/api';
import { isEqual } from 'lodash';
const Annotations = new Mongo.Collection(null);
const UnsentAnnotations = new Mongo.Collection(null);
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;
const discardedList = [];
const ANNOTATION_TYPE_PENCIL = 'pencil';
let annotationsStreamListener = null;
export function addAnnotationToDiscardedList(annotation) {
if (!discardedList.includes(annotation)) discardedList.push(annotation);
}
const clearPreview = (annotation) => {
UnsentAnnotations.remove({ id: annotation });
};
function clearFakeAnnotations() {
Annotations.remove({ id: /-fake/g });
UnsentAnnotations.remove({});
}
function handleAddedAnnotation({
@ -29,55 +29,11 @@ function handleAddedAnnotation({
const isOwn = Auth.meetingID === meetingId && Auth.userID === userId;
const query = addAnnotationQuery(meetingId, whiteboardId, userId, annotation);
if (!isOwn) {
Annotations.upsert(query.selector, query.modifier);
return;
Annotations.upsert(query.selector, query.modifier);
if (isOwn) {
UnsentAnnotations.remove({ id: `${annotation.id}` });
}
const fakeAnnotation = Annotations.findOne({ id: `${annotation.id}-fake` });
let fakePoints;
if (fakeAnnotation) {
fakePoints = fakeAnnotation.annotationInfo.points;
const { points: lastPoints } = annotation.annotationInfo;
if (annotation.annotationType !== 'pencil') {
Annotations.update(fakeAnnotation._id, {
$set: {
position: annotation.position,
'annotationInfo.color': isEqual(fakePoints, lastPoints) || annotation.status === DRAW_END
? annotation.annotationInfo.color : fakeAnnotation.annotationInfo.color,
},
$inc: { version: 1 }, // TODO: Remove all this version stuff
});
return;
}
}
Annotations.upsert(query.selector, query.modifier, (err) => {
if (err) {
logger.error({
logCode: 'whiteboard_annotation_upsert_error',
extraInfo: { error: err },
}, 'Error on adding an annotation');
return;
}
// Remove fake annotation for pencil on draw end
if (annotation.status === DRAW_END) {
Annotations.remove({ id: `${annotation.id}-fake` });
return;
}
if (annotation.status === DRAW_START) {
Annotations.update(fakeAnnotation._id, {
$set: {
position: annotation.position - 1,
},
$inc: { version: 1 }, // TODO: Remove all this version stuff
});
}
});
}
function handleRemovedAnnotation({
@ -85,14 +41,12 @@ function handleRemovedAnnotation({
}) {
const query = { meetingId, whiteboardId };
addAnnotationToDiscardedList(shapeId);
if (userId) {
query.userId = userId;
}
if (shapeId) {
query.id = { $in: [shapeId, `${shapeId}-fake`] };
query.id = shapeId;
}
Annotations.remove(query);
@ -109,7 +63,7 @@ export function initAnnotationsStreamListener() {
const startStreamHandlersPromise = new Promise((resolve) => {
const checkStreamHandlersInterval = setInterval(() => {
const streamHandlersSize = Object.values(Meteor.StreamerCentral.instances[`annotations-${Auth.meetingID}`].handlers)
.filter(el => el != undefined)
.filter(el => el !== undefined)
.length;
if (!streamHandlersSize) {
@ -122,10 +76,7 @@ export function initAnnotationsStreamListener() {
annotationsStreamListener.on('removed', handleRemovedAnnotation);
annotationsStreamListener.on('added', ({ annotations }) => {
// Call handleAddedAnnotation when this annotation is not in discardedList
annotations
.filter(({ annotation }) => !discardedList.includes(annotation.id))
.forEach(annotation => handleAddedAnnotation(annotation));
annotations.forEach(annotation => handleAddedAnnotation(annotation));
});
});
}
@ -172,41 +123,52 @@ const proccessAnnotationsQueue = async () => {
const annotations = annotationsQueue.splice(0, queueSize);
// console.log('annotationQueue.length', annotationsQueue, annotationsQueue.length);
await makeCall('sendBulkAnnotations', annotations.filter(({ id }) => !discardedList.includes(id)));
await makeCall('sendBulkAnnotations', annotations);
// ask tiago
const delayPerc = Math.min(annotationsMaxDelayQueueSize, queueSize) / annotationsMaxDelayQueueSize;
const delayDelta = annotationsBufferTimeMax - annotationsBufferTimeMin;
const delayTime = annotationsBufferTimeMin + (delayDelta * delayPerc);
// console.log("delayPerc:", delayPerc)
setTimeout(proccessAnnotationsQueue, delayTime);
};
export function sendAnnotation(annotation) {
const sendAnnotation = (annotation) => {
// Prevent sending annotations while disconnected
// TODO: Change this to add the annotation, but delay the send until we're
// reconnected. With this it will miss things
if (!Meteor.status().connected) return;
annotationsQueue.push(annotation);
if (!annotationsSenderIsRunning) setTimeout(proccessAnnotationsQueue, annotationsBufferTimeMin);
// skip optimistic for draw end since the smoothing is done in akka
if (annotation.status === DRAW_END) return;
const { position, ...relevantAnotation } = annotation;
const queryFake = addAnnotationQuery(
Auth.meetingID, annotation.wbId, Auth.userID,
{
...relevantAnotation,
id: `${annotation.id}-fake`,
position: Number.MAX_SAFE_INTEGER,
annotationInfo: {
...annotation.annotationInfo,
color: increaseBrightness(annotation.annotationInfo.color, 40),
if (annotation.status === DRAW_END) {
annotationsQueue.push(annotation);
if (!annotationsSenderIsRunning) setTimeout(proccessAnnotationsQueue, annotationsBufferTimeMin);
} else {
const { position, ...relevantAnotation } = annotation;
const queryFake = addAnnotationQuery(
Auth.meetingID, annotation.wbId, Auth.userID,
{
...relevantAnotation,
id: `${annotation.id}`,
position: Number.MAX_SAFE_INTEGER,
annotationInfo: {
...annotation.annotationInfo,
color: increaseBrightness(annotation.annotationInfo.color, 40),
},
},
},
);
);
Annotations.upsert(queryFake.selector, queryFake.modifier);
}
// This is a really hacky solution, but because of the previous code reuse we need to edit
// the pencil draw update modifier so that it sets the whole array instead of pushing to
// the end
const { status, annotationType } = relevantAnotation;
if (status === DRAW_UPDATE && annotationType === ANNOTATION_TYPE_PENCIL) {
delete queryFake.modifier.$push;
queryFake.modifier.$set['annotationInfo.points'] = annotation.annotationInfo.points;
}
UnsentAnnotations.upsert(queryFake.selector, queryFake.modifier);
}
};
WhiteboardMultiUser.find({ meetingId: Auth.meetingID }).observeChanges({
changed: clearFakeAnnotations,
@ -218,4 +180,9 @@ Users.find({ userId: Auth.userID }, { fields: { presenter: 1 } }).observeChanges
},
});
export default Annotations;
export {
Annotations,
UnsentAnnotations,
sendAnnotation,
clearPreview,
};

View File

@ -201,8 +201,7 @@ export default class WhiteboardOverlay extends Component {
resetTextShapeSession,
setTextShapeActiveId,
contextMenuHandler,
addAnnotationToDiscardedList,
undoAnnotation,
clearPreview,
updateCursor,
} = this.props;
@ -218,8 +217,7 @@ export default class WhiteboardOverlay extends Component {
resetTextShapeSession,
setTextShapeActiveId,
contextMenuHandler,
addAnnotationToDiscardedList,
undoAnnotation,
clearPreview,
};
return (
@ -257,6 +255,8 @@ WhiteboardOverlay.propTypes = {
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

View File

@ -2,7 +2,6 @@ import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
import WhiteboardOverlayService from './service';
import WhiteboardToolbarService from '../whiteboard-toolbar/service';
import WhiteboardOverlay from './component';
const WhiteboardOverlayContainer = (props) => {
@ -16,10 +15,9 @@ const WhiteboardOverlayContainer = (props) => {
};
export default withTracker(() => ({
undoAnnotation: WhiteboardToolbarService.undoAnnotation,
clearPreview: WhiteboardOverlayService.clearPreview,
contextMenuHandler: WhiteboardOverlayService.contextMenuHandler,
sendAnnotation: WhiteboardOverlayService.sendAnnotation,
addAnnotationToDiscardedList: WhiteboardOverlayService.addAnnotationToDiscardedList,
setTextShapeActiveId: WhiteboardOverlayService.setTextShapeActiveId,
resetTextShapeSession: WhiteboardOverlayService.resetTextShapeSession,
drawSettings: WhiteboardOverlayService.getWhiteboardToolbarValues(),

View File

@ -8,7 +8,7 @@ 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 = 5;
const POINTS_TO_BUFFER = 2;
export default class PencilDrawListener extends Component {
constructor() {
@ -64,8 +64,8 @@ export default class PencilDrawListener extends Component {
transformedSvgPoint = svgCoordinateToPercentages(transformedSvgPoint);
// sending the first message
const _points = [transformedSvgPoint.x, transformedSvgPoint.y];
this.handleDrawPencil(_points, DRAW_START, generateNewShapeId());
this.points = [transformedSvgPoint.x, transformedSvgPoint.y];
this.handleDrawPencil(this.points, DRAW_START, generateNewShapeId());
}
commonDrawMoveHandler(clientX, clientY) {
@ -147,7 +147,6 @@ export default class PencilDrawListener extends Component {
// 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.sendLastMessage();
this.discardAnnotation();
}
}
@ -171,7 +170,6 @@ export default class PencilDrawListener extends Component {
const { getCurrentShapeId } = actions;
this.handleDrawPencil(this.points, DRAW_UPDATE, getCurrentShapeId());
this.points = [];
}
}
@ -254,19 +252,16 @@ export default class PencilDrawListener extends Component {
discardAnnotation() {
const {
whiteboardId,
actions,
} = this.props;
const {
getCurrentShapeId,
addAnnotationToDiscardedList,
undoAnnotation,
clearPreview,
} = actions;
undoAnnotation(whiteboardId);
addAnnotationToDiscardedList(getCurrentShapeId());
this.resetState();
clearPreview(getCurrentShapeId());
}
render() {

View File

@ -1,6 +1,6 @@
import Storage from '/imports/ui/services/storage/session';
import Auth from '/imports/ui/services/auth';
import { sendAnnotation, addAnnotationToDiscardedList } from '/imports/ui/components/whiteboard/service';
import { sendAnnotation, clearPreview } from '/imports/ui/components/whiteboard/service';
import { publishCursorUpdate } from '/imports/ui/components/cursor/service';
const DRAW_SETTINGS = 'drawSettings';
@ -55,7 +55,6 @@ const updateCursor = (payload) => {
};
export default {
addAnnotationToDiscardedList,
sendAnnotation,
getWhiteboardToolbarValues,
setTextShapeActiveId,
@ -63,4 +62,5 @@ export default {
getCurrentUserId,
contextMenuHandler,
updateCursor,
clearPreview,
};

View File

@ -174,8 +174,6 @@ export default class ShapeDrawListener extends Component {
// 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.isDrawing = false;
this.sendLastMessage();
this.discardAnnotation();
}
}
@ -324,18 +322,16 @@ export default class ShapeDrawListener extends Component {
discardAnnotation() {
const {
whiteboardId,
actions,
} = this.props;
const {
getCurrentShapeId,
addAnnotationToDiscardedList,
undoAnnotation,
clearPreview,
} = actions;
undoAnnotation(whiteboardId);
addAnnotationToDiscardedList(getCurrentShapeId());
this.resetState();
clearPreview(getCurrentShapeId());
}
render() {

View File

@ -215,12 +215,11 @@ export default class TextDrawListener extends Component {
}
// 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();
if (isRightClick) {
this.discardAnnotation();
}
}
}
@ -460,18 +459,16 @@ export default class TextDrawListener extends Component {
discardAnnotation() {
const {
whiteboardId,
actions,
} = this.props;
const {
getCurrentShapeId,
addAnnotationToDiscardedList,
undoAnnotation,
clearPreview,
} = actions;
undoAnnotation(whiteboardId);
addAnnotationToDiscardedList(getCurrentShapeId());
this.resetState();
clearPreview(getCurrentShapeId());
}
render() {