From 40b18b0662b0d11ce109a1ffb703a4057de99840 Mon Sep 17 00:00:00 2001 From: Chad Pilkey Date: Mon, 6 Apr 2020 20:34:08 +0000 Subject: [PATCH] whiteboard performance improvements --- .../core/apps/WhiteboardModel.scala | 98 +++++++----- .../ui/components/presentation/component.jsx | 9 ++ .../ui/components/subscriptions/component.jsx | 2 +- .../reactive-annotation/service.js | 4 +- .../static-annotation/service.js | 2 +- .../whiteboard/annotation-group/container.jsx | 23 ++- .../whiteboard/annotation-group/service.js | 20 ++- .../annotations/ellipse/component.jsx | 26 ++-- .../whiteboard/annotations/helpers.js | 5 +- .../whiteboard/annotations/line/component.jsx | 26 ++-- .../annotations/pencil/component.jsx | 53 ++++--- .../annotations/rectangle/component.jsx | 25 ++-- .../whiteboard/annotations/text/component.jsx | 12 +- .../annotations/triangle/component.jsx | 28 ++-- .../ui/components/whiteboard/service.js | 141 +++++++----------- .../whiteboard-overlay/component.jsx | 8 +- .../whiteboard-overlay/container.jsx | 4 +- .../pencil-draw-listener/component.jsx | 17 +-- .../whiteboard/whiteboard-overlay/service.js | 4 +- .../shape-draw-listener/component.jsx | 10 +- .../text-draw-listener/component.jsx | 13 +- 21 files changed, 273 insertions(+), 257 deletions(-) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/WhiteboardModel.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/WhiteboardModel.scala index f73d74a4e5..7ada8b233a 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/WhiteboardModel.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/WhiteboardModel.scala @@ -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 diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index af7c7d4d5d..0276a44e4a 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -481,6 +481,15 @@ class PresentationArea extends PureComponent { width, height, }} + published + whiteboardId={currentSlide.id} + /> + Annotations.findOne({ +const getAnnotationById = _id => UnsentAnnotations.findOne({ _id, }); diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/static-annotation/service.js b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/static-annotation/service.js index 4550a60159..fb90fb58d5 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/static-annotation/service.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/static-annotation/service.js @@ -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, diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx index 6bdaeb2f37..5f9ca81d05 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/container.jsx @@ -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, +}) => ( ); 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, }; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/service.js b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/service.js index e0428fe9d5..683b8449a7 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/service.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-group/service.js @@ -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, }; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/ellipse/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/ellipse/component.jsx index 1da0de3b8b..01e9f7a415 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/ellipse/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/ellipse/component.jsx @@ -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 ( ); diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/helpers.js b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/helpers.js index 7c4ff2a769..5d67acd6fe 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/helpers.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/helpers.js @@ -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, }; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/line/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/line/component.jsx index 81e8dacf9d..18725c6d90 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/line/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/line/component.jsx @@ -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 ( ); diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/pencil/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/pencil/component.jsx index e367e04fb8..3a808503ea 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/pencil/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/pencil/component.jsx @@ -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 ( ); diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx index 3cafe4d3fb..7c87acc2b2 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx @@ -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; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/triangle/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/triangle/component.jsx index 06e49c1e86..2a2133def3 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/triangle/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/triangle/component.jsx @@ -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 { ); diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/service.js b/bigbluebutton-html5/imports/ui/components/whiteboard/service.js index f8712a3e36..4fa4bfe9ed 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/service.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/service.js @@ -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, +}; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/component.jsx index 09cce8e832..0ad127fd71 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/component.jsx @@ -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 diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx index 86b1e08959..90eef160d4 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/container.jsx @@ -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(), diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx index 4caf3d09f1..68d8092fff 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/pencil-draw-listener/component.jsx @@ -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() { diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/service.js b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/service.js index 95a0eb272a..534ffe471b 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/service.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/service.js @@ -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, }; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx index 554c2469a8..5b70a3a73b 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/shape-draw-listener/component.jsx @@ -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() { diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx index 518c3aeea8..056d81ce97 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-overlay/text-draw-listener/component.jsx @@ -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() {