diff --git a/src/lib/viewers/BaseViewer.js b/src/lib/viewers/BaseViewer.js index f41a2b685..e707abc04 100644 --- a/src/lib/viewers/BaseViewer.js +++ b/src/lib/viewers/BaseViewer.js @@ -161,10 +161,7 @@ class BaseViewer extends EventEmitter { this.mobileZoomChangeHandler = this.mobileZoomChangeHandler.bind(this); this.mobileZoomEndHandler = this.mobileZoomEndHandler.bind(this); this.handleAnnotatorEvents = this.handleAnnotatorEvents.bind(this); - this.handleAnnotationCreateEvent = this.handleAnnotationCreateEvent.bind(this); - this.handleAnnotationControlsClick = this.handleAnnotationControlsClick.bind(this); this.handleAnnotationControlsEscape = this.handleAnnotationControlsEscape.bind(this); - this.handleAnnotationCreatorChangeEvent = this.handleAnnotationCreatorChangeEvent.bind(this); this.handleFullscreenEnter = this.handleFullscreenEnter.bind(this); this.handleFullscreenExit = this.handleFullscreenExit.bind(this); this.createAnnotator = this.createAnnotator.bind(this); @@ -593,10 +590,6 @@ class BaseViewer extends EventEmitter { if (this.annotator && this.areNewAnnotationsEnabled()) { this.annotator.emit(ANNOTATOR_EVENT.setVisibility, true); - if (this.options.enableAnnotationsDiscoverability) { - this.annotator.toggleAnnotationMode(AnnotationMode.REGION); - this.processAnnotationModeChange(this.annotationControlsFSM.transition(AnnotationInput.RESET)); - } this.enableAnnotationControls(); } } @@ -989,7 +982,7 @@ class BaseViewer extends EventEmitter { const annotatorOptions = this.createAnnotatorOptions({ annotator: this.annotatorConf, features: options && options.features, - initialMode: this.options.enableAnnotationsDiscoverability ? AnnotationMode.REGION : AnnotationMode.NONE, + initialMode: this.getInitialAnnotationMode(), intl: (options && options.intl) || intlUtil.createAnnotatorIntl(), modeButtons: ANNOTATION_BUTTONS, }); @@ -1003,6 +996,10 @@ class BaseViewer extends EventEmitter { } } + getInitialAnnotationMode() { + return AnnotationMode.NONE; + } + /** * Initializes annotations. * @@ -1033,12 +1030,6 @@ class BaseViewer extends EventEmitter { // Add a custom listener for events emmited by the annotator this.annotator.addListener('annotatorevent', this.handleAnnotatorEvents); - - if (this.areNewAnnotationsEnabled() && this.annotationControls) { - this.annotator.addListener('annotations_create', this.handleAnnotationCreateEvent); - this.annotator.addListener('creator_staged_change', this.handleAnnotationCreatorChangeEvent); - this.annotator.addListener('creator_status_change', this.handleAnnotationCreatorChangeEvent); - } } /** @@ -1088,12 +1079,8 @@ class BaseViewer extends EventEmitter { * @return {void} */ handleAnnotationControlsEscape() { - if (this.options.enableAnnotationsDiscoverability) { - this.annotator.toggleAnnotationMode(AnnotationMode.REGION); - this.processAnnotationModeChange(this.annotationControlsFSM.transition(AnnotationInput.RESET)); - } else { - this.annotator.toggleAnnotationMode(AnnotationMode.NONE); - } + this.processAnnotationModeChange(this.annotationControlsFSM.transition(AnnotationInput.RESET)); + this.annotator.toggleAnnotationMode(AnnotationMode.NONE); } /** @@ -1122,23 +1109,6 @@ class BaseViewer extends EventEmitter { } }; - /** - * Handler for annotation controls button click event. - * - * @private - * @param {AnnotationMode} mode one of annotation modes - * @return {void} - */ - handleAnnotationControlsClick({ mode }) { - const nextMode = this.annotationControlsFSM.transition(AnnotationInput.CLICK, mode); - this.annotator.toggleAnnotationMode( - this.options.enableAnnotationsDiscoverability && nextMode === AnnotationMode.NONE - ? AnnotationMode.REGION - : nextMode, - ); - this.processAnnotationModeChange(nextMode); - } - /** * Handles the 'scrolltoannotation' event and calls the annotator scroll method * @param {string | Object} event - Annotation Event @@ -1303,20 +1273,6 @@ class BaseViewer extends EventEmitter { this.emit('annotatorevent', data); } - handleAnnotationCreateEvent({ annotation: { id } = {}, meta: { status } = {} }) { - // Only on success do we exit create annotation mode. If error occurs, - // we remain in create mode - if (status === 'success') { - this.annotator.emit('annotations_active_set', id); - - this.processAnnotationModeChange(this.annotationControlsFSM.transition(AnnotationInput.SUCCESS)); - } - } - - handleAnnotationCreatorChangeEvent({ status, type }) { - this.processAnnotationModeChange(this.annotationControlsFSM.transition(status, type)); - } - /** * Creates combined options to give to the annotator * diff --git a/src/lib/viewers/__tests__/BaseViewer-test.js b/src/lib/viewers/__tests__/BaseViewer-test.js index c002bddd3..bbcdfb8cd 100644 --- a/src/lib/viewers/__tests__/BaseViewer-test.js +++ b/src/lib/viewers/__tests__/BaseViewer-test.js @@ -9,7 +9,6 @@ import intl from '../../i18n'; import * as util from '../../util'; import * as icons from '../../icons/icons'; import * as constants from '../../constants'; -import { AnnotationInput } from '../../AnnotationControlsFSM'; import { AnnotationMode } from '../../AnnotationControls'; import { EXCLUDED_EXTENSIONS } from '../../extensions'; import { VIEWER_EVENT, LOAD_METRIC, ERROR_CODE } from '../../events'; @@ -574,28 +573,6 @@ describe('lib/viewers/BaseViewer', () => { expect(base.annotator.emit).toBeCalledWith(ANNOTATOR_EVENT.setVisibility, true); expect(base.enableAnnotationControls).toBeCalled(); }); - - test(`should show annotations and toggle annotations mode to REGION if enableAnnotationsDiscoverability is true`, () => { - jest.spyOn(base, 'areNewAnnotationsEnabled').mockReturnValue(true); - jest.spyOn(base, 'enableAnnotationControls').mockImplementation(); - - base.annotator = { - emit: jest.fn(), - toggleAnnotationMode: jest.fn(), - }; - base.annotationControls = { - destroy: jest.fn(), - }; - base.options.enableAnnotationsDiscoverability = true; - base.processAnnotationModeChange = jest.fn(); - - base.handleFullscreenExit(); - - expect(base.annotator.emit).toBeCalledWith(ANNOTATOR_EVENT.setVisibility, true); - expect(base.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.REGION); - expect(base.processAnnotationModeChange).toBeCalledWith(AnnotationMode.NONE); - expect(base.enableAnnotationControls).toBeCalled(); - }); }); describe('resize()', () => { @@ -1227,22 +1204,6 @@ describe('lib/viewers/BaseViewer', () => { expect(base.createAnnotatorOptions).toBeCalledWith(expect.objectContaining(annotationsOptions)); }); - test('should create annotator with initial mode region if discoverability is enabled', () => { - jest.spyOn(base, 'areAnnotationsEnabled').mockReturnValue(true); - jest.spyOn(base, 'createAnnotatorOptions').mockImplementation(); - - base.options.boxAnnotations = { - determineAnnotator: jest.fn().mockReturnValue(conf), - }; - base.options.enableAnnotationsDiscoverability = true; - - base.createAnnotator(); - - expect(base.createAnnotatorOptions).toBeCalledWith( - expect.objectContaining({ initialMode: AnnotationMode.REGION }), - ); - }); - test('should emit annotator_create event', () => { jest.spyOn(base, 'areAnnotationsEnabled').mockReturnValue(true); @@ -1291,20 +1252,6 @@ describe('lib/viewers/BaseViewer', () => { expect(base.addListener).toBeCalledWith('scale', expect.any(Function)); expect(base.addListener).toBeCalledWith('scrolltoannotation', base.handleScrollToAnnotation); expect(base.annotator.addListener).toBeCalledWith('annotatorevent', expect.any(Function)); - expect(base.annotator.addListener).toBeCalledWith('annotations_create', base.handleAnnotationCreateEvent); - expect(base.annotator.addListener).toBeCalledWith( - 'annotations_initialized', - base.handleAnnotationsInitialized, - ); - expect(base.annotator.addListener).toBeCalledWith( - 'creator_staged_change', - base.handleAnnotationCreatorChangeEvent, - ); - expect(base.annotator.addListener).toBeCalledWith( - 'creator_status_change', - base.handleAnnotationCreatorChangeEvent, - ); - expect(base.emit).toBeCalledWith('annotator', base.annotator); }); test('should call the correct handler to toggle annotation modes', () => { @@ -1805,107 +1752,16 @@ describe('lib/viewers/BaseViewer', () => { }); }); - describe('handleAnnotationCreateEvent()', () => { - beforeEach(() => { - base.annotator = { - emit: jest.fn(), - }; - base.annotationControls = { - destroy: jest.fn(), - setMode: jest.fn(), - }; - base.processAnnotationModeChange = jest.fn(); - }); - - const createEvent = status => ({ - annotation: { id: '123' }, - meta: { - status, - }, - }); - - ['error', 'pending'].forEach(status => { - test(`should not do anything if status is ${status}`, () => { - const event = createEvent(status); - base.handleAnnotationCreateEvent(event); - - expect(base.annotator.emit).not.toBeCalled(); - }); - }); - - test('should reset controls if status is success', () => { - const event = createEvent('success'); - base.handleAnnotationCreateEvent(event); - - expect(base.annotator.emit).toBeCalledWith('annotations_active_set', '123'); - expect(base.processAnnotationModeChange).toBeCalledWith(AnnotationMode.NONE); - }); - }); - - describe('handleAnnotationCreatorChangeEvent()', () => { - test('should set mode', () => { - base.annotationControls = { - destroy: jest.fn(), - setMode: jest.fn(), - }; - - base.processAnnotationModeChange = jest.fn(); - base.handleAnnotationCreatorChangeEvent({ status: AnnotationInput.CREATE, type: AnnotationMode.HIGHLIGHT }); - - expect(base.processAnnotationModeChange).toBeCalledWith(AnnotationMode.HIGHLIGHT); - }); - }); - describe('handleAnnotationControlsEscape()', () => { - test('should call toggleAnnotationMode with AnnotationMode.NONE if enableAnnotationsDiscoverability is false', () => { + test('should call toggleAnnotationMode with AnnotationMode.NONE', () => { base.annotator = { toggleAnnotationMode: jest.fn(), }; - base.options.enableAnnotationsDiscoverability = false; base.handleAnnotationControlsEscape(); expect(base.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.NONE); }); - - test('should reset annotationControlsFSM state and call toggleAnnotationMode with AnnotationMode.REGION if enableAnnotationsDiscoverability is true', () => { - base.annotator = { - toggleAnnotationMode: jest.fn(), - }; - base.options.enableAnnotationsDiscoverability = true; - base.processAnnotationModeChange = jest.fn(); - - base.handleAnnotationControlsEscape(); - - expect(base.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.REGION); - expect(base.processAnnotationModeChange).toBeCalledWith(AnnotationMode.NONE); - }); - }); - - describe('handleAnnotationControlsClick', () => { - beforeEach(() => { - base.annotator = { - toggleAnnotationMode: jest.fn(), - }; - base.processAnnotationModeChange = jest.fn(); - }); - - test('should call toggleAnnotationMode and processAnnotationModeChange', () => { - base.handleAnnotationControlsClick({ mode: AnnotationMode.REGION }); - - expect(base.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.REGION); - expect(base.processAnnotationModeChange).toBeCalledWith(AnnotationMode.REGION); - }); - - test('should call toggleAnnotationMode with appropriate mode if discoverability is enabled', () => { - base.options.enableAnnotationsDiscoverability = false; - base.handleAnnotationControlsClick({ mode: AnnotationMode.NONE }); - expect(base.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.NONE); - - base.options.enableAnnotationsDiscoverability = true; - base.handleAnnotationControlsClick({ mode: AnnotationMode.NONE }); - expect(base.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.REGION); - }); }); describe('processAnnotationModeChange()', () => { @@ -1954,4 +1810,10 @@ describe('lib/viewers/BaseViewer', () => { }); }); }); + + describe('getInitialAnnotationMode()', () => { + test('should return none as initial mode', () => { + expect(base.getInitialAnnotationMode()).toBe(AnnotationMode.NONE); + }); + }); }); diff --git a/src/lib/viewers/doc/DocBaseViewer.js b/src/lib/viewers/doc/DocBaseViewer.js index 87a500a94..19465703d 100644 --- a/src/lib/viewers/doc/DocBaseViewer.js +++ b/src/lib/viewers/doc/DocBaseViewer.js @@ -1,5 +1,5 @@ import throttle from 'lodash/throttle'; -import AnnotationControls from '../../AnnotationControls'; +import AnnotationControls, { AnnotationMode } from '../../AnnotationControls'; import BaseViewer from '../BaseViewer'; import Browser from '../../Browser'; import Controls from '../../Controls'; @@ -10,6 +10,7 @@ import Popup from '../../Popup'; import RepStatus from '../../RepStatus'; import PreviewError from '../../PreviewError'; import ThumbnailsSidebar from '../../ThumbnailsSidebar'; +import { AnnotationInput } from '../../AnnotationControlsFSM'; import { ANNOTATOR_EVENT, CLASS_BOX_PREVIEW_THUMBNAILS_CLOSE_ACTIVE, @@ -92,10 +93,15 @@ class DocBaseViewer extends BaseViewer { */ constructor(options) { super(options); + // Bind context for callbacks this.emitMetric = this.emitMetric.bind(this); this.handleAssetAndRepLoad = this.handleAssetAndRepLoad.bind(this); this.handleFindBarClose = this.handleFindBarClose.bind(this); + this.handleAnnotationControlsClick = this.handleAnnotationControlsClick.bind(this); + this.handleAnnotationControlsEscape = this.handleAnnotationControlsEscape.bind(this); + this.handleAnnotationCreateEvent = this.handleAnnotationCreateEvent.bind(this); + this.handleAnnotationCreatorChangeEvent = this.handleAnnotationCreatorChangeEvent.bind(this); this.onThumbnailSelectHandler = this.onThumbnailSelectHandler.bind(this); this.pagechangingHandler = this.pagechangingHandler.bind(this); this.pagerenderedHandler = this.pagerenderedHandler.bind(this); @@ -1262,6 +1268,11 @@ class DocBaseViewer extends BaseViewer { handleFullscreenExit() { this.pdfViewer.currentScaleValue = 'auto'; super.handleFullscreenExit(); + + if (this.annotator && this.areNewAnnotationsEnabled() && this.options.enableAnnotationsDiscoverability) { + this.annotator.toggleAnnotationMode(AnnotationMode.REGION); + this.processAnnotationModeChange(this.annotationControlsFSM.transition(AnnotationInput.RESET)); + } } /** @@ -1555,6 +1566,54 @@ class DocBaseViewer extends BaseViewer { // For documents of only 1 page, default thumbnails as closed return toggledState && numPages > 1; } + + // Annotation overrides + getInitialAnnotationMode() { + return this.options.enableAnnotationsDiscoverability ? AnnotationMode.REGION : AnnotationMode.NONE; + } + + initAnnotations() { + super.initAnnotations(); + + if (this.areNewAnnotationsEnabled() && this.annotationControls) { + this.annotator.addListener('annotations_create', this.handleAnnotationCreateEvent); + this.annotator.addListener('creator_staged_change', this.handleAnnotationCreatorChangeEvent); + this.annotator.addListener('creator_status_change', this.handleAnnotationCreatorChangeEvent); + } + } + + handleAnnotationControlsClick({ mode }) { + const nextMode = this.annotationControlsFSM.transition(AnnotationInput.CLICK, mode); + this.annotator.toggleAnnotationMode( + this.options.enableAnnotationsDiscoverability && nextMode === AnnotationMode.NONE + ? AnnotationMode.REGION + : nextMode, + ); + this.processAnnotationModeChange(nextMode); + } + + handleAnnotationControlsEscape() { + if (this.options.enableAnnotationsDiscoverability) { + this.annotator.toggleAnnotationMode(AnnotationMode.REGION); + this.processAnnotationModeChange(this.annotationControlsFSM.transition(AnnotationInput.RESET)); + } else { + this.annotator.toggleAnnotationMode(AnnotationMode.NONE); + } + } + + handleAnnotationCreateEvent({ annotation: { id } = {}, meta: { status } = {} }) { + // Only on success do we exit create annotation mode. If error occurs, + // we remain in create mode + if (status === 'success') { + this.annotator.emit('annotations_active_set', id); + + this.processAnnotationModeChange(this.annotationControlsFSM.transition(AnnotationInput.SUCCESS)); + } + } + + handleAnnotationCreatorChangeEvent({ status, type }) { + this.processAnnotationModeChange(this.annotationControlsFSM.transition(status, type)); + } } export default DocBaseViewer; diff --git a/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js b/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js index ee715b764..ab1abf414 100644 --- a/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js +++ b/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js @@ -1,19 +1,21 @@ /* eslint-disable no-unused-expressions */ import Api from '../../../api'; +import AnnotationControls, { AnnotationMode } from '../../../AnnotationControls'; import DocBaseViewer from '../DocBaseViewer'; import DocFindBar from '../DocFindBar'; import Browser from '../../../Browser'; import BaseViewer from '../../BaseViewer'; import Controls from '../../../Controls'; -import AnnotationControls from '../../../AnnotationControls'; import PageControls from '../../../PageControls'; import ZoomControls from '../../../ZoomControls'; import fullscreen from '../../../Fullscreen'; import DocPreloader from '../DocPreloader'; +import { AnnotationInput } from '../../../AnnotationControlsFSM'; import * as file from '../../../file'; import * as util from '../../../util'; import { + ANNOTATOR_EVENT, CLASS_HIDDEN, PERMISSION_DOWNLOAD, STATUS_ERROR, @@ -1934,17 +1936,48 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { }); describe('handleFullscreenExit()', () => { - test('should update the scale value, and resize the page', () => { + beforeEach(() => { + jest.spyOn(docBase, 'areNewAnnotationsEnabled').mockReturnValue(true); + jest.spyOn(docBase, 'enableAnnotationControls').mockImplementation(); + docBase.pdfViewer = { presentationModeState: 'fullscreen', currentScaleValue: 'pagefit', }; + docBase.annotator = { + emit: jest.fn(), + toggleAnnotationMode: jest.fn(), + }; + docBase.annotationControls = { + destroy: jest.fn(), + }; + docBase.processAnnotationModeChange = jest.fn(); + }); + + test('should update the scale value, and resize the page', () => { const resizeStub = jest.spyOn(docBase, 'resize').mockImplementation(); docBase.handleFullscreenExit(); expect(resizeStub).toBeCalled(); expect(docBase.pdfViewer.currentScaleValue).toBe('auto'); }); + + test('should not change mode if enableAnnotationsDiscoverability is false', () => { + docBase.handleFullscreenExit(); + + expect(docBase.annotator.toggleAnnotationMode).not.toBeCalled(); + }); + + test(`should show annotations and toggle annotations mode to REGION if enableAnnotationsDiscoverability is true`, () => { + docBase.options.enableAnnotationsDiscoverability = true; + + docBase.handleFullscreenExit(); + + expect(docBase.annotator.emit).toBeCalledWith(ANNOTATOR_EVENT.setVisibility, true); + expect(docBase.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.REGION); + expect(docBase.processAnnotationModeChange).toBeCalledWith(AnnotationMode.NONE); + expect(docBase.enableAnnotationControls).toBeCalled(); + }); }); describe('getScrollHandler()', () => { @@ -2656,5 +2689,185 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { expect(docBase.shouldThumbnailsBeToggled()).toBe(false); }); }); + + describe('initAnnotations()', () => { + beforeEach(() => { + docBase.options = { + container: document, + file: { + file_version: { + id: 123, + }, + }, + location: { + locale: 'en-US', + }, + }; + docBase.scale = 1.5; + docBase.annotator = { + init: jest.fn(), + addListener: jest.fn(), + }; + docBase.annotatorConf = { + CONSTRUCTOR: jest.fn(() => docBase.annotator), + }; + docBase.annotationControls = { + destroy: jest.fn(), + resetControls: jest.fn(), + }; + + jest.spyOn(docBase, 'areNewAnnotationsEnabled').mockReturnValue(true); + }); + + test('should initialize the annotator', () => { + jest.spyOn(docBase, 'emit'); + docBase.addListener = jest.fn(); + docBase.initAnnotations(); + + expect(docBase.annotator.init).toBeCalledWith(1.5); + expect(docBase.addListener).toBeCalledWith('toggleannotationmode', expect.any(Function)); + expect(docBase.addListener).toBeCalledWith('scale', expect.any(Function)); + expect(docBase.addListener).toBeCalledWith('scrolltoannotation', docBase.handleScrollToAnnotation); + expect(docBase.annotator.addListener).toBeCalledWith('annotatorevent', expect.any(Function)); + expect(docBase.annotator.addListener).toBeCalledWith( + 'annotations_create', + docBase.handleAnnotationCreateEvent, + ); + expect(docBase.annotator.addListener).toBeCalledWith( + 'annotations_initialized', + docBase.handleAnnotationsInitialized, + ); + expect(docBase.annotator.addListener).toBeCalledWith( + 'creator_staged_change', + docBase.handleAnnotationCreatorChangeEvent, + ); + expect(docBase.annotator.addListener).toBeCalledWith( + 'creator_status_change', + docBase.handleAnnotationCreatorChangeEvent, + ); + expect(docBase.emit).toBeCalledWith('annotator', docBase.annotator); + }); + }); + + describe('handleAnnotationCreateEvent()', () => { + beforeEach(() => { + docBase.annotator = { + emit: jest.fn(), + }; + docBase.annotationControls = { + destroy: jest.fn(), + setMode: jest.fn(), + }; + docBase.processAnnotationModeChange = jest.fn(); + }); + + const createEvent = status => ({ + annotation: { id: '123' }, + meta: { + status, + }, + }); + + ['error', 'pending'].forEach(status => { + test(`should not do anything if status is ${status}`, () => { + const event = createEvent(status); + docBase.handleAnnotationCreateEvent(event); + + expect(docBase.annotator.emit).not.toBeCalled(); + }); + }); + + test('should reset controls if status is success', () => { + const event = createEvent('success'); + docBase.handleAnnotationCreateEvent(event); + + expect(docBase.annotator.emit).toBeCalledWith('annotations_active_set', '123'); + expect(docBase.processAnnotationModeChange).toBeCalledWith(AnnotationMode.NONE); + }); + }); + + describe('handleAnnotationCreatorChangeEvent()', () => { + test('should set mode', () => { + docBase.annotationControls = { + destroy: jest.fn(), + setMode: jest.fn(), + }; + + docBase.processAnnotationModeChange = jest.fn(); + docBase.handleAnnotationCreatorChangeEvent({ + status: AnnotationInput.CREATE, + type: AnnotationMode.HIGHLIGHT, + }); + + expect(docBase.processAnnotationModeChange).toBeCalledWith(AnnotationMode.HIGHLIGHT); + }); + }); + + describe('handleAnnotationControlsEscape()', () => { + test('should reset annotationControlsFSM state and call toggleAnnotationMode with AnnotationMode.REGION if enableAnnotationsDiscoverability is true', () => { + docBase.annotator = { + toggleAnnotationMode: jest.fn(), + }; + docBase.options.enableAnnotationsDiscoverability = true; + docBase.processAnnotationModeChange = jest.fn(); + + docBase.handleAnnotationControlsEscape(); + + expect(docBase.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.REGION); + expect(docBase.processAnnotationModeChange).toBeCalledWith(AnnotationMode.NONE); + }); + + test('should set annotations mode to none', () => { + docBase.annotator = { + toggleAnnotationMode: jest.fn(), + }; + docBase.processAnnotationModeChange = jest.fn(); + + docBase.handleAnnotationControlsEscape(); + + expect(docBase.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.NONE); + expect(docBase.processAnnotationModeChange).not.toBeCalled(); + }); + }); + + describe('handleAnnotationControlsClick', () => { + beforeEach(() => { + docBase.annotator = { + toggleAnnotationMode: jest.fn(), + }; + docBase.processAnnotationModeChange = jest.fn(); + }); + + test('should call toggleAnnotationMode and processAnnotationModeChange', () => { + docBase.handleAnnotationControlsClick({ mode: AnnotationMode.REGION }); + + expect(docBase.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.REGION); + expect(docBase.processAnnotationModeChange).toBeCalledWith(AnnotationMode.REGION); + }); + + test('should call toggleAnnotationMode with appropriate mode if discoverability is enabled', () => { + docBase.options.enableAnnotationsDiscoverability = false; + docBase.handleAnnotationControlsClick({ mode: AnnotationMode.NONE }); + expect(docBase.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.NONE); + + docBase.options.enableAnnotationsDiscoverability = true; + docBase.handleAnnotationControlsClick({ mode: AnnotationMode.NONE }); + expect(docBase.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.REGION); + }); + }); + + describe('getInitialAnnotationMode()', () => { + test.each` + enableAnnotationsDiscoverability | expectedMode + ${false} | ${AnnotationMode.NONE} + ${true} | ${AnnotationMode.REGION} + `( + 'should return initial annotations mode based as $expectedMode if enableAnnotationsDiscoverability is $enableAnnotationsDiscoverability', + ({ enableAnnotationsDiscoverability, expectedMode }) => { + docBase.options.enableAnnotationsDiscoverability = enableAnnotationsDiscoverability; + expect(docBase.getInitialAnnotationMode()).toBe(expectedMode); + }, + ); + }); }); }); diff --git a/src/lib/viewers/image/ImageViewer.js b/src/lib/viewers/image/ImageViewer.js index 6bdf68052..b713ae5c3 100644 --- a/src/lib/viewers/image/ImageViewer.js +++ b/src/lib/viewers/image/ImageViewer.js @@ -1,5 +1,6 @@ import AnnotationControls from '../../AnnotationControls'; import ImageBaseViewer from './ImageBaseViewer'; +import { AnnotationInput } from '../../AnnotationControlsFSM'; import { CLASS_INVISIBLE } from '../../constants'; import { ICON_FULLSCREEN_IN, ICON_FULLSCREEN_OUT, ICON_ROTATE_LEFT } from '../../icons/icons'; import './Image.scss'; @@ -15,8 +16,9 @@ class ImageViewer extends ImageBaseViewer { this.api = options.api; this.rotateLeft = this.rotateLeft.bind(this); this.updatePannability = this.updatePannability.bind(this); - this.handleImageDownloadError = this.handleImageDownloadError.bind(this); + this.handleAnnotationControlsClick = this.handleAnnotationControlsClick.bind(this); this.handleAssetAndRepLoad = this.handleAssetAndRepLoad.bind(this); + this.handleImageDownloadError = this.handleImageDownloadError.bind(this); if (this.isMobile) { this.handleOrientationChange = this.handleOrientationChange.bind(this); @@ -428,6 +430,19 @@ class ImageViewer extends ImageBaseViewer { rotationAngle: this.rotationAngle, }); } + + /** + * Handler for annotation controls button click event. + * + * @private + * @param {AnnotationMode} mode one of annotation modes + * @return {void} + */ + handleAnnotationControlsClick({ mode }) { + const nextMode = this.annotationControlsFSM.transition(AnnotationInput.CLICK, mode); + this.annotator.toggleAnnotationMode(nextMode); + this.processAnnotationModeChange(nextMode); + } } export default ImageViewer; diff --git a/src/lib/viewers/image/__tests__/ImageViewer-test.js b/src/lib/viewers/image/__tests__/ImageViewer-test.js index 911a63cde..404e1ffa3 100644 --- a/src/lib/viewers/image/__tests__/ImageViewer-test.js +++ b/src/lib/viewers/image/__tests__/ImageViewer-test.js @@ -1,5 +1,5 @@ /* eslint-disable no-unused-expressions */ -import AnnotationControls from '../../../AnnotationControls'; +import AnnotationControls, { AnnotationMode } from '../../../AnnotationControls'; import ImageViewer from '../ImageViewer'; import BaseViewer from '../../BaseViewer'; import Browser from '../../../Browser'; @@ -576,4 +576,25 @@ describe('lib/viewers/image/ImageViewer', () => { expect(image.imageEl.src).toBe(url); }); }); + + describe('handleAnnotationControlsClick', () => { + beforeEach(() => { + image.annotator = { + toggleAnnotationMode: jest.fn(), + }; + image.processAnnotationModeChange = jest.fn(); + }); + + test('should call toggleAnnotationMode and processAnnotationModeChange', () => { + image.handleAnnotationControlsClick({ mode: AnnotationMode.REGION }); + + expect(image.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.REGION); + expect(image.processAnnotationModeChange).toBeCalledWith(AnnotationMode.REGION); + + image.handleAnnotationControlsClick({ mode: AnnotationMode.REGION }); + + expect(image.annotator.toggleAnnotationMode).toBeCalledWith(AnnotationMode.NONE); + expect(image.processAnnotationModeChange).toBeCalledWith(AnnotationMode.NONE); + }); + }); });