Skip to content

Commit

Permalink
feat(controls): Add react versions of text viewer controls (#1284)
Browse files Browse the repository at this point in the history
  • Loading branch information
jstoffan authored Nov 4, 2020
1 parent 443746e commit 3d9ef91
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 10 deletions.
3 changes: 3 additions & 0 deletions src/lib/Preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,9 @@ class Preview extends EventEmitter {
// Enable or disable hotkeys
this.options.useHotkeys = options.useHotkeys !== false;

// Enable or disable react ui
this.options.useReactControls = !!options.useReactControls;

// Custom Box3D application definition
this.options.box3dApplication = options.box3dApplication;

Expand Down
13 changes: 13 additions & 0 deletions src/lib/__tests__/Preview-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,19 @@ describe('lib/Preview', () => {

expect(preview.options.responseInterceptor).toBe(responseInterceptor);
});

test('should disable react controls by default', () => {
preview.parseOptions(preview.previewOptions);

expect(preview.options.useReactControls).toBe(false);
});

test('should enable react controls if provided', () => {
preview.previewOptions.useReactControls = true;
preview.parseOptions(preview.previewOptions);

expect(preview.options.useReactControls).toBe(true);
});
});

describe('createViewerOptions()', () => {
Expand Down
1 change: 1 addition & 0 deletions src/lib/viewers/controls/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './controls-root';
2 changes: 1 addition & 1 deletion src/lib/viewers/text/Markdown.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import './Text';
@import './PlainText';

// Overrides pre-wrap from plain text
.markdown-body {
Expand Down
13 changes: 13 additions & 0 deletions src/lib/viewers/text/MarkdownControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import ControlsBar from '../controls/controls-bar';
import FullscreenToggle, { Props as FullscreenToggleProps } from '../controls/fullscreen';

export type Props = FullscreenToggleProps;

export default function MarkdownControls({ onFullscreenToggle }: Props): JSX.Element {
return (
<ControlsBar>
<FullscreenToggle onFullscreenToggle={onFullscreenToggle} />
</ControlsBar>
);
}
15 changes: 14 additions & 1 deletion src/lib/viewers/text/MarkdownViewer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react';
import Controls from '../../Controls';
import MarkdownControls from './MarkdownControls';
import PlainTextViewer from './PlainTextViewer';
import { CLASS_HIDDEN, TEXT_STATIC_ASSETS_VERSION } from '../../constants';
import { ICON_FULLSCREEN_IN, ICON_FULLSCREEN_OUT } from '../../icons/icons';
Expand Down Expand Up @@ -79,7 +81,12 @@ class MarkdownViewer extends PlainTextViewer {
const md = this.initRemarkable();
this.markdownEl.innerHTML = md.render(content);

this.loadUI();
if (this.options.useReactControls) {
this.loadUIReact();
} else {
this.loadUI();
}

this.textEl.classList.remove(CLASS_HIDDEN);
this.loaded = true;
this.emit(VIEWER_EVENT.load);
Expand Down Expand Up @@ -108,6 +115,12 @@ class MarkdownViewer extends PlainTextViewer {
this.controls.add(__('exit_fullscreen'), this.toggleFullscreen, 'bp-exit-fullscreen-icon', ICON_FULLSCREEN_OUT);
}

renderUI() {
if (this.options.useReactControls) {
this.controls.render(<MarkdownControls onFullscreenToggle={this.toggleFullscreen} />);
}
}

/**
* Initializes and returns Remarkable parser.
*
Expand Down
File renamed without changes.
9 changes: 7 additions & 2 deletions src/lib/viewers/text/PlainTextViewer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import './Text.scss';
import './PlainText.scss';
import TextBaseViewer from './TextBaseViewer';
import Browser from '../../Browser';
import Popup from '../../Popup';
Expand Down Expand Up @@ -160,7 +160,12 @@ class PlainTextViewer extends TextBaseViewer {
this.codeEl.textContent = content;
}

this.loadUI();
if (this.options.useReactControls) {
this.loadUIReact();
} else {
this.loadUI();
}

this.textEl.classList.remove(CLASS_HIDDEN);

this.loaded = true;
Expand Down
31 changes: 29 additions & 2 deletions src/lib/viewers/text/TextBaseViewer.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React from 'react';
import BaseViewer from '../BaseViewer';
import Controls from '../../Controls';
import ControlsRoot from '../controls';
import TextControls from './TextControls';
import ZoomControls from '../../ZoomControls';
import { checkPermission } from '../../file';
import { CLASS_IS_PRINTABLE, CLASS_IS_SELECTABLE, PERMISSION_DOWNLOAD } from '../../constants';
import { ICON_FULLSCREEN_IN, ICON_FULLSCREEN_OUT } from '../../icons/icons';
import { checkPermission } from '../../file';

const ZOOM_DEFAULT = 1.0;
const ZOOM_MAX = 10;
Expand Down Expand Up @@ -77,7 +80,7 @@ class TextBaseViewer extends BaseViewer {
});

this.scale = newScale;
this.zoomControls.setCurrentScale(newScale);
this.renderUI();
}

/**
Expand Down Expand Up @@ -143,6 +146,30 @@ class TextBaseViewer extends BaseViewer {
this.controls.add(__('exit_fullscreen'), this.toggleFullscreen, 'bp-exit-fullscreen-icon', ICON_FULLSCREEN_OUT);
}

loadUIReact() {
this.controls = new ControlsRoot({ containerEl: this.containerEl });
this.renderUI();
}

renderUI() {
if (this.zoomControls) {
this.zoomControls.setCurrentScale(this.scale);
}

if (this.options.useReactControls) {
this.controls.render(
<TextControls
maxScale={ZOOM_MAX}
minScale={ZOOM_MIN}
onFullscreenToggle={this.toggleFullscreen}
onZoomIn={this.zoomIn}
onZoomOut={this.zoomOut}
scale={this.scale}
/>,
);
}
}

/**
* Handles keyboard events for media
*
Expand Down
28 changes: 28 additions & 0 deletions src/lib/viewers/text/TextControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import ControlsBar from '../controls/controls-bar';
import FullscreenToggle, { Props as FullscreenToggleProps } from '../controls/fullscreen';
import ZoomControls, { Props as ZoomControlsProps } from '../controls/zoom';

export type Props = FullscreenToggleProps & ZoomControlsProps;

export default function TextControls({
maxScale,
minScale,
onFullscreenToggle,
onZoomIn,
onZoomOut,
scale,
}: Props): JSX.Element {
return (
<ControlsBar>
<ZoomControls
maxScale={maxScale}
minScale={minScale}
onZoomIn={onZoomIn}
onZoomOut={onZoomOut}
scale={scale}
/>
<FullscreenToggle onFullscreenToggle={onFullscreenToggle} />
</ControlsBar>
);
}
17 changes: 17 additions & 0 deletions src/lib/viewers/text/__tests__/MarkdownControls-test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { shallow } from 'enzyme';
import ControlsBar from '../../controls/controls-bar';
import FullscreenToggle from '../../controls/fullscreen';
import MarkdownControls from '../MarkdownControls';

describe('MarkdownControls', () => {
describe('render', () => {
test('should return a valid wrapper', () => {
const onToggle = jest.fn();
const wrapper = shallow(<MarkdownControls onFullscreenToggle={onToggle} />);

expect(wrapper.exists(ControlsBar));
expect(wrapper.find(FullscreenToggle).prop('onFullscreenToggle')).toEqual(onToggle);
});
});
});
34 changes: 32 additions & 2 deletions src/lib/viewers/text/__tests__/MarkdownViewer-test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
/* eslint-disable no-unused-expressions */
import Remarkable from '../../../../third-party/text/0.114.0/remarkable.min.js';
import MarkdownViewer from '../MarkdownViewer';
import React from 'react';
import BaseViewer from '../../BaseViewer';
import ControlsRoot from '../../controls/controls-root';
import MarkdownControls from '../MarkdownControls';
import MarkdownViewer from '../MarkdownViewer';
import Popup from '../../../Popup';
import Remarkable from '../../../../third-party/text/0.114.0/remarkable.min.js';
import { TEXT_STATIC_ASSETS_VERSION, SELECTOR_BOX_PREVIEW } from '../../../constants';
import { VIEWER_EVENT } from '../../../events';

jest.mock('../../controls/controls-root');

let containerEl;
let markdown;
let rootEl;
Expand Down Expand Up @@ -123,18 +128,31 @@ describe('lib/viewers/text/MarkdownViewer', () => {
};
jest.spyOn(markdown, 'initRemarkable').mockReturnValue(md);
jest.spyOn(markdown, 'loadUI');
jest.spyOn(markdown, 'loadUIReact');
jest.spyOn(markdown, 'emit');

markdown.finishLoading('');

expect(markdown.initRemarkable).toBeCalled();
expect(md.render).toBeCalled();
expect(markdown.loadUI).toBeCalled();
expect(markdown.loadUIReact).not.toBeCalled();
expect(markdown.emit).toBeCalledWith(VIEWER_EVENT.load);
expect(markdown.loaded).toBe(true);
expect(markdown.textEl.classList.contains('bp-is-hidden')).toBe(false);
});

test('should finish loading and render react ui if option is enabled', () => {
jest.spyOn(markdown, 'loadUI');
jest.spyOn(markdown, 'loadUIReact');

markdown.options.useReactControls = true;
markdown.finishLoading('');

expect(markdown.loadUI).not.toBeCalled();
expect(markdown.loadUIReact).toBeCalled();
});

test('should show truncated download button if text is truncated', () => {
jest.spyOn(markdown, 'initRemarkable').mockReturnValue({
render: () => {},
Expand All @@ -149,4 +167,16 @@ describe('lib/viewers/text/MarkdownViewer', () => {
expect(markdown.showTruncatedDownloadButton).toBeCalled();
});
});

describe('loadUIReact()', () => {
test('should create controls root and render the react controls', () => {
markdown.options.useReactControls = true;
markdown.loadUIReact();

expect(markdown.controls).toBeInstanceOf(ControlsRoot);
expect(markdown.controls.render).toBeCalledWith(
<MarkdownControls onFullscreenToggle={markdown.toggleFullscreen} />,
);
});
});
});
13 changes: 13 additions & 0 deletions src/lib/viewers/text/__tests__/PlainTextViewer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,16 +411,29 @@ describe('lib/viewers/text/PlainTextViewer', () => {

test('should finish loading, show the text, and emit load', () => {
jest.spyOn(text, 'loadUI');
jest.spyOn(text, 'loadUIReact');
jest.spyOn(text, 'emit');

text.finishLoading('', true);

expect(text.loadUI).toBeCalled();
expect(text.loadUIReact).not.toBeCalled();
expect(text.emit).toBeCalledWith(VIEWER_EVENT.load);
expect(text.loaded).toBe(true);
expect(text.textEl.classList.contains('bp-is-hidden')).toBe(false);
});

test('should finish loading and render react ui if option is enabled', () => {
jest.spyOn(text, 'loadUI');
jest.spyOn(text, 'loadUIReact');

text.options.useReactControls = true;
text.finishLoading('', true);

expect(text.loadUI).not.toBeCalled();
expect(text.loadUIReact).toBeCalled();
});

test('should cleanup worker and show truncated download button if needed', () => {
text.workerSrc = 'blah';
text.truncated = true;
Expand Down
27 changes: 25 additions & 2 deletions src/lib/viewers/text/__tests__/TextBaseViewer-test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
/* eslint-disable no-unused-expressions */
import React from 'react';
import * as file from '../../../file';
import BaseViewer from '../../BaseViewer';
import Controls from '../../../Controls';
import ControlsRoot from '../../controls/controls-root';
import TextBaseViewer from '../TextBaseViewer';
import TextControls from '../TextControls';
import ZoomControls from '../../../ZoomControls';
import * as file from '../../../file';
import { PERMISSION_DOWNLOAD } from '../../../constants';

jest.mock('../../controls/controls-root');

let containerEl;
let textBase;

Expand Down Expand Up @@ -165,6 +169,25 @@ describe('lib/viewers/text/TextBaseViewer', () => {
});
});

describe('loadUIReact()', () => {
test('should create controls root and render the react controls', () => {
textBase.options.useReactControls = true;
textBase.loadUIReact();

expect(textBase.controls).toBeInstanceOf(ControlsRoot);
expect(textBase.controls.render).toBeCalledWith(
<TextControls
maxScale={10}
minScale={0.1}
onFullscreenToggle={textBase.toggleFullscreen}
onZoomIn={textBase.zoomIn}
onZoomOut={textBase.zoomOut}
scale={1}
/>,
);
});
});

describe('onKeydown()', () => {
test('should return false if controls are not initialized', () => {
expect(textBase.onKeydown()).toBe(false);
Expand Down
24 changes: 24 additions & 0 deletions src/lib/viewers/text/__tests__/TextControls-test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { shallow } from 'enzyme';
import ControlsBar from '../../controls/controls-bar';
import FullscreenToggle from '../../controls/fullscreen';
import TextControls from '../TextControls';
import ZoomControls from '../../controls/zoom';

describe('TextControls', () => {
describe('render', () => {
test('should return a valid wrapper', () => {
const onToggle = jest.fn();
const onZoomIn = jest.fn();
const onZoomOut = jest.fn();
const wrapper = shallow(
<TextControls onFullscreenToggle={onToggle} onZoomIn={onZoomIn} onZoomOut={onZoomOut} />,
);

expect(wrapper.exists(ControlsBar));
expect(wrapper.find(FullscreenToggle).prop('onFullscreenToggle')).toEqual(onToggle);
expect(wrapper.find(ZoomControls).prop('onZoomIn')).toEqual(onZoomIn);
expect(wrapper.find(ZoomControls).prop('onZoomOut')).toEqual(onZoomOut);
});
});
});

0 comments on commit 3d9ef91

Please sign in to comment.