From f0a92393b50c7fb31675bd82b501998fe093284f Mon Sep 17 00:00:00 2001 From: Jared Stoffan Date: Thu, 5 Nov 2020 12:32:59 -0800 Subject: [PATCH] feat(controls): Add react versions of findbar and sidebar controls --- .../controls/findbar/FindBarToggle.scss | 5 ++ .../controls/findbar/FindBarToggle.tsx | 15 ++++++ .../findbar/__tests__/FindBarToggle-test.tsx | 29 +++++++++++ src/lib/viewers/controls/findbar/index.ts | 2 + .../viewers/controls/icons/IconSearch18.tsx | 4 +- .../controls/icons/IconThumbnailsToggle18.tsx | 4 +- .../controls/sidebar/ThumbnailsToggle.scss | 5 ++ .../controls/sidebar/ThumbnailsToggle.tsx | 20 ++++++++ .../__tests__/ThumbnailsToggle-test.tsx | 29 +++++++++++ src/lib/viewers/controls/sidebar/index.ts | 2 + src/lib/viewers/doc/DocBaseViewer.js | 50 ++++++++++++++++--- src/lib/viewers/doc/DocControls.tsx | 34 +++++++++++++ .../doc/__tests__/DocBaseViewer-test.js | 44 +++++++++++++++- 13 files changed, 232 insertions(+), 11 deletions(-) create mode 100644 src/lib/viewers/controls/findbar/FindBarToggle.scss create mode 100644 src/lib/viewers/controls/findbar/FindBarToggle.tsx create mode 100644 src/lib/viewers/controls/findbar/__tests__/FindBarToggle-test.tsx create mode 100644 src/lib/viewers/controls/findbar/index.ts create mode 100644 src/lib/viewers/controls/sidebar/ThumbnailsToggle.scss create mode 100644 src/lib/viewers/controls/sidebar/ThumbnailsToggle.tsx create mode 100644 src/lib/viewers/controls/sidebar/__tests__/ThumbnailsToggle-test.tsx create mode 100644 src/lib/viewers/controls/sidebar/index.ts create mode 100644 src/lib/viewers/doc/DocControls.tsx diff --git a/src/lib/viewers/controls/findbar/FindBarToggle.scss b/src/lib/viewers/controls/findbar/FindBarToggle.scss new file mode 100644 index 000000000..220d5d7b5 --- /dev/null +++ b/src/lib/viewers/controls/findbar/FindBarToggle.scss @@ -0,0 +1,5 @@ +@import '../styles'; + +.bp-FindBarToggle { + @include bp-ControlButton; +} diff --git a/src/lib/viewers/controls/findbar/FindBarToggle.tsx b/src/lib/viewers/controls/findbar/FindBarToggle.tsx new file mode 100644 index 000000000..5d4536a1a --- /dev/null +++ b/src/lib/viewers/controls/findbar/FindBarToggle.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import IconSearch18 from '../icons/IconSearch18'; +import './FindBarToggle.scss'; + +export type Props = { + onFindBarToggle: () => void; +}; + +export default function FindBarToggle({ onFindBarToggle }: Props): JSX.Element { + return ( + + ); +} diff --git a/src/lib/viewers/controls/findbar/__tests__/FindBarToggle-test.tsx b/src/lib/viewers/controls/findbar/__tests__/FindBarToggle-test.tsx new file mode 100644 index 000000000..322c61166 --- /dev/null +++ b/src/lib/viewers/controls/findbar/__tests__/FindBarToggle-test.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import FindBarToggle from '../FindBarToggle'; +import IconSearch18 from '../../icons/IconSearch18'; + +describe('FindBarToggle', () => { + const getWrapper = (props = {}): ShallowWrapper => + shallow(); + + describe('event handlers', () => { + test('should forward the click from the button', () => { + const onToggle = jest.fn(); + const wrapper = getWrapper({ onFindBarToggle: onToggle }); + + wrapper.simulate('click'); + + expect(onToggle).toBeCalled(); + }); + }); + + describe('render', () => { + test('should return a valid wrapper', () => { + const wrapper = getWrapper(); + + expect(wrapper.hasClass('bp-FindBarToggle')).toBe(true); + expect(wrapper.exists(IconSearch18)).toBe(true); + }); + }); +}); diff --git a/src/lib/viewers/controls/findbar/index.ts b/src/lib/viewers/controls/findbar/index.ts new file mode 100644 index 000000000..6f7a37c77 --- /dev/null +++ b/src/lib/viewers/controls/findbar/index.ts @@ -0,0 +1,2 @@ +export * from './FindBarToggle'; +export { default } from './FindBarToggle'; diff --git a/src/lib/viewers/controls/icons/IconSearch18.tsx b/src/lib/viewers/controls/icons/IconSearch18.tsx index 5489f8b99..adc846279 100644 --- a/src/lib/viewers/controls/icons/IconSearch18.tsx +++ b/src/lib/viewers/controls/icons/IconSearch18.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -function IconSearch(props: React.SVGProps): JSX.Element { +function IconSearch18(props: React.SVGProps): JSX.Element { return ( @@ -15,4 +15,4 @@ function IconSearch(props: React.SVGProps): JSX.Element { ); } -export default IconSearch; +export default IconSearch18; diff --git a/src/lib/viewers/controls/icons/IconThumbnailsToggle18.tsx b/src/lib/viewers/controls/icons/IconThumbnailsToggle18.tsx index bf0c3127b..b1f1253b8 100644 --- a/src/lib/viewers/controls/icons/IconThumbnailsToggle18.tsx +++ b/src/lib/viewers/controls/icons/IconThumbnailsToggle18.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -function IconThumbnailsToggle(props: React.SVGProps): JSX.Element { +function IconThumbnailsToggle18(props: React.SVGProps): JSX.Element { return ( ): JSX.Element ); } -export default IconThumbnailsToggle; +export default IconThumbnailsToggle18; diff --git a/src/lib/viewers/controls/sidebar/ThumbnailsToggle.scss b/src/lib/viewers/controls/sidebar/ThumbnailsToggle.scss new file mode 100644 index 000000000..cd310a4f0 --- /dev/null +++ b/src/lib/viewers/controls/sidebar/ThumbnailsToggle.scss @@ -0,0 +1,5 @@ +@import '../styles'; + +.bp-ThumbnailsToggle { + @include bp-ControlButton; +} diff --git a/src/lib/viewers/controls/sidebar/ThumbnailsToggle.tsx b/src/lib/viewers/controls/sidebar/ThumbnailsToggle.tsx new file mode 100644 index 000000000..6c47af46f --- /dev/null +++ b/src/lib/viewers/controls/sidebar/ThumbnailsToggle.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import IconThumbnailsToggle18 from '../icons/IconThumbnailsToggle18'; +import './ThumbnailsToggle.scss'; + +export type Props = { + onThumbnailsToggle: () => void; +}; + +export default function ThumbnailsToggle({ onThumbnailsToggle }: Props): JSX.Element { + return ( + + ); +} diff --git a/src/lib/viewers/controls/sidebar/__tests__/ThumbnailsToggle-test.tsx b/src/lib/viewers/controls/sidebar/__tests__/ThumbnailsToggle-test.tsx new file mode 100644 index 000000000..375ac00ab --- /dev/null +++ b/src/lib/viewers/controls/sidebar/__tests__/ThumbnailsToggle-test.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import IconThumbnailsToggle18 from '../../icons/IconThumbnailsToggle18'; +import ThumbnailsToggle from '../ThumbnailsToggle'; + +describe('ThumbnailsToggle', () => { + const getWrapper = (props = {}): ShallowWrapper => + shallow(); + + describe('event handlers', () => { + test('should forward the click from the button', () => { + const onToggle = jest.fn(); + const wrapper = getWrapper({ onThumbnailsToggle: onToggle }); + + wrapper.simulate('click'); + + expect(onToggle).toBeCalled(); + }); + }); + + describe('render', () => { + test('should return a valid wrapper', () => { + const wrapper = getWrapper(); + + expect(wrapper.hasClass('bp-ThumbnailsToggle')).toBe(true); + expect(wrapper.exists(IconThumbnailsToggle18)).toBe(true); + }); + }); +}); diff --git a/src/lib/viewers/controls/sidebar/index.ts b/src/lib/viewers/controls/sidebar/index.ts new file mode 100644 index 000000000..5f464aa9d --- /dev/null +++ b/src/lib/viewers/controls/sidebar/index.ts @@ -0,0 +1,2 @@ +export * from './ThumbnailsToggle'; +export { default } from './ThumbnailsToggle'; diff --git a/src/lib/viewers/doc/DocBaseViewer.js b/src/lib/viewers/doc/DocBaseViewer.js index 07ec44601..d0e299168 100644 --- a/src/lib/viewers/doc/DocBaseViewer.js +++ b/src/lib/viewers/doc/DocBaseViewer.js @@ -1,15 +1,18 @@ +import React from 'react'; import throttle from 'lodash/throttle'; import AnnotationControls, { AnnotationMode } from '../../AnnotationControls'; import BaseViewer from '../BaseViewer'; import Browser from '../../Browser'; import Controls from '../../Controls'; -import PageControls from '../../PageControls'; -import ZoomControls from '../../ZoomControls'; +import ControlsRoot from '../controls/controls-root'; +import DocControls from './DocControls'; import DocFindBar from './DocFindBar'; +import PageControls from '../../PageControls'; import Popup from '../../Popup'; -import RepStatus from '../../RepStatus'; import PreviewError from '../../PreviewError'; +import RepStatus from '../../RepStatus'; import ThumbnailsSidebar from '../../ThumbnailsSidebar'; +import ZoomControls from '../../ZoomControls'; import { AnnotationInput, AnnotationState } from '../../AnnotationControlsFSM'; import { ANNOTATOR_EVENT, @@ -119,6 +122,7 @@ class DocBaseViewer extends BaseViewer { this.print = this.print.bind(this); this.setPage = this.setPage.bind(this); this.throttledScrollHandler = this.getScrollHandler().bind(this); + this.toggleFindBar = this.toggleFindBar.bind(this); this.toggleThumbnails = this.toggleThumbnails.bind(this); this.updateDiscoverabilityResinTag = this.updateDiscoverabilityResinTag.bind(this); this.zoomIn = this.zoomIn.bind(this); @@ -1045,6 +1049,32 @@ class DocBaseViewer extends BaseViewer { this.bindControlListeners(); } + loadUIReact() { + this.controls = new ControlsRoot({ containerEl: this.containerEl }); + this.renderUI(); + } + + renderUI() { + if (this.zoomControls) { + this.zoomControls.setCurrentScale(this.pdfViewer.currentScale); + } + + if (this.controls && this.options.useReactControls) { + this.controls.render( + , + ); + } + } + //-------------------------------------------------------------------------- // Event Listeners //-------------------------------------------------------------------------- @@ -1101,7 +1131,7 @@ class DocBaseViewer extends BaseViewer { } if (!this.isFindDisabled()) { - this.controls.add(__('toggle_findbar'), () => this.findBar.toggle(), 'bp-toggle-findbar-icon', ICON_SEARCH); + this.controls.add(__('toggle_findbar'), this.toggleFindBar, 'bp-toggle-findbar-icon', ICON_SEARCH); } this.zoomControls.init(this.pdfViewer.currentScale, { @@ -1144,7 +1174,11 @@ class DocBaseViewer extends BaseViewer { pagesinitHandler() { this.pdfViewer.currentScaleValue = 'auto'; - this.loadUI(); + if (this.options.useReactControls) { + this.loadUIReact(); + } else { + this.loadUI(); + } const { pagesCount, currentScale } = this.pdfViewer; @@ -1207,7 +1241,7 @@ class DocBaseViewer extends BaseViewer { return; } - this.zoomControls.setCurrentScale(this.pdfViewer.currentScale); + this.renderUI(); // Page rendered event this.emit('pagerender', pageNumber); @@ -1453,6 +1487,10 @@ class DocBaseViewer extends BaseViewer { this.pinchPage = null; } + toggleFindBar() { + this.findBar.toggle(); + } + /** * Callback when the toggle thumbnail sidebar button is clicked. * diff --git a/src/lib/viewers/doc/DocControls.tsx b/src/lib/viewers/doc/DocControls.tsx new file mode 100644 index 000000000..9795155a9 --- /dev/null +++ b/src/lib/viewers/doc/DocControls.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import ControlsBar from '../controls/controls-bar'; +import FindBarToggle, { Props as FindBarToggleProps } from '../controls/findbar'; +import FullscreenToggle, { Props as FullscreenToggleProps } from '../controls/fullscreen'; +import ThumbnailsToggle, { Props as ThumbnailsToggleProps } from '../controls/sidebar'; +import ZoomControls, { Props as ZoomControlsProps } from '../controls/zoom'; + +export type Props = FindBarToggleProps & FullscreenToggleProps & ThumbnailsToggleProps & ZoomControlsProps; + +export default function DocControls({ + maxScale, + minScale, + onFindBarToggle, + onFullscreenToggle, + onThumbnailsToggle, + onZoomIn, + onZoomOut, + scale, +}: Props): JSX.Element { + return ( + + + + + + + ); +} diff --git a/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js b/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js index cd8b2debf..4eaf5339f 100644 --- a/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js +++ b/src/lib/viewers/doc/__tests__/DocBaseViewer-test.js @@ -1,8 +1,11 @@ /* eslint-disable no-unused-expressions */ +import React from 'react'; import Api from '../../../api'; import AnnotationControls, { AnnotationMode } from '../../../AnnotationControls'; import AnnotationControlsFSM, { AnnotationInput, AnnotationState } from '../../../AnnotationControlsFSM'; +import ControlsRoot from '../../controls/controls-root'; import DocBaseViewer, { DISCOVERABILITY_STATES } from '../DocBaseViewer'; +import DocControls from '../DocControls'; import DocFindBar from '../DocFindBar'; import Browser from '../../../Browser'; import BaseViewer from '../../BaseViewer'; @@ -38,6 +41,8 @@ import { import { LOAD_METRIC, RENDER_EVENT, USER_DOCUMENT_THUMBNAIL_EVENTS, VIEWER_EVENT } from '../../../events'; import Timer from '../../../Timer'; +jest.mock('../../controls/controls-root'); + const LOAD_TIMEOUT_MS = 180000; // 3 min timeout const PRINT_TIMEOUT_MS = 1000; // Wait 1s before trying to print const PRINT_DIALOG_TIMEOUT_MS = 500; @@ -1683,6 +1688,30 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { }); }); + describe('loadUIReact()', () => { + test('should create controls root and render the react controls', () => { + docBase.pdfViewer = { + currentScale: 1, + }; + docBase.options.useReactControls = true; + docBase.loadUIReact(); + + expect(docBase.controls).toBeInstanceOf(ControlsRoot); + expect(docBase.controls.render).toBeCalledWith( + , + ); + }); + }); + describe('bindDOMListeners()', () => { beforeEach(() => { stubs.addEventListener = jest.spyOn(docBase.docEl, 'addEventListener').mockImplementation(); @@ -1768,6 +1797,7 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { describe('pagesinitHandler()', () => { beforeEach(() => { stubs.loadUI = jest.spyOn(docBase, 'loadUI').mockImplementation(); + stubs.loadUIReact = jest.spyOn(docBase, 'loadUIReact').mockImplementation(); stubs.setPage = jest.spyOn(docBase, 'setPage').mockImplementation(); stubs.getCachedPage = jest.spyOn(docBase, 'getCachedPage').mockImplementation(); stubs.emit = jest.spyOn(docBase, 'emit').mockImplementation(); @@ -1780,12 +1810,24 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => { }; docBase.pagesinitHandler(); + expect(docBase.docEl).toHaveClass('bp-is-scrollable'); expect(stubs.loadUI).toBeCalled(); + expect(stubs.loadUIReact).not.toBeCalled(); expect(stubs.setPage).toBeCalled(); - expect(docBase.docEl).toHaveClass('bp-is-scrollable'); expect(stubs.setupPages).toBeCalled(); }); + test('should load the React UI if the option is enabled', () => { + docBase.pdfViewer = { + currentScale: 'unknown', + }; + docBase.options.useReactControls = true; + docBase.pagesinitHandler(); + + expect(stubs.loadUI).not.toBeCalled(); + expect(stubs.loadUIReact).toBeCalled(); + }); + test("should broadcast that the preview is loaded if it hasn't already", () => { docBase.pdfViewer = { currentScale: 'unknown',