Skip to content

Commit

Permalink
feat(controls): Add react versions of findbar and sidebar controls (#…
Browse files Browse the repository at this point in the history
…1288)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
jstoffan and mergify[bot] authored Nov 6, 2020
1 parent 0dc7e65 commit 9426404
Show file tree
Hide file tree
Showing 13 changed files with 232 additions and 11 deletions.
5 changes: 5 additions & 0 deletions src/lib/viewers/controls/findbar/FindBarToggle.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import '../styles';

.bp-FindBarToggle {
@include bp-ControlButton;
}
15 changes: 15 additions & 0 deletions src/lib/viewers/controls/findbar/FindBarToggle.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<button className="bp-FindBarToggle" onClick={onFindBarToggle} title={__('toggle_findbar')} type="button">
<IconSearch18 />
</button>
);
}
29 changes: 29 additions & 0 deletions src/lib/viewers/controls/findbar/__tests__/FindBarToggle-test.tsx
Original file line number Diff line number Diff line change
@@ -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(<FindBarToggle onFindBarToggle={jest.fn()} {...props} />);

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);
});
});
});
2 changes: 2 additions & 0 deletions src/lib/viewers/controls/findbar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './FindBarToggle';
export { default } from './FindBarToggle';
4 changes: 2 additions & 2 deletions src/lib/viewers/controls/icons/IconSearch18.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';

function IconSearch(props: React.SVGProps<SVGSVGElement>): JSX.Element {
function IconSearch18(props: React.SVGProps<SVGSVGElement>): JSX.Element {
return (
<svg focusable="false" height={18} viewBox="0 0 15 14" width={18} {...props}>
<g fill="none" fillRule="evenodd">
Expand All @@ -15,4 +15,4 @@ function IconSearch(props: React.SVGProps<SVGSVGElement>): JSX.Element {
);
}

export default IconSearch;
export default IconSearch18;
4 changes: 2 additions & 2 deletions src/lib/viewers/controls/icons/IconThumbnailsToggle18.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';

function IconThumbnailsToggle(props: React.SVGProps<SVGSVGElement>): JSX.Element {
function IconThumbnailsToggle18(props: React.SVGProps<SVGSVGElement>): JSX.Element {
return (
<svg focusable="false" height={18} viewBox="0 0 18 18" width={18} {...props}>
<path
Expand All @@ -12,4 +12,4 @@ function IconThumbnailsToggle(props: React.SVGProps<SVGSVGElement>): JSX.Element
);
}

export default IconThumbnailsToggle;
export default IconThumbnailsToggle18;
5 changes: 5 additions & 0 deletions src/lib/viewers/controls/sidebar/ThumbnailsToggle.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import '../styles';

.bp-ThumbnailsToggle {
@include bp-ControlButton;
}
20 changes: 20 additions & 0 deletions src/lib/viewers/controls/sidebar/ThumbnailsToggle.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<button
className="bp-ThumbnailsToggle"
onClick={onThumbnailsToggle}
title={__('toggle_thumbnails')}
type="button"
>
<IconThumbnailsToggle18 />
</button>
);
}
Original file line number Diff line number Diff line change
@@ -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(<ThumbnailsToggle onThumbnailsToggle={jest.fn()} {...props} />);

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);
});
});
});
2 changes: 2 additions & 0 deletions src/lib/viewers/controls/sidebar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ThumbnailsToggle';
export { default } from './ThumbnailsToggle';
50 changes: 44 additions & 6 deletions src/lib/viewers/doc/DocBaseViewer.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(
<DocControls
maxScale={MAX_SCALE}
minScale={MIN_SCALE}
onFindBarToggle={this.toggleFindBar}
onFullscreenToggle={this.toggleFullscreen}
onThumbnailsToggle={this.toggleThumbnails}
onZoomIn={this.zoomIn}
onZoomOut={this.zoomOut}
scale={this.pdfViewer.currentScale}
/>,
);
}
}

//--------------------------------------------------------------------------
// Event Listeners
//--------------------------------------------------------------------------
Expand Down Expand Up @@ -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, {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -1207,7 +1241,7 @@ class DocBaseViewer extends BaseViewer {
return;
}

this.zoomControls.setCurrentScale(this.pdfViewer.currentScale);
this.renderUI();

// Page rendered event
this.emit('pagerender', pageNumber);
Expand Down Expand Up @@ -1452,6 +1486,10 @@ class DocBaseViewer extends BaseViewer {
this.pinchPage = null;
}

toggleFindBar() {
this.findBar.toggle();
}

/**
* Callback when the toggle thumbnail sidebar button is clicked.
*
Expand Down
34 changes: 34 additions & 0 deletions src/lib/viewers/doc/DocControls.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ControlsBar>
<ThumbnailsToggle onThumbnailsToggle={onThumbnailsToggle} />
<FindBarToggle onFindBarToggle={onFindBarToggle} />
<ZoomControls
maxScale={maxScale}
minScale={minScale}
onZoomIn={onZoomIn}
onZoomOut={onZoomOut}
scale={scale}
/>
<FullscreenToggle onFullscreenToggle={onFullscreenToggle} />
</ControlsBar>
);
}
44 changes: 43 additions & 1 deletion src/lib/viewers/doc/__tests__/DocBaseViewer-test.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(
<DocControls
maxScale={10}
minScale={0.1}
onFindBarToggle={docBase.toggleFindBar}
onFullscreenToggle={docBase.toggleFullscreen}
onThumbnailsToggle={docBase.toggleThumbnails}
onZoomIn={docBase.zoomIn}
onZoomOut={docBase.zoomOut}
scale={1}
/>,
);
});
});

describe('bindDOMListeners()', () => {
beforeEach(() => {
stubs.addEventListener = jest.spyOn(docBase.docEl, 'addEventListener').mockImplementation();
Expand Down Expand Up @@ -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();
Expand All @@ -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',
Expand Down

0 comments on commit 9426404

Please sign in to comment.