浏览代码

add print feature

* add some unit test
* optimization props type
* improvement coding rule
RoyLiu 4 年之前
父节点
当前提交
2795cd06be
共有 82 个文件被更改,包括 925 次插入641 次删除
  1. 22 18
      __test__/button.test.tsx
  2. 23 0
      __test__/dialog.test.tsx
  3. 6 2
      __test__/drawer.test.tsx
  4. 6 2
      __test__/selectBox.test.tsx
  5. 30 0
      __test__/slider.test.tsx
  6. 12 0
      __test__/textField.test.tsx
  7. 27 0
      __test__/typography.test.tsx
  8. 2 1
      actions/main.ts
  9. 0 7
      actions/pdf.ts
  10. 46 46
      components/AnnotationList/index.tsx
  11. 4 14
      components/AnnotationListHead/index.tsx
  12. 0 2
      components/AnnotationSelector/index.tsx
  13. 1 1
      components/Button/index.tsx
  14. 20 9
      components/Button/styled.ts
  15. 2 0
      components/Dialog/styled.ts
  16. 27 0
      components/Embed/index.tsx
  17. 26 0
      components/Embed/styled.ts
  18. 0 5
      components/FreeText/index.tsx
  19. 0 1
      components/FreeTextOption/index.tsx
  20. 0 1
      components/Highlight/index.tsx
  21. 0 1
      components/HighlightOption/index.tsx
  22. 2 1
      components/Icon/data.ts
  23. 0 7
      components/Ink/index.tsx
  24. 0 1
      components/InkOption/index.tsx
  25. 0 5
      components/Line/index.tsx
  26. 17 0
      components/Loading/index.tsx
  27. 51 0
      components/Loading/styled.ts
  28. 4 4
      components/Navbar/data.ts
  29. 0 3
      components/Navbar/index.tsx
  30. 5 4
      components/OuterRect/data.ts
  31. 0 1
      components/OuterRect/index.tsx
  32. 0 1
      components/OuterRectForLine/index.tsx
  33. 0 5
      components/Page/index.tsx
  34. 0 1
      components/SelectBox/index.tsx
  35. 1 5
      components/Shape/index.tsx
  36. 22 19
      components/ShapeOption/index.tsx
  37. 2 1
      components/Sliders/index.tsx
  38. 9 6
      components/Sliders/styled.ts
  39. 25 40
      components/StickyNote/index.tsx
  40. 12 17
      components/Tabs/index.tsx
  41. 20 17
      components/TextField/index.tsx
  42. 29 17
      components/TextField/styled.ts
  43. 0 1
      components/ThumbnailViewer/index.tsx
  44. 0 1
      components/Toolbar/index.tsx
  45. 1 7
      components/Typography/index.tsx
  46. 3 3
      components/Typography/styled.ts
  47. 11 17
      components/Viewer/index.tsx
  48. 2 5
      components/Watermark/index.tsx
  49. 34 23
      components/WatermarkOption/index.tsx
  50. 1 0
      config/index.js
  51. 1 0
      constants/actionTypes.ts
  52. 1 1
      constants/style.ts
  53. 0 165
      constants/type.ts
  54. 0 1
      containers/Annotation.tsx
  55. 4 13
      containers/AnnotationList.tsx
  56. 1 1
      containers/FreeTextTools.tsx
  57. 0 1
      containers/FreehandTools.tsx
  58. 0 1
      containers/HighlightTools.tsx
  59. 18 0
      containers/Loading.tsx
  60. 20 5
      containers/Navbar.tsx
  61. 0 1
      containers/PdfPage.tsx
  62. 0 1
      containers/PdfPages.tsx
  63. 0 1
      containers/PdfViewer.tsx
  64. 1 1
      containers/ShapeTools.tsx
  65. 0 1
      containers/WatermarkTool.tsx
  66. 164 0
      custom.d.ts
  67. 0 1
      helpers/annotation.ts
  68. 0 1
      helpers/markup.ts
  69. 23 16
      helpers/pdf.ts
  70. 0 8
      helpers/position.ts
  71. 6 5
      helpers/time.ts
  72. 64 2
      helpers/utility.ts
  73. 3 4
      helpers/watermark.ts
  74. 4 4
      package.json
  75. 4 0
      pages/index.tsx
  76. 1 0
      public/icons/button_close.svg
  77. 1 0
      reducers/index.ts
  78. 5 2
      reducers/main.ts
  79. 1 6
      reducers/pdf.ts
  80. 3 5
      store/initialMainState.ts
  81. 0 7
      store/initialPdfState.ts
  82. 95 65
      yarn.lock

+ 22 - 18
__test__/button.test.tsx

@@ -2,6 +2,7 @@
 /* eslint-disable no-undef */
 import React from 'react';
 import { render, cleanup, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
 
 import Button from '../components/Button';
 import { color } from '../constants/style';
@@ -15,31 +16,34 @@ describe('Button component', () => {
     expect(getByText('btn content').textContent).toBe('btn content');
   });
 
-  test('check button theme', () => {
-    const { container } = render(<Button appearance="primary">btn content</Button>);
-    const btn = container.querySelector('button');
-    const style = window.getComputedStyle(btn as HTMLElement) as { [key: string]: any };
+  test('check button primary theme', () => {
+    const { getByText } = render(
+      <Button appearance="primary">btn content</Button>
+    );
 
-    expect(style['border-color']).toBe(color.primary);
-    expect(style['background-color']).toBe('rgb(88, 106, 242)');
-    expect(style.color).toBe('white');
+    expect(getByText('btn content')).toHaveStyle(`
+      background-color: ${color.primary};
+      border-color: ${color.primary};
+      color: white;
+    `);
   });
 
-  test('click button', () => {
+  test('check click event', () => {
     let counter = 0;
-    const cb = (): void => { counter += 1; };
-    const { getByText } = render(<Button onClick={cb}>btn content</Button>);
-
-    fireEvent.click(getByText('btn content'));
+    const handleClick = (): void => {
+      counter += 1;
+    };
+    const { getByText } = render(
+      <Button onClick={handleClick}>btn content</Button>
+    );
+
+    fireEvent.mouseDown(getByText('btn content'));
     expect(counter).toBe(1);
   });
 
-  test('disable button', () => {
-    let counter = 0;
-    const cb = (): void => { counter += 1; };
-    const { getByText } = render(<Button isDisabled onClick={cb}>btn content</Button>);
+  test('check disable status', () => {
+    const { getByText } = render(<Button isDisabled>btn content</Button>);
 
-    fireEvent.click(getByText('btn content'));
-    expect(counter).toBe(0);
+    expect(getByText('btn content')).toBeDisabled();
   });
 });

+ 23 - 0
__test__/dialog.test.tsx

@@ -0,0 +1,23 @@
+/* eslint-disable import/no-extraneous-dependencies */
+/* eslint-disable no-undef */
+import React from 'react';
+import { render, cleanup } from '@testing-library/react';
+import '@testing-library/jest-dom';
+
+import Dialog from '../components/Dialog';
+
+describe('Dialog component', () => {
+  afterEach(cleanup);
+
+  test('check open status', () => {
+    const { getByText } = render(<Dialog open>content</Dialog>);
+
+    expect(getByText('content')).toBeVisible();
+  });
+
+  test('check close status', () => {
+    const { queryByText } = render(<Dialog open={false}>content</Dialog>);
+
+    expect(queryByText('content')).not.toBeInTheDocument();
+  });
+});

+ 6 - 2
__test__/drawer.test.tsx

@@ -16,7 +16,9 @@ describe('Drawer component', () => {
   test('close drawer', () => {
     const { getByTestId } = render(<Drawer>content</Drawer>);
     const ele = getByTestId('drawer');
-    const style = window.getComputedStyle(ele as HTMLElement) as { [key: string]: any };
+    const style = window.getComputedStyle(ele as HTMLElement) as {
+      [key: string]: any;
+    };
 
     expect(style.transform).toBe('translate(0,267px)');
   });
@@ -24,7 +26,9 @@ describe('Drawer component', () => {
   test('open drawer', () => {
     const { getByTestId } = render(<Drawer open>content</Drawer>);
     const ele = getByTestId('drawer');
-    const style = window.getComputedStyle(ele as HTMLElement) as { [key: string]: any };
+    const style = window.getComputedStyle(ele as HTMLElement) as {
+      [key: string]: any;
+    };
 
     expect(style.transform).toBe('translate(0,0)');
   });

+ 6 - 2
__test__/selectBox.test.tsx

@@ -23,7 +23,9 @@ describe('SelectBox component', () => {
 
   test('default value', () => {
     const { getAllByTestId } = render(<SelectBox options={options} />);
-    const ele = getAllByTestId('selected')[0].querySelector('div') as HTMLDivElement;
+    const ele = getAllByTestId('selected')[0].querySelector(
+      'div'
+    ) as HTMLDivElement;
 
     expect(ele.textContent).toBe('option 1');
   });
@@ -31,7 +33,9 @@ describe('SelectBox component', () => {
   test('open list', () => {
     const { container } = render(<SelectBox options={options} />);
 
-    fireEvent.mouseDown(container.querySelector('[data-testid="selected"]') as HTMLElement);
+    fireEvent.mouseDown(
+      container.querySelector('[data-testid="selected"]') as HTMLElement
+    );
 
     expect(container.querySelectorAll('span')[1].textContent).toBe('option 2');
   });

+ 30 - 0
__test__/slider.test.tsx

@@ -0,0 +1,30 @@
+/* eslint-disable import/no-extraneous-dependencies */
+/* eslint-disable no-undef */
+import React from 'react';
+import { render, cleanup } from '@testing-library/react';
+import '@testing-library/jest-dom';
+
+import Sliders from '../components/Sliders';
+
+describe('Slider component', () => {
+  afterEach(cleanup);
+
+  test('check sliders status', () => {
+    const { getByTestId } = render(<Sliders />);
+
+    expect(getByTestId('sliders')).toBeVisible();
+  });
+
+  // test('mouse move on sliders', async () => {
+  //   const { getByTestId } = render(<Sliders defaultValue={50} />);
+  //   fireEvent.mouseDown(getByTestId('sliders'), {
+  //     clientX: 60,
+  //     clientY: 15,
+  //     buttons: 1,
+  //   });
+
+  //   const style = window.getComputedStyle(getByTestId('track'));
+  //   console.log(style);
+  //   expect(getByTestId('track')).toHaveStyle('left: calc(50% - 10px);');
+  // });
+});

+ 12 - 0
__test__/textField.test.tsx

@@ -2,6 +2,7 @@
 /* eslint-disable no-undef */
 import React from 'react';
 import { render, cleanup, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
 
 import TextField from '../components/TextField';
 
@@ -27,4 +28,15 @@ describe('TextField component', () => {
 
     expect(inputNode.value).toBe('text');
   });
+
+  test('check disabled input', () => {
+    const handleChange = jest.fn();
+    const { getByTestId } = render(
+      <TextField disabled onChange={handleChange} />
+    );
+    fireEvent.change(getByTestId('input'), { target: { value: 'text' } });
+
+    expect(handleChange).toBeCalledTimes(0);
+    expect(getByTestId('input')).toBeDisabled();
+  });
 });

+ 27 - 0
__test__/typography.test.tsx

@@ -0,0 +1,27 @@
+/* eslint-disable import/no-extraneous-dependencies */
+/* eslint-disable no-undef */
+import React from 'react';
+import { render, cleanup } from '@testing-library/react';
+import '@testing-library/jest-dom';
+
+import Typography from '../components/Typography';
+
+describe('Typography component', () => {
+  afterEach(cleanup);
+
+  test('check title style', () => {
+    const { getByText } = render(
+      <Typography variant="title">title</Typography>
+    );
+
+    expect(getByText('title')).toHaveStyle('font-size: 16px;color: #000000;');
+  });
+
+  test('check subtitle style', () => {
+    const { queryByText } = render(
+      <Typography variant="subtitle">title</Typography>
+    );
+
+    expect(queryByText('title')).toHaveStyle('font-size: 14px;color: #000000;');
+  });
+});

+ 2 - 1
actions/main.ts

@@ -1,5 +1,4 @@
 import * as types from '../constants/actionTypes';
-import { ActionType } from '../constants/type';
 
 const actions: ActionType = dispatch => ({
   toggleDisplayMode: (state: string): void =>
@@ -12,6 +11,8 @@ const actions: ActionType = dispatch => ({
     dispatch({ type: types.SET_MARKUP_TOOL, payload: state }),
   setInfo: (state: Record<string, any>): void =>
     dispatch({ type: types.SET_INFO, payload: state }),
+  setLoading: (state: boolean): void =>
+    dispatch({ type: types.SET_LOADING, payload: state }),
 });
 
 export default actions;

+ 0 - 7
actions/pdf.ts

@@ -1,11 +1,4 @@
 import * as types from '../constants/actionTypes';
-import {
-  ProgressType,
-  ViewportType,
-  AnnotationType,
-  ActionType,
-  WatermarkType,
-} from '../constants/type';
 
 const actions: ActionType = dispatch => ({
   setTotalPage: (page: number): void =>

+ 46 - 46
components/AnnotationList/index.tsx

@@ -1,13 +1,8 @@
-import React, {
-  useEffect, useState, useRef, useCallback,
-} from 'react';
+import React, { useEffect, useState, useRef, useCallback } from 'react';
 import { useTranslation } from 'react-i18next';
 
 import Typography from '../Typography';
 import Item from '../AnnotationItem';
-import {
-  AnnotationType, ViewportType, ScrollStateType, PositionType,
-} from '../../constants/type';
 
 import { watchScroll } from '../../helpers/utility';
 import { getAnnotationText } from '../../helpers/annotation';
@@ -34,7 +29,10 @@ const AnnotationList: React.FC<Props> = ({
   const containerRef = useRef<HTMLDivElement>(null);
   const innerRef = useRef<HTMLDivElement>(null);
 
-  const getText = async (page: number, position: PositionType[]): Promise<any> => {
+  const getText = async (
+    page: number,
+    position: PositionType[]
+  ): Promise<any> => {
     const text = await getAnnotationText({
       pdf,
       viewport,
@@ -46,20 +44,23 @@ const AnnotationList: React.FC<Props> = ({
     return text;
   };
 
-  const scrollUpdate = useCallback((state: ScrollStateType): void => {
-    const innerHeight = innerRef.current?.offsetHeight || 0;
-    const wrapperHeight = containerRef.current?.offsetHeight || 0;
+  const scrollUpdate = useCallback(
+    (state: ScrollStateType): void => {
+      const innerHeight = innerRef.current?.offsetHeight || 0;
+      const wrapperHeight = containerRef.current?.offsetHeight || 0;
 
-    if (
-      wrapperHeight + state.lastY >= innerHeight
-      && renderQueue.length !== annotations.length
-    ) {
-      const start = renderQueue.length;
-      const end = renderQueue.length + 10;
-      const newQueue = [...renderQueue, ...annotations.slice(start, end)];
-      setQueue(newQueue);
-    }
-  }, [renderQueue, annotations]);
+      if (
+        wrapperHeight + state.lastY >= innerHeight &&
+        renderQueue.length !== annotations.length
+      ) {
+        const start = renderQueue.length;
+        const end = renderQueue.length + 10;
+        const newQueue = [...renderQueue, ...annotations.slice(start, end)];
+        setQueue(newQueue);
+      }
+    },
+    [renderQueue, annotations]
+  );
 
   useEffect(() => {
     const state = watchScroll(containerRef.current, scrollUpdate);
@@ -81,33 +82,32 @@ const AnnotationList: React.FC<Props> = ({
         <Typography light align="left">
           {`${annotations.length} ${t('annotation')}`}
         </Typography>
-        {isActive && renderQueue.map((ele, index) => {
-          const key = `annot_item_${index}`;
-          const {
-            obj_type,
-            obj_attr: {
-              page,
-              bdcolor,
-              position,
-              transparency,
-            },
-          } = ele;
-          const actualPage = page + 1;
-          const prevAnnot = annotations[index - 1];
-          const prevPage = index > 0 && prevAnnot ? prevAnnot.obj_attr.page + 1 : -1;
+        {isActive &&
+          renderQueue.map((ele, index) => {
+            const key = `annot_item_${index}`;
+            const {
+              obj_type,
+              obj_attr: { page, bdcolor, position, transparency },
+            } = ele;
+            const actualPage = page + 1;
+            const prevAnnot = annotations[index - 1];
+            const prevPage =
+              index > 0 && prevAnnot ? prevAnnot.obj_attr.page + 1 : -1;
 
-          return (
-            <Item
-              key={key}
-              type={obj_type}
-              page={actualPage}
-              bdcolor={bdcolor || ''}
-              transparency={transparency || 0}
-              getText={(): Promise<any> => getText(actualPage, position as PositionType[])}
-              showPageNum={actualPage !== prevPage}
-            />
-          );
-        })}
+            return (
+              <Item
+                key={key}
+                type={obj_type}
+                page={actualPage}
+                bdcolor={bdcolor || ''}
+                transparency={transparency || 0}
+                getText={(): Promise<any> =>
+                  getText(actualPage, position as PositionType[])
+                }
+                showPageNum={actualPage !== prevPage}
+              />
+            );
+          })}
       </div>
     </Body>
   );

+ 4 - 14
components/AnnotationListHead/index.tsx

@@ -1,15 +1,12 @@
 import React from 'react';
 import queryString from 'query-string';
 
-import { AnnotationType } from '../../constants/type';
 import Icon from '../Icon';
 import { downloadFileWithUri, uploadFile } from '../../helpers/utility';
 import { parseAnnotationFromXml } from '../../helpers/annotation';
 
 import { Separator } from '../../global/otherStyled';
-import {
-  Head, IconWrapper,
-} from '../../global/sidebarStyled';
+import { Head, IconWrapper } from '../../global/sidebarStyled';
 
 type Props = {
   close: () => void;
@@ -17,11 +14,7 @@ type Props = {
   hasAnnots: boolean;
 };
 
-const index: React.FC<Props> = ({
-  close,
-  addAnnots,
-  hasAnnots,
-}: Props) => {
+const index: React.FC<Props> = ({ close, addAnnots, hasAnnots }: Props) => {
   const handleExport = (): void => {
     if (hasAnnots) {
       const parsed = queryString.parse(window.location.search);
@@ -31,7 +24,7 @@ const index: React.FC<Props> = ({
   };
 
   const handleImport = (): void => {
-    uploadFile('.xfdf').then((data) => {
+    uploadFile('.xfdf').then(data => {
       const annotations = parseAnnotationFromXml(data);
       addAnnots(annotations);
     });
@@ -47,10 +40,7 @@ const index: React.FC<Props> = ({
         <Icon glyph="sort" />
       </IconWrapper>
       <IconWrapper onClick={handleExport}>
-        <Icon
-          glyph="annotation-export"
-          isDisabled={!hasAnnots}
-        />
+        <Icon glyph="annotation-export" isDisabled={!hasAnnots} />
       </IconWrapper>
       <IconWrapper onClick={handleImport}>
         <Icon glyph="import" />

+ 0 - 2
components/AnnotationSelector/index.tsx

@@ -7,8 +7,6 @@ import Box from '../Box';
 import Sliders from '../Sliders';
 import ColorSelector from '../../containers/ColorSelector';
 
-import { OnUpdateType } from '../../constants/type';
-
 import { Container, Subtitle, SliderWrapper } from './styled';
 
 type Props = {

+ 1 - 1
components/Button/index.tsx

@@ -32,7 +32,7 @@ const Button: React.FC<Props> = ({
   ...rest
 }: Props): React.ReactElement =>
   isDisabled ? (
-    <DisableButton>{children}</DisableButton>
+    <DisableButton disabled>{children}</DisableButton>
   ) : (
     <NormalButton {...rest} onMouseDown={onClick}>
       {children}

+ 20 - 9
components/Button/styled.ts

@@ -1,7 +1,7 @@
 import styled, { css } from 'styled-components';
 import { color } from '../../constants/style';
 
-const align: {[index: string]: string} = {
+const align: { [index: string]: string } = {
   left: 'flex-start',
   center: 'center',
   right: 'flex-end',
@@ -13,14 +13,14 @@ const staticStyles = css`
   box-sizing: border-box;
   font-size: 16px;
   font-style: normal;
-  transition: all .3s linear;
+  transition: all 0.3s linear;
   cursor: pointer;
   outline: none;
   padding: 5px 20px;
   min-width: 80px;
 `;
 
-const theme: {[index: string]: any} = {
+const theme: { [index: string]: any } = {
   default: css`
     color: black;
     background-color: transparent;
@@ -94,7 +94,7 @@ const theme: {[index: string]: any} = {
   `,
 };
 
-const activeTheme: {[index: string]: any} = {
+const activeTheme: { [index: string]: any } = {
   default: css`
     background-color: ${color['soft-blue']};
   `,
@@ -110,13 +110,24 @@ const activeTheme: {[index: string]: any} = {
   `,
 };
 
-export const NormalButton = styled('div')<{appearance?: string; shouldFitContainer?: boolean; align?: string; isActive?: boolean}>`
+export const NormalButton = styled('div')<{
+  appearance?: string;
+  shouldFitContainer?: boolean;
+  align?: string;
+  isActive?: boolean;
+}>`
   ${staticStyles}
   ${props => theme[props.appearance || 'default']};
-  ${props => (props.shouldFitContainer ? css`
-    width: 100%;
-  ` : null)}
-  ${props => (props.isActive ? activeTheme[props.appearance || 'default'] : null)}
+  ${props =>
+    props.shouldFitContainer
+      ? css`
+          width: 100%;
+        `
+      : null}
+  ${props =>
+    props.isActive
+      ? activeTheme[props.appearance || 'default']
+      : null}
 
   display: inline-flex;
   justify-content: ${props => align[props.align || 'center']};

+ 2 - 0
components/Dialog/styled.ts

@@ -8,3 +8,5 @@ export const Wrapper = styled.div`
   border: solid 1px rgba(0, 0, 0, 0.12);
   display: inline-block;
 `;
+
+export default Wrapper;

+ 27 - 0
components/Embed/index.tsx

@@ -0,0 +1,27 @@
+import React from 'react';
+
+import Portal from '../Portal';
+
+import { Wrapper, Bg, CloseImg } from './styled';
+
+const EmbedComp = () => {
+  const handleClose = () => {
+    const wrapper = document.getElementById('embed-wrapper') as HTMLElement;
+    const embeds = document.getElementsByTagName('embed');
+    if (embeds[0]) {
+      wrapper.style.display = 'none';
+      wrapper.removeChild(embeds[0]);
+    }
+  };
+
+  return (
+    <Portal>
+      <Wrapper id="embed-wrapper">
+        <Bg />
+        <CloseImg src="/icons/button_close.svg" onClick={handleClose} />
+      </Wrapper>
+    </Portal>
+  );
+};
+
+export default EmbedComp;

+ 26 - 0
components/Embed/styled.ts

@@ -0,0 +1,26 @@
+import styled from 'styled-components';
+
+export const Wrapper = styled.div`
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 100;
+  display: none;
+  justify-content: center;
+  align-items: center;
+`;
+
+export const Bg = styled.div`
+  background-color: rgba(0, 0, 0, 0.5);
+  width: 100%;
+  height: 100%;
+`;
+
+export const CloseImg = styled.img`
+  position: fixed;
+  top: 50px;
+  right: 50px;
+  transform: scale(5.5);
+`;

+ 0 - 5
components/FreeText/index.tsx

@@ -1,11 +1,6 @@
 import React, { useEffect, useRef } from 'react';
 
 import OuterRect from '../OuterRect';
-import {
-  AnnotationElementPropsType,
-  PositionType,
-  CoordType,
-} from '../../constants/type';
 import { rectCalc, parsePositionForBackend } from '../../helpers/position';
 
 import { AnnotationContainer } from '../../global/otherStyled';

+ 0 - 1
components/FreeTextOption/index.tsx

@@ -6,7 +6,6 @@ import Typography from '../Typography';
 import SliderWithTitle from '../SliderWithTitle';
 import SelectBox from '../SelectBox';
 import ColorSelector from '../../containers/ColorSelector';
-import { OptionPropsType, SelectOptionType } from '../../constants/type';
 
 import { Wrapper, Group, Item } from '../../global/toolStyled';
 

+ 0 - 1
components/Highlight/index.tsx

@@ -1,6 +1,5 @@
 import React from 'react';
 
-import { AnnotationElementPropsType } from '../../constants/type';
 import AnnotationSelector from '../AnnotationSelector';
 import Markup from '../Markup';
 import { rectCalc } from '../../helpers/position';

+ 0 - 1
components/HighlightOption/index.tsx

@@ -1,7 +1,6 @@
 import React from 'react';
 import { useTranslation } from 'react-i18next';
 
-import { OptionPropsType } from '../../constants/type';
 import Icon from '../Icon';
 import Typography from '../Typography';
 import SliderWithTitle from '../SliderWithTitle';

+ 2 - 1
components/Icon/data.ts

@@ -1,3 +1,4 @@
+// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
 // @ts-ignore
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 import React from 'react';
@@ -77,7 +78,7 @@ import RightArrow from '../../public/icons/right-arrow.svg';
 import Clear from '../../public/icons/sidebar/clear.svg';
 import None from '../../public/icons/sidebar/None.svg';
 
-const data: {[index: string]: any} = {
+const data: { [index: string]: any } = {
   close: {
     Normal: Close,
   },

+ 0 - 7
components/Ink/index.tsx

@@ -1,12 +1,5 @@
 import React from 'react';
 
-import {
-  AnnotationElementPropsType,
-  PointType,
-  HTMLCoordinateType,
-  CoordType,
-  UpdateData,
-} from '../../constants/type';
 import OuterRect from '../OuterRect';
 import {
   svgPath,

+ 0 - 1
components/InkOption/index.tsx

@@ -1,7 +1,6 @@
 import React from 'react';
 import { useTranslation } from 'react-i18next';
 
-import { OptionPropsType } from '../../constants/type';
 // import Icon from '../Icon';
 // import Button from '../Button';
 // import Typography from '../Typography';

+ 0 - 5
components/Line/index.tsx

@@ -2,11 +2,6 @@ import React from 'react';
 import { v4 as uuidv4 } from 'uuid';
 
 import Outer from '../OuterRectForLine';
-import {
-  AnnotationElementPropsType,
-  LinePositionType,
-  HTMLCoordinateType,
-} from '../../constants/type';
 import { parsePositionForBackend } from '../../helpers/position';
 
 import { AnnotationContainer } from '../../global/otherStyled';

+ 17 - 0
components/Loading/index.tsx

@@ -0,0 +1,17 @@
+import React from 'react';
+
+import { Spinner, Bounce1, Bounce2, Bounce3 } from './styled';
+
+type Props = {};
+
+const Loading: React.SFC<Props> = () => {
+  return (
+    <Spinner>
+      <Bounce1 />
+      <Bounce2 />
+      <Bounce3 />
+    </Spinner>
+  );
+};
+
+export default Loading;

+ 51 - 0
components/Loading/styled.ts

@@ -0,0 +1,51 @@
+import styled, { keyframes } from 'styled-components';
+
+const bouncedelay = keyframes`
+  0%, 80%, 100% { 
+    transform: scale(0);
+  } 40% { 
+    transform: scale(1.0);
+  }
+`;
+
+export const Spinner = styled.div`
+  margin: 2px auto 0;
+  display: inline-block;
+  width: auto;
+  text-align: center;
+`;
+
+export const Bounce1 = styled.div`
+  width: 14px;
+  height: 14px;
+  margin: 2px;
+  background-color: #676767;
+
+  border-radius: 100%;
+  display: inline-block;
+  animation: ${bouncedelay} 1.4s infinite ease-in-out both;
+  animation-delay: -0.32s;
+`;
+
+export const Bounce2 = styled.div`
+  width: 14px;
+  height: 14px;
+  margin: 2px;
+  background-color: #676767;
+
+  border-radius: 100%;
+  display: inline-block;
+  animation: ${bouncedelay} 1.4s infinite ease-in-out both;
+  animation-delay: -0.16s;
+`;
+
+export const Bounce3 = styled.div`
+  width: 14px;
+  height: 14px;
+  margin: 2px;
+  background-color: #676767;
+
+  border-radius: 100%;
+  display: inline-block;
+  animation: ${bouncedelay} 1.4s infinite ease-in-out both;
+`;

+ 4 - 4
components/Navbar/data.ts

@@ -12,10 +12,10 @@ export default {
       key: 'nav-thumbnails',
       content: 'thumbnails',
     },
-    // {
-    //   key: 'nav-print',
-    //   content: 'print',
-    // },
+    {
+      key: 'nav-print',
+      content: 'print',
+    },
     {
       key: 'nav-export',
       content: 'export',

+ 0 - 3
components/Navbar/index.tsx

@@ -13,7 +13,6 @@ type Props = {
   children: React.ReactNode;
   displayMode: string;
   fileName: string;
-  isSaving: boolean;
 };
 
 const Navbar: React.FC<Props> = ({
@@ -22,7 +21,6 @@ const Navbar: React.FC<Props> = ({
   children,
   displayMode,
   fileName,
-  isSaving,
 }: Props) => (
   <Container isHidden={displayMode === 'full'}>
     <Typography variant="title">{fileName}</Typography>
@@ -32,7 +30,6 @@ const Navbar: React.FC<Props> = ({
         key={ele.key}
         glyph={ele.content}
         isActive={navbarState === ele.content}
-        isDisabled={!!(isSaving && ele.key === 'nav-export')}
         onClick={(): void => {
           onClick(ele.content);
         }}

+ 5 - 4
components/OuterRect/data.ts

@@ -1,8 +1,9 @@
-import { CircleType } from '../../constants/type';
-
 const RADIUS = 6;
 
-const generateCirclesData = (width: number, height: number): Array<CircleType> => ([
+const generateCirclesData = (
+  width: number,
+  height: number
+): Array<CircleType> => [
   {
     direction: 'top-left',
     cx: RADIUS,
@@ -51,6 +52,6 @@ const generateCirclesData = (width: number, height: number): Array<CircleType> =
     cy: RADIUS,
     r: RADIUS,
   },
-]);
+];
 
 export default generateCirclesData;

+ 0 - 1
components/OuterRect/index.tsx

@@ -4,7 +4,6 @@ import { color } from '../../constants/style';
 import useCursorPosition from '../../hooks/useCursorPosition';
 import { getAbsoluteCoordinate } from '../../helpers/position';
 import { calcDragAndDropScale } from '../../helpers/utility';
-import { CoordType, PointType, CircleType } from '../../constants/type';
 
 import generateCirclesData from './data';
 import { SVG, Rect, Circle } from './styled';

+ 0 - 1
components/OuterRectForLine/index.tsx

@@ -3,7 +3,6 @@ import React, { useEffect, useState } from 'react';
 import { color } from '../../constants/style';
 import useCursorPosition from '../../hooks/useCursorPosition';
 import { getAbsoluteCoordinate } from '../../helpers/position';
-import { LinePositionType, PointType } from '../../constants/type';
 
 import { SVG, Circle } from './styled';
 

+ 0 - 5
components/Page/index.tsx

@@ -2,11 +2,6 @@ import React, { useEffect, useRef } from 'react';
 
 import Watermark from '../Watermark';
 import { renderPdfPage } from '../../helpers/pdf';
-import {
-  RenderingStateType,
-  ViewportType,
-  WatermarkType,
-} from '../../constants/type';
 
 import {
   PageWrapper,

+ 0 - 1
components/SelectBox/index.tsx

@@ -1,6 +1,5 @@
 import React, { useState, useEffect, useRef } from 'react';
 
-import { SelectOptionType } from '../../constants/type';
 import Icon from '../Icon';
 import Divider from '../Divider';
 

+ 1 - 5
components/Shape/index.tsx

@@ -2,11 +2,7 @@ import React from 'react';
 
 import OuterRect from '../OuterRect';
 import SvgShapeElement from '../SvgShapeElement';
-import {
-  AnnotationElementPropsType,
-  PositionType,
-  CoordType,
-} from '../../constants/type';
+
 import { rectCalc, parsePositionForBackend } from '../../helpers/position';
 
 import { AnnotationContainer } from '../../global/otherStyled';

+ 22 - 19
components/ShapeOption/index.tsx

@@ -1,19 +1,14 @@
 import React from 'react';
 import { useTranslation } from 'react-i18next';
 
-import { OptionPropsType } from '../../constants/type';
 import Typography from '../Typography';
 import SelectBox from '../SelectBox';
 import SliderWithTitle from '../SliderWithTitle';
 import ColorSelector from '../../containers/ColorSelector';
 
-import {
-  Wrapper,
-} from '../../global/toolStyled';
+import { Wrapper } from '../../global/toolStyled';
 
-import {
-  shapeOptions, typeOptions,
-} from './data';
+import { shapeOptions, typeOptions } from './data';
 
 const ShapeOption: React.SFC<OptionPropsType> = ({
   shape,
@@ -39,23 +34,27 @@ const ShapeOption: React.SFC<OptionPropsType> = ({
         <SelectBox
           options={shapeOptions}
           style={{ marginRight: '10px' }}
-          onChange={(item: Record<string, any>): void => { setDataState({ shape: item.key }); }}
+          onChange={(item: Record<string, any>): void => {
+            setDataState({ shape: item.key });
+          }}
         />
-        {
-          shape !== 'line' && shape !== 'arrow' && (
-            <SelectBox
-              options={typeOptions}
-              onChange={(item: Record<string, any>): void => { setDataState({ type: item.key }); }}
-            />
-          )
-        }
+        {shape !== 'line' && shape !== 'arrow' && (
+          <SelectBox
+            options={typeOptions}
+            onChange={(item: Record<string, any>): void => {
+              setDataState({ type: item.key });
+            }}
+          />
+        )}
       </Wrapper>
       <Wrapper>
         <ColorSelector
           title={t('color')}
           mode="shape"
           selectedColor={color}
-          onClick={(selected: string): void => { setDataState({ color: selected }); }}
+          onClick={(selected: string): void => {
+            setDataState({ color: selected });
+          }}
         />
       </Wrapper>
       <Wrapper>
@@ -63,7 +62,9 @@ const ShapeOption: React.SFC<OptionPropsType> = ({
           title={t('opacity')}
           value={opacity}
           tips={`${opacity}%`}
-          onSlide={(val: number): void => { setDataState({ opacity: val }); }}
+          onSlide={(val: number): void => {
+            setDataState({ opacity: val });
+          }}
         />
       </Wrapper>
       <Wrapper>
@@ -71,7 +72,9 @@ const ShapeOption: React.SFC<OptionPropsType> = ({
           title={t('width')}
           value={width}
           tips={`${width}pt`}
-          onSlide={(val: number): void => { setDataState({ width: val }); }}
+          onSlide={(val: number): void => {
+            setDataState({ width: val });
+          }}
           maximum={40}
         />
       </Wrapper>

+ 2 - 1
components/Sliders/index.tsx

@@ -106,11 +106,12 @@ const Sliders: React.FC<Props> = ({
     <OuterWrapper>
       <Wrapper
         ref={sliderRef}
+        data-testid="sliders"
         onMouseDown={handleMouseDown}
         onTouchStart={handleMouseDown}
       >
         <Rail track={valueState} />
-        <Track track={valueState} isActive={isActive} />
+        <Track data-testid="track" track={valueState} isActive={isActive} />
       </Wrapper>
     </OuterWrapper>
   );

+ 9 - 6
components/Sliders/styled.ts

@@ -14,14 +14,14 @@ export const Wrapper = styled.div`
   cursor: pointer;
 `;
 
-export const Rail = styled('div')<{track: number}>`
+export const Rail = styled('div')<{ track: number }>`
   height: 10px;
   width: ${props => props.track}%;
   border-radius: 7px;
   background-color: ${color.primary};
 `;
 
-export const Track = styled('span')<{track: number; isActive: boolean}>`
+export const Track = styled('span')<{ track: number; isActive: boolean }>`
   display: block;
   width: 20px;
   height: 20px;
@@ -29,15 +29,18 @@ export const Track = styled('span')<{track: number; isActive: boolean}>`
   background-color: ${color['french-blue']};
   cursor: pointer;
   top: -5px;
+  left: calc(${props => props.track}% - 10px);
   position: absolute;
   transition: box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
-  left: calc(${props => props.track}% - 10px);
   border: 2px solid white;
   box-sizing: border-box;
 
-  ${props => (props.isActive ? css`
-    box-shadow: 0px 0px 0px 8px rgba(144, 202, 249, 0.23);
-  ` : '')}
+  ${props =>
+    props.isActive
+      ? css`
+          box-shadow: 0px 0px 0px 8px rgba(144, 202, 249, 0.23);
+        `
+      : ''}
 
   &:hover {
     box-shadow: 0px 0px 0px 8px rgba(144, 202, 249, 0.23);

+ 25 - 40
components/StickyNote/index.tsx

@@ -1,8 +1,5 @@
 import React from 'react';
 
-import {
-  AnnotationElementPropsType, PositionType, CoordType,
-} from '../../constants/type';
 import Icon from '../Icon';
 import OuterRect from '../OuterRect';
 import { AnnotationContainer } from '../../global/otherStyled';
@@ -13,10 +10,7 @@ import { TextArea, TextAreaContainer, TrashCan } from './styled';
 const StickyNote: React.SFC<AnnotationElementPropsType> = ({
   viewport,
   scale,
-  obj_attr: {
-    position,
-    content,
-  },
+  obj_attr: { position, content },
   onEdit,
   isEdit,
   onBlur,
@@ -30,7 +24,9 @@ const StickyNote: React.SFC<AnnotationElementPropsType> = ({
     onEdit();
   };
 
-  const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>): void => {
+  const handleChange = (
+    event: React.ChangeEvent<HTMLTextAreaElement>
+  ): void => {
     const textValue = event.currentTarget.value;
 
     onUpdate({
@@ -66,38 +62,27 @@ const StickyNote: React.SFC<AnnotationElementPropsType> = ({
           onClick={handleClick}
         />
       </AnnotationContainer>
-      {
-        isEdit ? (
-          <TextAreaContainer
-            top={`${annotRect.top + 36}px`}
-            left={`${annotRect.left - 106}px`}
-          >
-            <TextArea
-              autoFocus
-              defaultValue={content}
-              onChange={handleChange}
-            />
-            <TrashCan>
-              <Icon
-                glyph="trash-2"
-                onClick={onDelete}
-              />
-            </TrashCan>
-          </TextAreaContainer>
-        ) : null
-      }
-      {
-        isEdit ? (
-          <OuterRect
-            top={annotRect.top}
-            left={annotRect.left}
-            width={25}
-            height={25}
-            onMove={handleMove}
-            onClick={onBlur}
-          />
-        ) : null
-      }
+      {isEdit ? (
+        <TextAreaContainer
+          top={`${annotRect.top + 36}px`}
+          left={`${annotRect.left - 106}px`}
+        >
+          <TextArea autoFocus defaultValue={content} onChange={handleChange} />
+          <TrashCan>
+            <Icon glyph="trash-2" onClick={onDelete} />
+          </TrashCan>
+        </TextAreaContainer>
+      ) : null}
+      {isEdit ? (
+        <OuterRect
+          top={annotRect.top}
+          left={annotRect.left}
+          width={25}
+          height={25}
+          onMove={handleMove}
+          onClick={onBlur}
+        />
+      ) : null}
     </>
   );
 };

+ 12 - 17
components/Tabs/index.tsx

@@ -1,7 +1,5 @@
 import React, { useState } from 'react';
 
-import { SelectOptionType } from '../../constants/type';
-
 import { Wrapper, BtnGroup, Btn } from './styled';
 
 type Props = {
@@ -9,10 +7,7 @@ type Props = {
   onChange: (selected: SelectOptionType) => void;
 };
 
-const Tabs: React.FC<Props> = ({
-  options,
-  onChange,
-}: Props) => {
+const Tabs: React.FC<Props> = ({ options, onChange }: Props) => {
   const [selectedIndex, setSelect] = useState(0);
 
   const handleClick = (index: number): void => {
@@ -23,17 +18,17 @@ const Tabs: React.FC<Props> = ({
   return (
     <Wrapper>
       <BtnGroup>
-        {
-          options.map((ele, index) => (
-            <Btn
-              key={ele.key}
-              isActive={index === selectedIndex}
-              onClick={(): void => { handleClick(index); }}
-            >
-              {ele.content}
-            </Btn>
-          ))
-        }
+        {options.map((ele, index) => (
+          <Btn
+            key={ele.key}
+            isActive={index === selectedIndex}
+            onClick={(): void => {
+              handleClick(index);
+            }}
+          >
+            {ele.content}
+          </Btn>
+        ))}
       </BtnGroup>
       {options[selectedIndex] ? options[selectedIndex].child : null}
     </Wrapper>

+ 20 - 17
components/TextField/index.tsx

@@ -14,37 +14,40 @@ type Props = {
   error?: boolean;
   shouldFitContainer?: boolean;
   variant?: 'standard' | 'multiline';
-}
+};
 
 type Ref = any;
 
-const TextField = forwardRef<Ref, Props>(({
-  variant = 'standard',
-  onChange,
-  disabled = false,
-  ...rest
-}: Props, ref) => {
-  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
-    if (onChange && !disabled) {
-      onChange(e.target.value);
-    }
-  };
+const TextField = forwardRef<Ref, Props>(
+  (
+    { variant = 'standard', onChange, disabled = false, ...rest }: Props,
+    ref
+  ) => {
+    const handleChange = (
+      e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
+    ): void => {
+      if (onChange && !disabled) {
+        onChange(e.target.value);
+      }
+    };
 
-  return (
-    variant === 'standard' ? (
+    return variant === 'standard' ? (
       <Input
+        data-testid="input"
         ref={ref}
+        disabled={disabled}
         onChange={handleChange}
         {...rest}
       />
     ) : (
       <TextArea
         ref={ref}
+        disabled={disabled}
         onChange={handleChange}
         {...rest}
       />
-    )
-  );
-});
+    );
+  }
+);
 
 export default TextField;

+ 29 - 17
components/TextField/styled.ts

@@ -7,7 +7,7 @@ const baseStyles = css`
   border-radius: 4px;
   padding: 7px 15px;
   outline: none;
-  transition: border 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms;
+  transition: border 200ms cubic-bezier(0, 0, 0.2, 1) 0ms;
   font-size: 14px;
   box-sizing: border-box;
 
@@ -17,29 +17,41 @@ const baseStyles = css`
   }
 `;
 
-export const Input = styled('input')<{error?: boolean; shouldFitContainer?: boolean}>`
+export const Input = styled('input')<{
+  error?: boolean;
+  shouldFitContainer?: boolean;
+}>`
   ${baseStyles}
   width: ${props => (props.shouldFitContainer ? '100%' : 'auto')};
 
-  ${props => (props.error ? css`
-    border: 1.5px solid ${color.error};
-  ` : css`
-    :focus {
-      border: 1.5px solid ${color.primary};
-    }
-  `)}
+  ${props =>
+    props.error
+      ? css`
+          border: 1.5px solid ${color.error};
+        `
+      : css`
+          :focus {
+            border: 1.5px solid ${color.primary};
+          }
+        `}
 `;
 
-export const TextArea = styled('textarea')<{error?: boolean; shouldFitContainer?: boolean}>`
+export const TextArea = styled('textarea')<{
+  error?: boolean;
+  shouldFitContainer?: boolean;
+}>`
   ${baseStyles}
   height: 54px;
   width: ${props => (props.shouldFitContainer ? '100%' : 'auto')};
 
-  ${props => (props.error ? css`
-    border: 1.5px solid ${color.error};
-  ` : css`
-    :focus {
-      border: 1.5px solid ${color.primary};
-    }
-  `)}
+  ${props =>
+    props.error
+      ? css`
+          border: 1.5px solid ${color.error};
+        `
+      : css`
+          :focus {
+            border: 1.5px solid ${color.primary};
+          }
+        `}
 `;

+ 0 - 1
components/ThumbnailViewer/index.tsx

@@ -4,7 +4,6 @@ import _ from 'lodash';
 import Icon from '../Icon';
 import Drawer from '../Drawer';
 import Thumbnail from '../Thumbnail';
-import { ScrollStateType } from '../../constants/type';
 import { watchScroll, scrollIntoView } from '../../helpers/utility';
 
 import { Container, Head, IconWrapper } from '../../global/sidebarStyled';

+ 0 - 1
components/Toolbar/index.tsx

@@ -5,7 +5,6 @@ import Icon from '../Icon';
 import Pagination from '../Pagination';
 import SelectBox from '../SelectBox';
 import Divider from '../Divider';
-import { SelectOptionType, ViewportType } from '../../constants/type';
 import { scaleCheck } from '../../helpers/utility';
 
 import { Container, ToggleButton } from './styled';

+ 1 - 7
components/Typography/index.tsx

@@ -23,13 +23,7 @@ const Typography: React.FC<Props> = ({
     Component = Subtitle;
   }
 
-  return (
-    <Component
-      {...rest}
-    >
-      {children}
-    </Component>
-  );
+  return <Component {...rest}>{children}</Component>;
 };
 
 export default Typography;

+ 3 - 3
components/Typography/styled.ts

@@ -2,7 +2,7 @@ import styled from 'styled-components';
 
 import { color } from '../../constants/style';
 
-export const Title = styled('p')<{light?: boolean; align?: string}>`
+export const Title = styled('p')<{ light?: boolean; align?: string }>`
   font-size: 16px;
   color: ${props => (props.light ? color.black38 : '#000000')};
   margin: 5px;
@@ -10,7 +10,7 @@ export const Title = styled('p')<{light?: boolean; align?: string}>`
   text-align: ${props => (props.align ? props.align : 'center')};
 `;
 
-export const Subtitle = styled('p')<{light?: boolean; align?: string}>`
+export const Subtitle = styled('p')<{ light?: boolean; align?: string }>`
   font-size: 14px;
   color: ${props => (props.light ? color.black38 : '#000000')};
   margin: 5px;
@@ -18,7 +18,7 @@ export const Subtitle = styled('p')<{light?: boolean; align?: string}>`
   text-align: ${props => (props.align ? props.align : 'center')};
 `;
 
-export const Body = styled('p')<{light?: boolean; align?: string}>`
+export const Body = styled('p')<{ light?: boolean; align?: string }>`
   font-size: 14px;
   color: ${props => (props.light ? color.black38 : '#000000')};
   margin: 5px;

+ 11 - 17
components/Viewer/index.tsx

@@ -1,7 +1,5 @@
 import React, { forwardRef } from 'react';
 
-import { ViewportType } from '../../constants/type';
-
 import { OuterWrapper, Wrapper } from './styled';
 
 type Props = {
@@ -12,21 +10,17 @@ type Props = {
 };
 type Ref = HTMLDivElement;
 
-const Viewer = forwardRef<Ref, Props>(({
-  children,
-  viewport,
-  rotation,
-  displayMode,
-}: Props, ref) => {
-  const width = (Math.abs(rotation) / 90) % 2 === 1 ? viewport.height : viewport.width;
+const Viewer = forwardRef<Ref, Props>(
+  ({ children, viewport, rotation, displayMode }: Props, ref) => {
+    const width =
+      (Math.abs(rotation) / 90) % 2 === 1 ? viewport.height : viewport.width;
 
-  return (
-    <OuterWrapper id="pdf_viewer" ref={ref} isFull={displayMode === 'full'}>
-      <Wrapper width={width}>
-        {children}
-      </Wrapper>
-    </OuterWrapper>
-  );
-});
+    return (
+      <OuterWrapper id="pdf_viewer" ref={ref} isFull={displayMode === 'full'}>
+        <Wrapper width={width}>{children}</Wrapper>
+      </OuterWrapper>
+    );
+  }
+);
 
 export default Viewer;

+ 2 - 5
components/Watermark/index.tsx

@@ -1,7 +1,5 @@
 import React from 'react';
 
-import { WatermarkType } from '../../constants/type';
-
 import { TextWrapper, Img } from './styled';
 
 type Props = WatermarkType & {
@@ -17,7 +15,7 @@ const index: React.FC<Props> = ({
   opacity,
   textcolor,
   rotation,
-}: Props) => (
+}: Props) =>
   type === 'text' ? (
     <TextWrapper
       style={{
@@ -37,7 +35,6 @@ const index: React.FC<Props> = ({
       }}
       src={imagepath}
     />
-  )
-);
+  );
 
 export default index;

+ 34 - 23
components/WatermarkOption/index.tsx

@@ -1,7 +1,6 @@
 import React from 'react';
 import { useTranslation } from 'react-i18next';
 
-import { WatermarkType, SelectOptionType } from '../../constants/type';
 import Button from '../Button';
 import Box from '../Box';
 import Icon from '../Icon';
@@ -13,7 +12,11 @@ import TextBox from '../WatermarkTextBox';
 import ColorSelector from '../../containers/ColorSelector';
 
 import {
-  Container, Head, Body, Footer, IconWrapper,
+  Container,
+  Head,
+  Body,
+  Footer,
+  IconWrapper,
 } from '../../global/sidebarStyled';
 
 type Props = WatermarkType & {
@@ -46,9 +49,7 @@ const WatermarkOption: React.SFC<Props> = ({
         <IconWrapper>
           <Icon glyph="left-back" onClick={onClick} />
         </IconWrapper>
-        <Typography light>
-          {t('watermark')}
-        </Typography>
+        <Typography light>{t('watermark')}</Typography>
       </Head>
       <Body>
         <Typography variant="subtitle" align="left">
@@ -63,32 +64,38 @@ const WatermarkOption: React.SFC<Props> = ({
                 <TextBox
                   t={t}
                   value={text}
-                  onChange={(value: string): void => { setDataState({ text: value }); }}
+                  onChange={(value: string): void => {
+                    setDataState({ text: value });
+                  }}
                 />
               ),
             },
           ]}
-          onChange={(option: SelectOptionType): void => { setDataState({ type: option.key }); }}
+          onChange={(option: SelectOptionType): void => {
+            setDataState({ type: option.key });
+          }}
         />
-        {
-          type === 'text' ? (
-            <>
-              <Divider orientation="horizontal" style={{ margin: '20px 0' }} />
-              <ColorSelector
-                title={t('color')}
-                mode="watermark"
-                selectedColor={textcolor}
-                onClick={(val: string): void => { setDataState({ textcolor: val }); }}
-              />
-            </>
-          ) : null
-        }
+        {type === 'text' ? (
+          <>
+            <Divider orientation="horizontal" style={{ margin: '20px 0' }} />
+            <ColorSelector
+              title={t('color')}
+              mode="watermark"
+              selectedColor={textcolor}
+              onClick={(val: string): void => {
+                setDataState({ textcolor: val });
+              }}
+            />
+          </>
+        ) : null}
         <Box mt="20">
           <SliderWithTitle
             title={t('opacity')}
             value={opacity * 100}
             tips={`${(opacity * 100).toFixed(0)}%`}
-            onSlide={(val: number): void => { setDataState({ opacity: val * 0.01 }); }}
+            onSlide={(val: number): void => {
+              setDataState({ opacity: val * 0.01 });
+            }}
           />
         </Box>
         <Box mt="20">
@@ -97,7 +104,9 @@ const WatermarkOption: React.SFC<Props> = ({
             value={rotation}
             maximum={360}
             tips={`${rotation}°`}
-            onSlide={(val: number): void => { setDataState({ rotation: val }); }}
+            onSlide={(val: number): void => {
+              setDataState({ rotation: val });
+            }}
           />
         </Box>
         <Box mt="20">
@@ -107,7 +116,9 @@ const WatermarkOption: React.SFC<Props> = ({
             tips={`${Math.round(scale * 100)}%`}
             minimum={50}
             maximum={250}
-            onSlide={(val: number): void => { setDataState({ scale: val / 100 }); }}
+            onSlide={(val: number): void => {
+              setDataState({ scale: val / 100 });
+            }}
           />
         </Box>
         <Divider orientation="horizontal" style={{ margin: '20px 0' }} />

+ 1 - 0
config/index.js

@@ -1,4 +1,5 @@
 let API_HOST = 'http://127.0.0.1:3000';
+// let API_HOST = 'http://192.168.172.3:3000';
 
 switch (process.env.NODE_ENV) {
   case 'production':

+ 1 - 0
constants/actionTypes.ts

@@ -3,6 +3,7 @@ export const SET_NAVBAR = 'SET_NAVBAR';
 export const SET_SIDEBAR = 'SET_SIDEBAR';
 export const SET_MARKUP_TOOL = 'SET_MARKUP_TOOL';
 export const SET_INFO = 'SET_INFO';
+export const SET_LOADING = 'SET_LOADING';
 
 export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
 export const SET_TOTAL_PAGE = 'SET_TOTAL_PAGE';

+ 1 - 1
constants/style.ts

@@ -7,7 +7,7 @@ export const mediaRule = {
   desktop: '1440px',
 };
 
-export const color: {[index: string]: any} = {
+export const color: { [index: string]: any } = {
   primary: '#586af2',
   'light-primary': 'rgba(88, 106, 242, 0.6)',
   secondary: '#2d3e4e',

+ 0 - 165
constants/type.ts

@@ -1,165 +0,0 @@
-export type RenderingStateType =
-  | 'RENDERING'
-  | 'LOADING'
-  | 'FINISHED'
-  | 'PAUSED';
-
-export type LineType = 'Highlight' | 'Underline' | 'Squiggly' | 'StrikeOut';
-
-export type ReducerFuncType = (
-  state: Record<string, any>,
-  action: { type: string; payload: any }
-) => any;
-
-export type ViewportType = {
-  width: number;
-  height: number;
-};
-
-export type ProgressType = {
-  loaded: number;
-  total: number;
-};
-
-export type ScrollStateType = {
-  right: boolean;
-  down: boolean;
-  lastX: number;
-  lastY: number;
-  subscriber: any;
-};
-
-export type SelectOptionType = {
-  key: string | number;
-  content: React.ReactNode;
-  child: React.ReactNode;
-};
-
-export type PositionType = {
-  top: number;
-  bottom: number;
-  left: number;
-  right: number;
-};
-
-export type HTMLCoordinateType = {
-  top: number;
-  left: number;
-  width: number;
-  height: number;
-};
-
-export type PointType = {
-  x: number;
-  y: number;
-};
-
-export type LinePositionType = {
-  start: PointType;
-  end: PointType;
-};
-
-export type AnnotationPositionType =
-  | string
-  | PositionType
-  | LinePositionType
-  | PointType
-  | (PositionType | PointType[])[];
-
-export type AnnotationAttributeType = {
-  page: number;
-  bdcolor?: string | undefined;
-  position?: AnnotationPositionType;
-  transparency?: number | undefined;
-  content?: string | undefined;
-  style?: number | undefined;
-  fcolor?: string | undefined;
-  ftransparency?: number | undefined;
-  bdwidth?: number | undefined;
-  fontname?: string | undefined;
-  fontsize?: number | undefined;
-  textcolor?: string | undefined;
-  is_arrow?: boolean | undefined;
-};
-
-export type AnnotationType = {
-  id?: string;
-  obj_type: string;
-  obj_attr: AnnotationAttributeType;
-};
-
-export type UpdateData = {
-  bdcolor?: string;
-  transparency?: number;
-  position?: AnnotationPositionType;
-  content?: string;
-  fontsize?: number;
-};
-
-export type OnUpdateType = (data: UpdateData) => void;
-
-type DispatchType = {
-  type: string;
-  payload: string | number | boolean | Record<string, any> | any[];
-};
-
-export type ActionType = (
-  dispatch: (obj: DispatchType) => void
-) => Record<string, any>;
-
-export type AnnotationElementPropsType = AnnotationType & {
-  isCovered: boolean;
-  isCollapse: boolean;
-  mousePosition: Record<string, any>;
-  onUpdate: OnUpdateType;
-  onDelete: () => void;
-  scale: number;
-  viewport: ViewportType;
-  onEdit: () => void;
-  isEdit: boolean;
-  onBlur: () => void;
-};
-
-export type OptionPropsType = {
-  type?: string;
-  color?: string;
-  opacity?: number;
-  fontName?: string;
-  fontSize?: number;
-  width?: number;
-  align?: string;
-  fontStyle?: string;
-  shape?: string;
-  text?: string;
-  setDataState?: (arg: Record<string, any>) => void;
-};
-
-export type CoordType = {
-  left: number;
-  top: number;
-  width?: number;
-  height?: number;
-};
-
-export type CircleType = {
-  direction: string;
-  cx: number;
-  cy: number;
-  r: number;
-};
-
-export type WatermarkType = {
-  type?: 'image' | 'text';
-  scale?: number;
-  opacity?: number;
-  rotation?: number;
-  pages?: string;
-  vertalign?: 'top' | 'center' | 'bottom';
-  horizalign?: 'left' | 'center' | 'right';
-  xoffset?: number;
-  yoffset?: number;
-  imagepath?: string;
-  text?: string;
-  textcolor?: string;
-  isfront?: 'yes' | 'no';
-};

+ 0 - 1
containers/Annotation.tsx

@@ -1,6 +1,5 @@
 import React, { useEffect, useState } from 'react';
 
-import { AnnotationType, OnUpdateType } from '../constants/type';
 import AnnotationWrapper from '../components/AnnotationWrapper';
 import Highlight from '../components/Highlight';
 import Ink from '../components/Ink';

+ 4 - 13
containers/AnnotationList.tsx

@@ -1,27 +1,18 @@
 import React from 'react';
 
 import { ANNOTATION_TYPE } from '../constants';
-import { AnnotationType } from '../constants/type';
 import Head from './AnnotationListHead';
 import Drawer from '../components/Drawer';
 import Body from '../components/AnnotationList';
 
 import useStore from '../store';
 
-import {
-  Container,
-} from '../global/sidebarStyled';
+import { Container } from '../global/sidebarStyled';
 
 const ANNOT_TYPE_ARRAY = Object.values(ANNOTATION_TYPE);
 
 const AnnotationList: React.FC = () => {
-  const [{
-    navbarState,
-    annotations,
-    viewport,
-    scale,
-    pdf,
-  }] = useStore();
+  const [{ navbarState, annotations, viewport, scale, pdf }] = useStore();
 
   const isActive = navbarState === 'annotations';
 
@@ -30,8 +21,8 @@ const AnnotationList: React.FC = () => {
       <Container>
         <Head />
         <Body
-          annotations={annotations.filter(
-            (ele: AnnotationType) => ANNOT_TYPE_ARRAY.includes(ele.obj_type),
+          annotations={annotations.filter((ele: AnnotationType) =>
+            ANNOT_TYPE_ARRAY.includes(ele.obj_type)
           )}
           viewport={viewport}
           scale={scale}

+ 1 - 1
containers/FreeTextTools.tsx

@@ -5,7 +5,7 @@ import Icon from '../components/Icon';
 import Button from '../components/Button';
 import ExpansionPanel from '../components/ExpansionPanel';
 import FreeTextOption from '../components/FreeTextOption';
-import { OptionPropsType } from '../constants/type';
+
 import { getAbsoluteCoordinate } from '../helpers/position';
 import { parseAnnotationObject } from '../helpers/annotation';
 

+ 0 - 1
containers/FreehandTools.tsx

@@ -1,7 +1,6 @@
 import React, { useState, useEffect, useCallback } from 'react';
 import { v4 as uuidv4 } from 'uuid';
 
-import { OptionPropsType, PointType } from '../constants/type';
 import { ANNOTATION_TYPE } from '../constants';
 import Icon from '../components/Icon';
 import Button from '../components/Button';

+ 0 - 1
containers/HighlightTools.tsx

@@ -4,7 +4,6 @@ import Icon from '../components/Icon';
 import Button from '../components/Button';
 import ExpansionPanel from '../components/ExpansionPanel';
 import HighlightOption from '../components/HighlightOption';
-import { OptionPropsType } from '../constants/type';
 
 import useActions from '../actions';
 import useStore from '../store';

+ 18 - 0
containers/Loading.tsx

@@ -0,0 +1,18 @@
+import React from 'react';
+
+import Dialog from '../components/Dialog';
+import LoadingComp from '../components/Loading';
+
+import useStore from '../store';
+
+const Loading = () => {
+  const [{ isLoading }] = useStore();
+
+  return (
+    <Dialog open={isLoading}>
+      <LoadingComp />
+    </Dialog>
+  );
+};
+
+export default Loading;

+ 20 - 5
containers/Navbar.tsx

@@ -8,14 +8,15 @@ import NavbarComponent from '../components/Navbar';
 import Search from './Search';
 import Thumbnails from './Thumbnails';
 import AnnotationList from './AnnotationList';
-import { downloadFileWithUri } from '../helpers/utility';
+import { downloadFileWithUri, printPdf } from '../helpers/utility';
+import { delay } from '../helpers/time';
 
 const Navbar: React.FC = () => {
   const [
-    { navbarState, displayMode, info, annotations, totalPage, isSaving },
+    { navbarState, displayMode, info, annotations, totalPage },
     dispatch,
   ] = useStore();
-  const { setNavbar } = useActions(dispatch);
+  const { setNavbar, setLoading } = useActions(dispatch);
 
   const onClick = (state: string): void => {
     switch (state) {
@@ -24,12 +25,27 @@ const Navbar: React.FC = () => {
         const fileName = atob(info.token as string);
         const outputPath = `/api/v1/output.pdf?f=${info.token}&user_id=${parsed.watermark}&pages=0-${totalPage}`;
         const originalPath = `/api/v1/original.pdf?transaction_id=${info.id}`;
-
+        setLoading(true);
         if (annotations.length) {
           downloadFileWithUri(fileName, outputPath);
         } else {
           downloadFileWithUri(fileName, originalPath);
         }
+        delay(1000).then(() => {
+          setLoading(false);
+        });
+        break;
+      }
+      case 'print': {
+        const parsed = queryString.parse(window.location.search);
+        const outputPath = `/api/v1/output.pdf?f=${info.token}&user_id=${parsed.watermark}&pages=0-${totalPage}`;
+        setLoading(true);
+        printPdf(`${window.location.origin}${outputPath}`).then(() => {
+          setLoading(false);
+        });
+        // printPdf(`http://192.168.172.3:3000${outputPath}`).then(() => {
+        //   setLoading(false);
+        // });
         break;
       }
       case navbarState:
@@ -49,7 +65,6 @@ const Navbar: React.FC = () => {
       fileName={
         info.token ? decodeURIComponent(escape(window.atob(info.token))) : ''
       }
-      isSaving={isSaving}
     >
       <Search />
       <AnnotationList />

+ 0 - 1
containers/PdfPage.tsx

@@ -1,6 +1,5 @@
 import React from 'react';
 
-import { AnnotationType, RenderingStateType } from '../constants/type';
 import Page from '../components/Page';
 import Annotation from './Annotation';
 import { getPdfPage } from '../helpers/pdf';

+ 0 - 1
containers/PdfPages.tsx

@@ -1,7 +1,6 @@
 import React, { useEffect, useState, useRef } from 'react';
 import _ from 'lodash';
 
-import { ScrollStateType } from '../constants/type';
 import Viewer from '../components/Viewer';
 import PdfPage from './PdfPage';
 import { watchScroll } from '../helpers/utility';

+ 0 - 1
containers/PdfViewer.tsx

@@ -6,7 +6,6 @@ import apiPath from '../constants/apiPath';
 import { initialPdfFile, fetchXfdf } from '../apis';
 import PdfPages from './PdfPages';
 import { fetchPdf } from '../helpers/pdf';
-import { ProgressType, ScrollStateType } from '../constants/type';
 import { scrollIntoView } from '../helpers/utility';
 import { parseAnnotationFromXml } from '../helpers/annotation';
 import { parseWatermarkFromXml } from '../helpers/watermark';

+ 1 - 1
containers/ShapeTools.tsx

@@ -6,7 +6,7 @@ import Button from '../components/Button';
 import ExpansionPanel from '../components/ExpansionPanel';
 import Icon from '../components/Icon';
 import ShapeOption from '../components/ShapeOption';
-import { OptionPropsType } from '../constants/type';
+
 import {
   getAbsoluteCoordinate,
   parsePositionForBackend,

+ 0 - 1
containers/WatermarkTool.tsx

@@ -3,7 +3,6 @@ import queryString from 'query-string';
 import dayjs from 'dayjs';
 import { useTranslation } from 'react-i18next';
 
-import { WatermarkType, AnnotationType } from '../constants/type';
 import Icon from '../components/Icon';
 import Button from '../components/Button';
 import Drawer from '../components/Drawer';

+ 164 - 0
custom.d.ts

@@ -3,6 +3,170 @@ declare module '*.svg' {
   export default content;
 }
 
+declare module 'pdfjs-dist';
+declare module 'pdfjs-dist/build/pdf.worker.entry';
 declare module 'query-string';
 declare module 'react-toast-notifications';
 declare module 'react-color';
+
+type SelectOptionType = {
+  key: string | number;
+  content: React.ReactNode;
+  child: React.ReactNode;
+};
+
+type RenderingStateType = 'RENDERING' | 'LOADING' | 'FINISHED' | 'PAUSED';
+
+type LineType = 'Highlight' | 'Underline' | 'Squiggly' | 'StrikeOut';
+
+type ReducerFuncType = (
+  state: Record<string, any>,
+  action: { type: string; payload: any }
+) => any;
+
+type ViewportType = {
+  width: number;
+  height: number;
+};
+
+type ProgressType = {
+  loaded: number;
+  total: number;
+};
+
+type ScrollStateType = {
+  right: boolean;
+  down: boolean;
+  lastX: number;
+  lastY: number;
+  subscriber: any;
+};
+
+type PositionType = {
+  top: number;
+  bottom: number;
+  left: number;
+  right: number;
+};
+
+type HTMLCoordinateType = {
+  top: number;
+  left: number;
+  width: number;
+  height: number;
+};
+
+type PointType = {
+  x: number;
+  y: number;
+};
+
+type LinePositionType = {
+  start: PointType;
+  end: PointType;
+};
+
+type AnnotationPositionType =
+  | string
+  | PositionType
+  | LinePositionType
+  | PointType
+  | (PositionType | PointType[])[];
+
+type AnnotationAttributeType = {
+  page: number;
+  bdcolor?: string | undefined;
+  position?: AnnotationPositionType;
+  transparency?: number | undefined;
+  content?: string | undefined;
+  style?: number | undefined;
+  fcolor?: string | undefined;
+  ftransparency?: number | undefined;
+  bdwidth?: number | undefined;
+  fontname?: string | undefined;
+  fontsize?: number | undefined;
+  textcolor?: string | undefined;
+  is_arrow?: boolean | undefined;
+};
+
+type AnnotationType = {
+  id?: string;
+  obj_type: string;
+  obj_attr: AnnotationAttributeType;
+};
+
+type UpdateData = {
+  bdcolor?: string;
+  transparency?: number;
+  position?: AnnotationPositionType;
+  content?: string;
+  fontsize?: number;
+};
+
+type OnUpdateType = (data: UpdateData) => void;
+
+type DispatchType = {
+  type: string;
+  payload: string | number | boolean | Record<string, any> | any[];
+};
+
+type ActionType = (
+  dispatch: (obj: DispatchType) => void
+) => Record<string, any>;
+
+type AnnotationElementPropsType = AnnotationType & {
+  isCovered: boolean;
+  isCollapse: boolean;
+  mousePosition: Record<string, any>;
+  onUpdate: OnUpdateType;
+  onDelete: () => void;
+  scale: number;
+  viewport: ViewportType;
+  onEdit: () => void;
+  isEdit: boolean;
+  onBlur: () => void;
+};
+
+type OptionPropsType = {
+  type?: string;
+  color?: string;
+  opacity?: number;
+  fontName?: string;
+  fontSize?: number;
+  width?: number;
+  align?: string;
+  fontStyle?: string;
+  shape?: string;
+  text?: string;
+  setDataState?: (arg: Record<string, any>) => void;
+};
+
+type CoordType = {
+  left: number;
+  top: number;
+  width?: number;
+  height?: number;
+};
+
+type CircleType = {
+  direction: string;
+  cx: number;
+  cy: number;
+  r: number;
+};
+
+type WatermarkType = {
+  type?: 'image' | 'text';
+  scale?: number;
+  opacity?: number;
+  rotation?: number;
+  pages?: string;
+  vertalign?: 'top' | 'center' | 'bottom';
+  horizalign?: 'left' | 'center' | 'right';
+  xoffset?: number;
+  yoffset?: number;
+  imagepath?: string;
+  text?: string;
+  textcolor?: string;
+  isfront?: 'yes' | 'no';
+};

+ 0 - 1
helpers/annotation.ts

@@ -1,7 +1,6 @@
 import { v4 as uuidv4 } from 'uuid';
 
 import { ANNOTATION_TYPE } from '../constants';
-import { AnnotationType, PositionType, ViewportType } from '../constants/type';
 import { getPdfPage, renderTextLayer } from './pdf';
 import { getPosition, parsePositionForBackend } from './position';
 import { normalizeRound, floatToHex } from './utility';

+ 0 - 1
helpers/markup.ts

@@ -1,5 +1,4 @@
 import { ANNOTATION_TYPE } from '../constants';
-import { AnnotationType, PositionType } from '../constants/type';
 import { parseAnnotationObject } from './annotation';
 
 type GetMarkupWithSelectionFunc = ({

+ 23 - 16
helpers/pdf.ts

@@ -1,19 +1,16 @@
-// @ts-ignore
 import pdfjs from 'pdfjs-dist';
-// @ts-ignore
 import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
-import { ProgressType, ViewportType } from '../constants/type';
 import { objIsEmpty } from './utility';
 import { delay } from './time';
 
 pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
 
 let normalizationRegex: any = null;
-const CHARACTERS_TO_NORMALIZE: {[index: string]: any} = {
-  '\u2018': '\'', // Left single quotation mark
-  '\u2019': '\'', // Right single quotation mark
-  '\u201A': '\'', // Single low-9 quotation mark
-  '\u201B': '\'', // Single high-reversed-9 quotation mark
+const CHARACTERS_TO_NORMALIZE: { [index: string]: any } = {
+  '\u2018': "'", // Left single quotation mark
+  '\u2019': "'", // Right single quotation mark
+  '\u201A': "'", // Single low-9 quotation mark
+  '\u201B': "'", // Single high-reversed-9 quotation mark
   '\u201C': '"', // Left double quotation mark
   '\u201D': '"', // Right double quotation mark
   '\u201E': '"', // Double low-9 quotation mark
@@ -24,7 +21,8 @@ const CHARACTERS_TO_NORMALIZE: {[index: string]: any} = {
 };
 
 export const fetchPdf = async (
-  src: string, cb?: (progress: ProgressType) => void,
+  src: string,
+  cb?: (progress: ProgressType) => void
 ): Promise<any> => {
   try {
     const loadingTask = pdfjs.getDocument({
@@ -74,11 +72,17 @@ export const renderPdfPage = async ({
   viewport: ViewportType;
 }): Promise<any> => {
   if (rootEle) {
-    const canvas: HTMLCanvasElement = rootEle.querySelectorAll('canvas')[0] as HTMLCanvasElement;
-    const textLayer: HTMLDivElement = rootEle.querySelector('[data-id="text-layer"]') as HTMLDivElement;
+    const canvas: HTMLCanvasElement = rootEle.querySelectorAll(
+      'canvas'
+    )[0] as HTMLCanvasElement;
+    const textLayer: HTMLDivElement = rootEle.querySelector(
+      '[data-id="text-layer"]'
+    ) as HTMLDivElement;
 
     if (canvas) {
-      const context: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
+      const context: CanvasRenderingContext2D = canvas.getContext(
+        '2d'
+      ) as CanvasRenderingContext2D;
       canvas.height = viewport.height;
       canvas.width = viewport.width;
 
@@ -114,7 +118,10 @@ export const normalize = (text: string): string => {
   return text.replace(normalizationRegex, ch => CHARACTERS_TO_NORMALIZE[ch]);
 };
 
-export const calcFindPhraseMatch = (pageContent: string, query: string): number[] => {
+export const calcFindPhraseMatch = (
+  pageContent: string,
+  query: string
+): number[] => {
   const matches = [];
   const queryLen = query.length;
   let matchIdx = -queryLen;
@@ -133,7 +140,7 @@ export const calcFindPhraseMatch = (pageContent: string, query: string): number[
 export const convertMatches = (
   queryString: string,
   matchIndex: number,
-  textContentItem: any[],
+  textContentItem: any[]
 ): Record<string, any> => {
   let i = 0;
   let iIndex = 0;
@@ -141,7 +148,7 @@ export const convertMatches = (
   const queryLen = queryString.length;
 
   // Loop over the divIdxs.
-  while (i !== end && matchIndex >= (iIndex + textContentItem[i].length)) {
+  while (i !== end && matchIndex >= iIndex + textContentItem[i].length) {
     iIndex += textContentItem[i].length;
     i += 1;
   }
@@ -163,7 +170,7 @@ export const convertMatches = (
 
   // Somewhat the same array as above, but use > instead of >= to get
   // the end position right.
-  while (i !== end && matchIndex > (iIndex + textContentItem[i].length)) {
+  while (i !== end && matchIndex > iIndex + textContentItem[i].length) {
     iIndex += textContentItem[i].length;
     i += 1;
   }

+ 0 - 8
helpers/position.ts

@@ -1,13 +1,5 @@
 import _ from 'lodash';
 
-import {
-  PositionType,
-  HTMLCoordinateType,
-  PointType,
-  AnnotationPositionType,
-  LinePositionType,
-} from '../constants/type';
-
 export const rectCalc = (
   position: PositionType,
   viewHeight: number,

+ 6 - 5
helpers/time.ts

@@ -1,7 +1,8 @@
-export const delay = (ms: number): Promise<number> => (
-  new Promise((resolve) => {
-    setTimeout(() => { resolve(); }, ms);
-  })
-);
+export const delay = (ms: number): Promise<number> =>
+  new Promise(resolve => {
+    setTimeout(() => {
+      resolve();
+    }, ms);
+  });
 
 export default {};

+ 64 - 2
helpers/utility.ts

@@ -1,8 +1,8 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
 /* eslint-disable no-underscore-dangle */
 import { fromEvent } from 'rxjs';
 import { debounceTime, throttleTime } from 'rxjs/operators';
-
-import { ScrollStateType, CoordType } from '../constants/type';
+// import printJS from 'print-js';
 
 export const objIsEmpty = (obj: Record<string, any>): boolean =>
   !Object.keys(obj).length;
@@ -256,3 +256,65 @@ export const normalizeRound: NormalizeRoundFunc = (num, fractionDigits) => {
   const frac = fractionDigits || FRACTIONDIGITS;
   return Math.round(num * 10 ** frac) / 10 ** frac;
 };
+
+const printDocument = (documentId: string): void => {
+  const doc = document.getElementById(documentId) as HTMLElement;
+  // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
+  // @ts-ignore
+  if (typeof doc.print === 'undefined') {
+    setTimeout(() => {
+      printDocument(documentId);
+    }, 1000);
+  } else {
+    setTimeout(() => {
+      // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
+      // @ts-ignore
+      doc.print();
+    }, 3000);
+  }
+};
+
+export const printPdf = async (url: string) => {
+  // eslint-disable-next-line global-require
+  const printJS = require('print-js');
+  const isIE11 = !!(window.navigator && window.navigator.msSaveOrOpenBlob);
+
+  await fetch(url, {
+    headers: {
+      'Content-type': 'application/pdf',
+    },
+  })
+    .then(data => data.blob())
+    .then(res => {
+      if (isIE11) {
+        const wrapper = document.getElementById('embed-wrapper') as HTMLElement;
+        wrapper.style.display = 'flex';
+        const ele = document.createElement('embed');
+        ele.setAttribute('type', 'application/pdf');
+        ele.setAttribute('id', 'pdf-embed');
+        ele.style.width = '70%';
+        ele.style.height = '70%';
+        ele.style.position = 'absolute';
+        ele.style.margin = 'auto';
+        ele.setAttribute('src', url);
+        wrapper.appendChild(ele);
+
+        printDocument('pdf-embed');
+      } else {
+        const fileReader = new FileReader();
+        fileReader.readAsDataURL(res);
+        fileReader.onload = fileLoadedEvent => {
+          if (fileLoadedEvent.target) {
+            const base64 = fileLoadedEvent.target.result as string;
+
+            printJS({
+              printable: base64.replace('data:application/pdf;base64,', ''),
+              type: 'pdf',
+              base64: true,
+              showModal: true,
+            });
+          }
+        };
+      }
+    });
+};

+ 3 - 4
helpers/watermark.ts

@@ -1,6 +1,3 @@
-import {
-  WatermarkType,
-} from '../constants/type';
 import { floatToHex } from './utility';
 import { xmlParser, getElementsByTagName } from './dom';
 
@@ -9,7 +6,9 @@ type WatermarkAnnotType = {
   obj_attr: WatermarkType;
 };
 
-export const parseWatermarkFromXml = (xmlString: string): WatermarkAnnotType => {
+export const parseWatermarkFromXml = (
+  xmlString: string
+): WatermarkAnnotType => {
   if (!xmlString) return { obj_type: 'watermark', obj_attr: {} };
 
   const xmlDoc = xmlParser(xmlString);

+ 4 - 4
package.json

@@ -7,7 +7,7 @@
     "start": "NODE_ENV=production next start",
     "export": "NODE_ENV=production ENV=production npm run build && next export",
     "lint": "eslint . --ext .ts,.tsx",
-    "test": "jest --config=./config/jest.config.js"
+    "test": "jest"
   },
   "pre-commit": [
     "lint"
@@ -23,6 +23,7 @@
     "next": "9.3.4",
     "next-i18next": "4.3.0",
     "pdfjs-dist": "2.3.200",
+    "print-js": "^1.0.63",
     "query-string": "5",
     "react": "16.13.0",
     "react-color": "^2.18.0",
@@ -39,8 +40,8 @@
   "devDependencies": {
     "@babel/node": "^7.2.2",
     "@babel/preset-env": "^7.8.3",
-    "@testing-library/jest-dom": "^4.2.3",
-    "@testing-library/react": "^9.3.2",
+    "@testing-library/jest-dom": "^5.8.0",
+    "@testing-library/react": "^10.0.4",
     "@types/jest": "^24.0.23",
     "@types/lodash": "^4.14.149",
     "@types/node": "^12.12.7",
@@ -62,7 +63,6 @@
     "eslint-plugin-react": "^7.19.0",
     "eslint-plugin-react-hooks": "^2.5.1",
     "jest": "24.9.0",
-    "jest-styled-components": "^6.2.1",
     "pre-commit": "^1.2.2",
     "prettier": "^1.19.1",
     "react-test-renderer": "^16.5.2",

+ 4 - 0
pages/index.tsx

@@ -7,6 +7,8 @@ import Toolbar from '../containers/Toolbar';
 import Placeholder from '../containers/Placeholder';
 import PdfViewer from '../containers/PdfViewer';
 import AutoSave from '../containers/AutoSave';
+import Loading from '../containers/Loading';
+import Embed from '../components/Embed';
 
 const index: NextPage = () => (
   <div>
@@ -16,6 +18,8 @@ const index: NextPage = () => (
     <Placeholder />
     <PdfViewer />
     <AutoSave />
+    <Loading />
+    <Embed />
   </div>
 );
 

文件差异内容过多而无法显示
+ 1 - 0
public/icons/button_close.svg


+ 1 - 0
reducers/index.ts

@@ -19,6 +19,7 @@ export default createReducer({
   [types.SET_SIDEBAR]: mainActions.setSidebarState,
   [types.SET_MARKUP_TOOL]: mainActions.setMarkupTool,
   [types.SET_INFO]: mainActions.setInfo,
+  [types.SET_LOADING]: mainActions.setLoading,
 
   [types.SET_CURRENT_PAGE]: pdfActions.setCurrentPage,
   [types.SET_TOTAL_PAGE]: pdfActions.setTotalPage,

+ 5 - 2
reducers/main.ts

@@ -1,5 +1,3 @@
-import { ReducerFuncType } from '../constants/type';
-
 export const toggleDisplayMode: ReducerFuncType = (state, { payload }) => ({
   ...state,
   displayMode: payload,
@@ -24,3 +22,8 @@ export const setInfo: ReducerFuncType = (state, { payload }) => ({
   ...state,
   info: payload,
 });
+
+export const setLoading: ReducerFuncType = (state, { payload }) => ({
+  ...state,
+  isLoading: payload,
+});

+ 1 - 6
reducers/pdf.ts

@@ -1,5 +1,3 @@
-import { ReducerFuncType } from '../constants/type';
-
 export const setCurrentPage: ReducerFuncType = (state, { payload }) => ({
   ...state,
   currentPage: payload,
@@ -37,10 +35,7 @@ export const changeRotate: ReducerFuncType = (state, { payload }) => ({
 
 export const addAnnotation: ReducerFuncType = (state, { payload }) => ({
   ...state,
-  annotations: [
-    ...state.annotations,
-    ...payload.annotations,
-  ],
+  annotations: [...state.annotations, ...payload.annotations],
   isInit: payload.init,
 });
 

+ 3 - 5
store/initialMainState.ts

@@ -3,9 +3,8 @@ export type StateType = {
   navbarState: 'search' | 'annotations' | 'thumbnails' | '';
   sidebarState: 'markup-tools' | 'create-form' | 'watermark' | '';
   markupToolState: 'highlight' | 'freehand' | 'text' | 'sticky' | 'shape' | '';
-  info: {token: string; id: string};
-  isSaved: boolean;
-  isSaving: boolean;
+  info: { token: string; id: string };
+  isLoading: boolean;
 };
 
 export default {
@@ -14,6 +13,5 @@ export default {
   sidebarState: '',
   markupToolState: '',
   info: {},
-  isSaved: false,
-  isSaving: false,
+  isLoading: false,
 };

+ 0 - 7
store/initialPdfState.ts

@@ -1,10 +1,3 @@
-import {
-  ProgressType,
-  ViewportType,
-  AnnotationType,
-  WatermarkType,
-} from '../constants/type';
-
 export type StateType = {
   totalPage: number;
   currentPage: number;

+ 95 - 65
yarn.lock

@@ -1057,7 +1057,7 @@
   dependencies:
     regenerator-runtime "^0.13.2"
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6":
   version "7.9.6"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f"
   integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==
@@ -1407,47 +1407,39 @@
     "@types/yargs" "^15.0.0"
     chalk "^3.0.0"
 
-"@sheerun/mutationobserver-shim@^0.3.2":
-  version "0.3.3"
-  resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25"
-  integrity sha512-DetpxZw1fzPD5xUBrIAoplLChO2VB8DlL5Gg+I1IR9b2wPqYIca2WSUxL5g1vLeR4MsQq1NeWriXAVffV+U1Fw==
-
-"@testing-library/dom@^6.15.0":
-  version "6.16.0"
-  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.16.0.tgz#04ada27ed74ad4c0f0d984a1245bb29b1fd90ba9"
-  integrity sha512-lBD88ssxqEfz0wFL6MeUyyWZfV/2cjEZZV3YRpb2IoJRej/4f1jB0TzqIOznTpfR1r34CNesrubxwIlAQ8zgPA==
+"@testing-library/dom@^7.2.2":
+  version "7.5.7"
+  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.5.7.tgz#c4bf683a65083d4a78644588cfa4ad684c113fc7"
+  integrity sha512-835MiwAxQE7xjSrhpeJbv41UQRmsPJQ0tGfzWiJMdZj2LBbdG5cT8Z44Viv11/XucCmJHr/v8q7VpZnuSimscg==
   dependencies:
-    "@babel/runtime" "^7.8.4"
-    "@sheerun/mutationobserver-shim" "^0.3.2"
-    "@types/testing-library__dom" "^6.12.1"
+    "@babel/runtime" "^7.9.6"
     aria-query "^4.0.2"
-    dom-accessibility-api "^0.3.0"
-    pretty-format "^25.1.0"
-    wait-for-expect "^3.0.2"
+    dom-accessibility-api "^0.4.4"
+    pretty-format "^25.5.0"
 
-"@testing-library/jest-dom@^4.2.3":
-  version "4.2.4"
-  resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz#00dfa0cbdd837d9a3c2a7f3f0a248ea6e7b89742"
-  integrity sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg==
+"@testing-library/jest-dom@^5.8.0":
+  version "5.8.0"
+  resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.8.0.tgz#815e830129c4dda6c8e9a725046397acec523669"
+  integrity sha512-9Y4FxYIxfwHpUyJVqI8EOfDP2LlEBqKwXE3F+V8ightji0M2rzQB+9kqZ5UJxNs+9oXJIgvYj7T3QaXLNHVDMw==
   dependencies:
-    "@babel/runtime" "^7.5.1"
-    chalk "^2.4.1"
-    css "^2.2.3"
+    "@babel/runtime" "^7.9.2"
+    "@types/testing-library__jest-dom" "^5.0.2"
+    chalk "^3.0.0"
+    css "^2.2.4"
     css.escape "^1.5.1"
-    jest-diff "^24.0.0"
-    jest-matcher-utils "^24.0.0"
-    lodash "^4.17.11"
-    pretty-format "^24.0.0"
+    jest-diff "^25.1.0"
+    jest-matcher-utils "^25.1.0"
+    lodash "^4.17.15"
     redent "^3.0.0"
 
-"@testing-library/react@^9.3.2":
-  version "9.5.0"
-  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-9.5.0.tgz#71531655a7890b61e77a1b39452fbedf0472ca5e"
-  integrity sha512-di1b+D0p+rfeboHO5W7gTVeZDIK5+maEgstrZbWZSSvxDyfDRkkyBE1AJR5Psd6doNldluXlCWqXriUfqu/9Qg==
+"@testing-library/react@^10.0.4":
+  version "10.0.4"
+  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.0.4.tgz#8e0e299cd91acc626d81ed8489fdc13df864c31d"
+  integrity sha512-2e1B5debfuiIGbvUuiSXybskuh7ZTVJDDvG/IxlzLOY9Co/mKFj9hIklAe2nGZYcOUxFaiqWrRZ9vCVGzJfRlQ==
   dependencies:
-    "@babel/runtime" "^7.8.4"
-    "@testing-library/dom" "^6.15.0"
-    "@types/testing-library__react" "^9.1.2"
+    "@babel/runtime" "^7.9.6"
+    "@testing-library/dom" "^7.2.2"
+    "@types/testing-library__react" "^10.0.1"
 
 "@types/babel__core@^7.1.0", "@types/babel__core@^7.1.7":
   version "7.1.7"
@@ -1527,6 +1519,14 @@
     "@types/istanbul-lib-coverage" "*"
     "@types/istanbul-lib-report" "*"
 
+"@types/jest@*":
+  version "25.2.3"
+  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf"
+  integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==
+  dependencies:
+    jest-diff "^25.2.1"
+    pretty-format "^25.2.1"
+
 "@types/jest@^24.0.23":
   version "24.9.1"
   resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.9.1.tgz#02baf9573c78f1b9974a5f36778b366aa77bd534"
@@ -1564,7 +1564,14 @@
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
   integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
 
-"@types/react-dom@*", "@types/react-dom@^16.9.4":
+"@types/react-dom@*":
+  version "16.9.8"
+  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423"
+  integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==
+  dependencies:
+    "@types/react" "*"
+
+"@types/react-dom@^16.9.4":
   version "16.9.7"
   resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.7.tgz#60844d48ce252d7b2dccf0c7bb937130e27c0cd2"
   integrity sha512-GHTYhM8/OwUCf254WO5xqR/aqD3gC9kSTLpopWGpQLpnw23jk44RvMHsyUSEplvRJZdHxhJGMMLF0kCPYHPhQA==
@@ -1608,17 +1615,17 @@
   dependencies:
     pretty-format "^25.1.0"
 
-"@types/testing-library__dom@^6.12.1":
-  version "6.14.0"
-  resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-6.14.0.tgz#1aede831cb4ed4a398448df5a2c54b54a365644e"
-  integrity sha512-sMl7OSv0AvMOqn1UJ6j1unPMIHRXen0Ita1ujnMX912rrOcawe4f7wu0Zt9GIQhBhJvH2BaibqFgQ3lP+Pj2hA==
+"@types/testing-library__jest-dom@^5.0.2":
+  version "5.7.0"
+  resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.7.0.tgz#078790bf4dc89152a74428591a228ec5f9433251"
+  integrity sha512-LoZ3uonlnAbJUz4bg6UoeFl+frfndXngmkCItSjJ8DD5WlRfVqPC5/LgJASsY/dy7AHH2YJ7PcsdASOydcVeFA==
   dependencies:
-    pretty-format "^24.3.0"
+    "@types/jest" "*"
 
-"@types/testing-library__react@^9.1.2":
-  version "9.1.3"
-  resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-9.1.3.tgz#35eca61cc6ea923543796f16034882a1603d7302"
-  integrity sha512-iCdNPKU3IsYwRK9JieSYAiX0+aYDXOGAmrC/3/M7AqqSDKnWWVv07X+Zk1uFSL7cMTUYzv4lQRfohucEocn5/w==
+"@types/testing-library__react@^10.0.1":
+  version "10.0.1"
+  resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-10.0.1.tgz#92bb4a02394bf44428e35f1da2970ed77f803593"
+  integrity sha512-RbDwmActAckbujLZeVO/daSfdL1pnjVqas25UueOkAY5r7vriavWf0Zqg7ghXMHa8ycD/kLkv8QOj31LmSYwww==
   dependencies:
     "@types/react-dom" "*"
     "@types/testing-library__dom" "*"
@@ -3298,7 +3305,7 @@ css.escape@^1.5.1:
   resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
   integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=
 
-css@2.2.4, css@^2.0.0, css@^2.2.3, css@^2.2.4:
+css@2.2.4, css@^2.0.0, css@^2.2.4:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
   integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==
@@ -3490,6 +3497,11 @@ diff-sequences@^24.9.0:
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
   integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
 
+diff-sequences@^25.2.6:
+  version "25.2.6"
+  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
+  integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
+
 diffie-hellman@^5.0.0:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -3521,10 +3533,10 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
-dom-accessibility-api@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.3.0.tgz#511e5993dd673b97c87ea47dba0e3892f7e0c983"
-  integrity sha512-PzwHEmsRP3IGY4gv/Ug+rMeaTIyTJvadCb+ujYXYeIylbHJezIyNToe8KfEgHTCEYyC+/bUghYOGg8yMGlZ6vA==
+dom-accessibility-api@^0.4.4:
+  version "0.4.4"
+  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.4.tgz#c2f9fb8b591bc19581e7ef3e6fe35baf1967c498"
+  integrity sha512-XBM62jdDc06IXSujkqw6BugEWiDkp6jphtzVJf1kgPQGvfzaU7/jRtRSF/mxc8DBCIm2LS3bN1dCa5Sfxx982A==
 
 dom-helpers@^5.0.1:
   version "5.1.4"
@@ -5257,7 +5269,7 @@ jest-config@^24.9.0:
     pretty-format "^24.9.0"
     realpath-native "^1.1.0"
 
-jest-diff@^24.0.0, jest-diff@^24.3.0, jest-diff@^24.9.0:
+jest-diff@^24.3.0, jest-diff@^24.9.0:
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
   integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
@@ -5267,6 +5279,16 @@ jest-diff@^24.0.0, jest-diff@^24.3.0, jest-diff@^24.9.0:
     jest-get-type "^24.9.0"
     pretty-format "^24.9.0"
 
+jest-diff@^25.1.0, jest-diff@^25.2.1, jest-diff@^25.5.0:
+  version "25.5.0"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
+  integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==
+  dependencies:
+    chalk "^3.0.0"
+    diff-sequences "^25.2.6"
+    jest-get-type "^25.2.6"
+    pretty-format "^25.5.0"
+
 jest-docblock@^24.3.0:
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2"
@@ -5313,6 +5335,11 @@ jest-get-type@^24.9.0:
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
   integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
 
+jest-get-type@^25.2.6:
+  version "25.2.6"
+  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
+  integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==
+
 jest-haste-map@^24.9.0:
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d"
@@ -5382,7 +5409,7 @@ jest-leak-detector@^24.9.0:
     jest-get-type "^24.9.0"
     pretty-format "^24.9.0"
 
-jest-matcher-utils@^24.0.0, jest-matcher-utils@^24.9.0:
+jest-matcher-utils@^24.9.0:
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
   integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==
@@ -5392,6 +5419,16 @@ jest-matcher-utils@^24.0.0, jest-matcher-utils@^24.9.0:
     jest-get-type "^24.9.0"
     pretty-format "^24.9.0"
 
+jest-matcher-utils@^25.1.0:
+  version "25.5.0"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867"
+  integrity sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==
+  dependencies:
+    chalk "^3.0.0"
+    jest-diff "^25.5.0"
+    jest-get-type "^25.2.6"
+    pretty-format "^25.5.0"
+
 jest-message-util@^24.9.0:
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3"
@@ -5533,13 +5570,6 @@ jest-snapshot@^24.9.0:
     pretty-format "^24.9.0"
     semver "^6.2.0"
 
-jest-styled-components@^6.2.1:
-  version "6.3.4"
-  resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-6.3.4.tgz#64de9c0ceae14f311248734c79dcc66b447f90f1"
-  integrity sha512-dc32l0/6n3FtsILODpvKNz6SLg50OmbJ/3r3oRh9jc2VIPPGZT5jWv7BKIcNCYH7E38ZK7uejNl3zJsCOIenng==
-  dependencies:
-    css "^2.2.4"
-
 jest-util@^24.9.0:
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162"
@@ -7044,7 +7074,7 @@ prettier@^1.19.1:
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
   integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
 
-pretty-format@^24.0.0, pretty-format@^24.3.0, pretty-format@^24.9.0:
+pretty-format@^24.9.0:
   version "24.9.0"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
   integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==
@@ -7054,7 +7084,7 @@ pretty-format@^24.0.0, pretty-format@^24.3.0, pretty-format@^24.9.0:
     ansi-styles "^3.2.0"
     react-is "^16.8.4"
 
-pretty-format@^25.1.0:
+pretty-format@^25.1.0, pretty-format@^25.2.1, pretty-format@^25.5.0:
   version "25.5.0"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
   integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
@@ -7064,6 +7094,11 @@ pretty-format@^25.1.0:
     ansi-styles "^4.0.0"
     react-is "^16.12.0"
 
+print-js@^1.0.63:
+  version "1.0.63"
+  resolved "https://registry.yarnpkg.com/print-js/-/print-js-1.0.63.tgz#de9258c3dfbf273ed8c02113d09240f09c31416d"
+  integrity sha512-WKf79bFeqJpwx5vcjvEuL0J1bRVA5QlKQY+usFksOx0WOApSJwQGgMlgM2PVub/R1uUMF4onc2SWRWPNz4R40g==
+
 private@^0.1.8:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -8907,11 +8942,6 @@ w3c-hr-time@^1.0.1:
   dependencies:
     browser-process-hrtime "^1.0.0"
 
-wait-for-expect@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.2.tgz#d2f14b2f7b778c9b82144109c8fa89ceaadaa463"
-  integrity sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag==
-
 walker@^1.0.7, walker@~1.0.5:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"