Skip to content

Commit

Permalink
New: Emit annotation thread events to the viewer and beyond (#377)
Browse files Browse the repository at this point in the history
Emits annotator and annotation thread specific events to the Viewer and Preview

Annotator Events: 
- annotationmodeenter: Enter an annotation mode
- annotationmodeexit: Exit an annotation mode
- annotationsfetched: Annotations have been fetched from the API
- annotationerror: Annotation error occurred

Annotation Thread Events:
- annotationpending: Thread was created but not saved
- annotationthreadsaved: Annotation thread saved on the server
- annotationthreaddeleted: Annotation thread deleted on the server
- annotationsaved: Annotation saved on an existing thread on the server
- annotationdeleted: Annotation deleted from an existing thread on the server
- annotationdeleteerror: Error while deleting an annotation on either an existing/new thread
- annotationcanceled: Annotation cancelled from posting on either an existing/new thread
- annotationcreateerror: Error while creating an annotation on either an existing/new thread
  • Loading branch information
pramodsum committed Sep 11, 2017
1 parent a718718 commit dec86c6
Show file tree
Hide file tree
Showing 19 changed files with 917 additions and 529 deletions.
7 changes: 2 additions & 5 deletions src/lib/annotations/AnnotationModeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class AnnotationModeController extends EventEmitter {

// TODO (@minhnguyen): Move annotator.bindCustomListenersOnThread logic to AnnotationModeController
this.annotator.bindCustomListenersOnThread(thread);
thread.addListener('annotationevent', (data) => {
thread.addListener('threadevent', (data) => {
this.handleAnnotationEvent(thread, data);
});
}
Expand All @@ -126,10 +126,7 @@ class AnnotationModeController extends EventEmitter {
return;
}

thread.removeAllListeners('threaddeleted');
thread.removeAllListeners('threadcleanup');
thread.removeAllListeners('annotationsaved');
thread.removeAllListeners('annotationevent');
thread.removeAllListeners('threadevent');
}

/**
Expand Down
138 changes: 100 additions & 38 deletions src/lib/annotations/AnnotationThread.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import Annotation from './Annotation';
import AnnotationService from './AnnotationService';
import * as annotatorUtil from './annotatorUtil';
import { ICON_PLACED_ANNOTATION } from '../icons/icons';
import { STATES, TYPES, CLASS_ANNOTATION_POINT_MARKER, DATA_TYPE_ANNOTATION_INDICATOR } from './annotationConstants';
import {
STATES,
TYPES,
CLASS_ANNOTATION_POINT_MARKER,
DATA_TYPE_ANNOTATION_INDICATOR,
THREAD_EVENT
} from './annotationConstants';

@autobind
class AnnotationThread extends EventEmitter {
Expand Down Expand Up @@ -77,7 +83,7 @@ class AnnotationThread extends EventEmitter {
this.element = null;
}

this.emit('threaddeleted');
this.emit(THREAD_EVENT.threadDelete);
}

/**
Expand Down Expand Up @@ -128,7 +134,7 @@ class AnnotationThread extends EventEmitter {
*
* @param {string} type - Type of annotation
* @param {string} text - Text of annotation to save
* @return {void}
* @return {Promise} - Annotation create promise
*/
saveAnnotation(type, text) {
const annotationData = this.createAnnotationData(type, text);
Expand All @@ -149,30 +155,26 @@ class AnnotationThread extends EventEmitter {
// Changing state from pending
this.state = STATES.hover;
// Save annotation on server
this.annotationService
return this.annotationService
.create(annotationData)
.then((savedAnnotation) => this.updateTemporaryAnnotation(tempAnnotation, savedAnnotation))
.catch(() => {
// Remove temporary annotation
this.deleteAnnotation(tempAnnotationID, /* useServer */ false);

// Broadcast error
this.emit('annotationcreateerror');
});
.catch(() => this.handleThreadSaveError(tempAnnotationID));
}

/**
* Deletes an annotation.
*
* @param {string} annotationID - ID of annotation to delete
* @param {boolean} [useServer] - Whether or not to delete on server, default true
* @return {void}
* @return {Promise} - Annotation delete promise
*/
deleteAnnotation(annotationID, useServer = true) {
// Ignore if no corresponding annotation exists in thread or user doesn't have permissions
const annotation = this.annotations.find((annot) => annot.annotationID === annotationID);
if (!annotation || (annotation.permissions && !annotation.permissions.can_delete)) {
return;
// Broadcast error
this.emit(THREAD_EVENT.deleteError);
return Promise.reject();
}

// Delete annotation on client
Expand Down Expand Up @@ -200,31 +202,36 @@ class AnnotationThread extends EventEmitter {
this.dialog.removeAnnotation(annotationID);
}

// Delete annotation on server
if (useServer) {
this.annotationService
.delete(annotationID)
.then(() => {
// Ensures that blank highlight comment is also deleted when removing
// the last comment on a highlight
canDeleteAnnotation =
this.annotations.length > 0 &&
this.annotations[0].permissions &&
this.annotations[0].permissions.can_delete;
if (annotatorUtil.isPlainHighlight(this.annotations) && canDeleteAnnotation) {
this.annotationService.delete(this.annotations[0].annotationID);
}

// Broadcast thread cleanup if needed
if (this.annotations.length === 0) {
this.emit('threadcleanup');
}
})
.catch(() => {
// Broadcast error
this.emit('annotationdeleteerror');
});
if (!useServer) {
return Promise.resolve();
}

// Delete annotation on server
return this.annotationService
.delete(annotationID)
.then(() => {
// Ensures that blank highlight comment is also deleted when removing
// the last comment on a highlight
canDeleteAnnotation =
this.annotations.length > 0 &&
this.annotations[0].permissions &&
this.annotations[0].permissions.can_delete;
if (annotatorUtil.isPlainHighlight(this.annotations) && canDeleteAnnotation) {
this.annotationService.delete(this.annotations[0].annotationID);
}

// Broadcast thread cleanup if needed
if (this.annotations.length === 0) {
this.emit(THREAD_EVENT.threadCleanup);
}

// Broadcast annotation deletion event
this.emit(THREAD_EVENT.delete);
})
.catch(() => {
// Broadcast error
this.emit(THREAD_EVENT.deleteError);
});
}

/**
Expand Down Expand Up @@ -416,9 +423,34 @@ class AnnotationThread extends EventEmitter {
if (!annotatorUtil.isPending(this.state)) {
return;
}

this.emit(THREAD_EVENT.cancel);
this.destroy();
}

/**
* Generate threadData with relevant information to be emitted with an
* annotation thread event
*
* @protected
* @return {Object} threadData - Annotation event thread data
*/
getThreadEventData() {
const threadData = {
type: this.type,
threadID: this.threadID
};

if (this.annotationService.user.id > 0) {
threadData.userId = this.annotationService.user.id;
}
if (this.threadNumber) {
threadData.threadNumber = this.threadNumber;
}

return threadData;
}

//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
Expand Down Expand Up @@ -458,7 +490,7 @@ class AnnotationThread extends EventEmitter {
this.dialog.removeAnnotation(tempAnnotation.annotationID);
}

this.emit('annotationsaved');
this.emit(THREAD_EVENT.save);
}

/**
Expand Down Expand Up @@ -551,6 +583,36 @@ class AnnotationThread extends EventEmitter {
deleteAnnotationWithID(data) {
this.deleteAnnotation(data.annotationID);
}

/**
* Deletes the temporary annotation if the annotation failed to save on the server
*
* @private
* @param {string} tempAnnotationID - ID of temporary annotation to be updated with annotation from server
* @return {void}
*/
handleThreadSaveError(tempAnnotationID) {
// Remove temporary annotation
this.deleteAnnotation(tempAnnotationID, /* useServer */ false);

// Broadcast error
this.emit(THREAD_EVENT.createError);
}

/**
* Emits a generic viewer event
*
* @private
* @emits viewerevent
* @param {string} event - Event name
* @param {Object} eventData - Event data
* @return {void}
*/
emit(event, eventData) {
const threadData = this.getThreadEventData();
super.emit(event, { data: threadData, eventData });
super.emit('threadevent', { event, data: threadData, eventData });
}
}

export default AnnotationThread;

0 comments on commit dec86c6

Please sign in to comment.