Skip to content

Commit

Permalink
Add annotation module (#1305)
Browse files Browse the repository at this point in the history
* feat(drawing): add annotation module
  • Loading branch information
ChenCodes authored Dec 9, 2020
1 parent e34cc02 commit 3d2149e
Show file tree
Hide file tree
Showing 15 changed files with 189 additions and 62 deletions.
32 changes: 32 additions & 0 deletions src/lib/AnnotationModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
bdlBoxBlue,
bdlGreenLight,
bdlWatermelonRed,
bdlYellorange,
bdlYellow,
bdlGrimace,
} from 'box-ui-elements/es/styles/variables';
import Cache from './Cache';

export const ANNOTATION_COLOR_KEY = 'annotation-color';

export const AnnotationColor = {
BOX_BLUE: bdlBoxBlue,
GREEN_LIGHT: bdlGreenLight,
WATERMELON_RED: bdlWatermelonRed,
YELLORANGE: bdlYellorange,
YELLOW: bdlYellow,
GRIMACE: bdlGrimace,
};

export default class AnnotationModule {
private cache: Cache;

constructor({ cache }: { cache: Cache }) {
this.cache = cache;
}

public getColor = (): string => (this.cache.get(ANNOTATION_COLOR_KEY) as string) || bdlBoxBlue;

public setColor = (color: string): void => this.cache.set(ANNOTATION_COLOR_KEY, color, true);
}
22 changes: 22 additions & 0 deletions src/lib/__tests__/AnnotationModule-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { bdlBoxBlue } from 'box-ui-elements/es/styles/variables';
import AnnotationModule from '../AnnotationModule';
import Cache from '../Cache';

describe('lib/AnnotationModule', () => {
test('getColor', () => {
const cache = new Cache();
const annotationModule = new AnnotationModule({ cache });

expect(annotationModule.getColor()).toBe(bdlBoxBlue);
});

test('setColor', () => {
const cache = new Cache();
const color = '#fff';
const annotationModule = new AnnotationModule({ cache });

annotationModule.setColor(color);

expect(annotationModule.getColor()).toBe(color);
});
});
3 changes: 3 additions & 0 deletions src/lib/viewers/BaseViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { getIconFromExtension, getIconFromName } from '../icons/icons';
import { VIEWER_EVENT, ERROR_CODE, LOAD_METRIC, DOWNLOAD_REACHABILITY_METRICS } from '../events';
import { AnnotationMode } from '../AnnotationControls';
import AnnotationControlsFSM, { AnnotationInput } from '../AnnotationControlsFSM';
import AnnotationModule from '../AnnotationModule';
import PreviewError from '../PreviewError';
import Timer from '../Timer';

Expand Down Expand Up @@ -153,6 +154,8 @@ class BaseViewer extends EventEmitter {

this.annotationControlsFSM = new AnnotationControlsFSM();

this.annotationModule = new AnnotationModule({ cache: this.cache });

// Bind context for callbacks
this.resetLoadTimeout = this.resetLoadTimeout.bind(this);
this.preventDefault = this.preventDefault.bind(this);
Expand Down
9 changes: 7 additions & 2 deletions src/lib/viewers/controls/annotations/AnnotationsControls.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import noop from 'lodash/noop';
import { bdlBoxBlue } from 'box-ui-elements/es/styles/variables';
import AnnotationsButton from './AnnotationsButton';
import IconDrawing24 from '../icons/IconDrawing24';
import IconHighlightText16 from '../icons/IconHighlightText16';
Expand All @@ -9,6 +10,7 @@ import { AnnotationMode } from './types';
import './AnnotationsControls.scss';

export type Props = {
annotationColor?: string;
annotationMode?: AnnotationMode;
hasDrawing?: boolean;
hasHighlight?: boolean;
Expand All @@ -18,6 +20,7 @@ export type Props = {
};

export default function AnnotationsControls({
annotationColor = bdlBoxBlue,
annotationMode = AnnotationMode.NONE,
hasDrawing = false,
hasHighlight = false,
Expand Down Expand Up @@ -62,18 +65,20 @@ export default function AnnotationsControls({
return null;
}

const isDrawingActive = annotationMode === AnnotationMode.DRAWING;

return (
<div className="bp-AnnotationsControls">
<AnnotationsButton
data-resin-target="draw"
data-testid="bp-AnnotationsControls-drawBtn"
isActive={annotationMode === AnnotationMode.DRAWING}
isActive={isDrawingActive}
isEnabled={showDrawing}
mode={AnnotationMode.DRAWING}
onClick={handleModeClick}
title={__('drawing_comment')}
>
<IconDrawing24 />
<IconDrawing24 fill={isDrawingActive ? annotationColor : '#fff'} />
</AnnotationsButton>
<AnnotationsButton
data-resin-target="highlightRegion"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { bdlBoxBlue } from 'box-ui-elements/es/styles/variables';
import { ReactWrapper, mount } from 'enzyme';
import AnnotationsControls from '../AnnotationsControls';
import { AnnotationMode } from '../types';
Expand Down Expand Up @@ -106,5 +107,15 @@ describe('AnnotationsControls', () => {

expect(element.hasClass('bp-AnnotationsControls')).toBe(true);
});

test.each`
fill | mode
${bdlBoxBlue} | ${AnnotationMode.DRAWING}
${'#fff'} | ${AnnotationMode.NONE}
`('should return an IconDrawing24 with the fill set as $fill if annotationMode is $mode', ({ fill, mode }) => {
const wrapper = getWrapper({ annotationMode: mode, hasDrawing: true });

expect(wrapper.find('IconDrawing24').props().fill).toBe(fill);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
.bp-ColorPickerControl-swatch {
width: 18px;
height: 18px;
background-color: $box-blue;
border: 2px solid #fff;
border-radius: 2px;
}
36 changes: 15 additions & 21 deletions src/lib/viewers/controls/color-picker/ColorPickerControl.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,42 @@
import React, { useState } from 'react';
import classNames from 'classnames';
import { bdlBoxBlue } from 'box-ui-elements/es/styles/variables';
import ColorPickerPalette from './ColorPickerPalette';
import { AnnotationMode } from '../annotations/types';
import './ColorPickerControl.scss';

export type Props = {
annotationMode?: AnnotationMode;
onAnnotationColorClick: (color: string) => void;
isActive?: boolean;
activeColor?: string;
colors: Array<string>;
onColorSelect: (color: string) => void;
};

export default function ColorPickerControl({
annotationMode,
isActive = false,
onAnnotationColorClick,
activeColor = bdlBoxBlue,
colors,
onColorSelect,
...rest
}: Props): JSX.Element | null {
const [isColorPickerToggled, setIsColorPickerToggled] = useState(false);

if (annotationMode !== AnnotationMode.DRAWING) {
return null;
}
const handleSelect = (color: string): void => {
setIsColorPickerToggled(false);
onColorSelect(color);
};

return (
<div className="bp-ColorPickerControl">
{isColorPickerToggled && (
<div className="bp-ColorPickerControl-palette">
<ColorPickerPalette
onColorSelect={(color: string): void => {
setIsColorPickerToggled(false);
onAnnotationColorClick(color);
}}
/>
<ColorPickerPalette colors={colors} data-testid="bp-ColorPickerPalette" onSelect={handleSelect} />
</div>
)}
<button
className={classNames('bp-ColorPickerControl-button', {
'bp-is-active': isActive,
})}
className="bp-ColorPickerControl-button"
data-testid="bp-ColorPickerControl-button"
onClick={(): void => setIsColorPickerToggled(!isColorPickerToggled)}
type="button"
{...rest}
>
<div className="bp-ColorPickerControl-swatch" />
<div className="bp-ColorPickerControl-swatch" style={{ backgroundColor: activeColor }} />
</button>
</div>
);
Expand Down
10 changes: 5 additions & 5 deletions src/lib/viewers/controls/color-picker/ColorPickerPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ import React from 'react';
import './ColorPickerPalette.scss';

export type Props = {
onColorSelect: (color: string) => void;
colors: Array<string>;
onSelect: (color: string) => void;
};

export default function ColorPickerPalette({ onColorSelect }: Props): JSX.Element {
const colors = ['#0061d5', '#26c281', '#ed3757', '#f5b31b', '#ffd700', '#4826c2'];

export default function ColorPickerPalette({ colors, onSelect }: Props): JSX.Element {
return (
<div className="bp-ColorPickerPalette">
{colors.map(color => {
return (
<button
key={color}
className="bp-ColorPickerPalette-button"
onClick={(): void => onColorSelect(color)}
data-testid="bp-ColorPickerPalette-button"
onClick={(): void => onSelect(color)}
style={{
backgroundColor: color,
}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
import * as React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { AnnotationMode } from '../../annotations/types';
import ColorPickerControl from '../ColorPickerControl';

describe('ColorPickerControl', () => {
const onAnnotationColorClick = jest.fn();
const defaultColor = '#fff';

const getWrapper = (props = {}): ShallowWrapper =>
shallow(<ColorPickerControl onAnnotationColorClick={onAnnotationColorClick} {...props} />);
shallow(<ColorPickerControl colors={[defaultColor]} onColorSelect={jest.fn()} {...props} />);

const getToggleButton = (wrapper: ShallowWrapper): ShallowWrapper => wrapper.find('.bp-ColorPickerControl-button');
const getColorPickerPalette = (wrapper: ShallowWrapper): ShallowWrapper =>
wrapper.find('[data-testid="bp-ColorPickerPalette"]');

describe('render', () => {
test('should render null if annotationMode is not AnnotationMode.DRAWING', () => {
const wrapper = getWrapper({ annotationMode: AnnotationMode.REGION });

expect(wrapper.isEmptyRender()).toBe(true);
});
const getToggleButton = (wrapper: ShallowWrapper): ShallowWrapper =>
wrapper.find('[data-testid="bp-ColorPickerControl-button"]');

describe('render', () => {
test('should not render ColorPickerPalette when the component is first mounted', () => {
const wrapper = getWrapper();

expect(wrapper.find('ColorPickerPalette').exists()).toBe(false);
expect(getColorPickerPalette(wrapper).exists()).toBe(false);
});

test('should render ColorPickerPalette when the toggle button is clicked', () => {
const wrapper = getWrapper({ annotationMode: AnnotationMode.DRAWING });
const wrapper = getWrapper();

getToggleButton(wrapper).simulate('click');

expect(wrapper.find('ColorPickerPalette').exists()).toBe(true);
expect(getColorPickerPalette(wrapper).exists()).toBe(true);
});
});

test('should render the toggle button with bp-is-active set to true if isActive is true', () => {
const wrapper = getWrapper({ annotationMode: AnnotationMode.DRAWING, isActive: true });
describe('handleSelect', () => {
test('should close the palette and call onColorSelect if a color is selected', () => {
const onColorSelect = jest.fn();
const wrapper = getWrapper({ onColorSelect });

getToggleButton(wrapper).simulate('click');
getColorPickerPalette(wrapper).simulate('select', defaultColor);

expect(getToggleButton(wrapper).hasClass('bp-is-active')).toBe(true);
expect(getColorPickerPalette(wrapper).exists()).toBe(false);
expect(onColorSelect).toHaveBeenCalledWith(defaultColor);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,33 @@ import { shallow, ShallowWrapper } from 'enzyme';
import ColorPickerPalette from '../ColorPickerPalette';

describe('ColorPickerPalette', () => {
const onColorSelect = jest.fn();
const defaultColor = '#fff';
const colors = [defaultColor];

const getWrapper = (props = {}): ShallowWrapper =>
shallow(<ColorPickerPalette onColorSelect={onColorSelect} {...props} />);
shallow(<ColorPickerPalette colors={colors} onSelect={jest.fn()} {...props} />);

const getButton = (wrapper: ShallowWrapper): ShallowWrapper =>
wrapper.find('[data-testid="bp-ColorPickerPalette-button"]');

describe('render', () => {
test('should render the six color swatches', () => {
test('should render a single color swatch', () => {
const wrapper = getWrapper();

expect(wrapper.find('button').length).toBe(6);
expect(getButton(wrapper).length).toBe(1);
});
});

describe('onSelect', () => {
test('should call onSelect with a button is clicked', () => {
const onSelect = jest.fn();
const wrapper = getWrapper({ onSelect });

getButton(wrapper)
.at(0)
.simulate('click');

expect(onSelect).toHaveBeenCalledWith(defaultColor);
});
});
});
9 changes: 6 additions & 3 deletions src/lib/viewers/controls/icons/IconDrawing24.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import * as React from 'react';

function IconDrawing24(props: React.SVGProps<SVGSVGElement>): JSX.Element {
function IconDrawing24({ fill, ...rest }: React.SVGProps<SVGSVGElement>): JSX.Element {
return (
<svg focusable="false" height={24} viewBox="0 0 18 18" width={24} {...props}>
<path d="M12.444 13.4l.677-.673a.876.876 0 00.15.995c.33.33.77.23 1.078-.14.743-.855.855-1.934.302-2.523-.743-.792-1.716-.2-2.656.736l-.973 1.01 1.17-1.366c1.5-1.7 2.13-3.16 1.236-4.03-.835-.827-1.986-.105-3.827 1.696-1.552 1.52-2.347 2.347-2.63 2.04-.217-.23.15-.736 2.02-2.964 2.558-3.048 3.636-5.185 2.53-6.25-1.13-1.086-2.44-.133-5.582 2.593C3.765 6.424 2.4 7.355 1.522 7.84c-.44.245-.63.708-.46 1.12.178.427.644.56 1.157.287.88-.462 2.433-1.563 4.662-3.504C9.616 3.36 10.182 2.9 10.465 3.2c.342.35-.276 1.33-2.65 4.106-2.65 3.083-2.94 4.26-2.07 5.185 1.006 1.072 2.38.2 4.655-2.01 1.802-1.76 1.913-1.913 2.02-1.794.112.12-.125.308-1.736 2.312-1.27 1.563-1.532 2.46-.874 3.16.605.652 1.466.35 2.637-.75z" />
<svg focusable="false" height={24} viewBox="0 0 18 18" width={24} {...rest}>
<path
d="M12.444 13.4l.677-.673a.876.876 0 00.15.995c.33.33.77.23 1.078-.14.743-.855.855-1.934.302-2.523-.743-.792-1.716-.2-2.656.736l-.973 1.01 1.17-1.366c1.5-1.7 2.13-3.16 1.236-4.03-.835-.827-1.986-.105-3.827 1.696-1.552 1.52-2.347 2.347-2.63 2.04-.217-.23.15-.736 2.02-2.964 2.558-3.048 3.636-5.185 2.53-6.25-1.13-1.086-2.44-.133-5.582 2.593C3.765 6.424 2.4 7.355 1.522 7.84c-.44.245-.63.708-.46 1.12.178.427.644.56 1.157.287.88-.462 2.433-1.563 4.662-3.504C9.616 3.36 10.182 2.9 10.465 3.2c.342.35-.276 1.33-2.65 4.106-2.65 3.083-2.94 4.26-2.07 5.185 1.006 1.072 2.38.2 4.655-2.01 1.802-1.76 1.913-1.913 2.02-1.794.112.12-.125.308-1.736 2.312-1.27 1.563-1.532 2.46-.874 3.16.605.652 1.466.35 2.637-.75z"
fill={fill}
/>
</svg>
);
}
Expand Down
10 changes: 6 additions & 4 deletions src/lib/viewers/doc/DocBaseViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class DocBaseViewer extends BaseViewer {
this.emitMetric = this.emitMetric.bind(this);
this.handleAssetAndRepLoad = this.handleAssetAndRepLoad.bind(this);
this.handleFindBarClose = this.handleFindBarClose.bind(this);
this.handleAnnotationColorClick = this.handleAnnotationColorClick.bind(this);
this.handleAnnotationColorChange = this.handleAnnotationColorChange.bind(this);
this.handleAnnotationControlsClick = this.handleAnnotationControlsClick.bind(this);
this.handleAnnotationControlsEscape = this.handleAnnotationControlsEscape.bind(this);
this.handleAnnotationCreateEvent = this.handleAnnotationCreateEvent.bind(this);
Expand Down Expand Up @@ -1089,13 +1089,14 @@ class DocBaseViewer extends BaseViewer {

this.controls.render(
<DocControls
annotationColor={this.annotationModule.getColor()}
annotationMode={this.annotationControlsFSM.getMode()}
hasDrawing={canDraw}
hasHighlight={canHighlight}
hasRegion={canAnnotate}
maxScale={MAX_SCALE}
minScale={MIN_SCALE}
onAnnotationColorClick={this.handleAnnotationColorClick}
onAnnotationColorChange={this.handleAnnotationColorChange}
onAnnotationModeClick={this.handleAnnotationControlsClick}
onAnnotationModeEscape={this.handleAnnotationControlsEscape}
onFindBarToggle={this.toggleFindBar}
Expand Down Expand Up @@ -1671,8 +1672,9 @@ class DocBaseViewer extends BaseViewer {
}
}

handleAnnotationColorClick() {
// TODO: Will implement in a separate PR
handleAnnotationColorChange(color) {
this.annotationModule.setColor(color);
this.renderUI();
}

handleAnnotationControlsClick({ mode }) {
Expand Down
Loading

0 comments on commit 3d2149e

Please sign in to comment.