Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Ensure drawings are only registered once with the controller #278

Merged
merged 4 commits into from
Nov 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 25 additions & 22 deletions src/AnnotationThread.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,25 @@ class AnnotationThread extends EventEmitter {

/** @param {Object} */
annotations: Object;

/** @param {AnnotationAPI} */
api: AnnotationAPI;

/** @param {string} */
fileVersionId: string;

/** @param {Location} */
location: ?Location;

/** @param {string} */
threadID: ?string;

/** @param {string} */
threadNumber: string;

/** @param {AnnotationType} */
type: AnnotationType;

/** @param {boolean} */
canComment: boolean;

Expand Down Expand Up @@ -120,10 +120,6 @@ class AnnotationThread extends EventEmitter {

this.element = null;
}

if (this.state !== STATES.pending) {
this.emit(THREAD_EVENT.threadDelete);
}
}

/**
Expand All @@ -147,7 +143,7 @@ class AnnotationThread extends EventEmitter {

/**
* Positions the annotation popover
*
*
* @return {void}
*/
position = () => {
Expand All @@ -157,14 +153,14 @@ class AnnotationThread extends EventEmitter {

/**
* Returns the parent element for the annotation popover
*
*
* @return {HTMLElement} Parent element for the annotation popover
*/
getPopoverParent() {
return util.shouldDisplayMobileUI(this.container)
? this.container
// $FlowFixMe
: util.getPageEl(this.annotatedElement, this.location.page);
: // $FlowFixMe
util.getPageEl(this.annotatedElement, this.location.page);
}

/**
Expand Down Expand Up @@ -251,16 +247,18 @@ class AnnotationThread extends EventEmitter {
this.renderAnnotationPopover();

// Save annotation on server
return this.api
// $FlowFixMe
.create(annotationData)
.then((savedAnnotation) => this.updateTemporaryAnnotation(id, savedAnnotation))
.catch((error) => this.handleThreadSaveError(error, id));
return (
this.api
// $FlowFixMe
.create(annotationData)
.then((savedAnnotation) => this.updateTemporaryAnnotation(id, savedAnnotation))
.catch((error) => this.handleThreadSaveError(error, id))
);
}

/**
* Update the annotation thread instance with annotation data/comments
*
*
* @param {Object} data - Annotation data
* @return {void}
*/
Expand Down Expand Up @@ -353,7 +351,6 @@ class AnnotationThread extends EventEmitter {

if (this.canDelete && this.comments.length <= 0) {
// If this annotation was the last one in the thread, destroy the thread
this.destroy();
this.threadID = null;
} else {
// Otherwise, display annotation with deleted comment
Expand All @@ -378,6 +375,12 @@ class AnnotationThread extends EventEmitter {
* @return {void}
*/
deleteErrorHandler(error: Error) {
// $FlowFixMe
const { page } = this.location;

// Re-render page
this.emit(THREAD_EVENT.render, { page });

// Broadcast error
this.emit(THREAD_EVENT.deleteError);
/* eslint-disable no-console */
Expand Down
2 changes: 1 addition & 1 deletion src/Annotator.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ class Annotator extends EventEmitter {
thread.renderAnnotationPopover();
thread.save(TYPES.point, commentText);

this.emit(THREAD_EVENT.threadSave, thread.getThreadEventData());
this.emit(THREAD_EVENT.save, thread.getThreadEventData());
return thread;
}

Expand Down
9 changes: 0 additions & 9 deletions src/__tests__/AnnotationThread-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,6 @@ describe('AnnotationThread', () => {
it('should unbind listeners and remove thread element and broadcast that the thread was deleted', () => {
thread.destroy();
expect(thread.unbindDOMListeners).toBeCalled();
expect(thread.emit).not.toBeCalledWith(THREAD_EVENT.threadDelete);
expect(thread.unmountPopover).toBeCalled();
});

it('should emit annotationthreaddeleted only if thread is not in a pending state', () => {
thread.state = STATES.inactive;
thread.destroy();
expect(thread.unbindDOMListeners).toBeCalled();
expect(thread.emit).toBeCalledWith(THREAD_EVENT.threadDelete);
expect(thread.unmountPopover).toBeCalled();
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/Annotator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ describe('Annotator', () => {

expect(thread.state).toEqual(STATES.active);
expect(thread.save).toBeCalledWith(TYPES.point, 'text');
expect(annotator.emit).toBeCalledWith(THREAD_EVENT.threadSave, expect.any(Object));
expect(annotator.emit).toBeCalledWith(THREAD_EVENT.save, expect.any(Object));
expect(result).not.toBeNull();
expect(thread.renderAnnotationPopover).toBeCalled();
});
Expand Down
2 changes: 0 additions & 2 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,6 @@ export const ANNOTATOR_EVENT = {

export const THREAD_EVENT = {
pending: 'annotationpending',
threadSave: 'annotationthreadsaved',
threadDelete: 'annotationthreaddeleted',
render: 'annotationrender',
save: 'annotationsaved',
delete: 'annotationdeleted',
Expand Down
34 changes: 21 additions & 13 deletions src/controllers/AnnotationModeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,6 @@ class AnnotationModeController extends EventEmitter {
constructor(annotatorType: string): void {
super();
this.annotatorType = annotatorType;

// $FlowFixMe
this.unregisterThread = this.unregisterThread.bind(this);
}

/**
Expand Down Expand Up @@ -136,7 +133,8 @@ class AnnotationModeController extends EventEmitter {
destroy(): void {
Object.keys(this.annotations).forEach((pageNum) => {
const pageThreads = this.annotations[pageNum].all() || [];
pageThreads.forEach(this.unregisterThread);
pageThreads.forEach((thread) => thread.removeListener('threadevent', this.handleThreadEvents));
this.annotations[pageNum].clear();
});

if (this.buttonEl) {
Expand Down Expand Up @@ -365,10 +363,21 @@ class AnnotationModeController extends EventEmitter {
return thread;
}

/**
* Custom comparator function to remove annotations from the rbush
*
* @param {DrawingThread} currentAnnotation current annotation in rbush
* @param {DrawingThread} annotationToRemove annotation which is being unregistered
* @return {boolean} Whether or not the
*/
deleteAnnotation(currentAnnotation: DrawingThread, annotationToRemove: DrawingThread): boolean {
return currentAnnotation.id === annotationToRemove.id;
}

/**
* Unregister a previously registered thread
*
*
*
* @param {AnnotationThread} thread - The thread to unregister with the controller
* @param {Object} annotation - The annotation with comments to register with the controller
* @return {void}
Expand All @@ -379,6 +388,9 @@ class AnnotationModeController extends EventEmitter {
}

this.annotations[thread.location.page].remove(thread);
if (this.annotations[thread.location.page].data.children.length === 0) {
delete this.annotations[thread.location.page];
}
this.emit(CONTROLLER_EVENT.unregister, thread);
thread.removeListener('threadevent', this.handleThreadEvents);
}
Expand Down Expand Up @@ -461,7 +473,7 @@ class AnnotationModeController extends EventEmitter {

/**
* Clean up any selected annotations
*
*
* @return {void}
*/
removeSelection(): void {}
Expand Down Expand Up @@ -500,22 +512,18 @@ class AnnotationModeController extends EventEmitter {
this.visibleThreadID = null;
break;
case THREAD_EVENT.render:
if (eventData) {
this.renderPage(eventData);
if (eventData && eventData.page) {
this.renderPage(eventData.page);
} else {
this.render();
}

break;
case THREAD_EVENT.delete:
// Thread should be cleaned up, unbind listeners - we
// don't do this in annotationdelete listener since thread
// may still need to respond to error messages
this.unregisterThread(thread);
break;
case THREAD_EVENT.threadDelete:
// Thread was deleted, remove from thread map
this.unregisterThread(thread);
this.emit(event, threadData);
break;
case THREAD_EVENT.deleteError:
Expand Down
7 changes: 5 additions & 2 deletions src/controllers/DrawingModeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ class DrawingModeController extends AnnotationModeController {
* @return {void}
*/
postDrawing(): void {
if (this.currentThread && this.currentThread.state === STATES.pending) {
if (
this.currentThread &&
this.currentThread.state === STATES.pending &&
this.currentThread.pathContainer.length > 0
) {
this.currentThread.save(TYPES.draw);
}

Expand Down Expand Up @@ -292,7 +296,6 @@ class DrawingModeController extends AnnotationModeController {

this.currentThread = undefined;
this.selectedThread = thread;
this.registerThread(thread);
this.unbindListeners();

// Clear existing canvases
Expand Down
15 changes: 4 additions & 11 deletions src/controllers/__tests__/AnnotationModeController-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,9 @@ describe('controllers/AnnotationModeController', () => {

it('should internally keep track of the registered thread', () => {
// eslint-disable-next-line new-cap
const pageThreads = {
all: jest.fn().mockReturnValue([thread]),
remove: jest.fn()
};
const pageThreads = new rbush();
pageThreads.all = jest.fn().mockReturnValue([thread]);
pageThreads.remove = jest.fn();

controller.annotations = { 1: pageThreads };

Expand Down Expand Up @@ -468,19 +467,13 @@ describe('controllers/AnnotationModeController', () => {
});

it('should re-render the annotations on render', () => {
controller.handleThreadEvents(thread, { event: THREAD_EVENT.render, eventData: 1 });
controller.handleThreadEvents(thread, { event: THREAD_EVENT.render, eventData: { page: 1 } });
expect(controller.renderPage).toBeCalled();

controller.handleThreadEvents(thread, { event: THREAD_EVENT.render });
expect(controller.render).toBeCalled();
});

it('should unregister thread on threadDelete', () => {
controller.handleThreadEvents(thread, { event: THREAD_EVENT.threadDelete, data: {} });
expect(controller.unregisterThread).toBeCalled();
expect(controller.emit).toBeCalledWith(THREAD_EVENT.threadDelete, expect.any(Object));
});

it('should unregister thread on deleteError', () => {
controller.handleThreadEvents(thread, { event: THREAD_EVENT.deleteError, data: {} });
expect(controller.emit).toBeCalledWith(CONTROLLER_EVENT.error, controller.localized.deleteError);
Expand Down
1 change: 0 additions & 1 deletion src/controllers/__tests__/DrawingModeController-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,6 @@ describe('controllers/DrawingModeController', () => {
});
expect(controller.unbindListeners).toBeCalled();
expect(controller.bindListeners).toBeCalled();
expect(controller.registerThread).toBeCalled();
expect(controller.currentThread).toBeUndefined();
expect(thread.handleStart).not.toBeCalled();
expect(thread.removeListener).toBeCalledWith('threadevent', expect.any(Function));
Expand Down
22 changes: 11 additions & 11 deletions src/doc/DocAnnotator.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class DocAnnotator extends Annotator {

if (annotationType === TYPES.point) {
let clientEvent = event;

// $FlowFixMe
if (this.hasTouch && event.targetTouches) {
if (event.targetTouches.length <= 0) {
Expand Down Expand Up @@ -383,7 +383,7 @@ class DocAnnotator extends Annotator {

/**
* Handles click events when not in an annotation mode
*
*
* @param {Event} event - Mouse event
* @return {void}
*/
Expand Down Expand Up @@ -428,9 +428,9 @@ class DocAnnotator extends Annotator {
};

/**
* Hides the create highlight dialog
*
* @param {Event} event - Mouse event
* Hides the create highlight dialog
*
* @param {Event} event - Mouse event
* @return {void}
*/
hideCreateDialog(event: ?Event) {
Expand Down Expand Up @@ -513,7 +513,7 @@ class DocAnnotator extends Annotator {

thread.state = STATES.active;
thread.save(highlightType, commentText);
this.emit(THREAD_EVENT.threadSave, thread.getThreadEventData());
this.emit(THREAD_EVENT.save, thread.getThreadEventData());
return thread;
}

Expand Down Expand Up @@ -576,7 +576,7 @@ class DocAnnotator extends Annotator {
}

let mouseEvent = event;

// $FlowFixMe
if (this.hasTouch && event.targetTouches) {
mouseEvent = event.targetTouches[0];
Expand Down Expand Up @@ -631,7 +631,7 @@ class DocAnnotator extends Annotator {
*/
highlightMousedownHandler = (event: Event) => {
this.mouseDownEvent = event;

// $FlowFixMe
if (this.hasTouch && event.targetTouches) {
this.mouseDownEvent = event.targetTouches[0];
Expand Down Expand Up @@ -668,7 +668,7 @@ class DocAnnotator extends Annotator {
}
return isPending;
});

// $FlowFixMe
return isPending || this.createHighlightDialog.isVisible;
}
Expand All @@ -693,7 +693,7 @@ class DocAnnotator extends Annotator {
}

let mouseUpEvent = event;

// $FlowFixMe
if (this.hasTouch && event.targetTouches) {
mouseUpEvent = event.targetTouches[0];
Expand Down Expand Up @@ -753,7 +753,7 @@ class DocAnnotator extends Annotator {
if (!this.plainHighlightEnabled && !this.commentHighlightEnabled) {
return false;
}

// $FlowFixMe
if (this.createHighlightDialog.isVisible) {
return true;
Expand Down
Loading