RoyLiu пре 4 година
родитељ
комит
5093d9f1e8
100 измењених фајлова са 1818 додато и 1566 уклоњено
  1. 4 1
      .editorconfig
  2. 0 44
      .eslintrc.js
  3. 76 0
      .eslintrc.json
  4. 4 0
      .prettierrc
  5. 22 18
      __test__/button.test.tsx
  6. 23 0
      __test__/dialog.test.tsx
  7. 6 2
      __test__/drawer.test.tsx
  8. 6 2
      __test__/selectBox.test.tsx
  9. 30 0
      __test__/slider.test.tsx
  10. 12 0
      __test__/textField.test.tsx
  11. 27 0
      __test__/typography.test.tsx
  12. 12 22
      actions/main.ts
  13. 20 33
      actions/pdf.ts
  14. 2 4
      components/AnnotationItem/index.tsx
  15. 46 46
      components/AnnotationList/index.tsx
  16. 4 14
      components/AnnotationListHead/index.tsx
  17. 29 38
      components/AnnotationSelector/index.tsx
  18. 4 13
      components/Box/index.tsx
  19. 3 1
      components/Box/styled.ts
  20. 14 15
      components/Button/index.tsx
  21. 21 9
      components/Button/styled.ts
  22. 36 36
      components/ColorSelector/index.tsx
  23. 20 20
      components/DeleteDialog/index.tsx
  24. 3 9
      components/Dialog/index.tsx
  25. 2 0
      components/Dialog/styled.ts
  26. 3 10
      components/Drawer/index.tsx
  27. 13 7
      components/Drawer/styled.ts
  28. 27 0
      components/Embed/index.tsx
  29. 24 0
      components/Embed/styled.ts
  30. 4 10
      components/ExpansionPanel/index.tsx
  31. 48 46
      components/FreeText/index.tsx
  32. 1 1
      components/FreeText/styled.ts
  33. 34 38
      components/FreeTextOption/index.tsx
  34. 2 8
      components/Head.tsx
  35. 21 30
      components/Highlight/index.tsx
  36. 14 5
      components/HighlightOption/index.tsx
  37. 2 1
      components/Icon/data.ts
  38. 12 18
      components/Icon/index.tsx
  39. 64 59
      components/Ink/index.tsx
  40. 9 4
      components/InkOption/index.tsx
  41. 71 54
      components/Line/index.tsx
  42. 17 0
      components/Loading/index.tsx
  43. 51 0
      components/Loading/styled.ts
  44. 1 1
      components/Markup/index.tsx
  45. 35 11
      components/Markup/styled.ts
  46. 2 7
      components/Modal/index.tsx
  47. 4 4
      components/Navbar/data.ts
  48. 10 13
      components/Navbar/index.tsx
  49. 5 4
      components/OuterRect/data.ts
  50. 24 20
      components/OuterRect/index.tsx
  51. 3 2
      components/OuterRectForLine/index.tsx
  52. 42 45
      components/Page/index.tsx
  53. 11 2
      components/Page/styled.ts
  54. 34 29
      components/PdfSkeleton/index.tsx
  55. 11 0
      components/PdfSkeleton/styled.ts
  56. 26 31
      components/SelectBox/index.tsx
  57. 23 17
      components/Shape/index.tsx
  58. 22 19
      components/ShapeOption/index.tsx
  59. 13 16
      components/Sliders/index.tsx
  60. 9 6
      components/Sliders/styled.ts
  61. 25 40
      components/StickyNote/index.tsx
  62. 12 17
      components/Tabs/index.tsx
  63. 20 17
      components/TextField/index.tsx
  64. 29 17
      components/TextField/styled.ts
  65. 11 12
      components/ThumbnailViewer/index.tsx
  66. 9 1
      components/ThumbnailViewer/styled.ts
  67. 49 16
      components/Toolbar/index.tsx
  68. 1 7
      components/Typography/index.tsx
  69. 3 3
      components/Typography/styled.ts
  70. 11 17
      components/Viewer/index.tsx
  71. 2 5
      components/Watermark/index.tsx
  72. 34 23
      components/WatermarkOption/index.tsx
  73. 1 0
      config/index.js
  74. 1 2
      constants/actionTypes.ts
  75. 1 1
      constants/style.ts
  76. 0 159
      constants/type.ts
  77. 6 12
      containers/Annotation.tsx
  78. 4 13
      containers/AnnotationList.tsx
  79. 5 8
      containers/AnnotationListHead.tsx
  80. 54 10
      containers/AutoSave.tsx
  81. 2 1
      containers/ColorSelector.tsx
  82. 14 9
      containers/CreateForm.tsx
  83. 34 31
      containers/FreeTextTools.tsx
  84. 68 51
      containers/FreehandTools.tsx
  85. 28 30
      containers/HighlightTools.tsx
  86. 18 0
      containers/Loading.tsx
  87. 29 18
      containers/MarkupTools.tsx
  88. 31 18
      containers/Navbar.tsx
  89. 11 15
      containers/PdfPage.tsx
  90. 8 16
      containers/PdfPages.tsx
  91. 22 22
      containers/PdfViewer.tsx
  92. 1 7
      containers/Placeholder.tsx
  93. 8 10
      containers/Search.tsx
  94. 97 79
      containers/ShapeTools.tsx
  95. 5 1
      containers/Sidebar.tsx
  96. 21 10
      containers/StickyNoteTools.tsx
  97. 10 5
      containers/Thumbnails.tsx
  98. 7 4
      containers/Toolbar.tsx
  99. 8 14
      containers/WatermarkTool.tsx
  100. 0 0
      custom.d.ts

+ 4 - 1
.editorconfig

@@ -1,6 +1,9 @@
+root = true
+
 [*]
-indent_style = space
 end_of_line = lf
+indent_style = space
 indent_size = 2
 charset = utf-8
 trim_trailing_whitespace = true
+insert_final_newline = true

+ 0 - 44
.eslintrc.js

@@ -1,44 +0,0 @@
-module.exports = {
-  "env": {
-    "browser": true,
-    "node": true,
-  },
-  "parser": "@typescript-eslint/parser",
-  "extends": ['airbnb', 'plugin:@typescript-eslint/recommended'],
-  "plugins": ['@typescript-eslint', 'prettier'],
-  "settings": {
-    'import/parsers': {
-      '@typescript-eslint/parser': ['.ts', '.tsx'],
-    },
-    'import/resolver': {
-      typescript: {},
-    },
-  },
-  "overrides": [
-    {
-     "files": ["*styled.js", "*styled.ts", "*Styled.ts"],
-      "rules": {
-        "@typescript-eslint/explicit-function-return-type": 0,
-        "import/prefer-default-export": 0
-      }
-    }
-  ],
-  "rules": {
-    "no-console": "off",
-    "react/jsx-filename-extension": [2, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],
-    "camelcase": "off",
-    "@typescript-eslint/camelcase": [0, { "properties": "never" }],
-    "@typescript-eslint/no-explicit-any": 0,
-    "@typescript-eslint/ban-ts-ignore": 0,
-    "import/extensions": [
-      "error",
-      "ignorePackages",
-      {
-        "js": "never",
-        "jsx": "never",
-        "ts": "never",
-        "tsx": "never"
-      }
-   ]
-  }
-};

+ 76 - 0
.eslintrc.json

@@ -0,0 +1,76 @@
+{
+  "env": {
+    "browser": true,
+    "es6": true
+  },
+  "extends": [
+    "plugin:react/recommended",
+    "airbnb",
+    "plugin:prettier/recommended",
+    "plugin:@typescript-eslint/eslint-recommended",
+    "plugin:@typescript-eslint/recommended"
+  ],
+  "globals": {
+    "Atomics": "readonly",
+    "SharedArrayBuffer": "readonly"
+  },
+  "parser": "@typescript-eslint/parser",
+  "parserOptions": {
+    "ecmaFeatures": {
+      "jsx": true
+    },
+    "ecmaVersion": 2018,
+    "sourceType": "module"
+  },
+  "plugins": [
+    "react",
+    "@typescript-eslint",
+    "prettier"
+  ],
+  "rules": {
+    "import/extensions": [
+      "error",
+      "ignorePackages",
+      {
+        "js": "never",
+        "jsx": "never",
+        "ts": "never",
+        "tsx": "never"
+      }
+    ],
+    "prettier/prettier": "error",
+    "@typescript-eslint/explicit-function-return-type": "off",
+    "@typescript-eslint/no-unused-vars": "off",
+    "camelcase": "off",
+    "@typescript-eslint/camelcase": [
+      0,
+      {
+        "properties": "never"
+      }
+    ],
+    "react/jsx-props-no-spreading": "off",
+    "react/jsx-filename-extension": [
+      1,
+      {
+        "extensions": [
+          ".js",
+          ".jsx",
+          ".ts",
+          ".tsx"
+        ]
+      }
+    ]
+  },
+  "settings": {
+    "import/resolver": {
+      "node": {
+        "extensions": [
+          ".js",
+          ".jsx",
+          ".ts",
+          ".tsx"
+        ]
+      }
+    }
+  }
+}

+ 4 - 0
.prettierrc

@@ -0,0 +1,4 @@
+{
+  "singleQuote": true,
+  "trailingComma": "es5"
+}

+ 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;');
+  });
+});

+ 12 - 22
actions/main.ts

@@ -1,28 +1,18 @@
 import * as types from '../constants/actionTypes';
-import { ActionType } from '../constants/type';
 
 const actions: ActionType = dispatch => ({
-  toggleDisplayMode: (state: string): void => (
-    dispatch({ type: types.TOGGLE_DISPLAY_MODE, payload: state })
-  ),
-  setNavbar: (state: string): void => (
-    dispatch({ type: types.SET_NAVBAR, payload: state })
-  ),
-  setSidebar: (state: string): void => (
-    dispatch({ type: types.SET_SIDEBAR, payload: state })
-  ),
-  setMarkupTool: (state: string): void => (
-    dispatch({ type: types.SET_MARKUP_TOOL, payload: state })
-  ),
-  setInfo: (state: Record<string, any>): void => (
-    dispatch({ type: types.SET_INFO, payload: state })
-  ),
-  setSaved: (state: boolean): void => (
-    dispatch({ type: types.SET_SAVED, payload: state })
-  ),
-  setSaving: (state: boolean): void => (
-    dispatch({ type: types.SET_SAVING, payload: state })
-  ),
+  toggleDisplayMode: (state: string): void =>
+    dispatch({ type: types.TOGGLE_DISPLAY_MODE, payload: state }),
+  setNavbar: (state: string): void =>
+    dispatch({ type: types.SET_NAVBAR, payload: state }),
+  setSidebar: (state: string): void =>
+    dispatch({ type: types.SET_SIDEBAR, payload: state }),
+  setMarkupTool: (state: string): void =>
+    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;

+ 20 - 33
actions/pdf.ts

@@ -1,39 +1,26 @@
 import * as types from '../constants/actionTypes';
-import {
-  ProgressType, ViewportType, AnnotationType, ActionType, WatermarkType,
-} from '../constants/type';
 
 const actions: ActionType = dispatch => ({
-  setTotalPage: (page: number): void => (
-    dispatch({ type: types.SET_TOTAL_PAGE, payload: page })
-  ),
-  setCurrentPage: (page: number): void => (
-    dispatch({ type: types.SET_CURRENT_PAGE, payload: page })
-  ),
-  setPdf: (pdf: Record<string, any>): void => (
-    dispatch({ type: types.SET_PDF, payload: pdf })
-  ),
-  setProgress: (progress: ProgressType): void => (
-    dispatch({ type: types.SET_PROGRESS, payload: progress })
-  ),
-  setViewport: (viewport: ViewportType): void => (
-    dispatch({ type: types.SET_VIEWPORT, payload: viewport })
-  ),
-  changeScale: (scale: number | string): void => (
-    dispatch({ type: types.CHANGE_SCALE, payload: scale })
-  ),
-  changeRotate: (rotation: number): void => (
-    dispatch({ type: types.CHANGE_ROTATE, payload: rotation })
-  ),
-  addAnnots: (annotations: AnnotationType[], init: boolean): void => (
-    dispatch({ type: types.ADD_ANNOTS, payload: { annotations, init } })
-  ),
-  updateAnnots: (annotations: AnnotationType[]): void => (
-    dispatch({ type: types.UPDATE_ANNOTS, payload: annotations })
-  ),
-  updateWatermark: (watermark: WatermarkType): void => (
-    dispatch({ type: types.UPDATE_WATERMARK, payload: watermark })
-  ),
+  setTotalPage: (page: number): void =>
+    dispatch({ type: types.SET_TOTAL_PAGE, payload: page }),
+  setCurrentPage: (page: number): void =>
+    dispatch({ type: types.SET_CURRENT_PAGE, payload: page }),
+  setPdf: (pdf: Record<string, any>): void =>
+    dispatch({ type: types.SET_PDF, payload: pdf }),
+  setProgress: (progress: ProgressType): void =>
+    dispatch({ type: types.SET_PROGRESS, payload: progress }),
+  setViewport: (viewport: ViewportType): void =>
+    dispatch({ type: types.SET_VIEWPORT, payload: viewport }),
+  changeScale: (scale: number | string): void =>
+    dispatch({ type: types.CHANGE_SCALE, payload: scale }),
+  changeRotate: (rotation: number): void =>
+    dispatch({ type: types.CHANGE_ROTATE, payload: rotation }),
+  addAnnots: (annotations: AnnotationType[], init = true): void =>
+    dispatch({ type: types.ADD_ANNOTS, payload: { annotations, init } }),
+  updateAnnots: (annotations: AnnotationType[]): void =>
+    dispatch({ type: types.UPDATE_ANNOTS, payload: annotations }),
+  updateWatermark: (watermark: WatermarkType): void =>
+    dispatch({ type: types.UPDATE_WATERMARK, payload: watermark }),
 });
 
 export default actions;

+ 2 - 4
components/AnnotationItem/index.tsx

@@ -5,9 +5,7 @@ import Divider from '../Divider';
 import { scrollIntoView } from '../../helpers/utility';
 
 import data from './data';
-import {
-  PageNumber, AnnotationBox,
-} from './styled';
+import { PageNumber, AnnotationBox } from './styled';
 
 type Props = {
   type: string;
@@ -16,7 +14,7 @@ type Props = {
   getText: () => Promise<any>;
   showPageNum?: boolean;
   transparency: number;
-}
+};
 
 const AnnotationItem = ({
   type,

+ 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" />

+ 29 - 38
components/AnnotationSelector/index.tsx

@@ -7,11 +7,7 @@ import Box from '../Box';
 import Sliders from '../Sliders';
 import ColorSelector from '../../containers/ColorSelector';
 
-import { OnUpdateType } from '../../constants/type';
-
-import {
-  Container, Subtitle, SliderWrapper,
-} from './styled';
+import { Container, Subtitle, SliderWrapper } from './styled';
 
 type Props = {
   onUpdate: OnUpdateType;
@@ -45,39 +41,34 @@ const index: React.FC<Props> = ({
 
   return (
     <Container>
-      {
-        openSlider ? (
-          <>
-            <Box w="40px" h="36px" d="flex" j="center">
-              <Icon glyph="right-arrow" onClick={sliderToggle} />
-            </Box>
-            <Divider style={{ margin: '0 10px 0 10px' }} />
-            <SliderWrapper>
-              <Sliders value={opacity} onChange={handleChange} />
-            </SliderWrapper>
-            <Subtitle>{`${opacity} %`}</Subtitle>
-          </>
-        ) : (
-          <>
-            <ColorSelector
-              selectedColor={colorProps}
-              onClick={handleClick}
-            />
-            <Subtitle>opacity</Subtitle>
-            <Button
-              appearance="dark"
-              style={{ height: '34px', width: '80px', fontSize: '12px' }}
-              onClick={sliderToggle}
-            >
-              {`${opacity} %`}
-            </Button>
-            <Divider style={{ margin: '0 6px 0 15px' }} />
-            <Box w="48px" h="36px" d="flex" j="center">
-              <Icon glyph="trash" onClick={onDelete} />
-            </Box>
-          </>
-        )
-      }
+      {openSlider ? (
+        <>
+          <Box w="40px" h="36px" d="flex" j="center">
+            <Icon glyph="right-arrow" onClick={sliderToggle} />
+          </Box>
+          <Divider style={{ margin: '0 10px 0 10px' }} />
+          <SliderWrapper>
+            <Sliders value={opacity} onChange={handleChange} />
+          </SliderWrapper>
+          <Subtitle>{`${opacity} %`}</Subtitle>
+        </>
+      ) : (
+        <>
+          <ColorSelector selectedColor={colorProps} onClick={handleClick} />
+          <Subtitle>opacity</Subtitle>
+          <Button
+            appearance="dark"
+            style={{ height: '34px', width: '80px', fontSize: '12px' }}
+            onClick={sliderToggle}
+          >
+            {`${opacity} %`}
+          </Button>
+          <Divider style={{ margin: '0 6px 0 15px' }} />
+          <Box w="48px" h="36px" d="flex" j="center">
+            <Icon glyph="trash" onClick={onDelete} />
+          </Box>
+        </>
+      )}
     </Container>
   );
 };

+ 4 - 13
components/Box/index.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 
 import { Wrapper } from './styled';
 
-const properties: {[index: string]: string} = {
+const properties: { [index: string]: string } = {
   m: 'margin',
   p: 'padding',
   d: 'display',
@@ -13,7 +13,7 @@ const properties: {[index: string]: string} = {
   h: 'height',
 };
 
-const directions: {[index: string]: string} = {
+const directions: { [index: string]: string } = {
   t: 'Top',
   r: 'Right',
   b: 'Bottom',
@@ -25,10 +25,7 @@ type Props = {
   children: React.ReactNode;
 };
 
-const Box: React.FC<Props> = ({
-  children,
-  ...rest
-}: Props) => {
+const Box: React.FC<Props> = ({ children, ...rest }: Props) => {
   const args = Object.keys(rest);
 
   if (!rest.width) {
@@ -45,13 +42,7 @@ const Box: React.FC<Props> = ({
     return `${property}: ${rest[ele]};`;
   });
 
-  return (
-    <Wrapper
-      styles={styles}
-    >
-      {children}
-    </Wrapper>
-  );
+  return <Wrapper styles={styles}>{children}</Wrapper>;
 };
 
 export default Box;

+ 3 - 1
components/Box/styled.ts

@@ -1,5 +1,7 @@
 import styled from 'styled-components';
 
-export const Wrapper = styled('div')<{styles?: string[]}>`
+export const Wrapper = styled('div')<{ styles?: string[] }>`
   ${props => props.styles}
 `;
+
+export default Wrapper;

+ 14 - 15
components/Button/index.tsx

@@ -1,12 +1,17 @@
 import React from 'react';
 
-import {
-  NormalButton,
-  DisableButton,
-} from './styled';
+import { NormalButton, DisableButton } from './styled';
 
 export type Props = {
-  appearance?: 'default' | 'primary' | 'primary-hollow' | 'danger-hollow' | 'default-hollow' | 'dark' | 'link' | 'danger-link';
+  appearance?:
+    | 'default'
+    | 'primary'
+    | 'primary-hollow'
+    | 'danger-hollow'
+    | 'default-hollow'
+    | 'dark'
+    | 'link'
+    | 'danger-link';
   id?: string;
   isDisabled?: boolean;
   onClick?: (e: React.MouseEvent<HTMLElement>) => void;
@@ -25,20 +30,14 @@ const Button: React.FC<Props> = ({
   isDisabled,
   onClick,
   ...rest
-}: Props): React.ReactElement => (
+}: Props): React.ReactElement =>
   isDisabled ? (
-    <DisableButton>
-      {children}
-    </DisableButton>
+    <DisableButton disabled>{children}</DisableButton>
   ) : (
-    <NormalButton
-      {...rest}
-      onMouseDown={onClick}
-    >
+    <NormalButton {...rest} onMouseDown={onClick}>
       {children}
     </NormalButton>
-  )
-);
+  );
 
 Button.defaultProps = {
   appearance: 'default',

+ 21 - 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']};
@@ -125,6 +136,7 @@ export const NormalButton = styled('div')<{appearance?: string; shouldFitContain
 
 export const DisableButton = styled.button`
   ${staticStyles}
+
   color: ${color.black38};
   background-color: #eeeff3;
   cursor: not-allowed;

+ 36 - 36
components/ColorSelector/index.tsx

@@ -1,7 +1,6 @@
 import React from 'react';
 import { ChromePicker } from 'react-color';
 
-import Portal from '../Portal';
 import Icon from '../Icon';
 import Typography from '../Typography';
 
@@ -21,7 +20,7 @@ type Props = {
   onClick: (color: string) => void;
   mode?: 'normal' | 'shape' | 'watermark';
   isCollapse: boolean;
-  pickerToggle: () => void;
+  pickerToggle: (e: React.MouseEvent<HTMLElement>) => void;
 };
 
 type ColorItem = {
@@ -42,46 +41,47 @@ const ColorSelector: React.FC<Props> = ({
 
   return (
     <>
-      { title ? (
-        <Typography variant="subtitle" style={{ marginTop: '8px' }} align="left">
+      {title ? (
+        <Typography
+          variant="subtitle"
+          style={{ marginTop: '8px' }}
+          align="left"
+        >
           {title}
         </Typography>
       ) : null}
       <Group>
-        {
-          colorList.map((ele: ColorItem) => (
-            <Item
-              key={ele.key}
-              selected={selectedColor === ele.color}
-              onMouseDown={(): void => { onClick(ele.color); }}
-            >
-              {
-                ele.icon ? (
-                  <Icon glyph={ele.icon} />
-                ) : (
-                  <Circle color={ele.color} />
-                )
-              }
-            </Item>
-          ))
-        }
-        <Item onClick={pickerToggle}>
+        {colorList.map((ele: ColorItem) => (
+          <Item
+            key={ele.key}
+            selected={selectedColor === ele.color}
+            onMouseDown={(): void => {
+              onClick(ele.color);
+            }}
+          >
+            {ele.icon ? (
+              <Icon glyph={ele.icon} />
+            ) : (
+              <Circle color={ele.color} />
+            )}
+          </Item>
+        ))}
+        <Item onMouseDown={pickerToggle}>
           <Icon glyph="color-picker" />
         </Item>
-        {
-          !isCollapse ? (
-            <Portal>
-              <PickerContainer>
-                <Blanket onClick={pickerToggle} />
-                <ChromePicker
-                  color={selectedColor}
-                  disableAlpha
-                  onChange={(color: any): void => { onClick(color.hex); }}
-                />
-              </PickerContainer>
-            </Portal>
-          ) : null
-        }
+        {!isCollapse ? (
+          <PickerContainer>
+            <Blanket onMouseDown={pickerToggle} />
+            <ChromePicker
+              color={selectedColor}
+              disableAlpha
+              onChange={(color: any, event: any): void => {
+                event.preventDefault();
+                onClick(color.hex);
+              }}
+            />
+          </PickerContainer>
+        ) : null}
       </Group>
     </>
   );

+ 20 - 20
components/DeleteDialog/index.tsx

@@ -1,36 +1,36 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 import { useTranslation } from 'react-i18next';
 
 import Dialog from '../Dialog';
 import Button from '../Button';
+import useKeyPress from '../../hooks/useKeyPress';
 
-import {
-  TextWrapper, BtnWrapper,
-} from './styled';
+import { TextWrapper, BtnWrapper } from './styled';
 
 type Props = {
-  open: boolean;
-  onCancel: (e: any) => void;
-  onDelete: (e: any) => void;
+  onCancel: () => void;
+  onDelete: () => void;
 };
 
-const index: React.FC<Props> = ({
-  open,
-  onCancel,
-  onDelete,
-}: Props) => {
+const index: React.FC<Props> = ({ onCancel, onDelete }: Props) => {
   const { t } = useTranslation('dialog');
+  const enterKeyPress = useKeyPress('Enter');
+  const escKeyPress = useKeyPress('Escape');
+
+  useEffect(() => {
+    if (enterKeyPress) {
+      onDelete();
+    }
+    if (escKeyPress) {
+      onCancel();
+    }
+  }, [enterKeyPress, escKeyPress]);
 
   return (
-    <Dialog open={open}>
-      <TextWrapper>
-        {t('deleteAnnotationAlert')}
-      </TextWrapper>
+    <Dialog open>
+      <TextWrapper>{t('deleteAnnotationAlert')}</TextWrapper>
       <BtnWrapper>
-        <Button
-          appearance="default-hollow"
-          onClick={onCancel}
-        >
+        <Button appearance="default-hollow" onClick={onCancel}>
           {t('cancel')}
         </Button>
         <Button

+ 3 - 9
components/Dialog/index.tsx

@@ -9,17 +9,11 @@ type Props = {
   open: boolean;
 };
 
-const Dialog: React.FC<Props> = ({
-  children,
-  open,
-}: Props) => (
+const Dialog: React.FC<Props> = ({ children, open }: Props) =>
   open ? (
     <Modal>
-      <Wrapper>
-        {children}
-      </Wrapper>
+      <Wrapper>{children}</Wrapper>
     </Modal>
-  ) : null
-);
+  ) : null;
 
 export default Dialog;

+ 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;

+ 3 - 10
components/Drawer/index.tsx

@@ -5,7 +5,7 @@ import Portal from '../Portal';
 import { Slide, Wrapper } from './styled';
 
 type Props = {
-  anchor?: 'left' | 'top' | 'right' |'bottom';
+  anchor?: 'left' | 'top' | 'right' | 'bottom';
   children: React.ReactNode;
   open?: boolean;
   zIndex?: number;
@@ -18,15 +18,8 @@ const Drawer: React.FC<Props> = ({
   zIndex = 3,
 }: Props) => (
   <Portal>
-    <Slide
-      open={open}
-      anchor={anchor}
-      data-testid="drawer"
-      zIndex={zIndex}
-    >
-      <Wrapper>
-        {children}
-      </Wrapper>
+    <Slide open={open} anchor={anchor} data-testid="drawer" zIndex={zIndex}>
+      <Wrapper>{children}</Wrapper>
     </Slide>
   </Portal>
 );

+ 13 - 7
components/Drawer/styled.ts

@@ -1,6 +1,6 @@
 import styled, { css } from 'styled-components';
 
-const position: {[index: string]: any} = {
+const position: { [index: string]: any } = {
   left: css`
     top: 0;
     bottom: 0;
@@ -27,7 +27,7 @@ const position: {[index: string]: any} = {
   `,
 };
 
-const close: {[index: string]: any} = {
+const close: { [index: string]: any } = {
   left: css`
     transform: translate(-267px, 0);
   `,
@@ -42,7 +42,11 @@ const close: {[index: string]: any} = {
   `,
 };
 
-export const Slide = styled('div')<{open: boolean; anchor: string; zIndex: number}>`
+export const Slide = styled('div')<{
+  open: boolean;
+  anchor: string;
+  zIndex: number;
+}>`
   position: fixed;
   transition: transform 225ms cubic-bezier(0, 0, 0.2, 1) 0ms;
   z-index: ${props => props.zIndex};
@@ -50,10 +54,12 @@ export const Slide = styled('div')<{open: boolean; anchor: string; zIndex: numbe
 
   ${props => position[props.anchor]}
 
-  ${props => (props.open ? css`
-    transform: translate(0, 0);
-  ` : close[props.anchor]
-  )}
+  ${props =>
+    props.open
+      ? css`
+          transform: translate(0, 0);
+        `
+      : close[props.anchor]}
 `;
 
 export const Wrapper = styled.div`

+ 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;

+ 24 - 0
components/Embed/styled.ts

@@ -0,0 +1,24 @@
+import styled from 'styled-components';
+
+export const Wrapper = styled.div`
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 100;
+  display: none;
+`;
+
+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);
+`;

+ 4 - 10
components/ExpansionPanel/index.tsx

@@ -1,8 +1,6 @@
 import React, { useState, useEffect } from 'react';
 
-import {
-  Container, Label, ContentWrapper, Content,
-} from './styled';
+import { Container, Label, ContentWrapper, Content } from './styled';
 
 type Props = {
   label: React.ReactNode;
@@ -25,14 +23,10 @@ const Collapse: React.FC<Props> = ({
 
   return (
     <Container showBottomBorder={isActive && showBottomBorder}>
-      <Label>
-        {label}
-      </Label>
-      { children ? (
+      <Label>{label}</Label>
+      {children ? (
         <ContentWrapper isCollapse={isCollapse}>
-          <Content>
-            {children}
-          </Content>
+          <Content>{children}</Content>
         </ContentWrapper>
       ) : null}
     </Container>

+ 48 - 46
components/FreeText/index.tsx

@@ -1,9 +1,6 @@
-import React from 'react';
+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';
@@ -11,13 +8,7 @@ import { TextWrapper, Input } from './styled';
 
 const FreeText: React.SFC<AnnotationElementPropsType> = ({
   obj_type,
-  obj_attr: {
-    content,
-    position,
-    fontname,
-    fontsize,
-    textcolor,
-  },
+  obj_attr: { content, position, fontname, fontsize, textcolor },
   scale,
   viewport,
   isCollapse,
@@ -26,12 +17,13 @@ const FreeText: React.SFC<AnnotationElementPropsType> = ({
   onUpdate,
   onBlur,
 }: AnnotationElementPropsType) => {
+  const inputRef = useRef<HTMLInputElement>(null);
   const annotRect = rectCalc(position as PositionType, viewport.height, scale);
 
   const handleChange = (event: React.FormEvent<HTMLInputElement>): void => {
     const textValue = event.currentTarget.value;
-    const fontWidth = textValue.length * (fontsize || 1);
-    const newPosition = { ...position as PositionType };
+    const fontWidth = textValue.length * ((fontsize as number) * 0.9 || 1);
+    const newPosition = { ...(position as PositionType) };
     newPosition.right = newPosition.left + fontWidth;
 
     onUpdate({
@@ -41,7 +33,10 @@ const FreeText: React.SFC<AnnotationElementPropsType> = ({
   };
 
   const handleScaleOrMove = ({
-    top, left, width = 0, height = 0,
+    top,
+    left,
+    width = 0,
+    height = 0,
   }: CoordType): void => {
     const newPosition = {
       top,
@@ -52,7 +47,12 @@ const FreeText: React.SFC<AnnotationElementPropsType> = ({
 
     onUpdate({
       fontsize: (height || annotRect.height) / scale,
-      position: parsePositionForBackend(obj_type, newPosition, viewport.height, scale),
+      position: parsePositionForBackend(
+        obj_type,
+        newPosition,
+        viewport.height,
+        scale
+      ),
     });
   };
 
@@ -62,45 +62,47 @@ const FreeText: React.SFC<AnnotationElementPropsType> = ({
     color: textcolor,
   };
 
+  useEffect(() => {
+    if (isEdit) {
+      setTimeout(() => {
+        if (inputRef.current) inputRef.current.focus();
+      }, 100);
+    }
+  }, [isEdit]);
+
   return (
     <div>
       <AnnotationContainer
         top={`${annotRect.top}px`}
         left={`${annotRect.left}px`}
         width={`${annotRect.width + 2}px`}
-        height={`${annotRect.height + 4}px`}
+        height={`${annotRect.height + 8}px`}
       >
-        {
-          isEdit ? (
-            <Input
-              defaultValue={content}
-              onChange={handleChange}
-              autoFocus
-              onBlur={onBlur}
-              style={styles}
-            />
-          ) : (
-            <TextWrapper
-              style={styles}
-            >
-              {content}
-            </TextWrapper>
-          )
-        }
-      </AnnotationContainer>
-      {
-        !isCollapse ? (
-          <OuterRect
-            top={annotRect.top}
-            left={annotRect.left}
-            width={annotRect.width}
-            height={annotRect.height}
-            onMove={handleScaleOrMove}
-            onScale={handleScaleOrMove}
-            onDoubleClick={onEdit}
+        {isEdit ? (
+          <Input
+            ref={inputRef}
+            defaultValue={content}
+            onChange={handleChange}
+            onBlur={onBlur}
+            style={styles}
           />
-        ) : ''
-      }
+        ) : (
+          <TextWrapper style={styles}>{content}</TextWrapper>
+        )}
+      </AnnotationContainer>
+      {!isCollapse ? (
+        <OuterRect
+          top={annotRect.top}
+          left={annotRect.left}
+          width={annotRect.width}
+          height={annotRect.height}
+          onMove={handleScaleOrMove}
+          onScale={handleScaleOrMove}
+          onDoubleClick={onEdit}
+        />
+      ) : (
+        ''
+      )}
     </div>
   );
 };

+ 1 - 1
components/FreeText/styled.ts

@@ -14,6 +14,6 @@ export const Input = styled.input`
   margin: 0;
   width: 95%;
   outline: none;
-  height: 93%;
+  height: 94%;
   display: inline-block;
 `;

+ 34 - 38
components/FreeTextOption/index.tsx

@@ -6,18 +6,10 @@ 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';
+import { Wrapper, Group, Item } from '../../global/toolStyled';
 
-import {
-  fontOptions,
-  sizeOptions,
-  alignOptions,
-  styleOptions,
-} from './data';
+import { fontOptions, sizeOptions, alignOptions, styleOptions } from './data';
 
 const TextOption: React.SFC<OptionPropsType> = ({
   fontName,
@@ -51,20 +43,20 @@ const TextOption: React.SFC<OptionPropsType> = ({
               setDataState({ fontName: option.child });
             }}
           />
-          {
-            styleOptions.map(ele => (
-              <Item
-                key={ele.key}
-                size="small"
-                selected={fontStyle === ele.child}
-                onClick={(): void => {
-                  setDataState({ fontStyle: ele.child === fontStyle ? '' : ele.child });
-                }}
-              >
-                <Icon glyph={ele.key} />
-              </Item>
-            ))
-          }
+          {styleOptions.map(ele => (
+            <Item
+              key={ele.key}
+              size="small"
+              selected={fontStyle === ele.child}
+              onClick={(): void => {
+                setDataState({
+                  fontStyle: ele.child === fontStyle ? '' : ele.child,
+                });
+              }}
+            >
+              <Icon glyph={ele.key} />
+            </Item>
+          ))}
         </Group>
       </Wrapper>
       <Wrapper width="40%">
@@ -94,25 +86,27 @@ const TextOption: React.SFC<OptionPropsType> = ({
           {t('align')}
         </Typography>
         <Group>
-          {
-            alignOptions.map(ele => (
-              <Item
-                key={ele.key}
-                size="small"
-                selected={align === ele.child}
-                onClick={(): void => { setDataState({ align: ele.child }); }}
-              >
-                <Icon glyph={ele.key} />
-              </Item>
-            ))
-          }
+          {alignOptions.map(ele => (
+            <Item
+              key={ele.key}
+              size="small"
+              selected={align === ele.child}
+              onClick={(): void => {
+                setDataState({ align: ele.child });
+              }}
+            >
+              <Icon glyph={ele.key} />
+            </Item>
+          ))}
         </Group>
       </Wrapper>
       <Wrapper>
         <ColorSelector
           title={t('color')}
           selectedColor={color}
-          onClick={(selected: string): void => { setDataState({ color: selected }); }}
+          onClick={(selected: string): void => {
+            setDataState({ color: selected });
+          }}
         />
       </Wrapper>
       <Wrapper>
@@ -120,7 +114,9 @@ const TextOption: 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>
     </>

+ 2 - 8
components/Head.tsx

@@ -24,10 +24,7 @@ const Head: React.SFC<Props> = ({
     <NextHead>
       <meta charSet="UTF-8" />
       <title>{t(title)}</title>
-      <meta
-        name="description"
-        content={t(description)}
-      />
+      <meta name="description" content={t(description)} />
       <meta name="viewport" content="width=device-width, initial-scale=1" />
       <link rel="icon" sizes="192x192" href="/static/touch-icon.png" />
       <link rel="apple-touch-icon" href="/static/touch-icon.png" />
@@ -35,10 +32,7 @@ const Head: React.SFC<Props> = ({
       <link rel="icon" href="/static/favicon.ico" />
       <meta property="og:url" content={url || defaultOGURL} />
       <meta property="og:title" content={title || ''} />
-      <meta
-        property="og:description"
-        content={t(description)}
-      />
+      <meta property="og:description" content={t(description)} />
       <meta name="twitter:site" content={url || defaultOGURL} />
       <meta name="twitter:card" content="summary_large_image" />
       <meta name="twitter:image" content={ogImage || defaultOGImage} />

+ 21 - 30
components/Highlight/index.tsx

@@ -1,22 +1,14 @@
 import React from 'react';
 
-import { AnnotationElementPropsType } from '../../constants/type';
 import AnnotationSelector from '../AnnotationSelector';
 import Markup from '../Markup';
 import { rectCalc } from '../../helpers/position';
 
-import {
-  Popper,
-} from './styled';
+import { Popper } from './styled';
 
 const Highlight: React.SFC<AnnotationElementPropsType> = ({
   obj_type,
-  obj_attr: {
-    page,
-    position,
-    bdcolor,
-    transparency,
-  },
+  obj_attr: { page, position, bdcolor, transparency },
   isCovered,
   mousePosition,
   isCollapse,
@@ -26,18 +18,18 @@ const Highlight: React.SFC<AnnotationElementPropsType> = ({
   viewport,
 }: AnnotationElementPropsType) => (
   <>
-    {
-      Array.isArray(position) && position.map((ele: any, index: number) => {
+    {Array.isArray(position) &&
+      position.map((ele: any, index: number) => {
         const annotRect = rectCalc(ele, viewport.height, scale);
 
         return (
           <Markup
             key={`block_${page + index}`}
             position={{
-              top: `${annotRect.top}px`,
-              left: `${annotRect.left}px`,
-              width: `${annotRect.width}px`,
-              height: `${annotRect.height}px`,
+              top: `${annotRect.top.toFixed(2)}px`,
+              left: `${annotRect.left.toFixed(2)}px`,
+              width: `${annotRect.width.toFixed(2)}px`,
+              height: `${annotRect.height.toFixed(2)}px`,
             }}
             bdcolor={bdcolor || ''}
             opacity={transparency || 0}
@@ -45,20 +37,19 @@ const Highlight: React.SFC<AnnotationElementPropsType> = ({
             isCovered={isCovered}
           />
         );
-      })
-    }
-    {
-      !isCollapse ? (
-        <Popper position={mousePosition}>
-          <AnnotationSelector
-            onUpdate={onUpdate}
-            onDelete={onDelete}
-            colorProps={bdcolor}
-            opacityProps={transparency ? transparency * 100 : 0}
-          />
-        </Popper>
-      ) : ''
-    }
+      })}
+    {!isCollapse ? (
+      <Popper position={mousePosition}>
+        <AnnotationSelector
+          onUpdate={onUpdate}
+          onDelete={onDelete}
+          colorProps={bdcolor}
+          opacityProps={transparency ? transparency * 100 : 0}
+        />
+      </Popper>
+    ) : (
+      ''
+    )}
   </>
 );
 

+ 14 - 5
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';
@@ -23,7 +22,11 @@ const HighlightOption: React.SFC<OptionPropsType> = ({
   return (
     <>
       <Wrapper>
-        <Typography variant="subtitle" style={{ marginTop: '8px' }} align="left">
+        <Typography
+          variant="subtitle"
+          style={{ marginTop: '8px' }}
+          align="left"
+        >
           {t('style')}
         </Typography>
         <Group>
@@ -31,7 +34,9 @@ const HighlightOption: React.SFC<OptionPropsType> = ({
             <Item
               key={ele.key}
               selected={type === ele.key}
-              onClick={(): void => { setDataState({ type: ele.key }); }}
+              onClick={(): void => {
+                setDataState({ type: ele.key });
+              }}
             >
               <Icon glyph={ele.icon} />
             </Item>
@@ -42,7 +47,9 @@ const HighlightOption: React.SFC<OptionPropsType> = ({
         <ColorSelector
           title={t('color')}
           selectedColor={color}
-          onClick={(selected: string): void => { setDataState({ color: selected }); }}
+          onClick={(selected: string): void => {
+            setDataState({ color: selected });
+          }}
         />
       </Wrapper>
       <Wrapper>
@@ -50,7 +57,9 @@ const HighlightOption: 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>
     </>

+ 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,
   },

+ 12 - 18
components/Icon/index.tsx

@@ -1,9 +1,6 @@
 import React from 'react';
 
-import {
-  IconWrapper,
-  ClickZone,
-} from './styled';
+import { IconWrapper, ClickZone } from './styled';
 import data from './data';
 
 type Props = {
@@ -12,8 +9,8 @@ type Props = {
   onClick?: (e: any) => void;
   onBlur?: () => void;
   style?: {};
-  isActive? : boolean;
-  isDisabled? : boolean;
+  isActive?: boolean;
+  isDisabled?: boolean;
 };
 
 const Icon: React.FC<Props> = ({
@@ -25,25 +22,22 @@ const Icon: React.FC<Props> = ({
   isActive = false,
   isDisabled = false,
 }: Props) => {
-  const {
-    Normal,
-    Hover,
-  } = data[glyph];
+  const { Normal, Hover } = data[glyph];
 
   return (
-    <IconWrapper
-      isHover={!!Hover}
-      style={style}
-      isDisabled={isDisabled}
-    >
+    <IconWrapper isHover={!!Hover} style={style} isDisabled={isDisabled}>
       {Normal && <Normal data-status="normal" />}
       {Hover && <Hover data-status="hover" />}
       {isActive && Hover ? <Hover data-status="active" /> : null}
       <ClickZone
         id={id}
-        onMouseDown={isDisabled ? (): void => {
-          // do something
-        } : onClick}
+        onMouseDown={
+          isDisabled
+            ? (): void => {
+                // do something
+              }
+            : onClick
+        }
         onBlur={onBlur}
       />
     </IconWrapper>

+ 64 - 59
components/Ink/index.tsx

@@ -1,44 +1,47 @@
 import React from 'react';
 
-import {
-  AnnotationElementPropsType, PointType, HTMLCoordinateType, CoordType, UpdateData,
-} from '../../constants/type';
 import OuterRect from '../OuterRect';
 import {
-  svgPath, bezierCommand, controlPoint, line,
+  svgPath,
+  bezierCommand,
+  controlPoint,
+  line,
 } from '../../helpers/svgBezierCurve';
 
 import { AnnotationContainer } from '../../global/otherStyled';
 import { SVG } from './styled';
 
 const Ink: React.SFC<AnnotationElementPropsType> = ({
-  obj_attr: {
-    position,
-    bdcolor,
-    bdwidth = 0,
-    transparency,
-  },
+  obj_attr: { position, bdcolor, bdwidth = 0, transparency },
   isCollapse,
   onUpdate,
   viewport,
   scale,
   id,
 }: AnnotationElementPropsType) => {
-  const pointCalc = (_points: PointType[][], h: number, s: number): PointType[][] => {
-    const reducer = _points.reduce((acc: PointType[][], cur: PointType[]): PointType[][] => {
-      const p = cur.map((point: PointType) => ({
-        x: point.x * s,
-        y: h - point.y * s,
-      }));
-      acc.push(p);
-      return acc;
-    }, []);
+  const pointCalc = (
+    _points: PointType[][],
+    h: number,
+    s: number
+  ): PointType[][] => {
+    const reducer = _points.reduce(
+      (acc: PointType[][], cur: PointType[]): PointType[][] => {
+        const p = cur.map((point: PointType) => ({
+          x: point.x * s,
+          y: h - point.y * s,
+        }));
+        acc.push(p);
+        return acc;
+      },
+      []
+    );
 
     return reducer;
   };
 
   const rectCalcWithPoint = (
-    pointsGroup: PointType[][], borderWidth: number,
+    pointsGroup: PointType[][],
+    borderWidth: number
   ): HTMLCoordinateType => {
     const xArray: number[] = [];
     const yArray: number[] = [];
@@ -59,14 +62,17 @@ const Ink: React.SFC<AnnotationElementPropsType> = ({
   };
 
   const borderWidth = bdwidth * scale;
-  const annotPoints = pointCalc(position as PointType[][], viewport.height, scale);
+  const annotPoints = pointCalc(
+    position as PointType[][],
+    viewport.height,
+    scale
+  );
   const annotRect = rectCalcWithPoint(annotPoints, borderWidth);
 
   const scaleOrMoveCalc = (
-    rect: HTMLCoordinateType, updatePoints: (data: UpdateData) => void,
-  ) => ({
-    top, left, width = 0, height = 0,
-  }: CoordType): void => {
+    rect: HTMLCoordinateType,
+    updatePoints: (data: UpdateData) => void
+  ) => ({ top, left, width = 0, height = 0 }: CoordType): void => {
     const xScaleRate = width / rect.width;
     const yScaleRate = height / rect.height;
     const xDistance = left - rect.left;
@@ -84,10 +90,10 @@ const Ink: React.SFC<AnnotationElementPropsType> = ({
 
   const handleScaleOrMove = scaleOrMoveCalc(annotRect, onUpdate);
 
-  const calcViewBox = ({
-    top, left, width, height,
-  }: HTMLCoordinateType,
-  _borderWidth: number): string => `
+  const calcViewBox = (
+    { top, left, width, height }: HTMLCoordinateType,
+    _borderWidth: number
+  ): string => `
     ${left - _borderWidth / 2} ${top - _borderWidth / 2} ${width} ${height}
   `;
 
@@ -99,38 +105,37 @@ const Ink: React.SFC<AnnotationElementPropsType> = ({
         width={`${annotRect.width}px`}
         height={`${annotRect.height}px`}
       >
-        <SVG
-          viewBox={calcViewBox(annotRect, bdwidth * scale)}
-        >
-          {
-            annotPoints.map((ele: PointType[], index: number) => {
-              const key = `${id}_path_${index}`;
-              return (
-                <path
-                  key={key}
-                  d={svgPath(ele as PointType[], bezierCommand(controlPoint(line, 0.2)))}
-                  fill="none"
-                  stroke={bdcolor}
-                  strokeWidth={bdwidth * scale}
-                  strokeOpacity={transparency}
-                />
-              );
-            })
-          }
+        <SVG viewBox={calcViewBox(annotRect, bdwidth * scale)}>
+          {annotPoints.map((ele: PointType[], index: number) => {
+            const key = `${id}_path_${index}`;
+            return (
+              <path
+                key={key}
+                d={svgPath(
+                  ele as PointType[],
+                  bezierCommand(controlPoint(line, 0.2))
+                )}
+                fill="none"
+                stroke={bdcolor}
+                strokeWidth={bdwidth * scale}
+                strokeOpacity={transparency}
+              />
+            );
+          })}
         </SVG>
       </AnnotationContainer>
-      {
-        !isCollapse ? (
-          <OuterRect
-            top={annotRect.top}
-            left={annotRect.left}
-            width={annotRect.width}
-            height={annotRect.height}
-            onMove={handleScaleOrMove}
-            onScale={handleScaleOrMove}
-          />
-        ) : ''
-      }
+      {!isCollapse ? (
+        <OuterRect
+          top={annotRect.top}
+          left={annotRect.left}
+          width={annotRect.width}
+          height={annotRect.height}
+          onMove={handleScaleOrMove}
+          onScale={handleScaleOrMove}
+        />
+      ) : (
+        ''
+      )}
     </>
   );
 };

+ 9 - 4
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';
@@ -54,7 +53,9 @@ const FreehandOption: React.SFC<OptionPropsType> = ({
         <ColorSelector
           title={t('color')}
           selectedColor={color}
-          onClick={(selected: string): void => { setDataState({ color: selected }); }}
+          onClick={(selected: string): void => {
+            setDataState({ color: selected });
+          }}
         />
       </Wrapper>
       <Wrapper>
@@ -62,7 +63,9 @@ const FreehandOption: 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>
@@ -70,7 +73,9 @@ const FreehandOption: React.SFC<OptionPropsType> = ({
           title={t('brushSize')}
           value={width}
           tips={`${width} pt`}
-          onSlide={(val: number): void => { setDataState({ width: val }); }}
+          onSlide={(val: number): void => {
+            setDataState({ width: val });
+          }}
           maximum={40}
         />
       </Wrapper>

+ 71 - 54
components/Line/index.tsx

@@ -2,9 +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';
@@ -27,7 +24,9 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
   const uuid = uuidv4();
 
   const getActualPoint = (
-    { start, end }: LinePositionType, h: number, s: number,
+    { start, end }: LinePositionType,
+    h: number,
+    s: number
   ): LinePositionType => {
     const x1 = start.x * scale;
     const y1 = h - start.y * s;
@@ -47,7 +46,7 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
 
   const pointCalc = (
     { start, end }: LinePositionType,
-    borderWidth: number,
+    borderWidth: number
   ): LinePositionType => ({
     start: {
       x: start.x + borderWidth,
@@ -60,7 +59,9 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
   });
 
   const rectCalc = (
-    { start, end }: LinePositionType, h: number, s: number,
+    { start, end }: LinePositionType,
+    h: number,
+    s: number
   ): HTMLCoordinateType => {
     const startY = h - start.y * s;
     const endY = h - end.y * s;
@@ -74,28 +75,46 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
   };
 
   const actualbdwidth = bdwidth * scale;
-  const annotRect = rectCalc(position as LinePositionType, viewport.height, scale);
-  const { start, end } = getActualPoint(position as LinePositionType, viewport.height, scale);
-  const {
-    start: completeStart,
-    end: completeEnd,
-  } = pointCalc({ start, end } as LinePositionType, actualbdwidth);
+  const annotRect = rectCalc(
+    position as LinePositionType,
+    viewport.height,
+    scale
+  );
+  const { start, end } = getActualPoint(
+    position as LinePositionType,
+    viewport.height,
+    scale
+  );
+  const { start: completeStart, end: completeEnd } = pointCalc(
+    { start, end } as LinePositionType,
+    actualbdwidth
+  );
 
   const actualWidth = annotRect.width + actualbdwidth + 15;
   const actualHeight = annotRect.height + actualbdwidth + 15;
 
-  const handleMove = ({ start: moveStart, end: moveEnd }: LinePositionType): void => {
+  const handleMove = ({
+    start: moveStart,
+    end: moveEnd,
+  }: LinePositionType): void => {
     const newPosition = {
       start: {
-        x: moveStart.x, y: moveStart.y,
+        x: moveStart.x,
+        y: moveStart.y,
       },
       end: {
-        x: moveEnd.x, y: moveEnd.y,
+        x: moveEnd.x,
+        y: moveEnd.y,
       },
     };
 
     onUpdate({
-      position: parsePositionForBackend(obj_type, newPosition, viewport.height, scale),
+      position: parsePositionForBackend(
+        obj_type,
+        newPosition,
+        viewport.height,
+        scale
+      ),
     });
   };
 
@@ -112,29 +131,27 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
           height={`${actualHeight}px`}
           viewBox={`${annotRect.left} ${annotRect.top} ${actualWidth} ${actualHeight}`}
         >
-          {
-            is_arrow ? (
-              <defs>
-                <marker
-                  id={uuid}
-                  markerWidth={actualbdwidth * 2}
-                  markerHeight={actualbdwidth * 3}
-                  refX={3}
-                  refY={2}
-                  orient="auto"
-                  markerUnits="strokeWidth"
-                >
-                  <polyline
-                    points="0.25,0.5 3.25,2 0.25,3.5 3.25,2 -0.25,2"
-                    stroke={bdcolor}
-                    strokeWidth={1}
-                    fill="none"
-                    strokeDasharray={100}
-                  />
-                </marker>
-              </defs>
-            ) : null
-          }
+          {is_arrow ? (
+            <defs>
+              <marker
+                id={uuid}
+                markerWidth={actualbdwidth * 2}
+                markerHeight={actualbdwidth * 3}
+                refX={3}
+                refY={2}
+                orient="auto"
+                markerUnits="strokeWidth"
+              >
+                <polyline
+                  points="0.25,0.5 3.25,2 0.25,3.5 3.25,2 -0.25,2"
+                  stroke={bdcolor}
+                  strokeWidth={1}
+                  fill="none"
+                  strokeDasharray={100}
+                />
+              </marker>
+            </defs>
+          ) : null}
           <line
             x1={completeStart.x}
             y1={completeStart.y}
@@ -147,21 +164,21 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
           />
         </svg>
       </AnnotationContainer>
-      {
-        !isCollapse ? (
-          <Outer
-            top={annotRect.top}
-            left={annotRect.left}
-            width={actualWidth}
-            height={actualHeight}
-            start={start}
-            end={end}
-            completeStart={completeStart}
-            completeEnd={completeEnd}
-            onMove={handleMove}
-          />
-        ) : ''
-      }
+      {!isCollapse ? (
+        <Outer
+          top={annotRect.top}
+          left={annotRect.left}
+          width={actualWidth}
+          height={actualHeight}
+          start={start}
+          end={end}
+          completeStart={completeStart}
+          completeEnd={completeEnd}
+          onMove={handleMove}
+        />
+      ) : (
+        ''
+      )}
     </>
   );
 };

+ 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;
+`;

+ 1 - 1
components/Markup/index.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { Markup } from './styled';
+import Markup from './styled';
 
 type Props = {
   position: Record<string, any>;

+ 35 - 11
components/Markup/styled.ts

@@ -1,19 +1,27 @@
 import styled, { css } from 'styled-components';
 
 const MarkupStyle: Record<string, any> = {
-  Highlight: css<{isCovered: boolean; bdcolor: string}>`
+  Highlight: css<{ isCovered: boolean; bdcolor: string }>`
     background-color: ${props => (props.isCovered ? '#297fb8' : props.bdcolor)};
   `,
-  Underline: css<{isCovered: boolean; bdcolor: string}>`
-    border-bottom: 2px solid ${props => (props.isCovered ? '#297fb8' : props.bdcolor)};
+  Underline: css<{ isCovered: boolean; bdcolor: string }>`
+    border-bottom: 2px solid
+      ${props => (props.isCovered ? '#297fb8' : props.bdcolor)};
   `,
-  Squiggly: css<{isCovered: boolean; bdcolor: string}>`
+  Squiggly: css<{ isCovered: boolean; bdcolor: string }>`
     overflow: hidden;
-    
+
     &:before {
       content: '';
       position: absolute;
-      background: radial-gradient(ellipse, transparent, transparent 8px, ${props => (props.isCovered ? '#297fb8' : props.bdcolor)} 9px, ${props => (props.isCovered ? '#297fb8' : props.bdcolor)} 10px, transparent 11px);
+      background: radial-gradient(
+        ellipse,
+        transparent,
+        transparent 8px,
+        ${props => (props.isCovered ? '#297fb8' : props.bdcolor)} 9px,
+        ${props => (props.isCovered ? '#297fb8' : props.bdcolor)} 10px,
+        transparent 11px
+      );
       background-size: 22px 26px;
       width: 100%;
       height: 5px;
@@ -23,7 +31,14 @@ const MarkupStyle: Record<string, any> = {
     &:after {
       content: '';
       position: absolute;
-      background: radial-gradient(ellipse, transparent, transparent 8px, ${props => (props.isCovered ? '#297fb8' : props.bdcolor)} 9px, ${props => (props.isCovered ? '#297fb8' : props.bdcolor)} 10px, transparent 11px);
+      background: radial-gradient(
+        ellipse,
+        transparent,
+        transparent 8px,
+        ${props => (props.isCovered ? '#297fb8' : props.bdcolor)} 9px,
+        ${props => (props.isCovered ? '#297fb8' : props.bdcolor)} 10px,
+        transparent 11px
+      );
       background-size: 22px 26px;
       width: 100%;
       height: 5px;
@@ -32,7 +47,7 @@ const MarkupStyle: Record<string, any> = {
       background-position: 0px -22px;
     }
   `,
-  StrikeOut: css<{isCovered: boolean; bdcolor: string}>`
+  StrikeOut: css<{ isCovered: boolean; bdcolor: string }>`
     &:after {
       content: '';
       position: absolute;
@@ -40,12 +55,19 @@ const MarkupStyle: Record<string, any> = {
       width: 100%;
       left: 0;
       top: 40%;
-      background-color: ${props => (props.isCovered ? '#297fb8' : props.bdcolor)};
-    };
+      background-color: ${props =>
+        props.isCovered ? '#297fb8' : props.bdcolor};
+    }
   `,
 };
 
-export const Markup = styled('div')<{position: Record<string, any>; bdcolor: string; markupType: string; opacity: number; isCovered?: boolean}>`
+const Markup = styled('div')<{
+  position: Record<string, any>;
+  bdcolor: string;
+  markupType: string;
+  opacity: number;
+  isCovered?: boolean;
+}>`
   position: absolute;
   cursor: pointer;
   opacity: ${props => props.opacity};
@@ -58,3 +80,5 @@ export const Markup = styled('div')<{position: Record<string, any>; bdcolor: str
   `}
   ${props => MarkupStyle[props.markupType]}
 `;
+
+export default Markup;

+ 2 - 7
components/Modal/index.tsx

@@ -9,16 +9,11 @@ type Props = {
   hideBackdrop?: boolean;
 };
 
-const Modal: React.FC<Props> = ({
-  children,
-  hideBackdrop = false,
-}: Props) => (
+const Modal: React.FC<Props> = ({ children, hideBackdrop = false }: Props) => (
   <Portal>
     <Container>
       {hideBackdrop ? null : <Blanket />}
-      <Content>
-        {children}
-      </Content>
+      <Content>{children}</Content>
     </Container>
   </Portal>
 );

+ 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',

+ 10 - 13
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,22 +21,20 @@ const Navbar: React.FC<Props> = ({
   children,
   displayMode,
   fileName,
-  isSaving,
 }: Props) => (
   <Container isHidden={displayMode === 'full'}>
     <Typography variant="title">{fileName}</Typography>
     <Separator />
-    {
-      data.btnGroup.map(ele => (
-        <Icon
-          key={ele.key}
-          glyph={ele.content}
-          isActive={navbarState === ele.content}
-          isDisabled={!!(isSaving && ele.key === 'nav-export')}
-          onClick={(): void => { onClick(ele.content); }}
-        />
-      ))
-    }
+    {data.btnGroup.map(ele => (
+      <Icon
+        key={ele.key}
+        glyph={ele.content}
+        isActive={navbarState === ele.content}
+        onClick={(): void => {
+          onClick(ele.content);
+        }}
+      />
+    ))}
     {children}
   </Container>
 );

+ 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;

+ 24 - 20
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';
@@ -56,7 +55,9 @@ const index: React.FC<Props> = ({
 
   const handleMouseDown = (e: React.MouseEvent | React.TouchEvent): void => {
     e.preventDefault();
-    const operatorId = (e.target as HTMLElement).getAttribute('data-id') as string;
+    const operatorId = (e.target as HTMLElement).getAttribute(
+      'data-id'
+    ) as string;
     const coord = getAbsoluteCoordinate(document.body, e);
 
     setRef(document.body);
@@ -79,7 +80,7 @@ const index: React.FC<Props> = ({
   const calcMoveResult = (
     currentPosition: PointType,
     startPosition: PointType,
-    objPosition: ObjPositionType,
+    objPosition: ObjPositionType
   ): CoordType => ({
     left: currentPosition.x - (startPosition.x - objPosition.left),
     top: currentPosition.y - (startPosition.y - objPosition.top),
@@ -87,7 +88,7 @@ const index: React.FC<Props> = ({
 
   const calcScaleResult = (
     currentPosition: PointType,
-    objPosition: ObjPositionType,
+    objPosition: ObjPositionType
   ): CoordType => {
     const scaleData = calcDragAndDropScale({
       ...objPosition,
@@ -109,19 +110,23 @@ const index: React.FC<Props> = ({
   useEffect(() => {
     if (cursorPosition.x && cursorPosition.y && state.clickX) {
       if (state.operator === 'move' && onMove) {
-        onMove(calcMoveResult(
-          { x: cursorPosition.x, y: cursorPosition.y },
-          { x: state.clickX, y: state.clickY },
-          state,
-        ));
+        onMove(
+          calcMoveResult(
+            { x: cursorPosition.x, y: cursorPosition.y },
+            { x: state.clickX, y: state.clickY },
+            state
+          )
+        );
       } else if (onScale) {
-        onScale(calcScaleResult(
-          {
-            x: cursorPosition.x,
-            y: cursorPosition.y,
-          },
-          state,
-        ));
+        onScale(
+          calcScaleResult(
+            {
+              x: cursorPosition.x,
+              y: cursorPosition.y,
+            },
+            state
+          )
+        );
       }
     }
   }, [cursorPosition, state]);
@@ -157,8 +162,8 @@ const index: React.FC<Props> = ({
         onTouchStart={handleMouseDown}
         data-id="move"
       />
-      {
-        onScale && data.map((attr: CircleType) => (
+      {onScale &&
+        data.map((attr: CircleType) => (
           <Circle
             key={attr.direction}
             data-id={attr.direction}
@@ -167,8 +172,7 @@ const index: React.FC<Props> = ({
             fill={color.primary}
             {...attr}
           />
-        ))
-      }
+        ))}
     </SVG>
   );
 };

+ 3 - 2
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';
 
@@ -44,7 +43,9 @@ const index: React.FC<Props> = ({
   const [cursorPosition, setRef] = useCursorPosition();
 
   const handleMouseDown = (e: React.MouseEvent): void => {
-    const operatorId = (e.target as HTMLElement).getAttribute('data-id') as string;
+    const operatorId = (e.target as HTMLElement).getAttribute(
+      'data-id'
+    ) as string;
     const coord = getAbsoluteCoordinate(document.body, e);
 
     setRef(document.body);

+ 42 - 45
components/Page/index.tsx

@@ -1,12 +1,7 @@
-import React, {
-  useEffect, useRef,
-} from 'react';
+import React, { useEffect, useState, useRef } from 'react';
 
 import Watermark from '../Watermark';
 import { renderPdfPage } from '../../helpers/pdf';
-import {
-  RenderingStateType, ViewportType, WatermarkType,
-} from '../../constants/type';
 
 import {
   PageWrapper,
@@ -19,7 +14,7 @@ import {
 type Props = {
   pageNum: number;
   renderingState?: RenderingStateType;
-  getPage?: () => void;
+  getPage?: () => Promise<any>;
   viewport: ViewportType;
   rotation?: number;
   scale: number;
@@ -39,34 +34,39 @@ const PageView: React.FC<Props> = ({
   watermark = {},
 }: Props) => {
   const rootEle = useRef<HTMLDivElement | null>(null);
-  let pdfPage: any = null;
+  const [pdfPage, setPdfPage] = useState<any>(null);
+  const [renderTask, setRenderTask] = useState<any>(null);
 
   const renderPage = async (): Promise<any> => {
     if (getPage) {
-      pdfPage = await getPage();
+      getPage().then(obj => {
+        setPdfPage(obj);
 
-      if (rootEle.current) {
-        await renderPdfPage({
-          rootEle: rootEle.current,
-          pdfPage,
-          viewport,
-        });
-      }
+        if (rootEle.current) {
+          renderPdfPage({
+            rootEle: rootEle.current,
+            pdfPage: obj,
+            viewport,
+            setRenderTask,
+          });
+        }
+      });
     }
   };
 
   useEffect(() => {
+    if (pdfPage) {
+      pdfPage.cleanup();
+    }
+    if (renderTask) {
+      renderTask.cancel();
+    }
+
     if (renderingState === 'RENDERING') {
       renderPage();
-    } else if (pdfPage && renderingState === 'LOADING') {
-      pdfPage.cleanup();
     }
   }, [renderingState, viewport]);
 
-  useEffect(() => (): void => {
-    if (pdfPage) pdfPage.cleanup();
-  }, []);
-
   return (
     <PageWrapper
       ref={rootEle}
@@ -76,29 +76,26 @@ const PageView: React.FC<Props> = ({
       height={viewport.height}
       rotation={rotation}
     >
-      {
-        renderingState === 'LOADING' ? (
-          <>
-            <TextLayer data-id="text-layer" />
-          </>
-        ) : (
-          <>
-            <PdfCanvas />
-            {watermark.text || watermark.imagepath ? (
-              <WatermarkLayer>
-                <Watermark
-                  viewScale={scale}
-                  {...watermark}
-                />
-              </WatermarkLayer>
-            ) : ''}
-            <TextLayer data-id="text-layer" />
-            <AnnotationLayer data-id="annotation-layer">
-              {annotations}
-            </AnnotationLayer>
-          </>
-        )
-      }
+      {renderingState === 'LOADING' ? (
+        <>
+          <TextLayer data-id="text-layer" />
+        </>
+      ) : (
+        <>
+          <PdfCanvas />
+          {watermark.text || watermark.imagepath ? (
+            <WatermarkLayer>
+              <Watermark viewScale={scale} {...watermark} />
+            </WatermarkLayer>
+          ) : (
+            ''
+          )}
+          <TextLayer data-id="text-layer" />
+          <AnnotationLayer data-id="annotation-layer">
+            {annotations}
+          </AnnotationLayer>
+        </>
+      )}
     </PageWrapper>
   );
 };

+ 11 - 2
components/Page/styled.ts

@@ -1,6 +1,10 @@
 import styled from 'styled-components';
 
-export const PageWrapper = styled('div')<{width: number; height: number; rotation?: number}>`
+export const PageWrapper = styled('div')<{
+  width: number;
+  height: number;
+  rotation?: number;
+}>`
   direction: ltr;
   position: relative;
   overflow: visible;
@@ -33,7 +37,7 @@ export const TextLayer = styled.div`
   bottom: 0;
   margin: auto;
   overflow: hidden;
-  line-height: 1.0;
+  line-height: 1;
 
   & > span {
     color: transparent;
@@ -41,6 +45,11 @@ export const TextLayer = styled.div`
     white-space: pre;
     cursor: text;
     transform-origin: 0% 0%;
+
+    ::selection {
+      color: transparent;
+      background: #d6eaff;
+    }
   }
 `;
 

+ 34 - 29
components/PdfSkeleton/index.tsx

@@ -1,41 +1,46 @@
 import React from 'react';
+import { useTranslation } from 'react-i18next';
 
 import Box from '../Box';
 import Skeleton from '../Skeleton';
 
-import { Container } from './styled';
+import { Container, Text } from './styled';
 
-const PdfSkeleton: React.FC = () => (
-  <Container>
-    <Skeleton variant="rect" width="50%" height="36px" />
-    <Skeleton variant="rect" width="45%" height="12px" />
-    <Skeleton variant="rect" width="40%" height="12px" />
+const PdfSkeleton: React.SFC = () => {
+  const { t } = useTranslation('toast');
+  return (
+    <Container>
+      <Skeleton variant="rect" width="50%" height="36px" />
+      <Skeleton variant="rect" width="45%" height="12px" />
+      <Skeleton variant="rect" width="40%" height="12px" />
 
-    <Box mt="30">
-      <Skeleton variant="rect" width="100%" height="14px" />
-      <Skeleton variant="rect" width="100%" height="14px" />
-      <Skeleton variant="rect" width="100%" height="14px" />
-      <Skeleton variant="rect" width="90%" height="14px" />
-    </Box>
+      <Box mt="30">
+        <Skeleton variant="rect" width="100%" height="14px" />
+        <Skeleton variant="rect" width="100%" height="14px" />
+        <Skeleton variant="rect" width="100%" height="14px" />
+        <Skeleton variant="rect" width="90%" height="14px" />
+      </Box>
 
-    <Box mt="5" d="flex">
-      <Skeleton variant="rect" width="100%" height="120px" />
-      <Box ml="15">
-        <Skeleton variant="rect" width="70%" height="16px" />
-        <Skeleton variant="rect" width="100%" height="12px" />
-        <Skeleton variant="rect" width="100%" height="12px" />
-        <Skeleton variant="rect" width="100%" height="12px" />
-        <Skeleton variant="rect" width="80%" height="12px" />
+      <Box mt="5" d="flex">
+        <Skeleton variant="rect" width="100%" height="120px" />
+        <Box ml="15">
+          <Skeleton variant="rect" width="70%" height="16px" />
+          <Skeleton variant="rect" width="100%" height="12px" />
+          <Skeleton variant="rect" width="100%" height="12px" />
+          <Skeleton variant="rect" width="100%" height="12px" />
+          <Skeleton variant="rect" width="80%" height="12px" />
+        </Box>
       </Box>
-    </Box>
 
-    <Box mt="15">
-      <Skeleton variant="rect" width="100%" height="14px" />
-      <Skeleton variant="rect" width="100%" height="14px" />
-      <Skeleton variant="rect" width="90%" height="14px" />
-      <Skeleton variant="rect" width="85%" height="14px" />
-    </Box>
-  </Container>
-);
+      <Box mt="15">
+        <Skeleton variant="rect" width="100%" height="14px" />
+        <Skeleton variant="rect" width="100%" height="14px" />
+        <Skeleton variant="rect" width="90%" height="14px" />
+        <Skeleton variant="rect" width="85%" height="14px" />
+      </Box>
+      <Text>{t('processing')}</Text>
+    </Container>
+  );
+};
 
 export default PdfSkeleton;

+ 11 - 0
components/PdfSkeleton/styled.ts

@@ -15,3 +15,14 @@ export const Container = styled.div`
   flex-direction: column;
   z-index: 1;
 `;
+
+export const Text = styled.p`
+  text-align: center;
+  font-size: 16px;
+  color: #757575;
+`;
+
+export default {
+  Container,
+  Text,
+};

+ 26 - 31
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';
 
@@ -79,7 +78,8 @@ const SelectBox: React.FC<Props> = ({
     if (!isCollapse && selectRef.current) {
       const position = selectRef.current.getBoundingClientRect();
       if (optionRef.current) {
-        optionRef.current.style.top = `${selectRef.current.clientHeight + position.top}px`;
+        optionRef.current.style.top = `${selectRef.current.clientHeight +
+          position.top}px`;
         optionRef.current.style.left = `${position.left}px`;
       }
     }
@@ -94,38 +94,33 @@ const SelectBox: React.FC<Props> = ({
         tabIndex={0}
         data-testid="selected"
       >
-        {
-          useInput && (typeof value === 'string' || typeof value === 'number') ? (
-            <InputContent
-              value={value}
-              onChange={handleChange}
-              onKeyDown={handleKeyDown}
-            />
-          ) : (
-            <Content>
-              {value}
-            </Content>
-          )
-        }
+        {useInput &&
+        (typeof value === 'string' || typeof value === 'number') ? (
+          <InputContent
+            value={value}
+            onChange={handleChange}
+            onKeyDown={handleKeyDown}
+          />
+        ) : (
+          <Content>{value}</Content>
+        )}
         {isDivide ? <Divider /> : null}
         <Icon glyph="down-arrow" />
       </Selected>
-      {
-        !isCollapse ? (
-          <OptionWrapper ref={optionRef} data-testid="option-list">
-            {
-              options.map((option: SelectOptionType, index: number) => (
-                <Option
-                  key={option.key}
-                  onMouseDown={(): void => { handleSelect(index); }}
-                >
-                  {option.content}
-                </Option>
-              ))
-            }
-          </OptionWrapper>
-        ) : null
-      }
+      {!isCollapse ? (
+        <OptionWrapper ref={optionRef} data-testid="option-list">
+          {options.map((option: SelectOptionType, index: number) => (
+            <Option
+              key={option.key}
+              onMouseDown={(): void => {
+                handleSelect(index);
+              }}
+            >
+              {option.content}
+            </Option>
+          ))}
+        </OptionWrapper>
+      ) : null}
     </Container>
   );
 };

+ 23 - 17
components/Shape/index.tsx

@@ -2,9 +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';
@@ -27,7 +25,10 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
   const annotRect = rectCalc(position as PositionType, viewport.height, scale);
 
   const handleScaleOrMove = ({
-    top, left, width = 0, height = 0,
+    top,
+    left,
+    width = 0,
+    height = 0,
   }: CoordType): void => {
     const newPosition = {
       top,
@@ -37,7 +38,12 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
     };
 
     onUpdate({
-      position: parsePositionForBackend(obj_type, newPosition, viewport.height, scale),
+      position: parsePositionForBackend(
+        obj_type,
+        newPosition,
+        viewport.height,
+        scale
+      ),
     });
   };
 
@@ -64,18 +70,18 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
           ftransparency={ftransparency}
         />
       </AnnotationContainer>
-      {
-        !isCollapse ? (
-          <OuterRect
-            top={annotRect.top}
-            left={annotRect.left}
-            width={actualWidth}
-            height={actualHeight}
-            onMove={handleScaleOrMove}
-            onScale={handleScaleOrMove}
-          />
-        ) : ''
-      }
+      {!isCollapse ? (
+        <OuterRect
+          top={annotRect.top}
+          left={annotRect.left}
+          width={actualWidth}
+          height={actualHeight}
+          onMove={handleScaleOrMove}
+          onScale={handleScaleOrMove}
+        />
+      ) : (
+        ''
+      )}
     </>
   );
 };

+ 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>

+ 13 - 16
components/Sliders/index.tsx

@@ -1,14 +1,8 @@
-import React, {
-  useEffect, useState, useRef,
-} from 'react';
+import React, { useEffect, useState, useRef } from 'react';
 import { fromEvent } from 'rxjs';
-import {
-  throttleTime,
-} from 'rxjs/operators';
+import { throttleTime } from 'rxjs/operators';
 
-import {
-  OuterWrapper, Wrapper, Rail, Track,
-} from './styled';
+import { OuterWrapper, Wrapper, Rail, Track } from './styled';
 
 type Props = {
   color?: 'primary' | 'secondary';
@@ -60,6 +54,7 @@ const Sliders: React.FC<Props> = ({
 
       if (onChange) {
         const val = Math.floor(percent * (maximum * 0.01));
+        console.log(val);
         onChange(val);
       }
     }
@@ -79,15 +74,19 @@ const Sliders: React.FC<Props> = ({
   };
 
   const handleMouseDown = (
-    event: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>,
+    event: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>
   ): void => {
     event.preventDefault();
 
     getFingerMoveValue(event);
     setActive(true);
 
-    mouseSubscription = fromEvent(document.body, 'mousemove').pipe(throttleTime(35)).subscribe(handleTouchMove);
-    touchSubscription = fromEvent(document.body, 'touchmove').pipe(throttleTime(35)).subscribe(handleTouchMove);
+    mouseSubscription = fromEvent(document.body, 'mousemove')
+      .pipe(throttleTime(35))
+      .subscribe(handleTouchMove);
+    touchSubscription = fromEvent(document.body, 'touchmove')
+      .pipe(throttleTime(35))
+      .subscribe(handleTouchMove);
     document.body.addEventListener('mouseup', handleTouchEnd);
   };
 
@@ -108,14 +107,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};
+          }
+        `}
 `;

+ 11 - 12
components/ThumbnailViewer/index.tsx

@@ -4,13 +4,10 @@ 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, Body, IconWrapper,
-} from '../../global/sidebarStyled';
-import { ThumbnailsWrapper } from './styled';
+import { Container, Head, IconWrapper } from '../../global/sidebarStyled';
+import { ThumbnailsWrapper, Body } from './styled';
 
 type Props = {
   isActive?: boolean;
@@ -56,8 +53,8 @@ const Thumbnails: React.FC<Props> = ({
 
   const updateThumbnails = (): void => {
     const renderingIndexQueue = _.range(scrollIndex - 2, scrollIndex + 4);
-    let index = scrollIndex - 5;
-    const end = scrollIndex + 5;
+    let index = scrollIndex - 6;
+    const end = scrollIndex + 6;
 
     while (scrollIndex) {
       if (elements[index]) {
@@ -68,7 +65,9 @@ const Thumbnails: React.FC<Props> = ({
             key={`thumbnail_${pageNum}`}
             pageNum={pageNum}
             getPdfImage={getPdfImage}
-            renderingState={renderingIndexQueue.includes(pageNum) ? 'RENDERING' : 'LOADING'}
+            renderingState={
+              renderingIndexQueue.includes(pageNum) ? 'RENDERING' : 'LOADING'
+            }
           />
         );
       }
@@ -100,7 +99,9 @@ const Thumbnails: React.FC<Props> = ({
 
   useEffect(() => {
     if (isActive && elements.length) {
-      const ele: HTMLElement = document.getElementById(`thumbnail_${currentPage}`) as HTMLElement;
+      const ele: HTMLElement = document.getElementById(
+        `thumbnail_${currentPage}`
+      ) as HTMLElement;
       scrollIntoView(ele);
     }
   }, [currentPage, isActive]);
@@ -114,9 +115,7 @@ const Thumbnails: React.FC<Props> = ({
           </IconWrapper>
         </Head>
         <Body ref={containerRef}>
-          <ThumbnailsWrapper>
-            {elements}
-          </ThumbnailsWrapper>
+          <ThumbnailsWrapper>{elements}</ThumbnailsWrapper>
         </Body>
       </Container>
     </Drawer>

+ 9 - 1
components/ThumbnailViewer/styled.ts

@@ -1,6 +1,14 @@
-import styled from 'styled-components';
+import styled from "styled-components";
 
 export const ThumbnailsWrapper = styled.div`
   display: inline-flex;
   flex-direction: column;
 `;
+
+export const Body = styled.div`
+  height: calc(100% - 30px);
+  overflow: scroll;
+  padding: 8px;
+  text-align: center;
+  position: relative;
+`;

+ 49 - 16
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';
@@ -51,9 +50,11 @@ const Toolbar: React.FC<Props> = ({
     changeRotate(r);
   };
 
-  const handleScaleSelect = async (selected: SelectOptionType): Promise<any> => {
+  const handleScaleSelect = async (
+    selected: SelectOptionType
+  ): Promise<any> => {
     if (selected.child === 'fit') {
-      const screenWidth = window.document.body.offsetWidth - 276;
+      const screenWidth = window.document.body.offsetWidth - 288;
       const originPdfWidth = viewport.width / scale;
       const rate = screenWidth / originPdfWidth;
       changeScale(rate);
@@ -73,11 +74,27 @@ const Toolbar: React.FC<Props> = ({
   return (
     <>
       <Container isHidden={displayMode === 'full'}>
-        <Pagination totalPage={totalPage} currentPage={currentPage} onChange={setCurrentPage} />
+        <Pagination
+          totalPage={totalPage}
+          currentPage={currentPage}
+          onChange={setCurrentPage}
+        />
         <Divider />
-        <Icon glyph="hand" style={{ width: '30px' }} onClick={handleHandClick} />
-        <Icon glyph="zoom-in" style={{ width: '30px' }} onClick={handleZoomIn} />
-        <Icon glyph="zoom-out" style={{ width: '30px' }} onClick={handleZoomOut} />
+        <Icon
+          glyph="hand"
+          style={{ width: '30px' }}
+          onClick={handleHandClick}
+        />
+        <Icon
+          glyph="zoom-in"
+          style={{ width: '30px' }}
+          onClick={handleZoomIn}
+        />
+        <Icon
+          glyph="zoom-out"
+          style={{ width: '30px' }}
+          onClick={handleZoomOut}
+        />
         <SelectBox
           options={data.scaleOptions}
           style={{ width: '94px' }}
@@ -87,17 +104,33 @@ const Toolbar: React.FC<Props> = ({
           defaultValue={`${Math.round(scale * 100)} %`}
         />
         <Divider />
-        <Icon glyph="rotate-left" style={{ width: '30px' }} onClick={handleCounterclockwiseRotation} />
-        <Icon glyph="rotate-right" style={{ width: '30px' }} onClick={handleClockwiseRotation} />
+        <Icon
+          glyph="rotate-left"
+          style={{ width: '30px' }}
+          onClick={handleCounterclockwiseRotation}
+        />
+        <Icon
+          glyph="rotate-right"
+          style={{ width: '30px' }}
+          onClick={handleClockwiseRotation}
+        />
       </Container>
       <ToggleButton>
-        {
-          displayMode === 'normal' ? (
-            <Icon glyph="tool-close" onClick={(): void => { toggleDisplayMode('full'); }} />
-          ) : (
-            <Icon glyph="tool-open" onClick={(): void => { toggleDisplayMode('normal'); }} />
-          )
-        }
+        {displayMode === 'normal' ? (
+          <Icon
+            glyph="tool-close"
+            onClick={(): void => {
+              toggleDisplayMode('full');
+            }}
+          />
+        ) : (
+          <Icon
+            glyph="tool-open"
+            onClick={(): void => {
+              toggleDisplayMode('normal');
+            }}
+          />
+        )}
       </ToggleButton>
     </>
   );

+ 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 - 2
constants/actionTypes.ts

@@ -3,8 +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_SAVED = 'SET_SAVED';
-export const SET_SAVING = 'SET_SAVING';
+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 - 159
constants/type.ts

@@ -1,159 +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';
-};

+ 6 - 12
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';
@@ -53,7 +52,7 @@ const Annotation: React.FC<Props> = ({
     setMouseOver(false);
   };
 
-  const handleUpdate: OnUpdateType = (data) => {
+  const handleUpdate: OnUpdateType = data => {
     const newAttributes = {
       ...annotations[index].obj_attr,
       ...data,
@@ -93,7 +92,7 @@ const Annotation: React.FC<Props> = ({
   };
 
   const handleKeyDown = (e: React.KeyboardEvent): void => {
-    if (e.keyCode === 46) {
+    if (e.key === 'Delete') {
       deleteDialogToggle();
     }
   };
@@ -129,10 +128,7 @@ const Annotation: React.FC<Props> = ({
   };
 
   return (
-    <AnnotationWrapper
-      ref={ref}
-      {...wrapperProps}
-    >
+    <AnnotationWrapper ref={ref} {...wrapperProps}>
       {((): React.ReactNode => {
         switch (obj_type) {
           case 'Ink':
@@ -151,11 +147,9 @@ const Annotation: React.FC<Props> = ({
             return <Highlight {...childProps} />;
         }
       })()}
-      <DeleteDialog
-        open={openDialog}
-        onCancel={deleteDialogToggle}
-        onDelete={handleDelete}
-      />
+      {openDialog && (
+        <DeleteDialog onCancel={deleteDialogToggle} onDelete={handleDelete} />
+      )}
     </AnnotationWrapper>
   );
 };

+ 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}

+ 5 - 8
containers/AnnotationListHead.tsx

@@ -5,17 +5,14 @@ import useStore from '../store';
 import useActions from '../actions';
 
 const AnnotationListHead: React.FC = () => {
-  const [{
-    annotations,
-  }, dispatch] = useStore();
-  const {
-    setNavbar,
-    addAnnots,
-  } = useActions(dispatch);
+  const [{ annotations }, dispatch] = useStore();
+  const { setNavbar, addAnnots } = useActions(dispatch);
 
   return (
     <Head
-      close={(): void => { setNavbar(''); }}
+      close={(): void => {
+        setNavbar('');
+      }}
       addAnnots={addAnnots}
       hasAnnots={!!annotations.length}
     />

+ 54 - 10
containers/AutoSave.tsx

@@ -1,27 +1,71 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
+import { Subscription, interval } from 'rxjs';
+import { take } from 'rxjs/operators';
+
 import { useTranslation } from 'react-i18next';
 import { useToasts } from 'react-toast-notifications';
 
-import useAutoSave from '../hooks/useAutoSave';
+import useStore from '../store';
+import { saveFile } from '../apis';
 
 const index: React.FC = () => {
   const { t } = useTranslation('toast');
-  const [isSaved, isSaving] = useAutoSave();
-  const { addToast } = useToasts();
+  const [{ info, annotations, initiated }] = useStore();
+  const [isSaved, setSaved] = useState(false);
+  const [isSaving, setSaving] = useState(false);
+  const { addToast, removeAllToasts } = useToasts();
+  let subscription: Subscription | null = null;
 
   useEffect(() => {
+    if ((info.id && annotations.length) || (initiated && !annotations.length)) {
+      if (subscription) subscription.unsubscribe();
+
+      setSaved(false);
+      setSaving(true);
+      const observable = interval(1000);
+
+      const data = {
+        transaction_id: info.id,
+        append_objects: annotations,
+      };
+
+      subscription = observable.pipe(take(4)).subscribe((x: number) => {
+        if (x === 3) {
+          saveFile(info.token, data).then(() => {
+            setSaving(false);
+            setSaved(true);
+          });
+        }
+      });
+    }
+
+    return (): void => {
+      if (subscription) subscription.unsubscribe();
+    };
+  }, [info, annotations]);
+
+  useEffect(() => {
+    removeAllToasts();
+
     if (isSaving) {
-      addToast(t('saving'), { appearance: 'info', placement: 'bottom-center', autoDismiss: true });
+      addToast(t('saving'), {
+        appearance: 'info',
+        placement: 'bottom-center',
+      });
     }
     if (!isSaving && isSaved) {
-      addToast(t('saved'), { appearance: 'success', placement: 'bottom-center', autoDismiss: true });
+      addToast(t('saved'), {
+        appearance: 'success',
+        placement: 'bottom-center',
+        autoDismiss: true,
+        onDismiss: () => {
+          removeAllToasts();
+        },
+      });
     }
   }, [isSaved, isSaving]);
 
-  return (
-    <>
-    </>
-  );
+  return <></>;
 };
 
 export default index;

+ 2 - 1
containers/ColorSelector.tsx

@@ -20,7 +20,8 @@ const ColorSelector: React.FC<Props> = ({
   const [isCollapse, setCollapse] = useState(true);
   const [{ sidebarState, markupToolState }] = useStore();
 
-  const pickerToggle = (): void => {
+  const pickerToggle = (e: React.MouseEvent<HTMLElement>): void => {
+    e.preventDefault();
     setCollapse(!isCollapse);
   };
 

+ 14 - 9
containers/CreateForm.tsx

@@ -22,16 +22,21 @@ const CreateForm: React.FC = () => {
     }
   };
 
-  return (
-    <ExpansionPanel
-      label={(
-        <Button shouldFitContainer align="left" onClick={(): void => { onClickSidebar('create-form'); }}>
-          <Icon glyph="create-form" style={{ marginRight: '10px' }} />
-          {t('createForm')}
-        </Button>
-      )}
-      isActive={sidebarState === 'create-form'}
+  const Label = (
+    <Button
+      shouldFitContainer
+      align="left"
+      onClick={(): void => {
+        onClickSidebar('create-form');
+      }}
     >
+      <Icon glyph="create-form" style={{ marginRight: '10px' }} />
+      {t('createForm')}
+    </Button>
+  );
+
+  return (
+    <ExpansionPanel label={Label} isActive={sidebarState === 'create-form'}>
       <BtnWrapper>
         <Button shouldFitContainer align="left">
           <Icon glyph="text-field" style={{ marginRight: '10px' }} />

+ 34 - 31
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';
 
@@ -25,7 +25,7 @@ const HighlightTools: React.FC<Props> = ({
 }: Props) => {
   const [data, setData] = useState({
     fontName: 'Helvetica',
-    fontSize: 12,
+    fontSize: 18,
     align: 'left',
     fontStyle: '',
     color: '#FCFF36',
@@ -44,10 +44,15 @@ const HighlightTools: React.FC<Props> = ({
   const addFreeText = (
     pageEle: HTMLElement,
     event: MouseEvent | TouchEvent,
-    attributes: OptionPropsType,
+    attributes: OptionPropsType
   ): void => {
     const {
-      fontStyle, fontName, fontSize = 0, opacity, color, align,
+      fontStyle,
+      fontName,
+      fontSize = 0,
+      opacity,
+      color,
+      align,
     } = attributes;
     const pageNum = pageEle.getAttribute('data-page-num') || 0;
     const coordinate = getAbsoluteCoordinate(pageEle, event);
@@ -59,7 +64,7 @@ const HighlightTools: React.FC<Props> = ({
         position: {
           top: coordinate.y,
           left: coordinate.x,
-          bottom: coordinate.y + fontSize,
+          bottom: coordinate.y + fontSize * scale,
           right: coordinate.x + 30,
         },
         transparency: opacity,
@@ -70,19 +75,22 @@ const HighlightTools: React.FC<Props> = ({
         content: '',
       },
     };
+
     const freeText = parseAnnotationObject(annotData, viewport.height, scale);
 
-    addAnnots([freeText], true);
+    addAnnots([freeText]);
   };
 
-  const handleMouseDown = useCallback((event: MouseEvent | TouchEvent): void => {
-    event.preventDefault();
-    const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
+  const handleMouseDown = useCallback(
+    (event: MouseEvent | TouchEvent): void => {
+      const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
 
-    if (pageEle.hasAttribute('data-page-num')) {
-      addFreeText(pageEle, event, data);
-    }
-  }, [data, viewport, scale]);
+      if (pageEle.hasAttribute('data-page-num')) {
+        addFreeText(pageEle, event, data);
+      }
+    },
+    [data, viewport, scale]
+  );
 
   useEffect(() => {
     const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
@@ -99,26 +107,21 @@ const HighlightTools: React.FC<Props> = ({
     };
   }, [isActive, handleMouseDown]);
 
-  return (
-    <ExpansionPanel
-      label={(
-        <Button
-          shouldFitContainer
-          align="left"
-          onClick={onClick}
-          isActive={isActive}
-        >
-          <Icon glyph="text" style={{ marginRight: '10px' }} />
-          {title}
-        </Button>
-      )}
+  const Label = (
+    <Button
+      shouldFitContainer
+      align="left"
+      onClick={onClick}
       isActive={isActive}
-      showBottomBorder
     >
-      <FreeTextOption
-        {...data}
-        setDataState={setDataState}
-      />
+      <Icon glyph="text" style={{ marginRight: '10px' }} />
+      {title}
+    </Button>
+  );
+
+  return (
+    <ExpansionPanel label={Label} isActive={isActive} showBottomBorder>
+      <FreeTextOption {...data} setDataState={setDataState} />
     </ExpansionPanel>
   );
 };

+ 68 - 51
containers/FreehandTools.tsx

@@ -1,14 +1,16 @@
 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';
 import ExpansionPanel from '../components/ExpansionPanel';
 import InkOption from '../components/InkOption';
 
-import { getAbsoluteCoordinate, parsePositionForBackend } from '../helpers/position';
+import {
+  getAbsoluteCoordinate,
+  parsePositionForBackend,
+} from '../helpers/position';
 import { parseAnnotationObject } from '../helpers/annotation';
 import useCursorPosition from '../hooks/useCursorPosition';
 
@@ -45,43 +47,55 @@ const FreehandTools: React.FC<Props> = ({
     }));
   };
 
-  const handleMouseDown = useCallback((e: MouseEvent | TouchEvent): void => {
-    const pageEle = (e.target as HTMLElement).parentNode as HTMLElement;
-
-    if (pageEle.hasAttribute('data-page-num')) {
-      setRef(pageEle);
-      const pageNum = pageEle.getAttribute('data-page-num') || 0;
-      const coordinate = getAbsoluteCoordinate(pageEle, e);
-      const id = uuidv4();
-
-      setUuid(id);
-
-      const annotData = {
-        id,
-        obj_type: ANNOTATION_TYPE.ink,
-        obj_attr: {
-          page: pageNum as number,
-          bdcolor: data.color,
-          bdwidth: data.width,
-          position: [[coordinate]],
-          transparency: data.opacity,
-        },
-      };
-      const freehand = parseAnnotationObject(annotData, viewport.height, scale);
-
-      addAnnots([freehand], true);
-    }
-  }, [data, viewport, scale]);
+  const handleMouseDown = useCallback(
+    (e: MouseEvent | TouchEvent): void => {
+      const pageEle = (e.target as HTMLElement).parentNode as HTMLElement;
+
+      if (pageEle.hasAttribute('data-page-num')) {
+        setRef(pageEle);
+        const pageNum = pageEle.getAttribute('data-page-num') || 0;
+        const coordinate = getAbsoluteCoordinate(pageEle, e);
+        const id = uuidv4();
+
+        setUuid(id);
+
+        const annotData = {
+          id,
+          obj_type: ANNOTATION_TYPE.ink,
+          obj_attr: {
+            page: pageNum as number,
+            bdcolor: data.color,
+            bdwidth: data.width,
+            position: [[coordinate]],
+            transparency: data.opacity,
+          },
+        };
+        const freehand = parseAnnotationObject(
+          annotData,
+          viewport.height,
+          scale
+        );
+
+        addAnnots([freehand]);
+      }
+    },
+    [data, viewport, scale]
+  );
 
   const handleMouseUp = useCallback((): void => {
     const index = annotations.length - 1;
     const position = annotations[index].obj_attr.position as PointType[][];
 
+    if (!position[0]) return;
+
     if (position[0].length === 1 && annotations[index].id === uuid) {
       const point = position[0][0];
-      annotations[index].obj_attr.position = [[
-        { x: point.x - 5, y: point.y - 5 }, { x: point.x + 5, y: point.y + 5 },
-      ]];
+      annotations[index].obj_attr.position = [
+        [
+          { x: point.x - 5, y: point.y - 5 },
+          { x: point.x + 5, y: point.y + 5 },
+        ],
+      ];
       updateAnnots([...annotations]);
     }
 
@@ -93,20 +107,25 @@ const FreehandTools: React.FC<Props> = ({
     const index = annotations.length - 1;
 
     if (
-      annotations[index] && annotations[index].id === uuid
-      && cursorPosition.x && cursorPosition.y
+      annotations[index] &&
+      annotations[index].id === uuid &&
+      cursorPosition.x &&
+      cursorPosition.y
     ) {
       const type = annotations[index].obj_type;
       const position = annotations[index].obj_attr.position as PointType[][];
       const coordinates = parsePositionForBackend(
-        type, { x: cursorPosition.x, y: cursorPosition.y }, viewport.height, scale,
+        type,
+        { x: cursorPosition.x, y: cursorPosition.y },
+        viewport.height,
+        scale
       ) as PointType;
 
       const lastPosition = position[0].slice(-1)[0];
 
       if (
-        coordinates.x !== lastPosition.x
-        && coordinates.y !== lastPosition.y
+        coordinates.x !== lastPosition.x &&
+        coordinates.y !== lastPosition.y
       ) {
         position[0].push(coordinates);
         annotations[index].obj_attr.position = position;
@@ -145,22 +164,20 @@ const FreehandTools: React.FC<Props> = ({
     };
   }, [isActive, handleMouseDown, handleMouseUp]);
 
-  return (
-    <ExpansionPanel
-      label={(
-        <Button
-          shouldFitContainer
-          align="left"
-          onClick={onClick}
-          isActive={isActive}
-        >
-          <Icon glyph="freehand" style={{ marginRight: '10px' }} />
-          {title}
-        </Button>
-      )}
+  const Label = (
+    <Button
+      shouldFitContainer
+      align="left"
+      onClick={onClick}
       isActive={isActive}
-      showBottomBorder
     >
+      <Icon glyph="freehand" style={{ marginRight: '10px' }} />
+      {title}
+    </Button>
+  );
+
+  return (
+    <ExpansionPanel label={Label} isActive={isActive} showBottomBorder>
       <InkOption
         type={data.type}
         color={data.color}

+ 28 - 30
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';
@@ -38,20 +37,24 @@ const HighlightTools: React.FC<Props> = ({
     }));
   };
 
-  const selectRange = useCallback((e: MouseEvent | TouchEvent): void => {
-    if (e.target && isActive) {
-      const textLayer = (e.target as HTMLElement).parentNode as HTMLElement;
-      if (textLayer && textLayer.getAttribute('data-id') === 'text-layer') {
-        const newMarkup = getMarkupWithSelection({
-          ...data, scale,
-        });
+  const selectRange = useCallback(
+    (e: MouseEvent | TouchEvent): void => {
+      if (e.target && isActive) {
+        const textLayer = (e.target as HTMLElement).parentNode as HTMLElement;
+        if (textLayer && textLayer.getAttribute('data-id') === 'text-layer') {
+          const newMarkup = getMarkupWithSelection({
+            ...data,
+            scale,
+          });
 
-        if (newMarkup) {
-          addAnnots([newMarkup], true);
+          if (newMarkup) {
+            addAnnots([newMarkup]);
+          }
         }
       }
-    }
-  }, [isActive, data, scale]);
+    },
+    [isActive, data, scale]
+  );
 
   useEffect(() => {
     if (isActive) {
@@ -65,26 +68,21 @@ const HighlightTools: React.FC<Props> = ({
     };
   }, [isActive, selectRange]);
 
-  return (
-    <ExpansionPanel
-      label={(
-        <Button
-          shouldFitContainer
-          align="left"
-          onClick={onClick}
-          isActive={isActive}
-        >
-          <Icon glyph="highlight" style={{ marginRight: '10px' }} />
-          {title}
-        </Button>
-      )}
+  const Label = (
+    <Button
+      shouldFitContainer
+      align="left"
+      onClick={onClick}
       isActive={isActive}
-      showBottomBorder
     >
-      <HighlightOption
-        {...data}
-        setDataState={setDataState}
-      />
+      <Icon glyph="highlight" style={{ marginRight: '10px' }} />
+      {title}
+    </Button>
+  );
+
+  return (
+    <ExpansionPanel label={Label} isActive={isActive} showBottomBorder>
+      <HighlightOption {...data} setDataState={setDataState} />
     </ExpansionPanel>
   );
 };

+ 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;

+ 29 - 18
containers/MarkupTools.tsx

@@ -40,44 +40,55 @@ const MarkupTools: React.FC = () => {
     }
   }, []);
 
-  return (
-    <ExpansionPanel
-      label={(
-        <Button
-          shouldFitContainer
-          align="left"
-          onClick={(): void => { onClickSidebar('markup-tools'); }}
-        >
-          <Icon glyph="markup-tools" style={{ marginRight: '10px' }} />
-          {t('markupTool')}
-        </Button>
-      )}
-      isActive={sidebarState === 'markup-tools'}
+  const Label = (
+    <Button
+      shouldFitContainer
+      align="left"
+      onClick={(): void => {
+        onClickSidebar('markup-tools');
+      }}
     >
+      <Icon glyph="markup-tools" style={{ marginRight: '10px' }} />
+      {t('markupTool')}
+    </Button>
+  );
+
+  return (
+    <ExpansionPanel label={Label} isActive={sidebarState === 'markup-tools'}>
       <HighlightTools
         title={t('annotate')}
         isActive={markupToolState === 'highlight'}
-        onClick={(): void => { onClickTool('highlight'); }}
+        onClick={(): void => {
+          onClickTool('highlight');
+        }}
       />
       <FreehandTools
         title={t('freehand')}
         isActive={markupToolState === 'freehand'}
-        onClick={(): void => { onClickTool('freehand'); }}
+        onClick={(): void => {
+          onClickTool('freehand');
+        }}
       />
       <TextTools
         title={t('textBox')}
         isActive={markupToolState === 'text'}
-        onClick={(): void => { onClickTool('text'); }}
+        onClick={(): void => {
+          onClickTool('text');
+        }}
       />
       <StickyNoteTools
         title={t('stickyNote')}
         isActive={markupToolState === 'sticky'}
-        onClick={(): void => { onClickTool('sticky'); }}
+        onClick={(): void => {
+          onClickTool('sticky');
+        }}
       />
       <ShapeTools
         title={t('shape')}
         isActive={markupToolState === 'shape'}
-        onClick={(): void => { onClickTool('shape'); }}
+        onClick={(): void => {
+          onClickTool('shape');
+        }}
       />
     </ExpansionPanel>
   );

+ 31 - 18
containers/Navbar.tsx

@@ -8,32 +8,44 @@ 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,
-  }, dispatch] = useStore();
-  const { setNavbar } = useActions(dispatch);
+  const [{ navbarState, displayMode, info, totalPage }, dispatch] = useStore();
+  const { setNavbar, setLoading } = useActions(dispatch);
 
   const onClick = (state: string): void => {
     switch (state) {
       case 'export': {
         const parsed = queryString.parse(window.location.search);
         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}`;
+        let outputPath = `/api/v1/output.pdf?f=${info.token}`;
+        if (parsed.watermark) {
+          outputPath = `/api/v1/output.pdf?f=${info.token}&user_id=${parsed.watermark}&pages=0-${totalPage}`;
+        }
 
-        if (annotations.length) {
-          downloadFileWithUri(fileName, outputPath);
-        } else {
-          downloadFileWithUri(fileName, originalPath);
+        setLoading(true);
+        downloadFileWithUri(fileName, outputPath);
+        // downloadFileWithUri(fileName, `http://192.168.172.3:3000${outputPath}`);
+        delay(1000).then(() => {
+          setLoading(false);
+        });
+        break;
+      }
+      case 'print': {
+        const parsed = queryString.parse(window.location.search);
+        let outputPath = `/api/v1/output.pdf?f=${info.token}`;
+        if (parsed.watermark) {
+          outputPath = `/api/v1/output.pdf?f=${info.token}&user_id=${parsed.watermark}&pages=0-${totalPage}`;
         }
+        setLoading(true);
+        printPdf(outputPath).then(() => {
+          setLoading(false);
+        });
+        // printPdf(`http://192.168.172.3:3000${outputPath}`).then(() => {
+        //   setLoading(false);
+        // });
         break;
       }
       case navbarState:
@@ -50,8 +62,9 @@ const Navbar: React.FC = () => {
       onClick={onClick}
       navbarState={navbarState}
       displayMode={displayMode}
-      fileName={info.token ? decodeURIComponent(escape(window.atob(info.token))) : ''}
-      isSaving={isSaving}
+      fileName={
+        info.token ? decodeURIComponent(escape(window.atob(info.token))) : ''
+      }
     >
       <Search />
       <AnnotationList />

+ 11 - 15
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';
@@ -12,22 +11,14 @@ type Props = {
   renderingState: RenderingStateType;
 };
 
-const PdfPage: React.FC<Props> = ({
-  index,
-  renderingState,
-}: Props) => {
-  const [{
-    viewport,
-    pdf,
-    rotation,
-    annotations,
-    scale,
-    watermark,
-  }] = useStore();
+const PdfPage: React.FC<Props> = ({ index, renderingState }: Props) => {
+  const [
+    { viewport, pdf, rotation, annotations, scale, watermark },
+  ] = useStore();
 
   const getAnnotationWithPage = (
     arr: AnnotationType[],
-    pageNum: number,
+    pageNum: number
   ): React.ReactNode[] => {
     const result: React.ReactNode[] = [];
 
@@ -36,7 +27,12 @@ const PdfPage: React.FC<Props> = ({
 
       if (page === pageNum) {
         result.push(
-          <Annotation key={`annotations_${pageNum + i}`} scale={scale} index={i} {...ele} />,
+          <Annotation
+            key={`annotations_${pageNum + i}`}
+            scale={scale}
+            index={i}
+            {...ele}
+          />
         );
       }
     });

+ 8 - 16
containers/PdfPages.tsx

@@ -1,9 +1,6 @@
-import React, {
-  useEffect, useState, useRef,
-} from 'react';
+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';
@@ -14,19 +11,12 @@ type Props = {
   scrollToUpdate: (state: ScrollStateType) => void;
 };
 
-const PdfPages: React.FC<Props> = ({
-  scrollToUpdate,
-}: Props) => {
+const PdfPages: React.FC<Props> = ({ scrollToUpdate }: Props) => {
   const [elements, setElement] = useState<React.ReactNode[]>([]);
   const containerRef = useRef<HTMLDivElement>(null);
-  const [{
-    totalPage,
-    currentPage,
-    viewport,
-    rotation,
-    displayMode,
-    annotations,
-  }] = useStore();
+  const [
+    { totalPage, currentPage, viewport, rotation, displayMode, annotations },
+  ] = useStore();
 
   const createPages = (): void => {
     const pagesContent: React.ReactNode[] = [];
@@ -61,7 +51,9 @@ const PdfPages: React.FC<Props> = ({
           <PdfPage
             key={key}
             index={pageNum}
-            renderingState={renderingIndexQueue.includes(pageNum) ? 'RENDERING' : 'LOADING'}
+            renderingState={
+              renderingIndexQueue.includes(pageNum) ? 'RENDERING' : 'LOADING'
+            }
           />
         );
       }

+ 22 - 22
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';
@@ -15,13 +14,10 @@ import useActions from '../actions';
 import useStore from '../store';
 
 const PdfViewer: React.FC = () => {
-  const [{
-    viewport,
-    pdf,
-    scale,
-    currentPage,
-    totalPage,
-  }, dispatch] = useStore();
+  const [
+    { viewport, pdf, scale, currentPage, totalPage },
+    dispatch,
+  ] = useStore();
   const {
     setTotalPage,
     setPdf,
@@ -35,7 +31,9 @@ const PdfViewer: React.FC = () => {
   } = useActions(dispatch);
   const currentPageRef = useRef(0);
 
-  const setLoadingProgress = (totalSize: number) => (progress: ProgressType): void => {
+  const setLoadingProgress = (totalSize: number) => (
+    progress: ProgressType
+  ): void => {
     setProgress({
       total: totalSize,
       loaded: progress.loaded,
@@ -53,18 +51,18 @@ const PdfViewer: React.FC = () => {
 
   const getXfdfFile = (token: string): void => {
     fetchXfdf(token)
-      .then((xfdf) => {
+      .then(xfdf => {
         const annotations = parseAnnotationFromXml(xfdf);
         const watermark = parseWatermarkFromXml(xfdf);
 
         if (watermark.obj_attr.type) {
-          addAnnots([...annotations, watermark]);
+          addAnnots([...annotations, watermark], false);
           updateWatermark(watermark.obj_attr);
         } else {
-          addAnnots(annotations);
+          addAnnots(annotations, false);
         }
       })
-      .catch((error) => {
+      .catch(error => {
         console.log(error);
       });
   };
@@ -84,7 +82,7 @@ const PdfViewer: React.FC = () => {
 
       const iPdf = await fetchPdf(
         `${config.API_HOST}${apiPath.getOriginalPdfFile}?transaction_id=${result.data.transaction_id}`,
-        setLoadingProgress(result.data.size),
+        setLoadingProgress(result.data.size)
       );
 
       setTotalPage(iPdf.numPages);
@@ -105,7 +103,9 @@ const PdfViewer: React.FC = () => {
 
   const changePdfContainerScale = (): void => {
     for (let i = 1; i <= totalPage; i += 1) {
-      const ele: HTMLDivElement = document.getElementById(`page_${i}`) as HTMLDivElement;
+      const ele: HTMLDivElement = document.getElementById(
+        `page_${i}`
+      ) as HTMLDivElement;
       if (ele) {
         ele.style.width = `${viewport.width}px`;
         ele.style.height = `${viewport.height}px`;
@@ -118,8 +118,12 @@ const PdfViewer: React.FC = () => {
   };
 
   const scrollToUpdate = (state: ScrollStateType): void => {
-    const ele: HTMLDivElement = document.getElementById('page_1') as HTMLDivElement;
-    const page = Math.round((state.lastY + ele.offsetHeight / 1.4) / (ele.offsetHeight + 50));
+    const ele: HTMLDivElement = document.getElementById(
+      'page_1'
+    ) as HTMLDivElement;
+    const page = Math.round(
+      (state.lastY + ele.offsetHeight / 1.4) / (ele.offsetHeight + 50)
+    );
 
     if (page !== currentPageRef.current) {
       setCurrentPage(page);
@@ -146,11 +150,7 @@ const PdfViewer: React.FC = () => {
     }
   }, [scale]);
 
-  return (
-    viewport.width ? (
-      <PdfPages scrollToUpdate={scrollToUpdate} />
-    ) : null
-  );
+  return viewport.width ? <PdfPages scrollToUpdate={scrollToUpdate} /> : null;
 };
 
 export default PdfViewer;

+ 1 - 7
containers/Placeholder.tsx

@@ -15,13 +15,7 @@ const Placeholder: React.FC = () => {
     });
   }, []);
 
-  return (
-    progress.loaded < progress.total || !done ? (
-      <PdfSkeleton />
-    ) : (
-      null
-    )
-  );
+  return progress.loaded < progress.total || !done ? <PdfSkeleton /> : null;
 };
 
 export default Placeholder;

+ 8 - 10
containers/Search.tsx

@@ -9,18 +9,14 @@ import { scrollIntoView } from '../helpers/utility';
 type MatchType = {
   page: number;
   index: number;
-}
+};
 
 const Search: React.FC = () => {
   let queryString = '';
   const [matchesMap, setMatchesMap] = useState<MatchType[]>([]);
   const [matchTotal, setMatchTotal] = useState(0);
   const [currentIndex, setCurrentIndex] = useState(-1);
-  const [{
-    navbarState,
-    pdf,
-    totalPage,
-  }, dispatch] = useStore();
+  const [{ navbarState, pdf, totalPage }, dispatch] = useStore();
   const { setNavbar } = useActions(dispatch);
 
   const extractTextItems = async (pageNum: number): Promise<string[]> => {
@@ -45,7 +41,7 @@ const Search: React.FC = () => {
     const content = normalize(contentItems.join('').toLowerCase());
     const matches = calcFindPhraseMatch(content, queryString);
     if (matches.length) {
-      matches.forEach((ele) => {
+      matches.forEach(ele => {
         matchesMap.push({
           page: pageNum,
           index: ele,
@@ -81,7 +77,7 @@ const Search: React.FC = () => {
   };
 
   const clickPrev = (): void => {
-    setCurrentIndex((cur) => {
+    setCurrentIndex(cur => {
       if (cur > 0) {
         return cur - 1;
       }
@@ -90,7 +86,7 @@ const Search: React.FC = () => {
   };
 
   const clickNext = (): void => {
-    setCurrentIndex((cur) => {
+    setCurrentIndex(cur => {
       if (cur + 1 < matchTotal) {
         return cur + 1;
       }
@@ -112,7 +108,9 @@ const Search: React.FC = () => {
   useEffect(() => {
     if (currentIndex >= 0) {
       const indexObj = matchesMap[currentIndex];
-      const pageDiv: HTMLDivElement = document.getElementById(`page_${indexObj.page}`) as HTMLDivElement;
+      const pageDiv: HTMLDivElement = document.getElementById(
+        `page_${indexObj.page}`
+      ) as HTMLDivElement;
       scrollIntoView(pageDiv);
     }
   }, [currentIndex, matchesMap]);

+ 97 - 79
containers/ShapeTools.tsx

@@ -6,8 +6,11 @@ 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 } from '../helpers/position';
+
+import {
+  getAbsoluteCoordinate,
+  parsePositionForBackend,
+} from '../helpers/position';
 import { parseAnnotationObject } from '../helpers/annotation';
 import useCursorPosition from '../hooks/useCursorPosition';
 
@@ -20,11 +23,7 @@ type Props = {
   onClick: () => void;
 };
 
-const Shape: React.FC<Props> = ({
-  title,
-  isActive,
-  onClick,
-}: Props) => {
+const Shape: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
   const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
   const [uuid, setUuid] = useState('');
   const [data, setData] = useState({
@@ -46,7 +45,13 @@ const Shape: React.FC<Props> = ({
     }));
   };
 
-  const convertPosition = (type: string, x1: number, y1: number, x2: number, y2: number): any => {
+  const convertPosition = (
+    type: string,
+    x1: number,
+    y1: number,
+    x2: number,
+    y2: number
+  ): any => {
     switch (type) {
       case 'Line':
         return {
@@ -69,46 +74,55 @@ const Shape: React.FC<Props> = ({
     }
   };
 
-  const addShape = useCallback((
-    pageEle: HTMLElement,
-    event: MouseEvent | TouchEvent,
-    attributes: OptionPropsType,
-  ): void => {
-    const {
-      shape = '', type, opacity, color, width = 0,
-    } = attributes;
-    const pageNum = pageEle.getAttribute('data-page-num') || 0;
-    const coordinate = getAbsoluteCoordinate(pageEle, event);
-    const id = uuidv4();
-
-    setUuid(id);
-    setStartPosition(coordinate);
-
-    const shapeType = ANNOTATION_TYPE[shape];
-    const position = convertPosition(
-      shapeType, coordinate.x - 8, coordinate.y - 8, coordinate.x + 8, coordinate.y + 8,
-    );
-    const annoteData = {
-      id,
-      obj_type: shapeType,
-      obj_attr: {
-        page: pageNum as number,
-        position,
-        bdcolor: color,
-        fcolor: type === 'fill' ? color : undefined,
-        transparency: opacity,
-        ftransparency: type === 'fill' ? opacity : undefined,
-        bdwidth: width,
-        is_arrow: shape === 'arrow',
-      },
-    };
-    const shapeAnnotation = parseAnnotationObject(annoteData, viewport.height, scale);
+  const addShape = useCallback(
+    (
+      pageEle: HTMLElement,
+      event: MouseEvent | TouchEvent,
+      attributes: OptionPropsType
+    ): void => {
+      const { shape = '', type, opacity, color, width = 0 } = attributes;
+      const pageNum = pageEle.getAttribute('data-page-num') || 0;
+      const coordinate = getAbsoluteCoordinate(pageEle, event);
+      const id = uuidv4();
+
+      setUuid(id);
+      setStartPosition(coordinate);
+
+      const shapeType = ANNOTATION_TYPE[shape];
+      const position = convertPosition(
+        shapeType,
+        coordinate.x - 8,
+        coordinate.y - 8,
+        coordinate.x + 8,
+        coordinate.y + 8
+      );
+      const annoteData = {
+        id,
+        obj_type: shapeType,
+        obj_attr: {
+          page: pageNum as number,
+          position,
+          bdcolor: color,
+          fcolor: type === 'fill' ? color : undefined,
+          transparency: opacity,
+          ftransparency: type === 'fill' ? opacity : undefined,
+          bdwidth: width,
+          is_arrow: shape === 'arrow',
+        },
+      };
+      const shapeAnnotation = parseAnnotationObject(
+        annoteData,
+        viewport.height,
+        scale
+      );
 
-    addAnnots([shapeAnnotation], true);
-  }, [viewport, scale, data]);
+      addAnnots([shapeAnnotation]);
+    },
+    [viewport, scale, data]
+  );
 
   const handleMouseDown = (event: MouseEvent | TouchEvent): void => {
-    event.preventDefault();
+    // event.preventDefault();
 
     const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
 
@@ -154,50 +168,54 @@ const Shape: React.FC<Props> = ({
     };
   }, [isActive, addShape]);
 
-  const handleUpdate = useCallback((start: Record<string, any>, end: Record<string, any>): void => {
-    const index = annotations.length - 1;
-    const { x: x1, y: y1 } = start;
-    const { x: x2, y: y2 } = end;
-
-    if (annotations[index] && annotations[index].id === uuid) {
-      const type = annotations[index].obj_type;
-      const position = convertPosition(type, x1, y1, x2, y2);
-
-      annotations[index].obj_attr.position = parsePositionForBackend(
-        type, position, viewport.height, scale,
-      );
-      updateAnnots([...annotations]);
-    }
-  }, [annotations, viewport, scale, uuid]);
+  const handleUpdate = useCallback(
+    (start: Record<string, any>, end: Record<string, any>): void => {
+      const index = annotations.length - 1;
+      const { x: x1, y: y1 } = start;
+      const { x: x2, y: y2 } = end;
+
+      if (annotations[index] && annotations[index].id === uuid) {
+        const type = annotations[index].obj_type;
+        const position = convertPosition(type, x1, y1, x2, y2);
+
+        annotations[index].obj_attr.position = parsePositionForBackend(
+          type,
+          position,
+          viewport.height,
+          scale
+        );
+        updateAnnots([...annotations]);
+      }
+    },
+    [annotations, viewport, scale, uuid]
+  );
 
   useEffect(() => {
     if (
-      startPosition.x && startPosition.y
-      && cursorPosition.x && cursorPosition.y
+      startPosition.x &&
+      startPosition.y &&
+      cursorPosition.x &&
+      cursorPosition.y
     ) {
       handleUpdate(startPosition, cursorPosition);
     }
   }, [startPosition, cursorPosition]);
 
-  return (
-    <ExpansionPanel
+  const Label = (
+    <Button
+      shouldFitContainer
+      align="left"
+      onClick={onClick}
       isActive={isActive}
-      label={(
-        <Button
-          shouldFitContainer
-          align="left"
-          onClick={onClick}
-          isActive={isActive}
-        >
-          <Icon glyph="shape" style={{ marginRight: '10px' }} />
-          {title}
-        </Button>
-      )}
     >
-      <ShapeOption
-        {...data}
-        setDataState={setDataState}
-      />
+      <Icon glyph="shape" style={{ marginRight: '10px' }} />
+      {title}
+    </Button>
+  );
+
+  return (
+    <ExpansionPanel isActive={isActive} label={Label}>
+      <ShapeOption {...data} setDataState={setDataState} />
     </ExpansionPanel>
   );
 };

+ 5 - 1
containers/Sidebar.tsx

@@ -29,7 +29,11 @@ const Sidebar: React.FC = () => {
 
   return (
     <SidebarWrapper isHidden={displayMode === 'full'}>
-      <Typography light style={{ marginLeft: '30px', marginTop: '46px' }} align="left">
+      <Typography
+        light
+        style={{ marginLeft: '30px', marginTop: '46px' }}
+        align="left"
+      >
         {t('mainMenu')}
       </Typography>
       <MarkupTools />

+ 21 - 10
containers/StickyNoteTools.tsx

@@ -24,7 +24,10 @@ const StickyNoteTools: React.FC<Props> = ({
   const [{ viewport, scale }, dispatch] = useStore();
   const { addAnnots } = useActions(dispatch);
 
-  const addStickyNote = (pageEle: HTMLElement, event: MouseEvent | TouchEvent): void => {
+  const addStickyNote = (
+    pageEle: HTMLElement,
+    event: MouseEvent | TouchEvent
+  ): void => {
     const pageNum = pageEle.getAttribute('data-page-num') || 0;
     const coordinate = getAbsoluteCoordinate(pageEle, event);
 
@@ -43,18 +46,21 @@ const StickyNoteTools: React.FC<Props> = ({
     };
     const stickyNote = parseAnnotationObject(annotData, viewport.height, scale);
 
-    addAnnots([stickyNote], true);
+    addAnnots([stickyNote]);
   };
 
-  const handleMouseDown = useCallback((event: MouseEvent | TouchEvent): void => {
-    event.preventDefault();
+  const handleMouseDown = useCallback(
+    (event: MouseEvent | TouchEvent): void => {
+      // event.preventDefault();
 
-    const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
+      const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
 
-    if (pageEle.hasAttribute('data-page-num')) {
-      addStickyNote(pageEle, event);
-    }
-  }, [viewport, scale]);
+      if (pageEle.hasAttribute('data-page-num')) {
+        addStickyNote(pageEle, event);
+      }
+    },
+    [viewport, scale]
+  );
 
   useEffect(() => {
     const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
@@ -74,7 +80,12 @@ const StickyNoteTools: React.FC<Props> = ({
 
   return (
     <BtnWrapper>
-      <Button shouldFitContainer align="left" onClick={onClick} isActive={isActive}>
+      <Button
+        shouldFitContainer
+        align="left"
+        onClick={onClick}
+        isActive={isActive}
+      >
         <Icon glyph="sticky-note" style={{ marginRight: '10px' }} />
         {title}
       </Button>

+ 10 - 5
containers/Thumbnails.tsx

@@ -6,9 +6,10 @@ import useActions from '../actions';
 import useStore from '../store';
 
 const Thumbnails: React.FC = () => {
-  const [{
-    pdf, totalPage, currentPage, navbarState, viewport,
-  }, dispatch] = useStore();
+  const [
+    { pdf, totalPage, currentPage, navbarState, viewport },
+    dispatch,
+  ] = useStore();
   const { setNavbar } = useActions(dispatch);
 
   const getPdfImage = async (pageNum: number): Promise<any> => {
@@ -16,7 +17,9 @@ const Thumbnails: React.FC = () => {
 
     const page = await pdf.getPage(pageNum);
     const canvas = document.createElement('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;
 
@@ -40,7 +43,9 @@ const Thumbnails: React.FC = () => {
         totalPage={totalPage}
         currentPage={currentPage}
         isActive={navbarState === 'thumbnails'}
-        close={(): void => { setNavbar(''); }}
+        close={(): void => {
+          setNavbar('');
+        }}
       />
     )
   );

+ 7 - 4
containers/Toolbar.tsx

@@ -8,9 +8,10 @@ import useStore from '../store';
 import { scrollIntoView } from '../helpers/utility';
 
 const Toolbar: React.FC = () => {
-  const [{
-    totalPage, currentPage, scale, rotation, viewport, displayMode,
-  }, dispatch] = useStore();
+  const [
+    { totalPage, currentPage, scale, rotation, viewport, displayMode },
+    dispatch,
+  ] = useStore();
   const {
     setCurrentPage: setCurrentPageAction,
     changeScale,
@@ -21,7 +22,9 @@ const Toolbar: React.FC = () => {
 
   const setCurrentPage = (num: number): void => {
     if (num > 0) {
-      const ele: HTMLElement = document.getElementById(`page_${num}`) as HTMLElement;
+      const ele: HTMLElement = document.getElementById(
+        `page_${num}`
+      ) as HTMLElement;
       scrollIntoView(ele);
     }
     setCurrentPageAction(num);

+ 8 - 14
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';
@@ -16,24 +15,19 @@ import { BtnWrapper } from '../global/toolStyled';
 const WatermarkTool: React.FC = () => {
   const { t } = useTranslation('sidebar');
   const [isActive, setActive] = useState(false);
-  const [{
-    totalPage,
-    watermark,
-    annotations,
-  }, dispatch] = useStore();
-  const {
-    setSidebar,
-    updateWatermark,
-    addAnnots,
-    updateAnnots,
-  } = useActions(dispatch);
+  const [{ totalPage, watermark, annotations }, dispatch] = useStore();
+  const { setSidebar, updateWatermark, addAnnots, updateAnnots } = useActions(
+    dispatch
+  );
 
   const setDataState = (obj: WatermarkType): void => {
     updateWatermark(obj);
   };
 
   const removeWatermarkInAnnots = (): void => {
-    const index = annotations.findIndex((ele: AnnotationType) => ele.obj_type === 'watermark');
+    const index = annotations.findIndex(
+      (ele: AnnotationType) => ele.obj_type === 'watermark'
+    );
     if (index >= 0) {
       annotations.splice(index, 1);
       updateAnnots(annotations);
@@ -63,7 +57,7 @@ const WatermarkTool: React.FC = () => {
     };
 
     removeWatermarkInAnnots();
-    addAnnots([watermarkData], true);
+    addAnnots([watermarkData]);
   };
 
   const handleDelete = (): void => {


Неке датотеке нису приказане због велике количине промена