Bläddra i källkod

fix some issue

* optimization scroll loading
* markup error when zoom in
* delete feature by hotkey
* prettier
RoyLiu 4 år sedan
förälder
incheckning
e8f391033f
69 ändrade filer med 2829 tillägg och 2255 borttagningar
  1. 4 1
      .editorconfig
  2. 0 44
      .eslintrc.js
  3. 76 0
      .eslintrc.json
  4. 4 0
      .prettierrc
  5. 10 21
      actions/main.ts
  6. 2 4
      components/AnnotationItem/index.tsx
  7. 29 36
      components/AnnotationSelector/index.tsx
  8. 14 15
      components/Button/index.tsx
  9. 36 36
      components/ColorSelector/index.tsx
  10. 20 20
      components/DeleteDialog/index.tsx
  11. 3 9
      components/Dialog/index.tsx
  12. 3 10
      components/Drawer/index.tsx
  13. 13 7
      components/Drawer/styled.ts
  14. 4 10
      components/ExpansionPanel/index.tsx
  15. 49 42
      components/FreeText/index.tsx
  16. 34 37
      components/FreeTextOption/index.tsx
  17. 2 8
      components/Head.tsx
  18. 21 29
      components/Highlight/index.tsx
  19. 14 4
      components/HighlightOption/index.tsx
  20. 12 18
      components/Icon/index.tsx
  21. 69 57
      components/Ink/index.tsx
  22. 9 3
      components/InkOption/index.tsx
  23. 74 52
      components/Line/index.tsx
  24. 1 1
      components/Markup/index.tsx
  25. 35 11
      components/Markup/styled.ts
  26. 2 7
      components/Modal/index.tsx
  27. 11 11
      components/Navbar/index.tsx
  28. 24 19
      components/OuterRect/index.tsx
  29. 3 1
      components/OuterRectForLine/index.tsx
  30. 30 30
      components/Page/index.tsx
  31. 25 15
      components/Shape/index.tsx
  32. 11 16
      components/Sliders/index.tsx
  33. 11 11
      components/ThumbnailViewer/index.tsx
  34. 9 1
      components/ThumbnailViewer/styled.ts
  35. 0 2
      constants/actionTypes.ts
  36. 19 13
      constants/type.ts
  37. 6 11
      containers/Annotation.tsx
  38. 54 10
      containers/AutoSave.tsx
  39. 2 1
      containers/ColorSelector.tsx
  40. 14 9
      containers/CreateForm.tsx
  41. 30 27
      containers/FreeTextTools.tsx
  42. 68 50
      containers/FreehandTools.tsx
  43. 28 29
      containers/HighlightTools.tsx
  44. 29 18
      containers/MarkupTools.tsx
  45. 7 9
      containers/Navbar.tsx
  46. 11 14
      containers/PdfPage.tsx
  47. 8 15
      containers/PdfPages.tsx
  48. 20 19
      containers/PdfViewer.tsx
  49. 1 7
      containers/Placeholder.tsx
  50. 8 10
      containers/Search.tsx
  51. 96 78
      containers/ShapeTools.tsx
  52. 5 1
      containers/Sidebar.tsx
  53. 20 9
      containers/StickyNoteTools.tsx
  54. 10 5
      containers/Thumbnails.tsx
  55. 7 4
      containers/Toolbar.tsx
  56. 7 12
      containers/WatermarkTool.tsx
  57. 11 9
      global/toolStyled.ts
  58. 67 35
      helpers/annotation.ts
  59. 82 91
      helpers/markup.ts
  60. 35 18
      helpers/position.ts
  61. 57 46
      helpers/utility.ts
  62. 0 56
      hooks/useAutoSave.ts
  63. 57 55
      hooks/useCursorPosition.ts
  64. 27 0
      hooks/useKeyPress.ts
  65. 9 9
      package.json
  66. 2 4
      reducers/index.ts
  67. 0 11
      reducers/main.ts
  68. 4 6
      reducers/middleware/index.ts
  69. 1364 976
      yarn.lock

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

+ 10 - 21
actions/main.ts

@@ -2,27 +2,16 @@ 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 }),
 });
 
 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,

+ 29 - 36
components/AnnotationSelector/index.tsx

@@ -9,9 +9,7 @@ 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 +43,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>
   );
 };

+ 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>{children}</DisableButton>
   ) : (
-    <NormalButton
-      {...rest}
-      onMouseDown={onClick}
-    >
+    <NormalButton {...rest} onMouseDown={onClick}>
       {children}
     </NormalButton>
-  )
-);
+  );
 
 Button.defaultProps = {
   appearance: 'default',

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

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

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

+ 49 - 42
components/FreeText/index.tsx

@@ -1,8 +1,10 @@
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
 
 import OuterRect from '../OuterRect';
 import {
-  AnnotationElementPropsType, PositionType, CoordType,
+  AnnotationElementPropsType,
+  PositionType,
+  CoordType,
 } from '../../constants/type';
 import { rectCalc, parsePositionForBackend } from '../../helpers/position';
 
@@ -11,13 +13,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 +22,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 newPosition = { ...(position as PositionType) };
     newPosition.right = newPosition.left + fontWidth;
 
     onUpdate({
@@ -41,7 +38,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 +52,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,6 +67,14 @@ const FreeText: React.SFC<AnnotationElementPropsType> = ({
     color: textcolor,
   };
 
+  useEffect(() => {
+    if (isEdit) {
+      setTimeout(() => {
+        if (inputRef.current) inputRef.current.focus();
+      }, 100);
+    }
+  }, [isEdit]);
+
   return (
     <div>
       <AnnotationContainer
@@ -70,37 +83,31 @@ const FreeText: React.SFC<AnnotationElementPropsType> = ({
         width={`${annotRect.width + 2}px`}
         height={`${annotRect.height + 4}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>
   );
 };

+ 34 - 37
components/FreeTextOption/index.tsx

@@ -8,16 +8,9 @@ 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 +44,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 +87,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 +115,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 - 29
components/Highlight/index.tsx

@@ -5,18 +5,11 @@ 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 +19,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 +38,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 - 4
components/HighlightOption/index.tsx

@@ -23,7 +23,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 +35,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 +48,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 +58,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>
     </>

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

+ 69 - 57
components/Ink/index.tsx

@@ -1,44 +1,54 @@
 import React from 'react';
 
 import {
-  AnnotationElementPropsType, PointType, HTMLCoordinateType, CoordType, UpdateData,
+  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 +69,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 +97,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 +112,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 - 3
components/InkOption/index.tsx

@@ -54,7 +54,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 +64,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 +74,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>

+ 74 - 52
components/Line/index.tsx

@@ -3,7 +3,9 @@ import { v4 as uuidv4 } from 'uuid';
 
 import Outer from '../OuterRectForLine';
 import {
-  AnnotationElementPropsType, LinePositionType, HTMLCoordinateType,
+  AnnotationElementPropsType,
+  LinePositionType,
+  HTMLCoordinateType,
 } from '../../constants/type';
 import { parsePositionForBackend } from '../../helpers/position';
 
@@ -27,7 +29,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 +51,7 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
 
   const pointCalc = (
     { start, end }: LinePositionType,
-    borderWidth: number,
+    borderWidth: number
   ): LinePositionType => ({
     start: {
       x: start.x + borderWidth,
@@ -60,7 +64,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 +80,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 +136,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 +169,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}
+        />
+      ) : (
+        ''
+      )}
     </>
   );
 };

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

+ 11 - 11
components/Navbar/index.tsx

@@ -27,17 +27,17 @@ const Navbar: React.FC<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}
+        isDisabled={!!(isSaving && ele.key === 'nav-export')}
+        onClick={(): void => {
+          onClick(ele.content);
+        }}
+      />
+    ))}
     {children}
   </Container>
 );

+ 24 - 19
components/OuterRect/index.tsx

@@ -56,7 +56,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 +81,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 +89,7 @@ const index: React.FC<Props> = ({
 
   const calcScaleResult = (
     currentPosition: PointType,
-    objPosition: ObjPositionType,
+    objPosition: ObjPositionType
   ): CoordType => {
     const scaleData = calcDragAndDropScale({
       ...objPosition,
@@ -109,19 +111,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 +163,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 +173,7 @@ const index: React.FC<Props> = ({
             fill={color.primary}
             {...attr}
           />
-        ))
-      }
+        ))}
     </SVG>
   );
 };

+ 3 - 1
components/OuterRectForLine/index.tsx

@@ -44,7 +44,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);

+ 30 - 30
components/Page/index.tsx

@@ -1,11 +1,11 @@
-import React, {
-  useEffect, useRef,
-} from 'react';
+import React, { useEffect, useRef } from 'react';
 
 import Watermark from '../Watermark';
 import { renderPdfPage } from '../../helpers/pdf';
 import {
-  RenderingStateType, ViewportType, WatermarkType,
+  RenderingStateType,
+  ViewportType,
+  WatermarkType,
 } from '../../constants/type';
 
 import {
@@ -63,9 +63,12 @@ const PageView: React.FC<Props> = ({
     }
   }, [renderingState, viewport]);
 
-  useEffect(() => (): void => {
-    if (pdfPage) pdfPage.cleanup();
-  }, []);
+  useEffect(
+    () => (): void => {
+      if (pdfPage) pdfPage.cleanup();
+    },
+    []
+  );
 
   return (
     <PageWrapper
@@ -76,29 +79,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>
   );
 };

+ 25 - 15
components/Shape/index.tsx

@@ -3,7 +3,9 @@ import React from 'react';
 import OuterRect from '../OuterRect';
 import SvgShapeElement from '../SvgShapeElement';
 import {
-  AnnotationElementPropsType, PositionType, CoordType,
+  AnnotationElementPropsType,
+  PositionType,
+  CoordType,
 } from '../../constants/type';
 import { rectCalc, parsePositionForBackend } from '../../helpers/position';
 
@@ -27,7 +29,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 +42,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 +74,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}
+        />
+      ) : (
+        ''
+      )}
     </>
   );
 };

+ 11 - 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';
@@ -79,15 +73,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);
   };
 
@@ -112,10 +110,7 @@ const Sliders: React.FC<Props> = ({
         onTouchStart={handleMouseDown}
       >
         <Rail track={valueState} />
-        <Track
-          track={valueState}
-          isActive={isActive}
-        />
+        <Track track={valueState} isActive={isActive} />
       </Wrapper>
     </OuterWrapper>
   );

+ 11 - 11
components/ThumbnailViewer/index.tsx

@@ -7,10 +7,8 @@ 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 +54,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 +66,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 +100,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 +116,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;
+`;

+ 0 - 2
constants/actionTypes.ts

@@ -3,8 +3,6 @@ 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_CURRENT_PAGE = 'SET_CURRENT_PAGE';
 export const SET_TOTAL_PAGE = 'SET_TOTAL_PAGE';

+ 19 - 13
constants/type.ts

@@ -1,10 +1,15 @@
-export type RenderingStateType = 'RENDERING' | 'LOADING' | 'FINISHED' | 'PAUSED';
+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
-);
+  state: Record<string, any>,
+  action: { type: string; payload: any }
+) => any;
 
 export type ViewportType = {
   width: number;
@@ -47,16 +52,19 @@ export type HTMLCoordinateType = {
 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 AnnotationPositionType =
+  | string
+  | PositionType
+  | LinePositionType
+  | PointType
+  | (PositionType | PointType[])[];
 
 export type AnnotationAttributeType = {
   page: number;
@@ -88,18 +96,16 @@ export type UpdateData = {
   fontsize?: number;
 };
 
-export type OnUpdateType = (
-  (data: UpdateData) => void
-);
+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>
-);
+  dispatch: (obj: DispatchType) => void
+) => Record<string, any>;
 
 export type AnnotationElementPropsType = AnnotationType & {
   isCovered: boolean;

+ 6 - 11
containers/Annotation.tsx

@@ -53,7 +53,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 +93,7 @@ const Annotation: React.FC<Props> = ({
   };
 
   const handleKeyDown = (e: React.KeyboardEvent): void => {
-    if (e.keyCode === 46) {
+    if (e.key === 'Delete') {
       deleteDialogToggle();
     }
   };
@@ -129,10 +129,7 @@ const Annotation: React.FC<Props> = ({
   };
 
   return (
-    <AnnotationWrapper
-      ref={ref}
-      {...wrapperProps}
-    >
+    <AnnotationWrapper ref={ref} {...wrapperProps}>
       {((): React.ReactNode => {
         switch (obj_type) {
           case 'Ink':
@@ -151,11 +148,9 @@ const Annotation: React.FC<Props> = ({
             return <Highlight {...childProps} />;
         }
       })()}
-      <DeleteDialog
-        open={openDialog}
-        onCancel={deleteDialogToggle}
-        onDelete={handleDelete}
-      />
+      {openDialog && (
+        <DeleteDialog onCancel={deleteDialogToggle} onDelete={handleDelete} />
+      )}
     </AnnotationWrapper>
   );
 };

+ 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, isInit }] = useStore();
+  const [isSaved, setSaved] = useState(false);
+  const [isSaving, setSaving] = useState(false);
+  const { addToast, removeAllToasts } = useToasts();
+  let subscription: Subscription | null = null;
 
   useEffect(() => {
+    if (info.id && isInit) {
+      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, isInit]);
+
+  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' }} />

+ 30 - 27
containers/FreeTextTools.tsx

@@ -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);
@@ -70,19 +75,22 @@ const HighlightTools: React.FC<Props> = ({
         content: '',
       },
     };
+
     const freeText = parseAnnotationObject(annotData, viewport.height, scale);
 
     addAnnots([freeText], true);
   };
 
-  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 - 50
containers/FreehandTools.tsx

@@ -8,7 +8,10 @@ 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 +48,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], true);
+      }
+    },
+    [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 +108,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 +165,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 - 29
containers/HighlightTools.tsx

@@ -38,20 +38,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], true);
+          }
         }
       }
-    }
-  }, [isActive, data, scale]);
+    },
+    [isActive, data, scale]
+  );
 
   useEffect(() => {
     if (isActive) {
@@ -65,26 +69,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>
   );
 };

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

+ 7 - 9
containers/Navbar.tsx

@@ -11,14 +11,10 @@ import AnnotationList from './AnnotationList';
 import { downloadFileWithUri } from '../helpers/utility';
 
 const Navbar: React.FC = () => {
-  const [{
-    navbarState,
-    displayMode,
-    info,
-    annotations,
-    totalPage,
-    isSaving,
-  }, dispatch] = useStore();
+  const [
+    { navbarState, displayMode, info, annotations, totalPage, isSaving },
+    dispatch,
+  ] = useStore();
   const { setNavbar } = useActions(dispatch);
 
   const onClick = (state: string): void => {
@@ -50,7 +46,9 @@ const Navbar: React.FC = () => {
       onClick={onClick}
       navbarState={navbarState}
       displayMode={displayMode}
-      fileName={info.token ? decodeURIComponent(escape(window.atob(info.token))) : ''}
+      fileName={
+        info.token ? decodeURIComponent(escape(window.atob(info.token))) : ''
+      }
       isSaving={isSaving}
     >
       <Search />

+ 11 - 14
containers/PdfPage.tsx

@@ -12,22 +12,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 +28,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 - 15
containers/PdfPages.tsx

@@ -1,6 +1,4 @@
-import React, {
-  useEffect, useState, useRef,
-} from 'react';
+import React, { useEffect, useState, useRef } from 'react';
 import _ from 'lodash';
 
 import { ScrollStateType } from '../constants/type';
@@ -14,19 +12,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 +52,9 @@ const PdfPages: React.FC<Props> = ({
           <PdfPage
             key={key}
             index={pageNum}
-            renderingState={renderingIndexQueue.includes(pageNum) ? 'RENDERING' : 'LOADING'}
+            renderingState={
+              renderingIndexQueue.includes(pageNum) ? 'RENDERING' : 'LOADING'
+            }
           />
         );
       }

+ 20 - 19
containers/PdfViewer.tsx

@@ -15,13 +15,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 +32,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,7 +52,7 @@ const PdfViewer: React.FC = () => {
 
   const getXfdfFile = (token: string): void => {
     fetchXfdf(token)
-      .then((xfdf) => {
+      .then(xfdf => {
         const annotations = parseAnnotationFromXml(xfdf);
         const watermark = parseWatermarkFromXml(xfdf);
 
@@ -64,7 +63,7 @@ const PdfViewer: React.FC = () => {
           addAnnots(annotations);
         }
       })
-      .catch((error) => {
+      .catch(error => {
         console.log(error);
       });
   };
@@ -84,7 +83,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 +104,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 +119,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 +151,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]);

+ 96 - 78
containers/ShapeTools.tsx

@@ -7,7 +7,10 @@ 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], true);
+    },
+    [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 />

+ 20 - 9
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);
 
@@ -46,15 +49,18 @@ const StickyNoteTools: React.FC<Props> = ({
     addAnnots([stickyNote], true);
   };
 
-  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);

+ 7 - 12
containers/WatermarkTool.tsx

@@ -16,24 +16,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);

+ 11 - 9
global/toolStyled.ts

@@ -2,7 +2,7 @@ import styled, { css } from 'styled-components';
 
 import { color } from '../constants/style';
 
-export const Wrapper = styled('div')<{width?: string}>`
+export const Wrapper = styled('div')<{ width?: string }>`
   margin-bottom: 10px;
   display: inline-block;
   height: auto;
@@ -16,7 +16,7 @@ export const Group = styled.div`
   align-items: center;
 `;
 
-export const Item = styled('div')<{size?: string; selected?: boolean}>`
+export const Item = styled('div')<{ size?: string; selected?: boolean }>`
   width: ${props => (props.size === 'small' ? '30px' : '40px')};
   height: ${props => (props.size === 'small' ? '30px' : '40px')};
   border-radius: 4px;
@@ -25,16 +25,18 @@ export const Item = styled('div')<{size?: string; selected?: boolean}>`
   align-items: center;
   cursor: pointer;
 
-  ${props => (props.selected ? css`
-    background-color: ${color['light-primary']};
-  ` : '')}
-
-  :hover {
+  ${props =>
+      props.selected
+        ? css`
+            background-color: ${color['light-primary']};
+          `
+        : ''}
+    :hover {
     background-color: ${color['light-primary']};
   }
 `;
 
-export const Circle = styled('div')<{color: string}>`
+export const Circle = styled('div')<{ color: string }>`
   width: 16px;
   height: 16px;
   border-radius: 8px;
@@ -63,7 +65,7 @@ export const PickerContainer = styled.div`
   justify-content: center;
   align-items: center;
 
-  div[class^="chrome-picker"] {
+  div[class^='chrome-picker'] {
     width: 350px !important;
   }
 `;

+ 67 - 35
helpers/annotation.ts

@@ -1,15 +1,16 @@
 import { v4 as uuidv4 } from 'uuid';
 
 import { ANNOTATION_TYPE } from '../constants';
-import {
-  AnnotationType, PositionType, ViewportType,
-} from '../constants/type';
+import { AnnotationType, PositionType, ViewportType } from '../constants/type';
 import { getPdfPage, renderTextLayer } from './pdf';
 import { getPosition, parsePositionForBackend } from './position';
 import { normalizeRound, floatToHex } from './utility';
 import { xmlParser, getElementsByTagName } from './dom';
 
-type GetFontAttributeFunc = (type: string, element: Record<string, any>) => Record<string, any>;
+type GetFontAttributeFunc = (
+  type: string,
+  element: Record<string, any>
+) => Record<string, any>;
 
 const getContent = (type: string, element: Record<string, any>): string => {
   if (type !== 'Text' && type !== 'FreeText') return '';
@@ -28,13 +29,18 @@ const getContent = (type: string, element: Record<string, any>): string => {
 
 const getFontAttribute: GetFontAttributeFunc = (type, element) => {
   if (type !== 'FreeText') return {};
-  const appearanceString = element.childNodes[1].innerHTML || element.childNodes[1].textContent;
+  const appearanceString =
+    element.childNodes[1].innerHTML || element.childNodes[1].textContent;
   const arr = appearanceString.split(' ');
 
   return {
     fontsize: parseInt(arr[5], 10),
     fontname: arr[4].substr(1),
-    textcolor: floatToHex(parseFloat(arr[0]), parseFloat(arr[1]), parseFloat(arr[2])),
+    textcolor: floatToHex(
+      parseFloat(arr[0]),
+      parseFloat(arr[1]),
+      parseFloat(arr[2])
+    ),
   };
 };
 
@@ -56,12 +62,22 @@ export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
         obj_attr: {
           page,
           position: getPosition(type, cur),
-          bdcolor: cur.attributes.color ? cur.attributes.color.value : undefined,
-          bdwidth: cur.attributes.width ? parseInt(cur.attributes.width.value, 10) : 0,
-          transparency: cur.attributes.opacity ? parseFloat(cur.attributes.opacity.value) : 1,
+          bdcolor: cur.attributes.color
+            ? cur.attributes.color.value
+            : undefined,
+          bdwidth: cur.attributes.width
+            ? parseInt(cur.attributes.width.value, 10)
+            : 0,
+          transparency: cur.attributes.opacity
+            ? parseFloat(cur.attributes.opacity.value)
+            : 1,
           content: getContent(type, cur) || undefined,
-          fcolor: cur.attributes['interior-color'] ? cur.attributes['interior-color'].value : undefined,
-          ftransparency: cur.attributes['interior-opacity'] ? cur.attributes['interior-opacity'].value : undefined,
+          fcolor: cur.attributes['interior-color']
+            ? cur.attributes['interior-color'].value
+            : undefined,
+          ftransparency: cur.attributes['interior-opacity']
+            ? cur.attributes['interior-opacity'].value
+            : undefined,
           is_arrow: cur.attributes.tail,
           ...getFontAttribute(type, cur),
         },
@@ -75,7 +91,12 @@ export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
 };
 
 // eslint-disable-next-line consistent-return
-const getEleText = (coord: any, elements: any, viewport: any, scale: any): string => {
+const getEleText = (
+  coord: any,
+  elements: any,
+  viewport: any,
+  scale: any
+): string => {
   const top = normalizeRound(viewport.height - coord.top * scale);
   const left = normalizeRound(coord.left * scale);
   const bottom = normalizeRound(viewport.height - coord.bottom * scale);
@@ -98,7 +119,7 @@ const getEleText = (coord: any, elements: any, viewport: any, scale: any): strin
           const start = Math.floor(textLength * rateL);
           const distanceR = eleRight - right;
           const rateR = distanceR / width;
-          const end = Math.floor(textLength - (textLength * rateR));
+          const end = Math.floor(textLength - textLength * rateR);
           return ` ${element.innerText.slice(start, end)}`;
         }
         if (eleLeft < left && eleRight > left) {
@@ -110,7 +131,7 @@ const getEleText = (coord: any, elements: any, viewport: any, scale: any): strin
         if (eleRight > right && eleLeft < right) {
           const distance = eleRight - right;
           const rate = distance / width;
-          const end = Math.floor(textLength - (textLength * rate));
+          const end = Math.floor(textLength - textLength * rate);
           return ` ${element.innerText.slice(0, end)}`;
         }
         if (eleLeft >= left && eleRight <= right) {
@@ -123,7 +144,11 @@ const getEleText = (coord: any, elements: any, viewport: any, scale: any): strin
 };
 
 export const getAnnotationText = async ({
-  viewport, scale, page, coords, pdf,
+  viewport,
+  scale,
+  page,
+  coords,
+  pdf,
 }: {
   viewport: ViewportType;
   scale: number;
@@ -132,7 +157,9 @@ export const getAnnotationText = async ({
   pdf: any;
 }): Promise<any> => {
   const pageContainer = document.getElementById(`page_${page}`) as HTMLElement;
-  const textLayer = pageContainer.querySelector('[data-id="text-layer"]') as HTMLElement;
+  const textLayer = pageContainer.querySelector(
+    '[data-id="text-layer"]'
+  ) as HTMLElement;
   const pdfPage = await getPdfPage(pdf, page);
 
   if (!textLayer.childNodes.length) {
@@ -142,6 +169,7 @@ export const getAnnotationText = async ({
       viewport,
     });
   }
+  // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
   // @ts-ignore
   const textElements = [...textLayer.childNodes];
   let text = '';
@@ -155,25 +183,29 @@ export const getAnnotationText = async ({
   return text;
 };
 
-export const parseAnnotationObject = ({
-  id,
-  obj_type,
-  obj_attr: {
-    page,
-    bdcolor,
-    transparency,
-    fcolor,
-    ftransparency,
-    position = '',
-    content,
-    style,
-    bdwidth,
-    fontname,
-    fontsize,
-    textcolor,
-    is_arrow,
-  },
-}: AnnotationType, pageHeight: number, scale: number): AnnotationType => ({
+export const parseAnnotationObject = (
+  {
+    id,
+    obj_type,
+    obj_attr: {
+      page,
+      bdcolor,
+      transparency,
+      fcolor,
+      ftransparency,
+      position = '',
+      content,
+      style,
+      bdwidth,
+      fontname,
+      fontsize,
+      textcolor,
+      is_arrow,
+    },
+  }: AnnotationType,
+  pageHeight: number,
+  scale: number
+): AnnotationType => ({
   id,
   obj_type,
   obj_attr: {

+ 82 - 91
helpers/markup.ts

@@ -2,39 +2,27 @@ import { ANNOTATION_TYPE } from '../constants';
 import { AnnotationType, PositionType } from '../constants/type';
 import { parseAnnotationObject } from './annotation';
 
-const EXTEND_RANGE = 2;
-
-type ScalePositionFunc = (
-  ({
-    top, left, bottom, right,
-  }: PositionType, scale: number) => PositionType
-);
-
-type GetMarkupWithSelectionFunc = (
-  ({
-    color, type, opacity, scale,
-  }: {
-    color: string;
-    type: string;
-    opacity: number;
-    scale: number;
-  }) => AnnotationType | null
-);
-
-const scalePosition: ScalePositionFunc = ({
-  top, left, bottom, right,
-}, scale) => ({
-  top: top / scale,
-  left: left / scale,
-  bottom: bottom / scale,
-  right: right / scale,
-});
+type GetMarkupWithSelectionFunc = ({
+  color,
+  type,
+  opacity,
+  scale,
+}: {
+  color: string;
+  type: string;
+  opacity: number;
+  scale: number;
+}) => AnnotationType | null;
 
 export const getMarkupWithSelection: GetMarkupWithSelectionFunc = ({
-  color, type, opacity, scale,
+  color,
+  type,
+  opacity,
+  scale,
 }) => {
-  const selection: any = document.getSelection();
-  if (!selection.rangeCount) return null;
+  const selection: Selection | null = window.getSelection();
+
+  if (!selection || !selection.rangeCount) return null;
 
   const {
     startContainer,
@@ -46,30 +34,38 @@ export const getMarkupWithSelection: GetMarkupWithSelectionFunc = ({
   const endElement = endContainer.parentNode as HTMLElement;
   const startPage = startElement?.parentNode?.parentNode as HTMLElement;
   const endPage = endElement?.parentNode?.parentNode as HTMLElement;
-  const startPageNum = parseInt(startPage.getAttribute('data-page-num') as string, 10);
-  const endPageNum = parseInt(endPage.getAttribute('data-page-num') as string, 10);
-  const textLayer = startPage.querySelector('[data-id="text-layer"]') as HTMLElement;
+  const startPageNum = parseInt(
+    startPage.getAttribute('data-page-num') as string,
+    10
+  );
+  const endPageNum = parseInt(
+    endPage.getAttribute('data-page-num') as string,
+    10
+  );
+  const textLayer = startPage.querySelector(
+    '[data-id="text-layer"]'
+  ) as HTMLElement;
   const pageHeight = startPage.offsetHeight;
 
   if (startPageNum !== endPageNum) return null;
   if (startOffset === endOffset && startOffset === endOffset) return null;
 
-  const startEle = startElement.cloneNode(true) as HTMLElement;
-  const endEle = endElement.cloneNode(true) as HTMLElement;
+  const startCloneEle = startElement.cloneNode(true) as HTMLElement;
+  const endCloneEle = endElement.cloneNode(true) as HTMLElement;
 
   const startText = startElement.innerText.substring(0, startOffset);
-  const endText = endEle.innerText.substring(endOffset);
+  const endText = endElement.innerText.substring(endOffset);
 
-  startEle.innerText = startText;
-  endEle.innerText = endText;
-  textLayer.appendChild(startEle);
-  textLayer.appendChild(endEle);
+  startCloneEle.innerText = startText;
+  endCloneEle.innerText = endText;
+  textLayer.appendChild(startCloneEle);
+  textLayer.appendChild(endCloneEle);
 
-  const startEleWidth = startEle.offsetWidth;
-  const endEleWidth = endEle.offsetWidth;
+  const startExtraWidth = startCloneEle.getBoundingClientRect().width;
+  const endExtraWidth = endCloneEle.getBoundingClientRect().width;
 
-  textLayer.removeChild(startEle);
-  textLayer.removeChild(endEle);
+  textLayer.removeChild(startCloneEle);
+  textLayer.removeChild(endCloneEle);
 
   const info: AnnotationType = {
     obj_type: '',
@@ -82,73 +78,68 @@ export const getMarkupWithSelection: GetMarkupWithSelectionFunc = ({
   };
   const position: PositionType[] = [];
 
+  const startRect = startElement.getBoundingClientRect();
+  const endRect = endElement.getBoundingClientRect();
+
   // left to right and up to down select
-  let startX = startElement.offsetLeft + startEleWidth;
-  let startY = startElement.offsetTop - EXTEND_RANGE;
-  let endX = endElement.offsetLeft + endElement.offsetWidth - endEleWidth;
-  let endY = endElement.offsetTop + endElement.offsetHeight + EXTEND_RANGE;
+  let startX = startElement.offsetLeft + startExtraWidth;
+  let startY = startElement.offsetTop;
+  let endX = endElement.offsetLeft + endRect.width - endExtraWidth;
+  let endY = endElement.offsetTop + endRect.height;
 
   if (startX > endX && startY >= endY) {
     // right to left and down to up select
-    startX = endElement.offsetLeft + startEleWidth;
-    startY = endElement.offsetTop - EXTEND_RANGE;
-    endX = startElement.offsetLeft + startElement.offsetWidth - endEleWidth;
-    endY = startElement.offsetTop + startElement.offsetHeight + EXTEND_RANGE;
+    startX = endElement.offsetLeft + startExtraWidth;
+    startY = endElement.offsetTop;
+    endX = startElement.offsetLeft + startRect.width - endExtraWidth;
+    endY = startElement.offsetTop + startRect.height;
   }
+  // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
   // @ts-ignore
   const textElements = [...textLayer.childNodes];
 
-  textElements.forEach((ele: any) => {
-    const {
-      offsetTop, offsetLeft, offsetHeight, offsetWidth,
-    } = ele;
+  textElements.forEach((ele: HTMLElement) => {
+    const { offsetTop, offsetLeft, offsetHeight, offsetWidth } = ele;
     const offsetRight = offsetLeft + offsetWidth;
     const offsetBottom = offsetTop + offsetHeight;
-    let coords = {
-      top: 0, left: 0, right: 0, bottom: 0,
+    const coords = {
+      top: offsetTop,
+      bottom: offsetBottom,
+      left: offsetLeft,
+      right: offsetRight,
     };
 
-    if (offsetTop >= startY && offsetBottom <= endY) {
-      if (startElement === endElement) {
+    if (
+      offsetTop >= startY + offsetHeight &&
+      offsetBottom <= endY - offsetHeight
+    ) {
+      position.push(coords);
+    } else if (offsetTop >= startY - 2 && offsetBottom <= endY + 2) {
+      if (startElement === endElement && startElement === ele) {
         // start and end same element
-        coords = {
-          top: offsetTop,
-          bottom: offsetBottom,
-          left: startX,
-          right: endX,
-        };
+        coords.left = startX;
+        coords.right = endX;
+        position.push(coords);
       } else if (startElement === ele) {
         // start element
-        coords = {
-          top: offsetTop,
-          bottom: offsetBottom,
-          left: startX,
-          right: offsetRight,
-        };
+        coords.left = startX;
+        position.push(coords);
       } else if (endElement === ele) {
         // end element
-        coords = {
-          top: offsetTop,
-          bottom: offsetBottom,
-          left: offsetLeft,
-          right: endX,
-        };
+        coords.right = endX;
+        position.push(coords);
       } else if (
-        (offsetLeft >= startX && offsetRight <= endX)
-        || (offsetTop > (startY + 5) && offsetBottom < (endY - 5))
-        || (offsetLeft >= startX && offsetBottom <= startY + offsetHeight + 5)
-        || (offsetRight <= endX && offsetTop >= endX - offsetHeight - 5)
+        (startX < offsetLeft && endX > offsetRight) ||
+        (offsetLeft > startX &&
+          offsetTop <= startY + 2 &&
+          endY > startY + offsetHeight) ||
+        (offsetRight < endX &&
+          offsetBottom >= endY - 2 &&
+          startY < endY - offsetHeight)
       ) {
         // middle element
-        coords = {
-          top: offsetTop,
-          bottom: offsetBottom,
-          left: offsetLeft,
-          right: offsetRight,
-        };
+        position.push(coords);
       }
-
-      position.push(scalePosition(coords, scale));
     }
   });
 
@@ -163,4 +154,4 @@ export const getMarkupWithSelection: GetMarkupWithSelectionFunc = ({
   return parseAnnotationObject(info, pageHeight, scale);
 };
 
-export default parseAnnotationObject;
+export default getMarkupWithSelection;

+ 35 - 18
helpers/position.ts

@@ -1,11 +1,17 @@
 import _ from 'lodash';
 
 import {
-  PositionType, HTMLCoordinateType, PointType, AnnotationPositionType, LinePositionType,
+  PositionType,
+  HTMLCoordinateType,
+  PointType,
+  AnnotationPositionType,
+  LinePositionType,
 } from '../constants/type';
 
 export const rectCalc = (
-  position: PositionType, viewHeight: number, scale: number,
+  position: PositionType,
+  viewHeight: number,
+  scale: number
 ): HTMLCoordinateType => ({
   top: viewHeight - position.top * scale,
   left: position.left * scale,
@@ -13,7 +19,10 @@ export const rectCalc = (
   height: (position.top - position.bottom) * scale,
 });
 
-export const getPosition = (type: string, element: Record<string, any>): AnnotationPositionType => {
+export const getPosition = (
+  type: string,
+  element: Record<string, any>
+): AnnotationPositionType => {
   switch (type) {
     case 'Ink': {
       const gestures: PointType[][] = [];
@@ -22,7 +31,9 @@ export const getPosition = (type: string, element: Record<string, any>): Annotat
       nodeList.forEach((ele: HTMLElement) => {
         if (ele.tagName === 'gesture') {
           const points: PointType[] = [];
-          const gestureArray = (ele.innerHTML || ele.textContent || '').split(';');
+          const gestureArray = (ele.innerHTML || ele.textContent || '').split(
+            ';'
+          );
 
           gestureArray.forEach((ele1: string) => {
             const pointArr = ele1.split(',');
@@ -99,16 +110,19 @@ export const getPosition = (type: string, element: Record<string, any>): Annotat
 };
 
 export const parsePositionForBackend = (
-  type: string, position: AnnotationPositionType, pageHeight: number, scale: number,
+  type: string,
+  position: AnnotationPositionType,
+  pageHeight: number,
+  scale: number
 ): AnnotationPositionType => {
   switch (type) {
     case 'Highlight':
     case 'Underline':
     case 'Squiggly':
     case 'StrikeOut': {
-      const coercionPositionTypeArray = position as PositionType[];
+      const forcePositionToArray = position as PositionType[];
 
-      return coercionPositionTypeArray.map((ele: PositionType) => ({
+      return forcePositionToArray.map((ele: PositionType) => ({
         left: ele.left / scale,
         bottom: (pageHeight - ele.bottom) / scale,
         right: ele.right / scale,
@@ -145,10 +159,12 @@ export const parsePositionForBackend = (
     case 'Ink': {
       if (Array.isArray(position)) {
         const points = position as PointType[][];
-        return [points[0].map(point => ({
-          x: point.x / scale,
-          y: (pageHeight - point.y) / scale,
-        }))];
+        return [
+          points[0].map(point => ({
+            x: point.x / scale,
+            y: (pageHeight - point.y) / scale,
+          })),
+        ];
       }
       const point = position as PointType;
       return {
@@ -167,13 +183,14 @@ export const parsePositionForBackend = (
 };
 
 type GetAbsoluteCoordinateType = (
-  (
-    parentElement: HTMLElement | SVGSVGElement | SVGCircleElement | null,
-    clickEvent: any
-  ) => { x: number; y: number }
-);
-
-export const getAbsoluteCoordinate: GetAbsoluteCoordinateType = (parentElement, clickEvent) => {
+  parentElement: HTMLElement | SVGSVGElement | SVGCircleElement | null,
+  clickEvent: any
+) => { x: number; y: number };
+
+export const getAbsoluteCoordinate: GetAbsoluteCoordinateType = (
+  parentElement,
+  clickEvent
+) => {
   if (!parentElement) return { x: 0, y: 0 };
 
   let pageX = 0;

+ 57 - 46
helpers/utility.ts

@@ -1,16 +1,15 @@
 /* eslint-disable no-underscore-dangle */
 import { fromEvent } from 'rxjs';
-import {
-  auditTime,
-  throttleTime,
-} from 'rxjs/operators';
+import { debounceTime, throttleTime } from 'rxjs/operators';
 
 import { ScrollStateType, CoordType } from '../constants/type';
 
-export const objIsEmpty = (obj: Record<string, any>): boolean => !Object.keys(obj).length;
+export const objIsEmpty = (obj: Record<string, any>): boolean =>
+  !Object.keys(obj).length;
 
 export const watchScroll = (
-  viewAreaElement: HTMLElement | null, cb: (state: ScrollStateType) => void,
+  viewAreaElement: HTMLElement | null,
+  cb: (state: ScrollStateType) => void
 ): ScrollStateType => {
   let rAF: number | null = null;
   const element = viewAreaElement as HTMLElement;
@@ -46,10 +45,9 @@ export const watchScroll = (
     });
   };
 
-  const subscriber = fromEvent(element, 'scroll').pipe(
-    throttleTime(160),
-    auditTime(250),
-  ).subscribe(debounceScroll);
+  const subscriber = fromEvent(element, 'scroll')
+    .pipe(throttleTime(150), debounceTime(150))
+    .subscribe(debounceScroll);
 
   state.subscriber = subscriber;
 
@@ -57,7 +55,9 @@ export const watchScroll = (
 };
 
 export const scrollIntoView = (
-  element: HTMLElement, spot?: {top: number}, skipOverflowHiddenElements = false,
+  element: HTMLElement,
+  spot?: { top: number },
+  skipOverflowHiddenElements = false
 ): void => {
   let parent: HTMLElement = element.offsetParent as HTMLElement;
   let offsetY = element.offsetTop + element.clientTop;
@@ -66,8 +66,10 @@ export const scrollIntoView = (
     return; // no need to scroll
   }
   while (
-    (parent.clientHeight === parent.scrollHeight && parent.clientWidth === parent.scrollWidth)
-    || (skipOverflowHiddenElements && getComputedStyle(parent).overflow === 'hidden')
+    (parent.clientHeight === parent.scrollHeight &&
+      parent.clientWidth === parent.scrollWidth) ||
+    (skipOverflowHiddenElements &&
+      getComputedStyle(parent).overflow === 'hidden')
   ) {
     if (parent.dataset._scaleY) {
       offsetY /= parseInt(parent.dataset._scaleY, 10);
@@ -109,28 +111,29 @@ export const downloadFileWithUri = (name: string, uri: string): void => {
   ele.remove();
 };
 
-export const uploadFile = (extension: string): Promise<any> => new Promise((resolve) => {
-  const fileInput = document.createElement('input');
-  fileInput.type = 'file';
-  fileInput.accept = extension;
-  fileInput.onchange = (): void => {
-    if (fileInput.files) {
-      const reader = new FileReader();
-      reader.onload = (): void => {
-        const contents = reader.result;
-        resolve(contents);
-      };
+export const uploadFile = (extension: string): Promise<any> =>
+  new Promise(resolve => {
+    const fileInput = document.createElement('input');
+    fileInput.type = 'file';
+    fileInput.accept = extension;
+    fileInput.onchange = (): void => {
+      if (fileInput.files) {
+        const reader = new FileReader();
+        reader.onload = (): void => {
+          const contents = reader.result;
+          resolve(contents);
+        };
 
-      if (extension.includes('xfdf')) {
-        reader.readAsText(fileInput.files[0]);
-      } else {
-        reader.readAsDataURL(fileInput.files[0]);
+        if (extension.includes('xfdf')) {
+          reader.readAsText(fileInput.files[0]);
+        } else {
+          reader.readAsDataURL(fileInput.files[0]);
+        }
       }
-    }
-  };
-  document.body.appendChild(fileInput);
-  fileInput.click();
-});
+    };
+    document.body.appendChild(fileInput);
+    fileInput.click();
+  });
 
 const componentToHex = (c: number): string => {
   const hex = c.toString(16);
@@ -139,20 +142,20 @@ const componentToHex = (c: number): string => {
 
 export const hexToRgb = (hex: string): any => {
   const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
-  return result ? {
-    r: parseInt(result[1], 16),
-    g: parseInt(result[2], 16),
-    b: parseInt(result[3], 16),
-  } : null;
+  return result
+    ? {
+        r: parseInt(result[1], 16),
+        g: parseInt(result[2], 16),
+        b: parseInt(result[3], 16),
+      }
+    : null;
 };
 
-export const rgbToHex = (r: number, g: number, b: number): string => (
-  `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`
-);
+export const rgbToHex = (r: number, g: number, b: number): string =>
+  `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
 
-export const floatToHex = (r: number, g: number, b: number): string => (
-  rgbToHex(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255))
-);
+export const floatToHex = (r: number, g: number, b: number): string =>
+  rgbToHex(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255));
 
 type ScaleDataType = CoordType & {
   operator: string;
@@ -163,7 +166,15 @@ type ScaleDataType = CoordType & {
 };
 
 export const calcDragAndDropScale = ({
-  top, left, width = 0, height = 0, operator, clickX, clickY, moveX, moveY,
+  top,
+  left,
+  width = 0,
+  height = 0,
+  operator,
+  clickX,
+  clickY,
+  moveX,
+  moveY,
 }: ScaleDataType): CoordType => {
   let scaleCoord = {
     left,
@@ -243,5 +254,5 @@ type NormalizeRoundFunc = (num: number, fractionDigits?: number) => number;
 
 export const normalizeRound: NormalizeRoundFunc = (num, fractionDigits) => {
   const frac = fractionDigits || FRACTIONDIGITS;
-  return Math.round(num * (10 ** frac)) / (10 ** frac);
+  return Math.round(num * 10 ** frac) / 10 ** frac;
 };

+ 0 - 56
hooks/useAutoSave.ts

@@ -1,56 +0,0 @@
-import { useEffect } from 'react';
-import { Subscription, interval } from 'rxjs';
-import { take } from 'rxjs/operators';
-
-import useActions from '../actions';
-import useStore from '../store';
-import { saveFile } from '../apis';
-
-const useAutoSave = (): [boolean, boolean] => {
-  const [{
-    info,
-    annotations,
-    isInit,
-    isSaved,
-    isSaving,
-  }, dispatch] = useStore();
-  const {
-    setSaved,
-    setSaving,
-  } = useActions(dispatch);
-  let subscription: Subscription | null = null;
-
-  useEffect(() => {
-    if (info.id && isInit) {
-      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, isInit]);
-
-  return [isSaved, isSaving];
-};
-
-export default useAutoSave;

+ 57 - 55
hooks/useCursorPosition.ts

@@ -1,10 +1,6 @@
-import {
-  useState, useRef, useCallback, useEffect,
-} from 'react';
+import { useState, useRef, useCallback, useEffect } from 'react';
 import { fromEvent, Subscription } from 'rxjs';
-import {
-  throttleTime,
-} from 'rxjs/operators';
+import { throttleTime } from 'rxjs/operators';
 
 import { getAbsoluteCoordinate } from '../helpers/position';
 
@@ -20,10 +16,11 @@ type CursorPosition = {
 };
 
 type UseCursorPositionType = (
-  (throttleTime?: number) => [
-    CursorPosition, (element: HTMLElement| SVGSVGElement | SVGCircleElement | null) => void
-  ]
-);
+  throttleTime?: number
+) => [
+  CursorPosition,
+  (element: HTMLElement | SVGSVGElement | SVGCircleElement | null) => void
+];
 
 const initialState = {
   x: null,
@@ -36,53 +33,54 @@ const initialState = {
 
 const useCursorPosition: UseCursorPositionType = (time = defaultTime) => {
   const [state, setState] = useState<CursorPosition>(initialState);
-  const [
-    element, setElement,
-  ] = useState<HTMLElement | SVGSVGElement | SVGCircleElement | null>(null);
+  const [element, setElement] = useState<
+    HTMLElement | SVGSVGElement | SVGCircleElement | null
+  >(null);
   const entered = useRef(false);
   const isDown = useRef(false);
 
-  const onMouseMoveEvent = useCallback((e: MouseEvent | Event): void => {
-    if (element) {
-      const {
-        clientX,
-        clientY,
-        screenX,
-        screenY,
-      } = e as MouseEvent;
-      const coordinate = getAbsoluteCoordinate(element, e as MouseEvent);
-
-      setState({
-        x: coordinate.x,
-        y: coordinate.y,
-        clientX,
-        clientY,
-        screenX,
-        screenY,
-      });
-    }
-  }, [element]);
+  const onMouseMoveEvent = useCallback(
+    (e: MouseEvent | Event): void => {
+      if (element) {
+        const { clientX, clientY, screenX, screenY } = e as MouseEvent;
+        const coordinate = getAbsoluteCoordinate(element, e as MouseEvent);
+
+        setState({
+          x: coordinate.x,
+          y: coordinate.y,
+          clientX,
+          clientY,
+          screenX,
+          screenY,
+        });
+      }
+    },
+    [element]
+  );
 
-  const onTouchMoveEvent = useCallback((e: TouchEvent | Event): void => {
-    if (element) {
-      const {
-        clientX,
-        clientY,
-        screenX,
-        screenY,
-      } = (e as TouchEvent).touches[0];
-      const coordinate = getAbsoluteCoordinate(element, e as TouchEvent);
-
-      setState({
-        x: coordinate.x,
-        y: coordinate.y,
-        clientX,
-        clientY,
-        screenX,
-        screenY,
-      });
-    }
-  }, [element]);
+  const onTouchMoveEvent = useCallback(
+    (e: TouchEvent | Event): void => {
+      if (element) {
+        const {
+          clientX,
+          clientY,
+          screenX,
+          screenY,
+        } = (e as TouchEvent).touches[0];
+        const coordinate = getAbsoluteCoordinate(element, e as TouchEvent);
+
+        setState({
+          x: coordinate.x,
+          y: coordinate.y,
+          clientX,
+          clientY,
+          screenX,
+          screenY,
+        });
+      }
+    },
+    [element]
+  );
 
   useEffect(() => {
     let mouseSubscription: Subscription | null = null;
@@ -118,8 +116,12 @@ const useCursorPosition: UseCursorPositionType = (time = defaultTime) => {
     };
 
     if (element) {
-      mouseSubscription = fromEvent(element, 'mousemove').pipe(throttleTime(time)).subscribe(onMouseMoveEvent);
-      touchSubscription = fromEvent(element, 'touchmove').pipe(throttleTime(time)).subscribe(onTouchMoveEvent);
+      mouseSubscription = fromEvent(element, 'mousemove')
+        .pipe(throttleTime(time))
+        .subscribe(onMouseMoveEvent);
+      touchSubscription = fromEvent(element, 'touchmove')
+        .pipe(throttleTime(time))
+        .subscribe(onTouchMoveEvent);
 
       const addEvent = element.addEventListener.bind(element);
       addEvent('mouseenter', onEnter);

+ 27 - 0
hooks/useKeyPress.ts

@@ -0,0 +1,27 @@
+import { useEffect, useState } from 'react';
+
+const useKeyPress = (targetKey: string) => {
+  const [keyPressed, setKeyPressed] = useState(false);
+
+  const downHandler = ({ key }: { key: string }) => {
+    if (key === targetKey) setKeyPressed(true);
+  };
+
+  const upHandler = ({ key }: { key: string }) => {
+    if (key === targetKey) setKeyPressed(false);
+  };
+
+  useEffect(() => {
+    window.addEventListener('keydown', downHandler);
+    window.addEventListener('keyup', upHandler);
+
+    return () => {
+      window.removeEventListener('keydown', downHandler);
+      window.removeEventListener('keyup', upHandler);
+    };
+  }, []);
+
+  return keyPressed;
+};
+
+export default useKeyPress;

+ 9 - 9
package.json

@@ -21,6 +21,7 @@
     "isomorphic-unfetch": "^3.0.0",
     "lodash": "^4.17.15",
     "next": "9.3.4",
+    "next-i18next": "4.3.0",
     "pdfjs-dist": "2.3.200",
     "query-string": "5",
     "react": "16.13.0",
@@ -52,15 +53,14 @@
     "babel-eslint": "^10.0.1",
     "babel-jest": "^25.0.0",
     "babel-plugin-styled-components": "^1.7.1",
-    "eslint": "^5.6.0",
-    "eslint-config-airbnb": "^17.1.0",
-    "eslint-config-prettier": "^6.7.0",
-    "eslint-import-resolver-typescript": "^2.0.0",
-    "eslint-plugin-import": "^2.18.2",
-    "eslint-plugin-jsx-a11y": "^6.1.1",
-    "eslint-plugin-prettier": "^3.1.1",
-    "eslint-plugin-react": "^7.11.1",
-    "eslint-plugin-react-hooks": "^2.2.0",
+    "eslint": "^6.8.0",
+    "eslint-config-airbnb": "^18.1.0",
+    "eslint-config-prettier": "^6.11.0",
+    "eslint-plugin-import": "^2.20.2",
+    "eslint-plugin-jsx-a11y": "^6.2.3",
+    "eslint-plugin-prettier": "^3.1.3",
+    "eslint-plugin-react": "^7.19.0",
+    "eslint-plugin-react-hooks": "^2.5.1",
     "jest": "24.9.0",
     "jest-styled-components": "^6.2.1",
     "pre-commit": "^1.2.2",

+ 2 - 4
reducers/index.ts

@@ -2,9 +2,9 @@ import * as mainActions from './main';
 import * as pdfActions from './pdf';
 import * as types from '../constants/actionTypes';
 
-const createReducer = (handlers: {[key: string]: any}) => (
+const createReducer = (handlers: { [key: string]: any }) => (
   state: Record<string, any>,
-  action: { type: string },
+  action: { type: string }
 ): any => {
   if (!Object.prototype.hasOwnProperty.call(handlers, action.type)) {
     return state;
@@ -19,8 +19,6 @@ export default createReducer({
   [types.SET_SIDEBAR]: mainActions.setSidebarState,
   [types.SET_MARKUP_TOOL]: mainActions.setMarkupTool,
   [types.SET_INFO]: mainActions.setInfo,
-  [types.SET_SAVED]: mainActions.setSaved,
-  [types.SET_SAVING]: mainActions.setSaving,
 
   [types.SET_CURRENT_PAGE]: pdfActions.setCurrentPage,
   [types.SET_TOTAL_PAGE]: pdfActions.setTotalPage,

+ 0 - 11
reducers/main.ts

@@ -1,4 +1,3 @@
-
 import { ReducerFuncType } from '../constants/type';
 
 export const toggleDisplayMode: ReducerFuncType = (state, { payload }) => ({
@@ -25,13 +24,3 @@ export const setInfo: ReducerFuncType = (state, { payload }) => ({
   ...state,
   info: payload,
 });
-
-export const setSaved: ReducerFuncType = (state, { payload }) => ({
-  ...state,
-  isSaved: payload,
-});
-
-export const setSaving: ReducerFuncType = (state, { payload }) => ({
-  ...state,
-  isSaving: payload,
-});

+ 4 - 6
reducers/middleware/index.ts

@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/camelcase */
 import { Dispatch } from 'react';
 import {
   CHANGE_SCALE,
@@ -7,11 +6,10 @@ import {
   SET_MARKUP_TOOL,
 } from '../../constants/actionTypes';
 
-const applyMiddleware = (
-  state: any, dispatch: Dispatch<any>,
-) => (
-  action: { type: string; payload?: any },
-): void => {
+const applyMiddleware = (state: any, dispatch: Dispatch<any>) => (action: {
+  type: string;
+  payload?: any;
+}): void => {
   dispatch(action);
   switch (action.type) {
     case TOGGLE_DISPLAY_MODE: {

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1364 - 976
yarn.lock