373 lines
10 KiB
JavaScript
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);
|
|
}
|