bigbluebutton-Github/bigbluebutton-html5/imports/api/annotations/server/modifiers/addAnnotation.js

373 lines
10 KiB
JavaScript

import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import Annotations from '/imports/api/annotations';
const ANNOTATION_TYPE_TEXT = 'text';
const ANNOTATION_TYPE_PENCIL = 'pencil';
// line, triangle, ellipse, rectangle
function handleCommonAnnotation(meetingId, whiteboardId, userId, annotation) {
const { id, status, annotationType, annotationInfo, wbId, position } = annotation;
const selector = {
meetingId,
id,
userId,
};
const modifier = {
$set: {
whiteboardId,
meetingId,
id,
status,
annotationType,
annotationInfo,
wbId,
position,
},
$inc: { version: 1 },
};
return { selector, modifier };
}
function handleTextUpdate(meetingId, whiteboardId, userId, annotation) {
const { id, status, annotationType, annotationInfo, wbId, position } = annotation;
const selector = {
meetingId,
id,
userId,
};
annotationInfo.text = annotationInfo.text.replace(/[\r]/g, '\n');
const modifier = {
$set: {
whiteboardId,
meetingId,
id,
status,
annotationType,
annotationInfo,
wbId,
position,
},
$inc: { version: 1 },
};
return { selector, modifier };
}
function handlePencilUpdate(meetingId, whiteboardId, userId, annotation) {
// fetching annotation statuses from the config
const ANOTATION_STATUSES = Meteor.settings.public.whiteboard.annotations.status;
const DRAW_START = ANOTATION_STATUSES.start;
const DRAW_UPDATE = ANOTATION_STATUSES.update;
const DRAW_END = ANOTATION_STATUSES.end;
const SERVER_CONFIG = Meteor.settings.app;
const PENCIL_CHUNK_SIZE = SERVER_CONFIG.pencilChunkLength || 100;
const { id, status, annotationType, annotationInfo, wbId, position } = annotation;
const baseSelector = {
meetingId,
id,
userId,
};
let baseModifier;
let chunkSelector;
let chunkModifier;
// fetching the Annotation object
const Annotation = Annotations.findOne(baseSelector);
// a helper func, to split the initial annotation.points into subdocuments
// returns an array of { selector, modifier } objects for subdocuments.
const createPencilObjects = () => {
const chunks = [];
// if the length of the points < PENCIL_CHUNK_SIZE then we simply return an array with one chunk
if (annotationInfo.points.length < PENCIL_CHUNK_SIZE) {
const chunkId = `${id}--${1}`;
chunks.push({
selector: {
meetingId,
userId,
id: chunkId,
},
modifier: {
$set: {
whiteboardId,
meetingId,
id: chunkId,
status,
annotationType,
annotationInfo,
wbId,
position,
},
$inc: { version: 1 },
},
});
return chunks;
}
// *default flow*
// length of the points >= PENCIL_CHUNK_SIZE, so we split them into subdocuments
// counter is used for generating ids.
let i = 0;
let counter = 1;
for (; i <= annotationInfo.points.length; i += PENCIL_CHUNK_SIZE, counter += 1) {
const chunkId = `${id}--${counter}`;
// we always need to attach the last coordinate from the previous subdocument
// to the front of the current subdocument, to connect the pencil path
const _annotationInfo = annotationInfo;
_annotationInfo.points = annotationInfo.points.slice(i === 0 ? 0 : i - 2, PENCIL_CHUNK_SIZE);
chunks.push({
selector: {
meetingId,
userId,
id: chunkId,
},
modifier: {
$set: {
whiteboardId,
meetingId,
id: chunkId,
status,
annotationType,
annotationInfo: _annotationInfo,
wbId,
position,
},
$inc: { version: 1 },
},
});
}
return chunks;
};
switch (status) {
case DRAW_START: {
// on start we split the points
const chunks = createPencilObjects();
// create the 'pencil_base'
baseModifier = {
id,
userId,
meetingId,
position,
annotationType: 'pencil_base',
numberOfChunks: chunks.length,
lastChunkLength: chunks[chunks.length - 1].length,
lastCoordinate: [
annotationInfo.points[annotationInfo.points.length - 2],
annotationInfo.points[annotationInfo.points.length - 1],
],
};
// upserting all the chunks
for (let i = 0; i < chunks.length; i += 1) {
Annotations.upsert(chunks[i].selector, chunks[i].modifier);
}
// base will be updated in the main addAnnotation event
return { selector: baseSelector, modifier: baseModifier };
}
case DRAW_UPDATE: {
// checking if "pencil_base" exists
if (Annotation) {
const { numberOfChunks, lastChunkLength } = Annotation;
// if lastChunkLength < PENCIL_CHUNK_SIZE then we can simply push points to the last object
if (lastChunkLength < PENCIL_CHUNK_SIZE) {
// creating a modifier for 'pencil_base'
baseModifier = {
$set: {
lastChunkLength: lastChunkLength + annotation.annotationInfo.points.length,
lastCoordinate: [
annotationInfo.points[annotationInfo.points.length - 2],
annotationInfo.points[annotationInfo.points.length - 1],
],
},
};
const chunkId = `${id}--${numberOfChunks}`;
chunkSelector = {
meetingId,
userId,
id: chunkId,
};
// fetching the last pencil sub-document
const chunk = Annotations.findOne(chunkSelector);
// adding the coordinates to the end of the last sub-document
annotationInfo.points = chunk.annotationInfo.points.concat(annotationInfo.points);
chunkModifier = {
$set: {
annotationInfo,
},
$inc: { version: 1 },
};
// if lastChunkLength > PENCIL_CHUNK_SIZE then we need to create another chunk
} else if (lastChunkLength >= PENCIL_CHUNK_SIZE) {
baseModifier = {
$set: {
numberOfChunks: numberOfChunks + 1,
lastChunkLength: annotationInfo.points.length,
lastCoordinate: [
annotationInfo.points[annotationInfo.points.length - 2],
annotationInfo.points[annotationInfo.points.length - 1],
],
},
};
const chunkId = `${id}--${numberOfChunks + 1}`;
chunkSelector = {
meetingId,
userId,
id: chunkId,
};
// pushing the last coordinate to the front of the current chunk's points
annotationInfo.points.unshift(Annotation.lastCoordinate[0], Annotation.lastCoordinate[1]);
chunkModifier = {
$set: {
whiteboardId,
meetingId,
userId,
id: chunkId,
status,
annotationType,
annotationInfo,
wbId,
position: Annotation.position,
},
$inc: { version: 1 },
};
}
// upserting the new subdocument
Annotations.upsert(chunkSelector, chunkModifier);
// base will be updated in the main AddAnnotation func
return { selector: baseSelector, modifier: baseModifier };
}
// **default flow**
// if we are here then it means that Annotation object is not in the db
// So creating everything similar to DRAW_START case
const _chunks = createPencilObjects();
// creating 'pencil_base' based on the info we received from createPencilObjects()
baseModifier = {
id,
userId,
meetingId,
position,
annotationType: 'pencil_base',
numberOfChunks: _chunks.length,
lastChunkLength: _chunks[_chunks.length - 1].length,
lastCoordinate: [
annotationInfo.points[annotationInfo.points.length - 2],
annotationInfo.points[annotationInfo.points.length - 1],
],
};
// upserting all the chunks
for (let i = 0; i < _chunks.length; i += 1) {
Annotations.upsert(_chunks[i].selector, _chunks[i].modifier);
}
// base will be updated in the main AddAnnotation func
return { selector: baseSelector, modifier: baseModifier };
}
case DRAW_END: {
// If a user just finished drawing with the pencil
// Removing all the sub-documents and replacing the 'pencil_base'
if (Annotation && Annotation.annotationType === 'pencil_base') {
// delete everything and replace base
const chunkIds = [];
for (let i = 0; i <= Annotation.numberOfChunks; i += 1) {
chunkIds.push(`${Annotation.id}--${i}`);
}
chunkSelector = {
meetingId,
userId,
id: { $in: chunkIds },
};
Annotations.remove(chunkSelector);
}
// Updating the main pencil object with the final info
baseModifier = {
$set: {
whiteboardId,
meetingId,
id,
status,
annotationType,
annotationInfo,
wbId,
position,
},
$inc: { version: 1 },
$unset: {
numberOfChunks: '',
lastChunkLength: '',
lastCoordinate: '',
},
};
return { selector: baseSelector, modifier: baseModifier };
}
default: {
return {};
}
}
}
export default function addAnnotation(meetingId, whiteboardId, userId, annotation) {
check(meetingId, String);
check(whiteboardId, String);
check(annotation, Object);
let query;
switch (annotation.annotationType) {
case ANNOTATION_TYPE_TEXT:
query = handleTextUpdate(meetingId, whiteboardId, userId, annotation);
break;
case ANNOTATION_TYPE_PENCIL:
query = handlePencilUpdate(meetingId, whiteboardId, userId, annotation);
break;
default:
query = handleCommonAnnotation(meetingId, whiteboardId, userId, annotation);
break;
}
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Adding annotation to collection: ${err}`);
}
const { insertedId } = numChanged;
if (insertedId) {
return Logger.info(`Added annotation id=${annotation.id} whiteboard=${whiteboardId}`);
}
return Logger.info(`Upserted annotation id=${annotation.id} whiteboard=${whiteboardId}`);
};
return Annotations.upsert(query.selector, query.modifier, cb);
}