Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(drawing): add ColorPickerControl for images #1307

Merged
merged 8 commits into from
Dec 9, 2020
Merged
2 changes: 2 additions & 0 deletions src/lib/AnnotationModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const AnnotationColor = {
GRIMACE: bdlGrimace,
};

export const ANNOTATION_COLORS = Object.values(AnnotationColor);

export default class AnnotationModule {
private cache: Cache;

Expand Down
28 changes: 28 additions & 0 deletions src/lib/viewers/controls/annotations/DrawingControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import ColorPickerControl from '../color-picker';
import { ANNOTATION_COLORS } from '../../../AnnotationModule';
import { AnnotationMode } from './types';

export type Props = {
annotationColor?: string;
annotationMode?: AnnotationMode;
onAnnotationColorChange: (color: string) => void;
};

export default function DrawingControls({
annotationColor,
annotationMode,
onAnnotationColorChange,
}: Props): JSX.Element | null {
if (annotationMode !== AnnotationMode.DRAWING) {
return null;
}

return (
<ColorPickerControl
activeColor={annotationColor}
colors={ANNOTATION_COLORS}
onColorSelect={onAnnotationColorChange}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import DrawingControls from '../DrawingControls';
import { AnnotationMode } from '../types';

describe('DrawingControls', () => {
const getWrapper = (props = {}): ShallowWrapper =>
shallow(<DrawingControls onAnnotationColorChange={jest.fn()} {...props} />);

describe('render', () => {
test('should return nothing if annotationMode is not DRAWING', () => {
const wrapper = getWrapper();
expect(wrapper.isEmptyRender()).toBe(true);
});

test('should return ColorPickerControl if annotationMode is DRAWING', () => {
const wrapper = getWrapper({ annotationMode: AnnotationMode.DRAWING });

expect(wrapper.exists('ColorPickerControl')).toBe(true);
});
});
});
8 changes: 6 additions & 2 deletions src/lib/viewers/controls/controls-bar/ControlsBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import React from 'react';
import './ControlsBar.scss';

export type Props = {
children: React.ReactNode;
children?: React.ReactNode;
};

export default function ControlsBar({ children, ...rest }: Props): JSX.Element {
export default function ControlsBar({ children, ...rest }: Props): JSX.Element | null {
if (!children) {
return null;
}

return (
<div className="bp-ControlsBar" {...rest}>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,12 @@ describe('ControlsBar', () => {
expect(wrapper.contains(children)).toBe(true);
expect(wrapper.hasClass('bp-ControlsBar')).toBe(true);
});

test('should return null if the children property is undefined', () => {
const children = undefined;
const wrapper = shallow(<ControlsBar>{children}</ControlsBar>);

expect(wrapper.isEmptyRender()).toBe(true);
});
});
});
28 changes: 10 additions & 18 deletions src/lib/viewers/doc/DocControls.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import React from 'react';
import AnnotationsControls, { Props as AnnotationsControlsProps } from '../controls/annotations';
import ColorPickerControl, { Props as ColorPickerControlProps } from '../controls/color-picker';
import ControlsBar from '../controls/controls-bar';
import DrawingControls, { Props as DrawingControlsProps } from '../controls/annotations/DrawingControls';
import FindBarToggle, { Props as FindBarToggleProps } from '../controls/findbar';
import FullscreenToggle, { Props as FullscreenToggleProps } from '../controls/fullscreen';
import PageControls, { Props as PageControlsProps } from '../controls/page';
import ThumbnailsToggle, { Props as ThumbnailsToggleProps } from '../controls/sidebar';
import ZoomControls, { Props as ZoomControlsProps } from '../controls/zoom';
import { AnnotationColor } from '../../AnnotationModule';
import { AnnotationMode } from '../controls/annotations/types';

const colors = Object.values(AnnotationColor);

export type Props = AnnotationsControlsProps &
ColorPickerControlProps &
DrawingControlsProps &
FindBarToggleProps &
FullscreenToggleProps &
PageControlsProps &
ThumbnailsToggleProps &
ZoomControlsProps & {
onAnnotationColorChange: (color: string) => void;
};
ZoomControlsProps;

export default function DocControls({
annotationColor,
Expand Down Expand Up @@ -73,15 +67,13 @@ export default function DocControls({
onAnnotationModeEscape={onAnnotationModeEscape}
/>
</ControlsBar>
{hasDrawing && annotationMode === AnnotationMode.DRAWING && (
<ControlsBar>
<ColorPickerControl
activeColor={annotationColor}
colors={colors}
onColorSelect={onAnnotationColorChange}
/>
</ControlsBar>
)}
<ControlsBar>
<DrawingControls
annotationColor={annotationColor}
annotationMode={annotationMode}
onAnnotationColorChange={onAnnotationColorChange}
/>
</ControlsBar>
</>
);
}
45 changes: 30 additions & 15 deletions src/lib/viewers/image/ImageControls.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import React from 'react';
import AnnotationsControls, { Props as AnnotationsControlsProps } from '../controls/annotations';
import ControlsBar from '../controls/controls-bar';
import DrawingControls, { Props as DrawingControlsProps } from '../controls/annotations/DrawingControls';
import FullscreenToggle, { Props as FullscreenToggleProps } from '../controls/fullscreen';
import RotateControl, { Props as RotateControlProps } from '../controls/rotate';
import ZoomControls, { Props as ZoomControlsProps } from '../controls/zoom';

export type Props = AnnotationsControlsProps & FullscreenToggleProps & RotateControlProps & ZoomControlsProps;
export type Props = AnnotationsControlsProps &
DrawingControlsProps &
FullscreenToggleProps &
RotateControlProps &
ZoomControlsProps;

export default function ImageControls({
annotationColor,
annotationMode,
hasDrawing,
hasHighlight,
hasRegion,
onAnnotationColorChange,
onAnnotationModeClick,
onAnnotationModeEscape,
onFullscreenToggle,
Expand All @@ -22,19 +28,28 @@ export default function ImageControls({
scale,
}: Props): JSX.Element {
return (
<ControlsBar>
<ZoomControls onZoomIn={onZoomIn} onZoomOut={onZoomOut} scale={scale} />
<RotateControl onRotateLeft={onRotateLeft} />
<FullscreenToggle onFullscreenToggle={onFullscreenToggle} />
<AnnotationsControls
annotationColor={annotationColor}
annotationMode={annotationMode}
hasDrawing={hasDrawing}
hasHighlight={hasHighlight}
hasRegion={hasRegion}
onAnnotationModeClick={onAnnotationModeClick}
onAnnotationModeEscape={onAnnotationModeEscape}
/>
</ControlsBar>
<>
<ControlsBar>
<ZoomControls onZoomIn={onZoomIn} onZoomOut={onZoomOut} scale={scale} />
<RotateControl onRotateLeft={onRotateLeft} />
<FullscreenToggle onFullscreenToggle={onFullscreenToggle} />
<AnnotationsControls
annotationColor={annotationColor}
annotationMode={annotationMode}
hasDrawing={hasDrawing}
hasHighlight={hasHighlight}
hasRegion={hasRegion}
onAnnotationModeClick={onAnnotationModeClick}
onAnnotationModeEscape={onAnnotationModeEscape}
/>
</ControlsBar>
<ControlsBar>
<DrawingControls
annotationColor={annotationColor}
annotationMode={annotationMode}
onAnnotationColorChange={onAnnotationColorChange}
/>
</ControlsBar>
</>
);
}
8 changes: 8 additions & 0 deletions src/lib/viewers/image/ImageViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ImageViewer extends ImageBaseViewer {
// Bind context for callbacks
this.applyCursorFtux = this.applyCursorFtux.bind(this);
this.getViewportDimensions = this.getViewportDimensions.bind(this);
this.handleAnnotationColorChange = this.handleAnnotationColorChange.bind(this);
this.handleAnnotationControlsClick = this.handleAnnotationControlsClick.bind(this);
this.handleAnnotationCreateEvent = this.handleAnnotationCreateEvent.bind(this);
this.handleAssetAndRepLoad = this.handleAssetAndRepLoad.bind(this);
Expand Down Expand Up @@ -407,10 +408,12 @@ class ImageViewer extends ImageBaseViewer {

this.controls.render(
<ImageControls
annotationColor={this.annotationModule.getColor()}
annotationMode={this.annotationControlsFSM.getMode()}
hasDrawing={canDraw}
hasHighlight={false}
hasRegion={canAnnotate}
onAnnotationColorChange={this.handleAnnotationColorChange}
onAnnotationModeClick={this.handleAnnotationControlsClick}
onAnnotationModeEscape={this.handleAnnotationControlsEscape}
onFullscreenToggle={this.toggleFullscreen}
Expand Down Expand Up @@ -549,6 +552,11 @@ class ImageViewer extends ImageBaseViewer {
});
}

handleAnnotationColorChange(color) {
this.annotationModule.setColor(color);
this.renderUI();
}

/**
* Handler for annotation controls button click event.
*
Expand Down
21 changes: 21 additions & 0 deletions src/lib/viewers/image/__tests__/ImageViewer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,9 @@ describe('lib/viewers/image/ImageViewer', () => {

describe('loadUIReact()', () => {
beforeEach(() => {
image.annotationModule = {
getColor: jest.fn(),
};
image.options.useReactControls = true;
});

Expand All @@ -435,6 +438,7 @@ describe('lib/viewers/image/ImageViewer', () => {
hasDrawing={false}
hasHighlight={false}
hasRegion={false}
onAnnotationColorChange={image.handleAnnotationColorChange}
onAnnotationModeClick={image.handleAnnotationControlsClick}
onAnnotationModeEscape={image.handleAnnotationControlsEscape}
onFullscreenToggle={image.toggleFullscreen}
Expand Down Expand Up @@ -703,6 +707,23 @@ describe('lib/viewers/image/ImageViewer', () => {
});
});

describe('handleAnnotationColorChange', () => {
beforeEach(() => {
image.annotationModule = {
setColor: jest.fn(),
};
image.renderUI = jest.fn();
});

test('should call setColor and renderUI', () => {
const color = '#fff';
image.handleAnnotationColorChange(color);

expect(image.annotationModule.setColor).toBeCalledWith(color);
expect(image.renderUI).toHaveBeenCalled();
});
});

describe('handleAnnotationControlsClick', () => {
beforeEach(() => {
image.annotator = {
Expand Down