diff --git a/src/lib/AnnotationControls.scss b/src/lib/AnnotationControls.scss index 36359b2f1..b52de78f4 100644 --- a/src/lib/AnnotationControls.scss +++ b/src/lib/AnnotationControls.scss @@ -19,4 +19,8 @@ } } } + + &.is-hidden { + display: none; + } } diff --git a/src/lib/AnnotationControls.ts b/src/lib/AnnotationControls.ts index c625dc027..f45578d00 100644 --- a/src/lib/AnnotationControls.ts +++ b/src/lib/AnnotationControls.ts @@ -1,10 +1,13 @@ import noop from 'lodash/noop'; import { ICON_REGION_COMMENT } from './icons/icons'; import Controls, { CLASS_BOX_CONTROLS_GROUP_BUTTON } from './Controls'; +import fullscreen from './Fullscreen'; export const CLASS_ANNOTATIONS_GROUP = 'bp-AnnotationControls-group'; export const CLASS_REGION_BUTTON = 'bp-AnnotationControls-regionBtn'; + export const CLASS_BUTTON_ACTIVE = 'is-active'; +export const CLASS_GROUP_HIDE = 'is-hidden'; export type RegionHandler = ({ isRegionActive, event }: { isRegionActive: boolean; event: MouseEvent }) => void; export type Options = { @@ -17,14 +20,14 @@ export default class AnnotationControls { /** @property {Controls} - Controls object */ private controls: Controls; + /** @property {HTMLElement} - Controls element */ + private controlsElement: HTMLElement; + /** @property {boolean} - Region comment mode active state */ private isRegionActive = false; /** * [constructor] - * - * @param {Controls} controls - Viewer controls - * @return {AnnotationControls} Instance of AnnotationControls */ constructor(controls: Controls) { if (!controls || !(controls instanceof Controls)) { @@ -32,23 +35,67 @@ export default class AnnotationControls { } this.controls = controls; + this.controlsElement = controls.controlsEl; + + this.attachEventHandlers(); + } + + /** + * [destructor] + */ + public destroy(): void { + fullscreen.removeListener('enter', this.handleFullscreenEnter); + fullscreen.removeListener('exit', this.handleFullscreenExit); } + /** + * Attaches event handlers + */ + private attachEventHandlers(): void { + fullscreen.addListener('enter', this.handleFullscreenEnter); + fullscreen.addListener('exit', this.handleFullscreenExit); + } + + /** + * Handle fullscreen change + */ + private handleFullscreenChange = (isFullscreen: boolean): void => { + const groupElement = this.controlsElement.querySelector(`.${CLASS_ANNOTATIONS_GROUP}`); + + if (!groupElement) { + return; + } + + if (isFullscreen) { + groupElement.classList.add(CLASS_GROUP_HIDE); + } else { + groupElement.classList.remove(CLASS_GROUP_HIDE); + } + }; + + /** + * Enter fullscreen handler + */ + private handleFullscreenEnter = (): void => this.handleFullscreenChange(true); + + /** + * Exit fullscreen handler + */ + private handleFullscreenExit = (): void => this.handleFullscreenChange(false); + /** * Region comment button click handler - * - * @param {RegionHandler} onRegionClick - region click handler in options - * @param {MouseEvent} event - mouse event - * @return {void} */ private handleRegionClick = (onRegionClick: RegionHandler) => (event: MouseEvent): void => { - const regionButtonElement = event.target as HTMLButtonElement; + const regionButtonElement = this.controlsElement.querySelector(`.${CLASS_REGION_BUTTON}`); - this.isRegionActive = !this.isRegionActive; - if (this.isRegionActive) { - regionButtonElement.classList.add(CLASS_BUTTON_ACTIVE); - } else { - regionButtonElement.classList.remove(CLASS_BUTTON_ACTIVE); + if (regionButtonElement) { + this.isRegionActive = !this.isRegionActive; + if (this.isRegionActive) { + regionButtonElement.classList.add(CLASS_BUTTON_ACTIVE); + } else { + regionButtonElement.classList.remove(CLASS_BUTTON_ACTIVE); + } } onRegionClick({ isRegionActive: this.isRegionActive, event }); @@ -56,9 +103,6 @@ export default class AnnotationControls { /** * Initialize the annotation controls with options. - * - * @param {RegionHandler} [options.onRegionClick] - Callback when region comment button is clicked - * @return {void} */ public init({ onRegionClick = noop }: Options = {}): void { const groupElement = this.controls.addGroup(CLASS_ANNOTATIONS_GROUP); diff --git a/src/lib/__tests__/AnnotationControls-test.js b/src/lib/__tests__/AnnotationControls-test.js index bd4a2b561..0650a02ad 100644 --- a/src/lib/__tests__/AnnotationControls-test.js +++ b/src/lib/__tests__/AnnotationControls-test.js @@ -1,7 +1,13 @@ /* eslint-disable no-unused-expressions */ -import AnnotationControls, { CLASS_REGION_BUTTON, CLASS_BUTTON_ACTIVE } from '../AnnotationControls'; -import Controls, { CLASS_BOX_CONTROLS_GROUP_BUTTON } from '../Controls'; import { ICON_REGION_COMMENT } from '../icons/icons'; +import AnnotationControls, { + CLASS_ANNOTATIONS_GROUP, + CLASS_BUTTON_ACTIVE, + CLASS_GROUP_HIDE, + CLASS_REGION_BUTTON, +} from '../AnnotationControls'; +import Controls, { CLASS_BOX_CONTROLS_GROUP_BUTTON } from '../Controls'; +import fullscreen from '../Fullscreen'; let annotationControls; let stubs = {}; @@ -16,8 +22,20 @@ describe('lib/AnnotationControls', () => { beforeEach(() => { fixture.load('__tests__/AnnotationControls-test.html'); const controls = new Controls(document.getElementById('test-annotation-controls-container')); - annotationControls = new AnnotationControls(controls); + + stubs.classListAdd = sandbox.stub(); + stubs.classListRemove = sandbox.stub(); + stubs.fullscreenAddListener = sandbox.stub(fullscreen, 'addListener'); stubs.onRegionClick = sandbox.stub(); + stubs.querySelector = sandbox.stub().returns({ + classList: { + add: stubs.classListAdd, + remove: stubs.classListRemove, + }, + }); + + annotationControls = new AnnotationControls(controls); + annotationControls.controlsElement.querySelector = stubs.querySelector; }); afterEach(() => { @@ -33,11 +51,27 @@ describe('lib/AnnotationControls', () => { expect(annotationControls.controls).not.to.be.undefined; }); + it('should attach event listeners', () => { + expect(stubs.fullscreenAddListener).to.be.calledTwice; + }); + it('should throw an exception if controls is not provided', () => { expect(() => new AnnotationControls()).to.throw(Error, 'controls must be an instance of Controls'); }); }); + describe('destroy()', () => { + beforeEach(() => { + stubs.fullscreenRemoveListener = sandbox.stub(fullscreen, 'removeListener'); + }); + + it('should remove all listeners', () => { + annotationControls.destroy(); + + expect(stubs.fullscreenRemoveListener).to.be.calledTwice; + }); + }); + describe('init()', () => { beforeEach(() => { stubs.add = sandbox.stub(annotationControls.controls, 'add'); @@ -60,19 +94,6 @@ describe('lib/AnnotationControls', () => { }); describe('handleRegionClick()', () => { - beforeEach(() => { - stubs.classListAdd = sandbox.stub(); - stubs.classListRemove = sandbox.stub(); - stubs.event = sandbox.stub({ - target: { - classList: { - add: stubs.classListAdd, - remove: stubs.classListRemove, - }, - }, - }); - }); - it('should activate region button then deactivate', () => { expect(annotationControls.isRegionActive).to.be.false; @@ -94,4 +115,16 @@ describe('lib/AnnotationControls', () => { }); }); }); + + describe('handleFullscreenChange()', () => { + it('should hide entire group if fullscreen is active', () => { + annotationControls.handleFullscreenEnter(); + expect(stubs.querySelector).to.be.calledWith(`.${CLASS_ANNOTATIONS_GROUP}`); + expect(stubs.classListAdd).to.be.calledWith(CLASS_GROUP_HIDE); + + annotationControls.handleFullscreenExit(); + expect(stubs.querySelector).to.be.calledWith(`.${CLASS_ANNOTATIONS_GROUP}`); + expect(stubs.classListRemove).to.be.calledWith(CLASS_GROUP_HIDE); + }); + }); }); diff --git a/src/lib/viewers/doc/DocBaseViewer.js b/src/lib/viewers/doc/DocBaseViewer.js index a4f59b5a8..ab1c53c09 100644 --- a/src/lib/viewers/doc/DocBaseViewer.js +++ b/src/lib/viewers/doc/DocBaseViewer.js @@ -164,6 +164,10 @@ class DocBaseViewer extends BaseViewer { URL.revokeObjectURL(this.printURL); } + if (this.annotationControls) { + this.annotationControls.destroy(); + } + if (this.pageControls) { this.pageControls.removeListener('pagechange', this.setPage); }