Sfoglia il codice sorgente

refactor for UX version

RoyLiu 4 anni fa
parent
commit
bbb41cb912
78 ha cambiato i file con 1329 aggiunte e 611 eliminazioni
  1. 3 3
      __test__/button.test.tsx
  2. 6 6
      __test__/textField.test.tsx
  3. 2 2
      actions/main.ts
  4. 23 11
      components/AnnotationItem/data.ts
  5. 7 0
      components/AnnotationItem/index.tsx
  6. 4 6
      components/AnnotationItem/styled.ts
  7. 7 2
      components/AnnotationList/index.tsx
  8. 22 23
      components/Button/styled.ts
  9. 2 2
      components/ClickAwayListener.tsx
  10. 22 0
      components/Cursor/index.tsx
  11. 41 0
      components/Cursor/styled.ts
  12. 2 3
      components/Divider/styled.ts
  13. 2 4
      components/ExpansionPanel/styled.ts
  14. 1 1
      components/FreeText/index.tsx
  15. 1 3
      components/FreeText/styled.ts
  16. 5 1
      components/Icon/data.ts
  17. 20 4
      components/Icon/index.tsx
  18. 1 1
      components/Icon/styled.ts
  19. 70 0
      components/Image/index.tsx
  20. 8 0
      components/Image/styled.ts
  21. 71 0
      components/InputBox/index.tsx
  22. 55 0
      components/InputBox/styled.ts
  23. 0 1
      components/Line/index.tsx
  24. 1 3
      components/Modal/styled.ts
  25. 3 3
      components/OuterRect/index.tsx
  26. 3 3
      components/OuterRectForLine/index.tsx
  27. 1 1
      components/Page/styled.ts
  28. 5 7
      components/Pagination/styled.ts
  29. 2 2
      components/PdfSkeleton/styled.ts
  30. 5 7
      components/Search/styled.ts
  31. 49 39
      components/SelectBox/index.tsx
  32. 5 7
      components/SelectBox/styled.ts
  33. 9 13
      components/Shape/index.tsx
  34. 1 3
      components/Skeleton/styled.ts
  35. 3 4
      components/Sliders/styled.ts
  36. 3 5
      components/Tabs/styled.ts
  37. 72 47
      components/TextField/index.tsx
  38. 20 51
      components/TextField/styled.ts
  39. 1 3
      components/Thumbnail/styled.ts
  40. 1 1
      components/ThumbnailViewer/index.tsx
  41. 1 1
      components/Tooltip/styled.ts
  42. 9 8
      components/Typography/styled.ts
  43. 2 3
      components/Viewer/index.tsx
  44. 3 3
      components/Viewer/styled.ts
  45. 7 3
      components/WatermarkTextBox/index.tsx
  46. 1 1
      constants/actionTypes.ts
  47. 7 0
      constants/index.ts
  48. 0 35
      constants/style.ts
  49. 7 0
      containers/Annotation.tsx
  50. 91 0
      containers/CheckboxTool.tsx
  51. 0 62
      containers/CreateForm.tsx
  52. 85 0
      containers/FormTools.tsx
  53. 2 2
      containers/FreeTextTools.tsx
  54. 2 6
      containers/FreehandTools.tsx
  55. 110 0
      containers/ImageTool.tsx
  56. 33 0
      containers/InsertCursor.tsx
  57. 51 61
      containers/MarkupTools.tsx
  58. 2 10
      containers/PdfPages.tsx
  59. 12 41
      containers/PdfViewer.tsx
  60. 95 0
      containers/RadioButtonTool.tsx
  61. 27 23
      containers/Sidebar.tsx
  62. 2 4
      containers/StickyNoteTools.tsx
  63. 94 0
      containers/TextfieldTool.tsx
  64. 7 5
      containers/WatermarkTool.tsx
  65. 8 1
      custom.d.ts
  66. 2 10
      global/otherStyled.ts
  67. 2 4
      global/toolStyled.ts
  68. 44 1
      helpers/annotation.ts
  69. 1 1
      helpers/globalStyles.css
  70. 14 9
      helpers/position.ts
  71. 31 0
      helpers/theme.ts
  72. 6 1
      pages/_app.js
  73. 0 2
      pages/index.tsx
  74. 1 1
      reducers/index.ts
  75. 2 2
      reducers/main.ts
  76. 3 24
      reducers/middleware/index.ts
  77. 1 1
      store/index.tsx
  78. 5 14
      store/initialMainState.ts

+ 3 - 3
__test__/button.test.tsx

@@ -5,7 +5,7 @@ import { render, cleanup, fireEvent } from '@testing-library/react';
 import '@testing-library/jest-dom';
 
 import Button from '../components/Button';
-import { color } from '../constants/style';
+import theme from '../helpers/theme';
 
 describe('Button component', () => {
   afterEach(cleanup);
@@ -22,8 +22,8 @@ describe('Button component', () => {
     );
 
     expect(getByText('btn content')).toHaveStyle(`
-      background-color: ${color.primary};
-      border-color: ${color.primary};
+      background-color: ${theme.colors.primary};
+      border-color: ${theme.colors.primary};
       color: white;
     `);
   });

+ 6 - 6
__test__/textField.test.tsx

@@ -4,25 +4,25 @@ import React from 'react';
 import { render, cleanup, fireEvent } from '@testing-library/react';
 import '@testing-library/jest-dom';
 
-import TextField from '../components/TextField';
+import InputBox from '../components/InputBox';
 
-describe('TextField component', () => {
+describe('Input Box component', () => {
   afterEach(cleanup);
 
   test('input element', () => {
-    const { container } = render(<TextField />);
+    const { container } = render(<InputBox />);
     const inputNode = container.querySelector('input') as HTMLElement;
     expect(inputNode.tagName).toBe('INPUT');
   });
 
   test('textarea element', () => {
-    const { container } = render(<TextField variant="multiline" />);
+    const { container } = render(<InputBox variant="multiline" />);
     const textAreaNode = container.querySelector('TextArea') as HTMLElement;
     expect(textAreaNode.tagName).toBe('TEXTAREA');
   });
 
   test('onChange event', () => {
-    const { container } = render(<TextField />);
+    const { container } = render(<InputBox />);
     const inputNode = container.querySelector('input') as HTMLInputElement;
     fireEvent.change(inputNode, { target: { value: 'text' } });
 
@@ -32,7 +32,7 @@ describe('TextField component', () => {
   test('check disabled input', () => {
     const handleChange = jest.fn();
     const { getByTestId } = render(
-      <TextField disabled onChange={handleChange} />
+      <InputBox disabled onChange={handleChange} />
     );
     fireEvent.change(getByTestId('input'), { target: { value: 'text' } });
 

+ 2 - 2
actions/main.ts

@@ -7,8 +7,8 @@ const actions: ActionType = dispatch => ({
     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 }),
+  setTool: (state: string): void =>
+    dispatch({ type: types.SET_TOOL, payload: state }),
   setInfo: (state: Record<string, any>): void =>
     dispatch({ type: types.SET_INFO, payload: state }),
   setLoading: (state: boolean): void =>

+ 23 - 11
components/AnnotationItem/data.ts

@@ -1,48 +1,60 @@
 const data: Record<string, any> = {
   Highlight: {
-    text: 'highlight',
+    text: 'Highlight',
     icon: 'highlight',
   },
   Underline: {
-    text: 'underline',
+    text: 'Underline',
     icon: 'underline',
   },
   Squiggly: {
-    text: 'squiggly',
+    text: 'Squiggly',
     icon: 'squiggly',
   },
   StrikeOut: {
-    text: 'strikeout',
+    text: 'Strikeout',
     icon: 'strikeout',
   },
   Ink: {
-    text: 'freehand',
+    text: 'Freehand',
     icon: 'freehand',
   },
   FreeText: {
-    text: 'text box',
+    text: 'Text box',
     icon: 'text',
   },
   Text: {
-    text: 'sticky note',
+    text: 'Sticky note',
     icon: 'sticky-note',
   },
   Square: {
-    text: 'square',
+    text: 'Square',
     icon: 'square',
   },
   Circle: {
-    text: 'circle',
+    text: 'Circle',
     icon: 'circle',
   },
   Line: {
-    text: 'line',
+    text: 'Line',
     icon: 'line',
   },
   Arrow: {
-    text: 'arrow line',
+    text: 'Arrow line',
     icon: 'arrow',
   },
+  textfield: {
+    text: 'Textfield',
+    icon: 'textfield',
+  },
+  checkbox: {
+    text: 'Checkbox',
+    icon: 'checkbox',
+  },
+  radio: {
+    text: 'Radio Button',
+    icon: 'radio-button',
+  },
 };
 
 export default data;

+ 7 - 0
components/AnnotationItem/index.tsx

@@ -55,4 +55,11 @@ const AnnotationItem = ({
   );
 };
 
+AnnotationItem.defaultProps = {
+  page: 0,
+  title: '',
+  date: '',
+  showPageNum: false,
+};
+
 export default AnnotationItem;

+ 4 - 6
components/AnnotationItem/styled.ts

@@ -1,18 +1,16 @@
 import styled from 'styled-components';
 
-import { color } from '../../constants/style';
-
 export const PageNumber = styled.div`
-  font-size: 12px;
+  font-size: 1rem;
   font-weight: bold;
-  color: ${color.primary};
+  color: ${({ theme }) => theme.colors.primary};
   text-align: right;
   margin-bottom: 5px;
 `;
 
 export const AnnotationBox = styled.div`
   border-radius: 4px;
-  border: solid 1px ${color.black38};
+  border: solid 1px ${({ theme }) => theme.colors.black38};
   padding: 12px;
   width: 235px;
   margin-bottom: 12px;
@@ -27,7 +25,7 @@ export const Group = styled.div`
   align-items: center;
 
   span {
-    font-size: 12px;
+    font-size: 1rem;
     color: gray;
   }
 

+ 7 - 2
components/AnnotationList/index.tsx

@@ -44,19 +44,24 @@ const AnnotationList: React.FC<Props> = ({
             const key = `annotation_item_${index}`;
             const {
               obj_type,
-              obj_attr: { page, title, date },
+              obj_attr: { page, title, date, style },
             } = ele;
             const actualPage = page + 1;
             const prevAnnot = annotations[index - 1];
             const prevPage =
               index > 0 && prevAnnot ? prevAnnot.obj_attr.page + 1 : -1;
 
+            let annotType = obj_type;
+            if (obj_type === 'checkbox' && style === '1') {
+              annotType = 'radio';
+            }
+
             return (
               <Item
                 key={key}
                 title={title}
                 date={date}
-                type={obj_type}
+                type={annotType}
                 page={actualPage}
                 showPageNum={actualPage !== prevPage}
               />

+ 22 - 23
components/Button/styled.ts

@@ -1,5 +1,4 @@
 import styled, { css } from 'styled-components';
-import { color } from '../../constants/style';
 
 const align: { [index: string]: string } = {
   left: 'flex-start',
@@ -11,7 +10,7 @@ const staticStyles = css`
   border-width: 0;
   border-radius: 4px;
   box-sizing: border-box;
-  font-size: 16px;
+  font-size: 1.2rem;
   font-style: normal;
   transition: all 0.3s linear;
   cursor: pointer;
@@ -20,39 +19,39 @@ const staticStyles = css`
   min-width: 80px;
 `;
 
-const theme: { [index: string]: any } = {
+const btnTheme: { [index: string]: any } = {
   default: css`
     color: black;
     background-color: transparent;
     border: 1.5px solid transparent;
 
     &:hover {
-      background-color: ${color['soft-blue']};
+      background-color: ${({ theme }) => theme.colors['soft-blue']};
     }
   `,
   primary: css`
     color: white;
-    background-color: ${color.primary};
-    border: 1.5px solid ${color.primary};
+    background-color: ${({ theme }) => theme.colors.primary};
+    border: 1.5px solid ${({ theme }) => theme.colors.primary};
 
     &:hover {
-      background-color: ${color['french-blue']};
-      border: 1.5px solid ${color['french-blue']};
+      background-color: ${({ theme }) => theme.colors['french-blue']};
+      border: 1.5px solid ${({ theme }) => theme.colors['french-blue']};
     }
   `,
   'primary-hollow': css`
     color: black;
     background-color: white;
-    border: 1.5px solid ${color.primary};
+    border: 1.5px solid ${({ theme }) => theme.colors.primary};
 
     &:hover {
-      background-color: ${color['primary-light']};
+      background-color: ${({ theme }) => theme.colors['primary-light']};
     }
   `,
   'danger-hollow': css`
-    color: ${color.danger};
+    color: ${({ theme }) => theme.colors.danger};
     background-color: white;
-    border: 1.5px solid ${color.danger};
+    border: 1.5px solid ${({ theme }) => theme.colors.danger};
 
     &:hover {
       background-color: white;
@@ -61,22 +60,22 @@ const theme: { [index: string]: any } = {
   'default-hollow': css`
     color: black;
     background-color: white;
-    border: 1.5px solid ${color.black56};
+    border: 1.5px solid ${({ theme }) => theme.colors.black56};
 
     &:hover {
       color: white;
-      background-color: ${color.black38};
+      background-color: ${({ theme }) => theme.colors.black38};
     }
   `,
   link: css`
-    color: ${color.primary};
+    color: ${({ theme }) => theme.colors.primary};
     background-color: transparent;
     border: none;
     text-decoration: underline;
     padding: 5px 0;
   `,
   'danger-link': css`
-    color: ${color.error};
+    color: ${({ theme }) => theme.colors.error};
     background-color: transparent;
     border: none;
     text-decoration: underline;
@@ -96,17 +95,17 @@ const theme: { [index: string]: any } = {
 
 const activeTheme: { [index: string]: any } = {
   default: css`
-    background-color: ${color['soft-blue']};
+    background-color: ${({ theme }) => theme.colors['soft-blue']};
   `,
   primary: css`
-    background-color: ${color['french-blue']};
-    border: 1.5px solid ${color['french-blue']};
+    background-color: ${({ theme }) => theme.colors['french-blue']};
+    border: 1.5px solid ${({ theme }) => theme.colors['french-blue']};
   `,
   'primary-hollow': css`
-    background-color: ${color['primary-light']};
+    background-color: ${({ theme }) => theme.colors['primary-light']};
   `,
   'default-hollow': css`
-    background-color: ${color.black38};
+    background-color: ${({ theme }) => theme.colors.black38};
   `,
 };
 
@@ -117,7 +116,7 @@ export const NormalButton = styled('div')<{
   isActive?: boolean;
 }>`
   ${staticStyles}
-  ${props => theme[props.appearance || 'default']};
+  ${props => btnTheme[props.appearance || 'default']};
   ${props =>
     props.shouldFitContainer
       ? css`
@@ -137,7 +136,7 @@ export const NormalButton = styled('div')<{
 export const DisableButton = styled.button`
   ${staticStyles}
 
-  color: ${color.black38};
+  color: ${({ theme }) => theme.colors.black38};
   background-color: #eeeff3;
   cursor: not-allowed;
 `;

+ 2 - 2
components/ClickAwayListener.tsx

@@ -31,9 +31,9 @@ const ClickAwayListener: React.FC<Props> = ({
   }, [onClick]);
 
   return (
-    <div ref={node} {...rest}>
+    <span ref={node} {...rest}>
       {children}
-    </div>
+    </span>
   );
 };
 

+ 22 - 0
components/Cursor/index.tsx

@@ -0,0 +1,22 @@
+import React from 'react';
+
+import { Zone } from './styled';
+
+type Props = {
+  appearance: ToolType | FormType | '';
+  x: number;
+  y: number;
+};
+
+const Cursor: React.FC<Props> = ({ appearance, x, y }: Props) => {
+  return (
+    <Zone
+      style={{ top: `${y + 5}px`, left: `${x + 5}px` }}
+      appearance={appearance}
+    >
+      <p>Click to insert</p>
+    </Zone>
+  );
+};
+
+export default Cursor;

+ 41 - 0
components/Cursor/styled.ts

@@ -0,0 +1,41 @@
+import styled, { css } from 'styled-components';
+
+const size: Record<string, any> = {
+  textfield: css`
+    width: 150px;
+    height: 50px;
+  `,
+  checkbox: css`
+    width: 50px;
+    height: 50px;
+  `,
+  radio: css`
+    border-radius: 100%;
+    width: 50px;
+    height: 50px;
+  `,
+};
+
+export const Zone = styled.div<{
+  appearance: ToolType | FormType | '';
+}>`
+  position: fixed;
+  background-color: rgba(88, 106, 242, 0.4);
+  width: 0;
+  height: 0;
+  border-radius: 8px;
+  z-index: 99999;
+
+  ${props => size[props.appearance]}
+
+  p {
+    font-size: 1rem;
+    color: rgb(88, 106, 242);
+    opacity: 0.6;
+    margin: auto;
+    margin-top: 50px;
+    text-align: center;
+  }
+`;
+
+export default Zone;

+ 2 - 3
components/Divider/styled.ts

@@ -1,7 +1,5 @@
 import styled, { css } from 'styled-components';
 
-import { color } from '../../constants/style';
-
 export const Component = styled('hr')<{
   light?: boolean;
   absolute?: boolean;
@@ -12,7 +10,8 @@ export const Component = styled('hr')<{
   margin: 6px 0;
   border: none;
   flex-shrink: 0;
-  background-color: ${props => (props.light ? 'white' : color['light-gray'])};
+  background-color: ${props =>
+    props.light ? 'white' : ({ theme }) => theme.colors['light-gray']};
 
   ${props =>
     props.absolute

+ 2 - 4
components/ExpansionPanel/styled.ts

@@ -1,7 +1,5 @@
 import styled, { css } from 'styled-components';
 
-import { color } from '../../constants/style';
-
 export const Container = styled('div')<{ showBottomBorder: boolean }>`
   display: inline-flex;
   flex-direction: column;
@@ -10,7 +8,7 @@ export const Container = styled('div')<{ showBottomBorder: boolean }>`
   ${props =>
     props.showBottomBorder
       ? css`
-          border-bottom: 1px solid ${color.black12};
+          border-bottom: 1px solid ${({ theme }) => theme.colors.black12};
         `
       : ''}
 `;
@@ -23,7 +21,7 @@ export const ContentWrapper = styled('div')<{ isCollapse: boolean }>`
   display: inline-block;
   transition: max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1) 0ms;
   max-height: ${props => (props.isCollapse ? '0px' : '800px')};
-  background-color: ${color['hyper-light-gray']};
+  background-color: ${({ theme }) => theme.colors['hyper-light-gray']};
   overflow: hidden;
 `;
 

+ 1 - 1
components/FreeText/index.tsx

@@ -6,7 +6,7 @@ import { rectCalc, parsePositionForBackend } from '../../helpers/position';
 import { AnnotationContainer } from '../../global/otherStyled';
 import { TextWrapper, Input } from './styled';
 
-const FreeText: React.SFC<AnnotationElementPropsType> = ({
+const FreeText: React.FC<AnnotationElementPropsType> = ({
   id,
   obj_type,
   obj_attr: { content, position, fontname, fontsize, textcolor },

+ 1 - 3
components/FreeText/styled.ts

@@ -1,7 +1,5 @@
 import styled from 'styled-components';
 
-import { color } from '../../constants/style';
-
 export const TextWrapper = styled.div`
   text-align: left;
   user-select: none;
@@ -9,7 +7,7 @@ export const TextWrapper = styled.div`
 `;
 
 export const Input = styled.input`
-  border: 1px solid ${color.primary};
+  border: 1px solid ${({ theme }) => theme.colors.primary};
   padding: 0;
   margin: 0;
   width: 95%;

+ 5 - 1
components/Icon/data.ts

@@ -77,6 +77,7 @@ import Trash2 from '../../public/icons/selector/scale_delete.svg';
 import RightArrow from '../../public/icons/right-arrow.svg';
 import Clear from '../../public/icons/sidebar/clear.svg';
 import None from '../../public/icons/sidebar/None.svg';
+import Dropdown from '../../public/icons/sidebar/Dropdown00.svg';
 
 const data: { [index: string]: any } = {
   close: {
@@ -222,7 +223,7 @@ const data: { [index: string]: any } = {
   fill: {
     Normal: Fill,
   },
-  'text-field': {
+  textfield: {
     Normal: TextField,
   },
   checkbox: {
@@ -267,6 +268,9 @@ const data: { [index: string]: any } = {
   none: {
     Normal: None,
   },
+  'dropdown-arrow': {
+    Normal: Dropdown,
+  },
 };
 
 export default data;

+ 20 - 4
components/Icon/index.tsx

@@ -15,12 +15,12 @@ type Props = {
 
 const Icon: React.FC<Props> = ({
   glyph,
-  id = '',
+  id,
   onClick,
   onBlur,
   style,
-  isActive = false,
-  isDisabled = false,
+  isActive,
+  isDisabled,
 }: Props) => {
   const { Normal, Hover } = data[glyph];
 
@@ -31,17 +31,33 @@ const Icon: React.FC<Props> = ({
       {isActive && Hover ? <Hover data-status="active" /> : null}
       <ClickZone
         id={id}
-        onMouseDown={
+        onClick={
           isDisabled
             ? (): void => {
                 // do something
               }
             : onClick
         }
+        onMouseDown={e => {
+          e.preventDefault();
+        }}
         onBlur={onBlur}
       />
     </IconWrapper>
   );
 };
 
+Icon.defaultProps = {
+  id: '',
+  onClick: () => {
+    // do something
+  },
+  onBlur: () => {
+    // do something
+  },
+  style: {},
+  isActive: false,
+  isDisabled: false,
+};
+
 export default Icon;

+ 1 - 1
components/Icon/styled.ts

@@ -2,7 +2,7 @@ import styled, { css } from 'styled-components';
 
 export const IconWrapper = styled('div')<{
   isHover: boolean;
-  isDisabled: boolean;
+  isDisabled?: boolean;
 }>`
   position: relative;
   display: inline-flex;

+ 70 - 0
components/Image/index.tsx

@@ -0,0 +1,70 @@
+import React from 'react';
+
+import OuterRect from '../OuterRect';
+import { rectCalc, parsePositionForBackend } from '../../helpers/position';
+
+import { AnnotationContainer } from '../../global/otherStyled';
+import Img from './styled';
+
+const Image: React.FC<AnnotationElementPropsType> = ({
+  id,
+  obj_type,
+  obj_attr: { src = '', position },
+  scale,
+  viewport,
+  isCollapse,
+  onUpdate,
+}: AnnotationElementPropsType) => {
+  const annotRect = rectCalc(position as PositionType, viewport.height, scale);
+
+  const handleScaleOrMove = ({
+    top,
+    left,
+    width = 0,
+    height = 0,
+  }: CoordType): void => {
+    const newPosition = {
+      top,
+      left,
+      bottom: top + (height || annotRect.height),
+      right: left + (width || annotRect.width),
+    };
+
+    onUpdate({
+      position: parsePositionForBackend(
+        obj_type,
+        newPosition,
+        viewport.height,
+        scale
+      ),
+    });
+  };
+
+  return (
+    <>
+      <AnnotationContainer
+        id={id}
+        top={`${annotRect.top}px`}
+        left={`${annotRect.left}px`}
+        width={`${annotRect.width}px`}
+        height={`${annotRect.height}px`}
+      >
+        <Img src={src} alt="" />
+      </AnnotationContainer>
+      {!isCollapse ? (
+        <OuterRect
+          top={annotRect.top}
+          left={annotRect.left}
+          width={annotRect.width}
+          height={annotRect.height}
+          onMove={handleScaleOrMove}
+          onScale={handleScaleOrMove}
+        />
+      ) : (
+        ''
+      )}
+    </>
+  );
+};
+
+export default Image;

+ 8 - 0
components/Image/styled.ts

@@ -0,0 +1,8 @@
+import styled from 'styled-components';
+
+const Img = styled.img`
+  width: 100%;
+  height: 100%;
+`;
+
+export default Img;

+ 71 - 0
components/InputBox/index.tsx

@@ -0,0 +1,71 @@
+import React, { forwardRef } from 'react';
+
+import { Input, TextArea } from './styled';
+
+type Props = {
+  id?: string;
+  name?: string;
+  onChange?: (val: string) => void;
+  onBlur?: () => void;
+  value?: string | number;
+  defaultValue?: string | number;
+  placeholder?: string;
+  disabled?: boolean;
+  error?: boolean;
+  shouldFitContainer?: boolean;
+  variant?: 'standard' | 'multiline';
+};
+
+type Ref = any;
+
+const InputBox = forwardRef<Ref, Props>(
+  (
+    { variant = 'standard', onChange, disabled = false, ...rest }: Props,
+    ref
+  ) => {
+    const handleChange = (
+      e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
+    ): void => {
+      if (onChange && !disabled) {
+        onChange(e.target.value);
+      }
+    };
+
+    return variant === 'standard' ? (
+      <Input
+        data-testid="input"
+        ref={ref}
+        disabled={disabled}
+        onChange={handleChange}
+        {...rest}
+      />
+    ) : (
+      <TextArea
+        ref={ref}
+        disabled={disabled}
+        onChange={handleChange}
+        {...rest}
+      />
+    );
+  }
+);
+
+InputBox.defaultProps = {
+  id: '',
+  name: '',
+  onChange: () => {
+    // do something
+  },
+  onBlur: () => {
+    // do something
+  },
+  value: '',
+  defaultValue: undefined,
+  placeholder: '',
+  disabled: false,
+  error: false,
+  shouldFitContainer: false,
+  variant: 'standard',
+};
+
+export default InputBox;

+ 55 - 0
components/InputBox/styled.ts

@@ -0,0 +1,55 @@
+import styled, { css } from 'styled-components';
+
+const baseStyles = css`
+  border: 1.5px solid ${({ theme }) => theme.colors.black38};
+  border-radius: 4px;
+  padding: 7px 15px;
+  outline: none;
+  transition: border 200ms cubic-bezier(0, 0, 0.2, 1) 0ms;
+  font-size: 1.2rem;
+  box-sizing: border-box;
+
+  :disabled {
+    color: ${({ theme }) => theme.colors.black56};
+    cursor: not-allowed;
+  }
+`;
+
+export const Input = styled('input')<{
+  error?: boolean;
+  shouldFitContainer?: boolean;
+}>`
+  ${baseStyles}
+  width: ${props => (props.shouldFitContainer ? '100%' : 'auto')};
+
+  ${props =>
+    props.error
+      ? css`
+          border: 1.5px solid ${({ theme }) => theme.colors.error};
+        `
+      : css`
+          :focus {
+            border: 1.5px solid ${({ theme }) => theme.colors.primary};
+          }
+        `}
+`;
+
+export const TextArea = styled('textarea')<{
+  error?: boolean;
+  shouldFitContainer?: boolean;
+}>`
+  ${baseStyles}
+  height: 54px;
+  width: ${props => (props.shouldFitContainer ? '100%' : 'auto')};
+
+  ${props =>
+    props.error
+      ? css`
+          border: 1.5px solid ${({ theme }) => theme.colors.error};
+        `
+      : css`
+          :focus {
+            border: 1.5px solid ${({ theme }) => theme.colors.primary};
+          }
+        `}
+`;

+ 0 - 1
components/Line/index.tsx

@@ -3,7 +3,6 @@ import { v4 as uuidv4 } from 'uuid';
 
 import Outer from '../OuterRectForLine';
 import { parsePositionForBackend } from '../../helpers/position';
-
 import { AnnotationContainer } from '../../global/otherStyled';
 
 const Note: React.SFC<AnnotationElementPropsType> = ({

+ 1 - 3
components/Modal/styled.ts

@@ -1,7 +1,5 @@
 import styled from 'styled-components';
 
-import { color } from '../../constants/style';
-
 export const Container = styled.div`
   position: fixed;
   z-index: 100;
@@ -18,7 +16,7 @@ export const Blanket = styled.div`
   bottom: 0;
   top: 0;
   left: 0;
-  background-color: ${color.black05};
+  background-color: ${({ theme }) => theme.colors.black05};
 `;
 
 export const Content = styled.div`

+ 3 - 3
components/OuterRect/index.tsx

@@ -1,6 +1,6 @@
 import React, { useEffect, useState } from 'react';
 
-import { color } from '../../constants/style';
+import theme from '../../helpers/theme';
 import useCursorPosition from '../../hooks/useCursorPosition';
 import { getAbsoluteCoordinate } from '../../helpers/position';
 import { calcDragAndDropScale } from '../../helpers/utility';
@@ -157,7 +157,7 @@ const index: React.FC<Props> = ({
         y={6}
         width={width + 12}
         height={height + 12}
-        stroke={onScale ? color.primary : 'transparency'}
+        stroke={onScale ? theme.colors.primary : 'transparency'}
         onMouseDown={handleMouseDown}
         onTouchStart={handleMouseDown}
         data-id="move"
@@ -169,7 +169,7 @@ const index: React.FC<Props> = ({
             data-id={attr.direction}
             onMouseDown={handleMouseDown}
             onTouchStart={handleMouseDown}
-            fill={color.primary}
+            fill={theme.colors.primary}
             {...attr}
           />
         ))}

+ 3 - 3
components/OuterRectForLine/index.tsx

@@ -1,6 +1,6 @@
 import React, { useEffect, useState } from 'react';
 
-import { color } from '../../constants/style';
+import theme from '../../helpers/theme';
 import useCursorPosition from '../../hooks/useCursorPosition';
 import { getAbsoluteCoordinate } from '../../helpers/position';
 
@@ -119,7 +119,7 @@ const index: React.FC<Props> = ({
       <Circle
         data-id="start"
         onMouseDown={handleMouseDown}
-        fill={color.primary}
+        fill={theme.colors.primary}
         cx={completeStart.x + MARGIN_DISTANCE}
         cy={completeStart.y + MARGIN_DISTANCE}
         r={6}
@@ -127,7 +127,7 @@ const index: React.FC<Props> = ({
       <Circle
         data-id="end"
         onMouseDown={handleMouseDown}
-        fill={color.primary}
+        fill={theme.colors.primary}
         cx={completeEnd.x + MARGIN_DISTANCE}
         cy={completeEnd.y + MARGIN_DISTANCE}
         r={6}

+ 1 - 1
components/Page/styled.ts

@@ -80,5 +80,5 @@ export const Inner = styled.div`
   justify-content: center;
   align-items: center;
   height: 100%;
-  font-size: 18px;
+  font-size: 1.5rem;
 `;

+ 5 - 7
components/Pagination/styled.ts

@@ -1,19 +1,17 @@
 import styled, { css } from 'styled-components';
 
-import { color } from '../../constants/style';
-
 export const Container = styled.div`
   display: inline-flex;
   align-items: center;
 `;
 
 export const Text = styled.span`
-  font-size: 12px;
+  font-size: 1rem;
   margin: 8px;
 `;
 
 export const Input = styled.input`
-  background-color: ${color['light-gray']};
+  background-color: ${({ theme }) => theme.colors['light-gray']};
   outline: none;
   border: none;
   width: 70px;
@@ -28,7 +26,7 @@ export const ArrowButton = styled('button')<{ variant: string }>`
   width: 26px;
   outline: none;
   border: none;
-  background-color: ${color['light-gray']};
+  background-color: ${({ theme }) => theme.colors['light-gray']};
   cursor: pointer;
   position: relative;
   display: flex;
@@ -55,7 +53,7 @@ export const ArrowButton = styled('button')<{ variant: string }>`
             transform-origin: 50%;
           }
           :hover:after {
-            border-right: 5px solid ${color.black38};
+            border-right: 5px solid ${({ theme }) => theme.colors.black38};
           }
         `
       : css`
@@ -75,7 +73,7 @@ export const ArrowButton = styled('button')<{ variant: string }>`
             position: absolute;
           }
           :hover:after {
-            border-left: 5px solid ${color.black38};
+            border-left: 5px solid ${({ theme }) => theme.colors.black38};
           }
         `}
 `;

+ 2 - 2
components/PdfSkeleton/styled.ts

@@ -6,7 +6,7 @@ export const Container = styled.div`
   width: 730px;
   height: 900px;
   position: fixed;
-  left: 0;
+  left: 270px;
   right: 0;
   top: 0;
   margin: auto;
@@ -18,7 +18,7 @@ export const Container = styled.div`
 
 export const Text = styled.p`
   text-align: center;
-  font-size: 16px;
+  font-size: 1.35rem;
   color: #757575;
 `;
 

+ 5 - 7
components/Search/styled.ts

@@ -1,7 +1,5 @@
 import styled, { css } from 'styled-components';
 
-import { color } from '../../constants/style';
-
 export const Wrapper = styled('div')<{ open: boolean }>`
   border-bottom-left-radius: 4px;
   border-bottom-right-radius: 4px;
@@ -23,7 +21,7 @@ export const InputWrapper = styled.div`
   border-top-left-radius: 4px;
   border-bottom-left-radius: 4px;
   overflow: hidden;
-  background-color: ${color['light-gray']};
+  background-color: ${({ theme }) => theme.colors['light-gray']};
   display: flex;
   justify-content: space-between;
   align-items: center;
@@ -31,7 +29,7 @@ export const InputWrapper = styled.div`
 `;
 
 export const Input = styled.input`
-  background-color: ${color['light-gray']};
+  background-color: ${({ theme }) => theme.colors['light-gray']};
   outline: none;
   border: none;
   width: 66%;
@@ -51,7 +49,7 @@ export const ArrowButton = styled('button')<{ variant: string }>`
   width: 30px;
   outline: none;
   border: none;
-  background-color: ${color['light-gray']};
+  background-color: ${({ theme }) => theme.colors['light-gray']};
   cursor: pointer;
   position: relative;
   display: flex;
@@ -71,7 +69,7 @@ export const ArrowButton = styled('button')<{ variant: string }>`
             border-bottom: 5px solid black;
           }
           :hover:after {
-            border-bottom: 5px solid ${color.black38};
+            border-bottom: 5px solid ${({ theme }) => theme.colors.black38};
           }
         `
       : css`
@@ -87,7 +85,7 @@ export const ArrowButton = styled('button')<{ variant: string }>`
             border-top: 5px solid black;
           }
           :hover:after {
-            border-top: 5px solid ${color.black38};
+            border-top: 5px solid ${({ theme }) => theme.colors.black38};
           }
         `}
 `;

+ 49 - 39
components/SelectBox/index.tsx

@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
 
 import Icon from '../Icon';
 import Divider from '../Divider';
+import ClickAwayListener from '../ClickAwayListener';
 
 import {
   Container,
@@ -13,7 +14,7 @@ import {
 } from './styled';
 
 type Props = {
-  onChange?: (item: SelectOptionType) => void;
+  onChange: (item: SelectOptionType) => void;
   options: SelectOptionType[];
   defaultValue?: React.ReactNode;
   isDivide?: boolean;
@@ -25,8 +26,8 @@ const SelectBox: React.FC<Props> = ({
   onChange,
   options,
   defaultValue,
-  isDivide = false,
-  useInput = false,
+  isDivide,
+  useInput,
   style,
 }: Props) => {
   const selectRef = useRef<HTMLDivElement>(null);
@@ -86,43 +87,52 @@ const SelectBox: React.FC<Props> = ({
   });
 
   return (
-    <Container style={style}>
-      <Selected
-        ref={selectRef}
-        onMouseDown={handleClick}
-        onBlur={handleBlur}
-        tabIndex={0}
-        data-testid="selected"
-      >
-        {useInput &&
-        (typeof value === 'string' || typeof value === 'number') ? (
-          <InputContent
-            value={value}
-            onChange={handleChange}
-            onKeyDown={handleKeyDown}
-          />
-        ) : (
-          <Content>{value}</Content>
-        )}
-        {isDivide ? <Divider /> : null}
-        <Icon glyph="down-arrow" />
-      </Selected>
-      {!isCollapse ? (
-        <OptionWrapper ref={optionRef} data-testid="option-list">
-          {options.map((option: SelectOptionType, index: number) => (
-            <Option
-              key={option.key}
-              onMouseDown={(): void => {
-                handleSelect(index);
-              }}
-            >
-              {option.content}
-            </Option>
-          ))}
-        </OptionWrapper>
-      ) : null}
-    </Container>
+    <ClickAwayListener onClick={handleBlur}>
+      <Container style={style}>
+        <Selected
+          ref={selectRef}
+          onMouseDown={handleClick}
+          onBlur={handleBlur}
+          tabIndex={0}
+          data-testid="selected"
+        >
+          {useInput &&
+          (typeof value === 'string' || typeof value === 'number') ? (
+            <InputContent
+              value={value}
+              onChange={handleChange}
+              onKeyDown={handleKeyDown}
+            />
+          ) : (
+            <Content>{value}</Content>
+          )}
+          {isDivide ? <Divider /> : null}
+          <Icon glyph="down-arrow" />
+        </Selected>
+        {!isCollapse ? (
+          <OptionWrapper ref={optionRef} data-testid="option-list">
+            {options.map((option: SelectOptionType, index: number) => (
+              <Option
+                key={option.key}
+                onMouseDown={(): void => {
+                  handleSelect(index);
+                }}
+              >
+                {option.content}
+              </Option>
+            ))}
+          </OptionWrapper>
+        ) : null}
+      </Container>
+    </ClickAwayListener>
   );
 };
 
+SelectBox.defaultProps = {
+  isDivide: false,
+  useInput: false,
+  style: {},
+  defaultValue: '',
+};
+
 export default SelectBox;

+ 5 - 7
components/SelectBox/styled.ts

@@ -1,11 +1,9 @@
-// @ts-ignore
 import styled from 'styled-components';
-import { color } from '../../constants/style';
 
 export const Container = styled.div`
   position: relative;
   cursor: pointer;
-  font-size: 12px;
+  font-size: 1rem;
   min-width: 74px;
   display: inline-block;
 `;
@@ -14,7 +12,7 @@ export const Selected = styled.div`
   box-sizing: border-box;
   display: inline-flex;
   align-items: center;
-  background-color: ${color.gray};
+  background-color: ${({ theme }) => theme.colors.gray};
   height: 32px;
   border-radius: 4px;
   padding: 0 6px 0 10px;
@@ -24,14 +22,14 @@ export const Selected = styled.div`
 `;
 
 export const InputContent = styled.input`
-  background-color: ${color.gray};
+  background-color: ${({ theme }) => theme.colors.gray};
   border: none;
   outline: none;
   width: 50%;
 `;
 
 export const Content = styled.div`
-  background-color: ${color.gray};
+  background-color: ${({ theme }) => theme.colors.gray};
   border: none;
   outline: none;
   width: 100%;
@@ -58,6 +56,6 @@ export const Option = styled.span`
   padding: 5px 24px 5px 16px;
 
   :hover {
-    background-color: ${color['soft-blue']};
+    background-color: ${({ theme }) => theme.colors['soft-blue']};
   }
 `;

+ 9 - 13
components/Shape/index.tsx

@@ -7,7 +7,7 @@ import { rectCalc, parsePositionForBackend } from '../../helpers/position';
 
 import { AnnotationContainer } from '../../global/otherStyled';
 
-const Note: React.SFC<AnnotationElementPropsType> = ({
+const Shape: React.FC<AnnotationElementPropsType> = ({
   id,
   obj_type,
   obj_attr: {
@@ -48,26 +48,22 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
     });
   };
 
-  const actualbdwidth = bdwidth * scale;
-  const actualWidth = annotRect.width;
-  const actualHeight = annotRect.height;
-
   return (
     <>
       <AnnotationContainer
         id={id}
         top={`${annotRect.top}px`}
         left={`${annotRect.left}px`}
-        width={`${actualWidth}px`}
-        height={`${actualHeight}px`}
+        width={`${annotRect.width}px`}
+        height={`${annotRect.height}px`}
       >
         <SvgShapeElement
           shape={obj_type}
-          width={actualWidth}
-          height={actualHeight}
+          width={annotRect.width}
+          height={annotRect.height}
           bdcolor={bdcolor}
           transparency={transparency}
-          bdwidth={actualbdwidth}
+          bdwidth={bdwidth * scale}
           fcolor={fcolor}
           ftransparency={ftransparency}
         />
@@ -76,8 +72,8 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
         <OuterRect
           top={annotRect.top}
           left={annotRect.left}
-          width={actualWidth}
-          height={actualHeight}
+          width={annotRect.width}
+          height={annotRect.height}
           onMove={handleScaleOrMove}
           onScale={handleScaleOrMove}
         />
@@ -88,4 +84,4 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
   );
 };
 
-export default Note;
+export default Shape;

+ 1 - 3
components/Skeleton/styled.ts

@@ -1,7 +1,5 @@
 import styled, { css, keyframes } from 'styled-components';
 
-import { color } from '../../constants/style';
-
 const animate = keyframes`
   0% {
     opacity: 1;
@@ -35,7 +33,7 @@ const shape: { [index: string]: any } = {
 
 export const Element = styled('div')<{ variant: string }>`
   display: block;
-  background-color: ${color.gray};
+  background-color: ${({ theme }) => theme.colors.gray};
   animation: ${animate} 1.5s ease-in-out infinite;
   margin: 5px 0;
 

+ 3 - 4
components/Sliders/styled.ts

@@ -1,5 +1,4 @@
 import styled, { css } from 'styled-components';
-import { color } from '../../constants/style';
 
 export const OuterWrapper = styled.div`
   padding: 10px;
@@ -9,7 +8,7 @@ export const Wrapper = styled.div`
   height: 10px;
   width: 100%;
   border-radius: 7px;
-  background-color: ${color.black23};
+  background-color: ${({ theme }) => theme.colors.black23};
   position: relative;
   cursor: pointer;
 `;
@@ -18,7 +17,7 @@ export const Rail = styled('div')<{ track: number }>`
   height: 10px;
   width: ${props => props.track}%;
   border-radius: 7px;
-  background-color: ${color.primary};
+  background-color: ${({ theme }) => theme.colors.primary};
 `;
 
 export const Track = styled('span')<{ track: number; isActive: boolean }>`
@@ -26,7 +25,7 @@ export const Track = styled('span')<{ track: number; isActive: boolean }>`
   width: 20px;
   height: 20px;
   border-radius: 10px;
-  background-color: ${color['french-blue']};
+  background-color: ${({ theme }) => theme.colors['french-blue']};
   cursor: pointer;
   top: -5px;
   left: calc(${props => props.track}% - 10px);

+ 3 - 5
components/Tabs/styled.ts

@@ -1,7 +1,5 @@
 import styled, { css } from 'styled-components';
 
-import { color } from '../../constants/style';
-
 export const Wrapper = styled.div`
   width: 100%;
 `;
@@ -21,15 +19,15 @@ export const Btn = styled('button')<{ isActive?: boolean }>`
   background-color: white;
   border: none;
   cursor: pointer;
-  color: ${color.primary};
-  font-size: 14px;
+  color: ${({ theme }) => theme.colors.primary};
+  font-size: 1.2rem;
   font-weight: bold;
   box-sizing: border-box;
 
   ${props =>
     props.isActive
       ? css`
-          background-color: ${color.primary};
+          background-color: ${({ theme }) => theme.colors.primary};
           color: white;
         `
       : null}

+ 72 - 47
components/TextField/index.tsx

@@ -1,53 +1,78 @@
-import React, { forwardRef } from 'react';
-
-import { Input, TextArea } from './styled';
-
-type Props = {
-  id?: string;
-  name?: string;
-  onChange?: (val: string) => void;
-  onBlur?: () => void;
-  value?: string | number;
-  defaultValue?: string | number;
-  placeholder?: string;
-  disabled?: boolean;
-  error?: boolean;
-  shouldFitContainer?: boolean;
-  variant?: 'standard' | 'multiline';
-};
+import React from 'react';
+
+import OuterRect from '../OuterRect';
+import { rectCalc, parsePositionForBackend } from '../../helpers/position';
+import { AnnotationContainer } from '../../global/otherStyled';
+
+import { TextBox } from './styled';
+
+const TextField: React.FC<AnnotationElementPropsType> = ({
+  id,
+  obj_type,
+  obj_attr: { position, style },
+  scale,
+  viewport,
+  isCollapse,
+  onUpdate,
+}: AnnotationElementPropsType) => {
+  const annotRect = rectCalc(position as PositionType, viewport.height, scale);
 
-type Ref = any;
-
-const TextField = forwardRef<Ref, Props>(
-  (
-    { variant = 'standard', onChange, disabled = false, ...rest }: Props,
-    ref
-  ) => {
-    const handleChange = (
-      e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
-    ): void => {
-      if (onChange && !disabled) {
-        onChange(e.target.value);
-      }
+  const handleScaleOrMove = ({
+    top,
+    left,
+    width = 0,
+    height = 0,
+  }: CoordType): void => {
+    const newPosition = {
+      top,
+      left,
+      bottom: top + (height || annotRect.height),
+      right: left + (width || annotRect.width),
     };
 
-    return variant === 'standard' ? (
-      <Input
-        data-testid="input"
-        ref={ref}
-        disabled={disabled}
-        onChange={handleChange}
-        {...rest}
-      />
-    ) : (
-      <TextArea
-        ref={ref}
-        disabled={disabled}
-        onChange={handleChange}
-        {...rest}
-      />
-    );
+    onUpdate({
+      fontsize: (height || annotRect.height) / scale,
+      position: parsePositionForBackend(
+        obj_type,
+        newPosition,
+        viewport.height,
+        scale
+      ),
+    });
+  };
+
+  let formType = obj_type;
+  if (obj_type === 'checkbox' && style === '1') {
+    formType = 'radio';
   }
-);
+
+  return (
+    <>
+      <AnnotationContainer
+        id={id}
+        top={`${annotRect.top}px`}
+        left={`${annotRect.left}px`}
+        width={`${annotRect.width}px`}
+        height={`${annotRect.height}px`}
+      >
+        <TextBox type={formType}>
+          {obj_type === 'textfield' ? '輸入框' : ''}
+        </TextBox>
+      </AnnotationContainer>
+      {!isCollapse ? (
+        <OuterRect
+          top={annotRect.top}
+          left={annotRect.left}
+          width={annotRect.width}
+          height={annotRect.height}
+          onMove={handleScaleOrMove}
+          onScale={handleScaleOrMove}
+        />
+      ) : (
+        ''
+      )}
+    </>
+  );
+};
 
 export default TextField;

+ 20 - 51
components/TextField/styled.ts

@@ -1,57 +1,26 @@
 import styled, { css } from 'styled-components';
 
-import { color } from '../../constants/style';
+const style: Record<string, any> = {
+  textfield: '',
+  checkbox: css`
+    border-radius: 8px;
+  `,
+  radio: css`
+    border-radius: 100%;
+  `,
+};
 
-const baseStyles = css`
-  border: 1.5px solid ${color.black38};
-  border-radius: 4px;
-  padding: 7px 15px;
-  outline: none;
-  transition: border 200ms cubic-bezier(0, 0, 0.2, 1) 0ms;
-  font-size: 14px;
-  box-sizing: border-box;
+export const TextBox = styled.div<{ type: string }>`
+  width: 100%;
+  height: 100%;
+  background-color: rgba(51, 190, 219, 0.3);
+  font-size: 1.3rem;
+  color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
 
-  :disabled {
-    color: ${color.black56};
-    cursor: not-allowed;
-  }
+  ${props => style[props.type]}
 `;
 
-export const Input = styled('input')<{
-  error?: boolean;
-  shouldFitContainer?: boolean;
-}>`
-  ${baseStyles}
-  width: ${props => (props.shouldFitContainer ? '100%' : 'auto')};
-
-  ${props =>
-    props.error
-      ? css`
-          border: 1.5px solid ${color.error};
-        `
-      : css`
-          :focus {
-            border: 1.5px solid ${color.primary};
-          }
-        `}
-`;
-
-export const TextArea = styled('textarea')<{
-  error?: boolean;
-  shouldFitContainer?: boolean;
-}>`
-  ${baseStyles}
-  height: 54px;
-  width: ${props => (props.shouldFitContainer ? '100%' : 'auto')};
-
-  ${props =>
-    props.error
-      ? css`
-          border: 1.5px solid ${color.error};
-        `
-      : css`
-          :focus {
-            border: 1.5px solid ${color.primary};
-          }
-        `}
-`;
+export default TextBox;

+ 1 - 3
components/Thumbnail/styled.ts

@@ -1,7 +1,5 @@
 import styled from 'styled-components';
 
-import { color } from '../../constants/style';
-
 export const Wrapper = styled.div`
   display: flex;
   justify-content: center;
@@ -16,7 +14,7 @@ export const PageNum = styled.div`
 `;
 
 export const Img = styled.img`
-  border: 1px solid ${color.black38};
+  border: 1px solid ${({ theme }) => theme.colors.black38};
   width: auto;
   height: 100%;
   box-sizing: border-box;

+ 1 - 1
components/ThumbnailViewer/index.tsx

@@ -10,7 +10,7 @@ import { Container, Head, IconWrapper } from '../../global/sidebarStyled';
 import { ThumbnailsWrapper, Body } from './styled';
 
 type Props = {
-  isActive?: boolean;
+  isActive: boolean;
   close: () => void;
   currentPage: number;
   totalPage: number;

+ 1 - 1
components/Tooltip/styled.ts

@@ -10,7 +10,7 @@ export const TooltipContainer = styled.div`
   z-index: 1000;
   color: #fff;
   border-radius: 2px;
-  font-size: 12px;
+  font-size: 1rem;
   background-color: rgba(0, 0, 0, 0.6);
   min-height: 50px;
   min-width: 86px;

+ 9 - 8
components/Typography/styled.ts

@@ -1,26 +1,27 @@
 import styled from 'styled-components';
 
-import { color } from '../../constants/style';
-
 export const Title = styled('p')<{ light?: boolean; align?: string }>`
-  font-size: 16px;
-  color: ${props => (props.light ? color.black38 : '#000000')};
+  font-size: 1.35rem;
+  color: ${props =>
+    props.light ? ({ theme }) => theme.colors.black38 : '#000000'};
   margin: 5px;
   white-space: nowrap;
   text-align: ${props => (props.align ? props.align : 'center')};
 `;
 
 export const Subtitle = styled('p')<{ light?: boolean; align?: string }>`
-  font-size: 14px;
-  color: ${props => (props.light ? color.black38 : '#000000')};
+  font-size: 1.2rem;
+  color: ${props =>
+    props.light ? ({ theme }) => theme.colors.black38 : '#000000'};
   margin: 5px;
   white-space: nowrap;
   text-align: ${props => (props.align ? props.align : 'center')};
 `;
 
 export const Body = styled('p')<{ light?: boolean; align?: string }>`
-  font-size: 14px;
-  color: ${props => (props.light ? color.black38 : '#000000')};
+  font-size: 1.2rem;
+  color: ${props =>
+    props.light ? ({ theme }) => theme.colors.black38 : '#000000'};
   margin: 5px;
   text-align: ${props => (props.align ? props.align : 'center')};
 `;

+ 2 - 3
components/Viewer/index.tsx

@@ -6,17 +6,16 @@ type Props = {
   children: React.ReactNode;
   viewport: ViewportType;
   rotation: number;
-  displayMode: string;
 };
 type Ref = HTMLDivElement;
 
 const Viewer = forwardRef<Ref, Props>(
-  ({ children, viewport, rotation, displayMode }: Props, ref) => {
+  ({ children, viewport, rotation }: Props, ref) => {
     const width =
       (Math.abs(rotation) / 90) % 2 === 1 ? viewport.height : viewport.width;
 
     return (
-      <OuterWrapper id="pdf_viewer" ref={ref} isFull={displayMode === 'full'}>
+      <OuterWrapper id="pdf_viewer" ref={ref}>
         <Wrapper width={width}>{children}</Wrapper>
       </OuterWrapper>
     );

+ 3 - 3
components/Viewer/styled.ts

@@ -1,10 +1,10 @@
 import styled from 'styled-components';
 
-export const OuterWrapper = styled('div')<{ isFull: boolean }>`
+export const OuterWrapper = styled.div`
   position: fixed;
-  left: ${props => (props.isFull ? 0 : '267px')};
+  left: 267px;
   right: 0;
-  top: ${props => (props.isFull ? '-34px' : '60px')};
+  top: 60px;
   bottom: 0;
   overflow: scroll;
   text-align: center;

+ 7 - 3
components/WatermarkTextBox/index.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import TextField from '../TextField';
+import InputBox from '../InputBox';
 import Typography from '../Typography';
 
 import { ContentWrapper } from './styled';
@@ -14,13 +14,13 @@ type Props = {
 const TextBox: React.FC<Props> = ({
   t,
   onChange,
-  value = '',
+  value,
 }: Props): React.ReactElement => (
   <>
     <ContentWrapper>
       <Typography variant="subtitle">{t('text')}</Typography>
     </ContentWrapper>
-    <TextField
+    <InputBox
       variant="multiline"
       onChange={onChange}
       value={value}
@@ -29,4 +29,8 @@ const TextBox: React.FC<Props> = ({
   </>
 );
 
+TextBox.defaultProps = {
+  value: '',
+};
+
 export default TextBox;

+ 1 - 1
constants/actionTypes.ts

@@ -1,7 +1,7 @@
 export const TOGGLE_DISPLAY_MODE = 'TOGGLE_DISPLAY_MODE';
 export const SET_NAVBAR = 'SET_NAVBAR';
 export const SET_SIDEBAR = 'SET_SIDEBAR';
-export const SET_MARKUP_TOOL = 'SET_MARKUP_TOOL';
+export const SET_TOOL = 'SET_TOOL';
 export const SET_INFO = 'SET_INFO';
 export const SET_LOADING = 'SET_LOADING';
 

+ 7 - 0
constants/index.ts

@@ -9,8 +9,14 @@ export const MARKUP_TYPE: Record<string, any> = {
   strikeout: 'StrikeOut',
 };
 
+export const FORM_TYPE: Record<string, any> = {
+  textfield: 'textfield',
+  checkbox: 'checkbox',
+};
+
 export const ANNOTATION_TYPE: Record<string, any> = {
   ...MARKUP_TYPE,
+  ...FORM_TYPE,
   ink: 'Ink',
   freetext: 'FreeText',
   text: 'Text',
@@ -18,4 +24,5 @@ export const ANNOTATION_TYPE: Record<string, any> = {
   circle: 'Circle',
   line: 'Line',
   arrow: 'Line',
+  image: 'Image',
 };

+ 0 - 35
constants/style.ts

@@ -1,35 +0,0 @@
-export const mediaRule = {
-  mobileS: '320px',
-  mobileM: '375px',
-  mobileL: '425px',
-  tablet: '768px',
-  laptop: '1024px',
-  desktop: '1440px',
-};
-
-export const color: { [index: string]: any } = {
-  primary: '#586af2',
-  'light-primary': 'rgba(88, 106, 242, 0.6)',
-  secondary: '#2d3e4e',
-  'primary-light': '#d7def9',
-  'french-blue': '#3645b4',
-  'lemon-yellow': '#fcff36',
-  'strong-pink': '#ff1b89',
-  'neon-green': '#02ff36',
-  'light-blue': '#27befd',
-  info: '#2979ff',
-  success: '#43a047',
-  error: '#d32f2f',
-  danger: '#fe2712',
-  warning: '#ffa000',
-  gray: '#d8d8d8',
-  'light-gray': '#e5e5e5',
-  'hyper-light-gray': '#f2f2f2',
-  'soft-blue': 'rgba(94, 112, 241, 0.12)',
-  black05: 'rgba(0, 0, 0, 0.05)',
-  black12: 'rgba(0, 0, 0, 0.12)',
-  black23: 'rgba(0, 0, 0, 0.23)',
-  black38: 'rgba(0, 0, 0, 0.38)',
-  black56: 'rgba(0, 0, 0, 0.56)',
-  black87: 'rgba(0, 0, 0, 0.87)',
-};

+ 7 - 0
containers/Annotation.tsx

@@ -7,6 +7,8 @@ import FreeText from '../components/FreeText';
 import StickyNote from '../components/StickyNote';
 import Shape from '../components/Shape';
 import Line from '../components/Line';
+import Image from '../components/Image';
+import TextField from '../components/TextField';
 import DeleteDialog from '../components/DeleteDialog';
 import useCursorPosition from '../hooks/useCursorPosition';
 import ClickAwayListener from '../components/ClickAwayListener';
@@ -153,6 +155,11 @@ const Annotation: React.FC<Props> = ({
             case 'Line':
             case 'Arrow':
               return <Line {...childProps} />;
+            case 'Image':
+              return <Image {...childProps} />;
+            case 'textfield':
+            case 'checkbox':
+              return <TextField {...childProps} />;
             default:
               return <Highlight {...childProps} />;
           }

+ 91 - 0
containers/CheckboxTool.tsx

@@ -0,0 +1,91 @@
+import React, { useEffect, useCallback } from 'react';
+
+import { ANNOTATION_TYPE } from '../constants';
+import Button from '../components/Button';
+import Icon from '../components/Icon';
+import { getAbsoluteCoordinate } from '../helpers/position';
+import {
+  parseAnnotationObject,
+  appendUserIdAndDate,
+} from '../helpers/annotation';
+
+import useActions from '../actions';
+import useStore from '../store';
+
+import { BtnWrapper } from '../global/toolStyled';
+
+type Props = {
+  title: string;
+  isActive: boolean;
+  onClick: () => void;
+};
+
+const CheckboxTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
+  const [{ scale, viewport }, dispatch] = useStore();
+  const { addAnnots } = useActions(dispatch);
+
+  const addCheckbox = (
+    pageEle: HTMLElement,
+    event: MouseEvent | TouchEvent
+  ): void => {
+    const pageNum = pageEle.getAttribute('data-page-num') || 0;
+    const coordinate = getAbsoluteCoordinate(pageEle, event);
+
+    const annotData = {
+      obj_type: ANNOTATION_TYPE.checkbox,
+      obj_attr: {
+        page: pageNum as number,
+        style: '0',
+        position: {
+          top: coordinate.y,
+          left: coordinate.x,
+          bottom: coordinate.y + 50,
+          right: coordinate.x + 50,
+        },
+      },
+    };
+
+    const newCheckbox = appendUserIdAndDate(
+      parseAnnotationObject(annotData, viewport.height, scale)
+    );
+
+    addAnnots([newCheckbox]);
+  };
+
+  const handleClick = useCallback(
+    (event: MouseEvent | TouchEvent): void => {
+      const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
+
+      if (pageEle.hasAttribute('data-page-num')) {
+        addCheckbox(pageEle, event);
+      }
+    },
+    [viewport, scale]
+  );
+
+  useEffect(() => {
+    if (isActive) {
+      document.addEventListener('click', handleClick);
+    }
+
+    return () => {
+      document.removeEventListener('click', handleClick);
+    };
+  }, [isActive]);
+
+  return (
+    <BtnWrapper>
+      <Button
+        shouldFitContainer
+        align="left"
+        isActive={isActive}
+        onClick={onClick}
+      >
+        <Icon glyph="checkbox" style={{ marginRight: '10px' }} />
+        {title}
+      </Button>
+    </BtnWrapper>
+  );
+};
+
+export default CheckboxTool;

+ 0 - 62
containers/CreateForm.tsx

@@ -1,62 +0,0 @@
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-
-import Button from '../components/Button';
-import Icon from '../components/Icon';
-import ExpansionPanel from '../components/ExpansionPanel';
-
-import useActions from '../actions';
-import useStore from '../store';
-import { BtnWrapper } from '../global/toolStyled';
-
-const CreateForm: React.FC = () => {
-  const { t } = useTranslation('sidebar');
-  const [{ sidebarState }, dispatch] = useStore();
-  const { setSidebar } = useActions(dispatch);
-
-  const onClickSidebar = (state: string): void => {
-    if (state === sidebarState) {
-      setSidebar('');
-    } else {
-      setSidebar(state);
-    }
-  };
-
-  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' }} />
-          {t('textField')}
-        </Button>
-      </BtnWrapper>
-      <BtnWrapper>
-        <Button shouldFitContainer align="left">
-          <Icon glyph="checkbox" style={{ marginRight: '10px' }} />
-          {t('checkBox')}
-        </Button>
-      </BtnWrapper>
-      <BtnWrapper>
-        <Button shouldFitContainer align="left">
-          <Icon glyph="radio-button" style={{ marginRight: '10px' }} />
-          {t('radioButton')}
-        </Button>
-      </BtnWrapper>
-    </ExpansionPanel>
-  );
-};
-
-export default CreateForm;

+ 85 - 0
containers/FormTools.tsx

@@ -0,0 +1,85 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import Button from '../components/Button';
+import Icon from '../components/Icon';
+import ExpansionPanel from '../components/ExpansionPanel';
+
+import TextfieldTool from './TextfieldTool';
+import CheckboxTool from './CheckboxTool';
+import RadioButtonTool from './RadioButtonTool';
+
+import useStore from '../store';
+import useActions from '../actions';
+
+type Props = {
+  sidebarState: string;
+  onClickSidebar: (state: string) => void;
+};
+
+const FormTools: React.FC<Props> = ({
+  sidebarState,
+  onClickSidebar,
+}: Props) => {
+  const { t } = useTranslation('sidebar');
+  const [{ toolState }, dispatch] = useStore();
+  const { setTool } = useActions(dispatch);
+
+  const isActive = sidebarState === 'create-form';
+
+  const onClickTool = (state: string): void => {
+    if (state === toolState) {
+      setTool('');
+    } else {
+      setTool(state);
+    }
+  };
+
+  const Label = (
+    <Button
+      shouldFitContainer
+      align="left"
+      onClick={(): void => {
+        onClickSidebar('create-form');
+      }}
+    >
+      <Icon glyph="create-form" style={{ marginRight: '10px' }} />
+      {t('createForm')}
+      <Icon
+        glyph="dropdown-arrow"
+        style={{
+          transform: isActive ? 'rotate(180deg)' : 'rotate(0deg)',
+          marginLeft: '10px',
+        }}
+      />
+    </Button>
+  );
+
+  return (
+    <ExpansionPanel label={Label} isActive={isActive}>
+      <TextfieldTool
+        title={t('textField')}
+        isActive={toolState === 'textfield'}
+        onClick={(): void => {
+          onClickTool('textfield');
+        }}
+      />
+      <CheckboxTool
+        title={t('checkBox')}
+        isActive={toolState === 'checkbox'}
+        onClick={(): void => {
+          onClickTool('checkbox');
+        }}
+      />
+      <RadioButtonTool
+        title={t('radioButton')}
+        isActive={toolState === 'radio'}
+        onClick={(): void => {
+          onClickTool('radio');
+        }}
+      />
+    </ExpansionPanel>
+  );
+};
+
+export default FormTools;

+ 2 - 2
containers/FreeTextTools.tsx

@@ -21,7 +21,7 @@ type Props = {
   onClick: () => void;
 };
 
-const HighlightTools: React.FC<Props> = ({
+const HighlightTool: React.FC<Props> = ({
   title,
   isActive,
   onClick,
@@ -131,4 +131,4 @@ const HighlightTools: React.FC<Props> = ({
   );
 };
 
-export default HighlightTools;
+export default HighlightTool;

+ 2 - 6
containers/FreehandTools.tsx

@@ -27,11 +27,7 @@ type Props = {
   onClick: () => void;
 };
 
-const FreehandTools: React.FC<Props> = ({
-  title,
-  isActive,
-  onClick,
-}: Props) => {
+const FreehandTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
   const [cursorPosition, setRef] = useCursorPosition(40);
 
   const [uuid, setUuid] = useState('');
@@ -198,4 +194,4 @@ const FreehandTools: React.FC<Props> = ({
   );
 };
 
-export default FreehandTools;
+export default FreehandTool;

+ 110 - 0
containers/ImageTool.tsx

@@ -0,0 +1,110 @@
+import React, { useRef } from 'react';
+import { useTranslation } from 'react-i18next';
+import styled from 'styled-components';
+
+import Button from '../components/Button';
+import Icon from '../components/Icon';
+import { ANNOTATION_TYPE } from '../constants';
+import {
+  parseAnnotationObject,
+  appendUserIdAndDate,
+} from '../helpers/annotation';
+
+import useStore from '../store';
+import useActions from '../actions';
+
+import { BtnWrapper } from '../global/toolStyled';
+
+type Props = {
+  onClickSidebar: (state: string) => void;
+};
+
+const FileInput = styled.input`
+  display: none;
+`;
+
+const ImageTool: React.FC<Props> = ({ onClickSidebar }: Props) => {
+  const { t } = useTranslation('sidebar');
+  const inputRef = useRef<HTMLInputElement>(null);
+  const [{ currentPage, viewport, scale }, dispatch] = useStore();
+  const { addAnnots } = useActions(dispatch);
+
+  const handleClick = () => {
+    onClickSidebar('add-image');
+
+    if (inputRef.current) {
+      inputRef.current.click();
+    }
+  };
+
+  const getImgDimension = (url: string) => {
+    return new Promise(resolve => {
+      const img = new Image();
+      img.src = url;
+      img.onload = () => {
+        resolve({ width: img.width, height: img.height });
+      };
+    });
+  };
+
+  const handleFiles = (e: React.FormEvent<EventTarget>) => {
+    e.stopPropagation();
+    e.preventDefault();
+
+    if (inputRef.current && inputRef.current.files) {
+      const file = inputRef.current.files[0];
+
+      if (file) {
+        const objectUrl = window.URL.createObjectURL(file);
+
+        getImgDimension(objectUrl).then(({ width, height }: any) => {
+          let objWidth = width;
+          let objHeight = height;
+
+          if (width > viewport.width || height > viewport.height) {
+            const widthRate = viewport.width / width;
+            objWidth = width * widthRate - 100;
+            objHeight = height * widthRate - 100;
+          }
+
+          const annotData = {
+            obj_type: ANNOTATION_TYPE.image,
+            obj_attr: {
+              page: currentPage,
+              position: {
+                top: viewport.height / 2 - objHeight / 2,
+                left: viewport.width / 2 - objWidth / 2,
+                bottom: viewport.height / 2 + objHeight / 2,
+                right: viewport.width / 2 + objWidth / 2,
+              },
+              src: objectUrl,
+            },
+          };
+
+          const imageObj = appendUserIdAndDate(
+            parseAnnotationObject(annotData, viewport.height, scale)
+          );
+
+          addAnnots([imageObj]);
+        });
+      }
+    }
+  };
+
+  return (
+    <BtnWrapper>
+      <Button shouldFitContainer align="left" onClick={handleClick}>
+        <Icon glyph="add-image" style={{ marginRight: '10px' }} />
+        {t('addImages')}
+      </Button>
+      <FileInput
+        type="file"
+        ref={inputRef}
+        accept="image/*"
+        onChange={handleFiles}
+      />
+    </BtnWrapper>
+  );
+};
+
+export default ImageTool;

+ 33 - 0
containers/InsertCursor.tsx

@@ -0,0 +1,33 @@
+import React, { useEffect } from 'react';
+
+import Cursor from '../components/Cursor';
+import useCursorPosition from '../hooks/useCursorPosition';
+
+import useStore from '../store';
+
+const InsertCursor = () => {
+  const [{ toolState }] = useStore();
+  const [cursorPosition, setRef] = useCursorPosition(25);
+
+  useEffect(() => {
+    const viewer = document.getElementById('pdf_viewer') as HTMLDivElement;
+
+    if (toolState && viewer) {
+      setRef(viewer);
+      viewer.style.cursor = 'crosshair';
+    } else if (viewer) {
+      setRef(null);
+      viewer.style.cursor = 'auto';
+    }
+  }, [toolState]);
+
+  return ['textfield', 'checkbox', 'radio'].includes(toolState) ? (
+    <Cursor
+      x={cursorPosition.clientX || -1000}
+      y={cursorPosition.clientY || -1000}
+      appearance={toolState}
+    />
+  ) : null;
+};
+
+export default InsertCursor;

+ 51 - 61
containers/MarkupTools.tsx

@@ -1,109 +1,99 @@
-import React, { useEffect } from 'react';
-import { useRouter } from 'next/router';
+import React from 'react';
 import { useTranslation } from 'react-i18next';
 
-// import Button from '../components/Button';
-// import ExpansionPanel from '../components/ExpansionPanel';
-// import Icon from '../components/Icon';
+import Button from '../components/Button';
+import ExpansionPanel from '../components/ExpansionPanel';
+import Icon from '../components/Icon';
 import HighlightTools from './HighlightTools';
-import FreehandTools from './FreehandTools';
-import TextTools from './FreeTextTools';
-import StickyNoteTools from './StickyNoteTools';
+import FreehandTool from './FreehandTool';
+import TextTool from './FreeTextTool';
+import StickyNoteTool from './StickyNoteTool';
 import ShapeTools from './ShapeTools';
-import WatermarkTool from './WatermarkTool';
 
 import useActions from '../actions';
 import useStore from '../store';
 
-const MarkupTools: React.FC = () => {
-  const { t } = useTranslation('sidebar');
-  const [{ sidebarState }, dispatch] = useStore();
-  const { setSidebar } = useActions(dispatch);
-
-  const router = useRouter();
+type Props = {
+  sidebarState: string;
+  onClickSidebar: (state: string) => void;
+};
 
-  // const onClickSidebar = (state: string): void => {
-  //   if (state === sidebarState) {
-  //     setSidebar('');
-  //   } else {
-  //     setSidebar(state);
-  //   }
-  // };
+const MarkupTools: React.FC<Props> = ({
+  sidebarState,
+  onClickSidebar,
+}: Props) => {
+  const { t } = useTranslation('sidebar');
+  const [{ toolState }, dispatch] = useStore();
+  const { setTool } = useActions(dispatch);
 
   const onClickTool = (state: string): void => {
-    if (state === sidebarState) {
-      setSidebar('');
+    if (state === toolState) {
+      setTool('');
     } else {
-      setSidebar(state);
+      setTool(state);
     }
   };
 
-  // useEffect(() => {
-  //   if (sidebarState !== 'markup-tools') {
-  //     setSidebar('');
-  //   }
-  // }, []);
-
-  // const Label = (
-  //   <Button
-  //     shouldFitContainer
-  //     align="left"
-  //     onClick={(): void => {
-  //       onClickSidebar('markup-tools');
-  //     }}
-  //   >
-  //     <Icon glyph="markup-tools" style={{ marginRight: '10px' }} />
-  //     {t('markupTool')}
-  //   </Button>
-  // );
+  const isActive = sidebarState === 'markup-tools';
 
-  useEffect(() => {
-    if (router.query.tool === 'shape') {
-      setSidebar('shape');
-    } else {
-      setSidebar('highlight');
-    }
-  }, [router]);
+  const Label = (
+    <Button
+      shouldFitContainer
+      align="left"
+      onClick={(): void => {
+        onClickSidebar('markup-tools');
+      }}
+    >
+      <Icon glyph="markup-tools" style={{ marginRight: '10px' }} />
+      {t('markupTool')}
+      <Icon
+        glyph="dropdown-arrow"
+        style={{
+          transform: isActive ? 'rotate(180deg)' : 'rotate(0deg)',
+          marginLeft: '10px',
+        }}
+      />
+    </Button>
+  );
 
   return (
-    <>
+    <ExpansionPanel label={Label} isActive={isActive}>
       <HighlightTools
         title={t('annotate')}
-        isActive={sidebarState === 'highlight'}
+        isActive={toolState === 'highlight'}
         onClick={(): void => {
           onClickTool('highlight');
         }}
       />
-      <FreehandTools
+      <FreehandTool
         title={t('freehand')}
-        isActive={sidebarState === 'freehand'}
+        isActive={toolState === 'freehand'}
         onClick={(): void => {
           onClickTool('freehand');
         }}
       />
-      <TextTools
+      <TextTool
         title={t('textBox')}
-        isActive={sidebarState === 'text'}
+        isActive={toolState === 'text'}
         onClick={(): void => {
           onClickTool('text');
         }}
       />
-      <StickyNoteTools
+      <StickyNoteTool
         title={t('stickyNote')}
-        isActive={sidebarState === 'sticky'}
+        isActive={toolState === 'sticky'}
         onClick={(): void => {
           onClickTool('sticky');
         }}
       />
       <ShapeTools
         title={t('shape')}
-        isActive={sidebarState === 'shape'}
+        isActive={toolState === 'shape'}
         onClick={(): void => {
           onClickTool('shape');
         }}
       />
-      <WatermarkTool />
-    </>
+    </ExpansionPanel>
   );
 };
 

+ 2 - 10
containers/PdfPages.tsx

@@ -17,10 +17,7 @@ let timer = 0;
 
 const PdfPages: React.FC<Props> = ({ scrollToUpdate }: Props) => {
   const containerRef = useRef<HTMLDivElement>(null);
-  const [
-    { totalPage, scale, viewport, rotation, displayMode },
-    dispatch,
-  ] = useStore();
+  const [{ totalPage, scale, viewport, rotation }, dispatch] = useStore();
   const { changeScale } = useActions(dispatch);
   const [zoom] = useGestureScale(containerRef);
 
@@ -43,12 +40,7 @@ const PdfPages: React.FC<Props> = ({ scrollToUpdate }: Props) => {
   }, [zoom]);
 
   return (
-    <Viewer
-      ref={containerRef}
-      viewport={viewport}
-      rotation={rotation}
-      displayMode={displayMode}
-    >
+    <Viewer ref={containerRef} viewport={viewport} rotation={rotation}>
       {totalPage &&
         Array(totalPage)
           .fill(1)

+ 12 - 41
containers/PdfViewer.tsx

@@ -1,4 +1,4 @@
-import React, { useEffect, useRef } from 'react';
+import React, { useEffect } from 'react';
 import queryString from 'query-string';
 import config from '../config';
 import apiPath from '../constants/apiPath';
@@ -6,18 +6,17 @@ import apiPath from '../constants/apiPath';
 import { initialPdfFile, fetchXfdf } from '../apis';
 import PdfPages from './PdfPages';
 import { fetchPdf } from '../helpers/pdf';
-import { scrollIntoView } from '../helpers/utility';
-import { parseAnnotationFromXml } from '../helpers/annotation';
+import {
+  parseAnnotationFromXml,
+  parseFormElementFromXml,
+} from '../helpers/annotation';
 import { parseWatermarkFromXml } from '../helpers/watermark';
 
 import useActions from '../actions';
 import useStore from '../store';
 
 const PdfViewer: React.FC = () => {
-  const [
-    { viewport, pdf, scale, currentPage, totalPage },
-    dispatch,
-  ] = useStore();
+  const [{ viewport, pdf, scale }, dispatch] = useStore();
   const {
     setTotalPage,
     setPdf,
@@ -29,7 +28,6 @@ const PdfViewer: React.FC = () => {
     changeScale,
     updateWatermark,
   } = useActions(dispatch);
-  const currentPageRef = useRef(0);
 
   const setLoadingProgress = (totalSize: number) => (
     progress: ProgressType
@@ -53,13 +51,14 @@ const PdfViewer: React.FC = () => {
     fetchXfdf(token)
       .then(xfdf => {
         const annotations = parseAnnotationFromXml(xfdf);
+        const forms = parseFormElementFromXml(xfdf);
         const watermark = parseWatermarkFromXml(xfdf);
 
         if (watermark.obj_attr.type) {
-          addAnnots([...annotations, watermark], false);
+          addAnnots([...annotations, ...forms, watermark], false);
           updateWatermark(watermark.obj_attr);
         } else {
-          addAnnots(annotations, false);
+          addAnnots([...annotations, ...forms], false);
         }
       })
       .catch(error => {
@@ -93,30 +92,14 @@ const PdfViewer: React.FC = () => {
 
       iViewport.width = Math.round(iViewport.width);
       iViewport.height = Math.round(iViewport.height);
-      const screenWidth = window.document.body.offsetWidth - 5;
+      const screenWidth = window.document.body.offsetWidth - 286;
       const originPdfWidth = iViewport.width;
-      const rate = (screenWidth / originPdfWidth).toFixed(2);
 
+      const rate = (screenWidth / originPdfWidth).toFixed(2);
       changeScale(rate);
     }
   };
 
-  const changePdfContainerScale = (): void => {
-    for (let i = 1; i <= totalPage; i += 1) {
-      const ele: HTMLDivElement = document.getElementById(
-        `page_${i}`
-      ) as HTMLDivElement;
-      if (ele) {
-        ele.style.width = `${viewport.width}px`;
-        ele.style.height = `${viewport.height}px`;
-
-        if (i === currentPageRef.current) {
-          scrollIntoView(ele);
-        }
-      }
-    }
-  };
-
   const scrollToUpdate = (state: ScrollStateType): void => {
     const ele: HTMLDivElement = document.getElementById(
       'page_1'
@@ -125,25 +108,13 @@ const PdfViewer: React.FC = () => {
       (state.lastY + ele.offsetHeight / 1.4) / (ele.offsetHeight + 50)
     );
 
-    if (pageNum !== currentPageRef.current) {
-      setCurrentPage(pageNum);
-    }
+    setCurrentPage(pageNum);
   };
 
   useEffect(() => {
     pdfStarter();
   }, []);
 
-  useEffect(() => {
-    currentPageRef.current = currentPage;
-  }, [currentPage]);
-
-  useEffect(() => {
-    if (pdf) {
-      changePdfContainerScale();
-    }
-  }, [viewport]);
-
   useEffect(() => {
     if (pdf) {
       getViewport(pdf, scale);

+ 95 - 0
containers/RadioButtonTool.tsx

@@ -0,0 +1,95 @@
+import React, { useEffect, useCallback } from 'react';
+
+import { ANNOTATION_TYPE } from '../constants';
+import Button from '../components/Button';
+import Icon from '../components/Icon';
+import { getAbsoluteCoordinate } from '../helpers/position';
+import {
+  parseAnnotationObject,
+  appendUserIdAndDate,
+} from '../helpers/annotation';
+
+import useActions from '../actions';
+import useStore from '../store';
+
+import { BtnWrapper } from '../global/toolStyled';
+
+type Props = {
+  title: string;
+  isActive: boolean;
+  onClick: () => void;
+};
+
+const RadioButtonTool: React.FC<Props> = ({
+  title,
+  isActive,
+  onClick,
+}: Props) => {
+  const [{ scale, viewport }, dispatch] = useStore();
+  const { addAnnots } = useActions(dispatch);
+
+  const addRadioButton = (
+    pageEle: HTMLElement,
+    event: MouseEvent | TouchEvent
+  ): void => {
+    const pageNum = pageEle.getAttribute('data-page-num') || 0;
+    const coordinate = getAbsoluteCoordinate(pageEle, event);
+
+    const annotData = {
+      obj_type: ANNOTATION_TYPE.checkbox,
+      obj_attr: {
+        page: pageNum as number,
+        style: '1',
+        position: {
+          top: coordinate.y,
+          left: coordinate.x,
+          bottom: coordinate.y + 50,
+          right: coordinate.x + 50,
+        },
+      },
+    };
+
+    const newRadioButton = appendUserIdAndDate(
+      parseAnnotationObject(annotData, viewport.height, scale)
+    );
+
+    addAnnots([newRadioButton]);
+  };
+
+  const handleClick = useCallback(
+    (event: MouseEvent | TouchEvent): void => {
+      const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
+
+      if (pageEle.hasAttribute('data-page-num')) {
+        addRadioButton(pageEle, event);
+      }
+    },
+    [viewport, scale]
+  );
+
+  useEffect(() => {
+    if (isActive) {
+      document.addEventListener('click', handleClick);
+    }
+
+    return () => {
+      document.removeEventListener('click', handleClick);
+    };
+  }, [isActive]);
+
+  return (
+    <BtnWrapper>
+      <Button
+        shouldFitContainer
+        align="left"
+        isActive={isActive}
+        onClick={onClick}
+      >
+        <Icon glyph="radio-button" style={{ marginRight: '10px' }} />
+        {title}
+      </Button>
+    </BtnWrapper>
+  );
+};
+
+export default RadioButtonTool;

+ 27 - 23
containers/Sidebar.tsx

@@ -1,33 +1,34 @@
 import React from 'react';
 import { useTranslation } from 'react-i18next';
 
-// import Button from '../components/Button';
 import Typography from '../components/Typography';
-// import Icon from '../components/Icon';
-// import CreateForm from './CreateForm';
+import FormTools from './FormTools';
 import MarkupTools from './MarkupTools';
+import ImageTool from './ImageTool';
+import WatermarkTool from './WatermarkTool';
+import InsertCursor from './InsertCursor';
 
-// import useActions from '../actions';
 import useStore from '../store';
+import useActions from '../actions';
 
-// import { BtnWrapper } from '../global/toolStyled';
 import { SidebarWrapper } from '../global/otherStyled';
 
 const Sidebar: React.FC = () => {
   const { t } = useTranslation('sidebar');
-  const [{ displayMode }] = useStore();
-  // const { setSidebar } = useActions(dispatch);
 
-  // const onClick = (state: string): void => {
-  //   if (state === sidebarState) {
-  //     setSidebar('');
-  //   } else {
-  //     setSidebar(state);
-  //   }
-  // };
+  const [{ sidebarState }, dispatch] = useStore();
+  const { setSidebar } = useActions(dispatch);
+
+  const handleClickSidebar = (state: string): void => {
+    if (state === sidebarState) {
+      setSidebar('');
+    } else {
+      setSidebar(state);
+    }
+  };
 
   return (
-    <SidebarWrapper isHidden={displayMode === 'full'}>
+    <SidebarWrapper>
       <Typography
         light
         style={{ marginLeft: '30px', marginTop: '46px' }}
@@ -35,14 +36,17 @@ const Sidebar: React.FC = () => {
       >
         {t('mainMenu')}
       </Typography>
-      <MarkupTools />
-      {/* <CreateForm /> */}
-      {/* <BtnWrapper>
-        <Button shouldFitContainer align="left" onClick={(): void => { onClick('add-image'); }}>
-          <Icon glyph="add-image" style={{ marginRight: '10px' }} />
-          {t('addImages')}
-        </Button>
-      </BtnWrapper> */}
+      <MarkupTools
+        sidebarState={sidebarState}
+        onClickSidebar={handleClickSidebar}
+      />
+      <FormTools
+        sidebarState={sidebarState}
+        onClickSidebar={handleClickSidebar}
+      />
+      <ImageTool onClickSidebar={handleClickSidebar} />
+      <WatermarkTool onClickSidebar={handleClickSidebar} />
+      <InsertCursor />
     </SidebarWrapper>
   );
 };

+ 2 - 4
containers/StickyNoteTools.tsx

@@ -19,7 +19,7 @@ type Props = {
   onClick: () => void;
 };
 
-const StickyNoteTools: React.FC<Props> = ({
+const StickyNoteTool: React.FC<Props> = ({
   title,
   isActive,
   onClick,
@@ -56,8 +56,6 @@ const StickyNoteTools: React.FC<Props> = ({
 
   const handleMouseDown = useCallback(
     (event: MouseEvent | TouchEvent): void => {
-      // event.preventDefault();
-
       const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
 
       if (pageEle.hasAttribute('data-page-num')) {
@@ -98,4 +96,4 @@ const StickyNoteTools: React.FC<Props> = ({
   );
 };
 
-export default StickyNoteTools;
+export default StickyNoteTool;

+ 94 - 0
containers/TextfieldTool.tsx

@@ -0,0 +1,94 @@
+import React, { useEffect, useCallback } from 'react';
+
+import { ANNOTATION_TYPE } from '../constants';
+import Button from '../components/Button';
+import Icon from '../components/Icon';
+import { getAbsoluteCoordinate } from '../helpers/position';
+import {
+  parseAnnotationObject,
+  appendUserIdAndDate,
+} from '../helpers/annotation';
+
+import useActions from '../actions';
+import useStore from '../store';
+
+import { BtnWrapper } from '../global/toolStyled';
+
+type Props = {
+  title: string;
+  isActive: boolean;
+  onClick: () => void;
+};
+
+const TextfieldTool: React.FC<Props> = ({
+  title,
+  isActive,
+  onClick,
+}: Props) => {
+  const [{ scale, viewport }, dispatch] = useStore();
+  const { addAnnots } = useActions(dispatch);
+
+  const addTextfield = (
+    pageEle: HTMLElement,
+    event: MouseEvent | TouchEvent
+  ): void => {
+    const pageNum = pageEle.getAttribute('data-page-num') || 0;
+    const coordinate = getAbsoluteCoordinate(pageEle, event);
+
+    const annotData = {
+      obj_type: ANNOTATION_TYPE.textfield,
+      obj_attr: {
+        page: pageNum as number,
+        position: {
+          top: coordinate.y,
+          left: coordinate.x,
+          bottom: coordinate.y + 50,
+          right: coordinate.x + 150,
+        },
+      },
+    };
+
+    const newTextfield = appendUserIdAndDate(
+      parseAnnotationObject(annotData, viewport.height, scale)
+    );
+
+    addAnnots([newTextfield]);
+  };
+
+  const handleClick = useCallback(
+    (event: MouseEvent | TouchEvent): void => {
+      const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
+
+      if (pageEle.hasAttribute('data-page-num')) {
+        addTextfield(pageEle, event);
+      }
+    },
+    [viewport, scale]
+  );
+
+  useEffect(() => {
+    if (isActive) {
+      document.addEventListener('click', handleClick);
+    }
+
+    return () => {
+      document.removeEventListener('click', handleClick);
+    };
+  }, [isActive]);
+
+  return (
+    <BtnWrapper>
+      <Button
+        shouldFitContainer
+        align="left"
+        isActive={isActive}
+        onClick={onClick}
+      >
+        <Icon glyph="textfield" style={{ marginRight: '10px' }} />
+        {title}
+      </Button>
+    </BtnWrapper>
+  );
+};
+
+export default TextfieldTool;

+ 7 - 5
containers/WatermarkTool.tsx

@@ -12,13 +12,15 @@ import useStore from '../store';
 
 import { BtnWrapper } from '../global/toolStyled';
 
-const WatermarkTool: React.FC = () => {
+type Props = {
+  onClickSidebar: (state: string) => void;
+};
+
+const WatermarkTool: React.FC<Props> = ({ onClickSidebar }: Props) => {
   const { t } = useTranslation('sidebar');
   const [isActive, setActive] = useState(false);
   const [{ totalPage, watermark, annotations }, dispatch] = useStore();
-  const { setSidebar, updateWatermark, addAnnots, updateAnnots } = useActions(
-    dispatch
-  );
+  const { updateWatermark, addAnnots, updateAnnots } = useActions(dispatch);
 
   const setDataState = (obj: WatermarkType): void => {
     updateWatermark(obj);
@@ -36,7 +38,7 @@ const WatermarkTool: React.FC = () => {
 
   const handleClick = (): void => {
     setActive(!isActive);
-    setSidebar(!isActive ? 'watermark' : '');
+    onClickSidebar('watermark');
   };
 
   const handleSave = (): void => {

+ 8 - 1
custom.d.ts

@@ -23,6 +23,12 @@ type RenderingStateType = 'RENDERING' | 'LOADING' | 'FINISHED' | 'PAUSED';
 
 type LineType = 'Highlight' | 'Underline' | 'Squiggly' | 'StrikeOut';
 
+type FormType = 'textfield' | 'checkbox' | 'radio';
+
+type ToolType = 'highlight' | 'freehand' | 'text' | 'sticky' | 'shape';
+
+type SidebarType = 'markup-tools' | 'create-form' | 'watermark' | 'image';
+
 type ReducerFuncType = (
   state: Record<string, any>,
   action: { type: string; payload: any }
@@ -85,7 +91,7 @@ type AnnotationAttributeType = {
   position?: AnnotationPositionType;
   transparency?: number | undefined;
   content?: string | undefined;
-  style?: number | undefined;
+  style?: string | undefined;
   fcolor?: string | undefined;
   ftransparency?: number | undefined;
   bdwidth?: number | undefined;
@@ -93,6 +99,7 @@ type AnnotationAttributeType = {
   fontsize?: number | undefined;
   textcolor?: string | undefined;
   is_arrow?: boolean | undefined;
+  src?: string | undefined;
 };
 
 type AnnotationType = {

+ 2 - 10
global/otherStyled.ts

@@ -4,7 +4,7 @@ export const Separator = styled.div`
   flex: 1 1 auto;
 `;
 
-export const SidebarWrapper = styled('div')<{ isHidden: boolean }>`
+export const SidebarWrapper = styled.div`
   position: fixed;
   top: 60px;
   bottom: 0px;
@@ -15,15 +15,7 @@ export const SidebarWrapper = styled('div')<{ isHidden: boolean }>`
   overflow: auto;
 
   transition: left 225ms cubic-bezier(0, 0, 0.2, 1) 0ms;
-
-  ${props =>
-    props.isHidden
-      ? css`
-          left: -267px;
-        `
-      : css`
-          left: 0;
-        `}
+  left: 0;
 `;
 
 export const AnnotationContainer = styled('div')<{

+ 2 - 4
global/toolStyled.ts

@@ -1,7 +1,5 @@
 import styled, { css } from 'styled-components';
 
-import { color } from '../constants/style';
-
 export const Wrapper = styled('div')<{ width?: string }>`
   margin-bottom: 10px;
   display: inline-block;
@@ -28,11 +26,11 @@ export const Item = styled('div')<{ size?: string; selected?: boolean }>`
   ${props =>
       props.selected
         ? css`
-            background-color: ${color['light-primary']};
+            background-color: ${({ theme }) => theme.colors['light-primary']};
           `
         : ''}
     :hover {
-    background-color: ${color['light-primary']};
+    background-color: ${({ theme }) => theme.colors['light-primary']};
   }
 `;
 

+ 44 - 1
helpers/annotation.ts

@@ -3,7 +3,7 @@ import { v4 as uuidv4 } from 'uuid';
 import dayjs from 'dayjs';
 import queryString from 'query-string';
 
-import { ANNOTATION_TYPE } from '../constants';
+import { ANNOTATION_TYPE, FORM_TYPE } from '../constants';
 import { getPdfPage, renderTextLayer } from './pdf';
 import { getPosition, parsePositionForBackend } from './position';
 import { normalizeRound, floatToHex } from './utility';
@@ -94,6 +94,47 @@ export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
   return filterAnnots;
 };
 
+export const parseFormElementFromXml = (
+  xmlString: string
+): AnnotationType[] => {
+  if (!xmlString) return [];
+
+  const xmlDoc = xmlParser(xmlString);
+  const elements = xmlDoc.firstElementChild || xmlDoc.firstChild;
+  const element = getElementsByTagName(elements as ChildNode, 'widgets') || [];
+  const annotations: Element[] = Array.prototype.slice.call(element);
+  const filterForm = annotations.reduce((acc: any[], cur: any) => {
+    const type = FORM_TYPE[cur.tagName];
+
+    if (type) {
+      const page = parseInt(cur.attributes.page.value, 10);
+      acc.push({
+        id: uuidv4(),
+        obj_type: type,
+        obj_attr: {
+          date: cur.attributes.date ? cur.attributes.date.value : undefined,
+          page,
+          position: getPosition(type, cur),
+          bdcolor: cur.attributes.color
+            ? cur.attributes.color.value
+            : undefined,
+          style: cur.attributes.style ? cur.attributes.style.value : undefined,
+          bdwidth: cur.attributes.width
+            ? parseInt(cur.attributes.width.value, 10)
+            : 0,
+          transparency: cur.attributes.opacity
+            ? cur.attributes.opacity.value
+            : 1,
+        },
+      });
+    }
+
+    return acc;
+  }, []);
+
+  return filterForm;
+};
+
 // eslint-disable-next-line consistent-return
 const getEleText = (
   coord: any,
@@ -205,6 +246,7 @@ export const parseAnnotationObject = (
       fontsize,
       textcolor,
       is_arrow,
+      src,
     },
   }: AnnotationType,
   pageHeight: number,
@@ -226,6 +268,7 @@ export const parseAnnotationObject = (
     fontsize,
     textcolor,
     is_arrow,
+    src,
   },
 });
 

+ 1 - 1
helpers/globalStyles.css

@@ -2,7 +2,7 @@ html, body {
   margin: 0;
   background-color: #c8c8c8;
   height: 100%;
-  font-size: 14px;
+  font-size: 12px;
   overflow: auto;
   -ms-overflow-style: -ms-autohiding-scrollbar;
 }

+ 14 - 9
helpers/position.ts

@@ -67,7 +67,9 @@ export const getPosition = (
       };
     }
     case 'Text':
-    case 'FreeText': {
+    case 'FreeText':
+    case 'textfield':
+    case 'checkbox': {
       const rect = element.attributes.rect.value.split(',');
       return {
         left: parseInt(rect[0], 10),
@@ -112,9 +114,9 @@ export const parsePositionForBackend = (
     case 'Underline':
     case 'Squiggly':
     case 'StrikeOut': {
-      const forcePositionToArray = position as PositionType[];
+      const positionArray = position as PositionType[];
 
-      return forcePositionToArray.map((ele: PositionType) => ({
+      return positionArray.map((ele: PositionType) => ({
         left: ele.left / scale,
         bottom: (pageHeight - ele.bottom) / scale,
         right: ele.right / scale,
@@ -124,14 +126,17 @@ export const parsePositionForBackend = (
     case 'Square':
     case 'Circle':
     case 'FreeText':
-    case 'Text': {
-      const coercionPositionType = position as PositionType;
+    case 'Text':
+    case 'Image':
+    case 'textfield':
+    case 'checkbox': {
+      const normalPosition = position as PositionType;
 
       return {
-        left: coercionPositionType.left / scale,
-        bottom: (pageHeight - coercionPositionType.bottom) / scale,
-        right: coercionPositionType.right / scale,
-        top: (pageHeight - coercionPositionType.top) / scale,
+        left: normalPosition.left / scale,
+        bottom: (pageHeight - normalPosition.bottom) / scale,
+        right: normalPosition.right / scale,
+        top: (pageHeight - normalPosition.top) / scale,
       };
     }
     case 'Line': {

+ 31 - 0
helpers/theme.ts

@@ -0,0 +1,31 @@
+const breakpoints = ['320px', '425px', '768px', '1024px', '1440px'];
+
+export default {
+  breakpoints,
+  colors: {
+    primary: '#586af2',
+    'light-primary': 'rgba(88, 106, 242, 0.6)',
+    secondary: '#2d3e4e',
+    'primary-light': '#d7def9',
+    'french-blue': '#3645b4',
+    'lemon-yellow': '#fcff36',
+    'strong-pink': '#ff1b89',
+    'neon-green': '#02ff36',
+    'light-blue': '#27befd',
+    info: '#2979ff',
+    success: '#43a047',
+    error: '#d32f2f',
+    danger: '#fe2712',
+    warning: '#ffa000',
+    gray: '#d8d8d8',
+    'light-gray': '#e5e5e5',
+    'hyper-light-gray': '#f2f2f2',
+    'soft-blue': 'rgba(94, 112, 241, 0.12)',
+    black05: 'rgba(0, 0, 0, 0.05)',
+    black12: 'rgba(0, 0, 0, 0.12)',
+    black23: 'rgba(0, 0, 0, 0.23)',
+    black38: 'rgba(0, 0, 0, 0.38)',
+    black56: 'rgba(0, 0, 0, 0.56)',
+    black87: 'rgba(0, 0, 0, 0.87)',
+  },
+};

+ 6 - 1
pages/_app.js

@@ -1,8 +1,11 @@
 import App from 'next/app';
 import React from 'react';
+import { ThemeProvider } from 'styled-components';
 import { ToastProvider } from 'react-toast-notifications';
 import { StoreProvider } from '../store';
 
+import theme from '../helpers/theme';
+
 import '../i18n';
 import '../helpers/globalStyles.css';
 
@@ -13,7 +16,9 @@ class MainApp extends App {
     return (
       <StoreProvider>
         <ToastProvider placement="bottom-center">
-          <Component {...pageProps} />
+          <ThemeProvider theme={theme}>
+            <Component {...pageProps} />
+          </ThemeProvider>
         </ToastProvider>
       </StoreProvider>
     );

+ 0 - 2
pages/index.tsx

@@ -4,7 +4,6 @@ import { NextPage } from 'next';
 import Navbar from '../containers/Navbar';
 import Sidebar from '../containers/Sidebar';
 import Toolbar from '../containers/Toolbar';
-import FullScreenButton from '../containers/FullScreenButton';
 import Placeholder from '../containers/Placeholder';
 import PdfViewer from '../containers/PdfViewer';
 import AutoSave from '../containers/AutoSave';
@@ -18,7 +17,6 @@ const index: NextPage = () => (
     <Toolbar />
     <Placeholder />
     <PdfViewer />
-    <FullScreenButton />
     <AutoSave />
     <Loading />
     <Embed />

+ 1 - 1
reducers/index.ts

@@ -18,7 +18,7 @@ export default createReducer({
   [types.TOGGLE_DISPLAY_MODE]: mainActions.toggleDisplayMode,
   [types.SET_NAVBAR]: mainActions.setNavbarState,
   [types.SET_SIDEBAR]: mainActions.setSidebarState,
-  [types.SET_MARKUP_TOOL]: mainActions.setMarkupTool,
+  [types.SET_TOOL]: mainActions.setTool,
   [types.SET_INFO]: mainActions.setInfo,
   [types.SET_LOADING]: mainActions.setLoading,
 

+ 2 - 2
reducers/main.ts

@@ -13,9 +13,9 @@ export const setSidebarState: ReducerFuncType = (state, { payload }) => ({
   sidebarState: payload,
 });
 
-export const setMarkupTool: ReducerFuncType = (state, { payload }) => ({
+export const setTool: ReducerFuncType = (state, { payload }) => ({
   ...state,
-  markupToolState: payload,
+  toolState: payload,
 });
 
 export const setInfo: ReducerFuncType = (state, { payload }) => ({

+ 3 - 24
reducers/middleware/index.ts

@@ -1,36 +1,15 @@
 import { Dispatch } from 'react';
 
-import {
-  CHANGE_SCALE,
-  TOGGLE_DISPLAY_MODE,
-  SET_SIDEBAR,
-  SET_MARKUP_TOOL,
-} from '../../constants/actionTypes';
+import { SET_SIDEBAR, SET_TOOL } from '../../constants/actionTypes';
 
-const applyMiddleware = (state: any, dispatch: Dispatch<any>) => (action: {
+const applyMiddleware = (dispatch: Dispatch<any>) => (action: {
   type: string;
   payload?: any;
 }): void => {
   dispatch(action);
   switch (action.type) {
-    case TOGGLE_DISPLAY_MODE: {
-      if (action.payload === 'full') {
-        const screenWidth = window.document.body.offsetWidth - 5;
-        const originPdfWidth = state.viewport.width / state.scale;
-        const rate = (screenWidth / originPdfWidth).toFixed(2);
-        dispatch({ type: CHANGE_SCALE, payload: rate });
-      } else {
-        const screenWidth = window.document.body.offsetWidth - 288;
-        const originPdfWidth = state.viewport.width / state.scale;
-        const rate = screenWidth / originPdfWidth;
-        dispatch({ type: CHANGE_SCALE, payload: rate });
-      }
-      break;
-    }
     case SET_SIDEBAR: {
-      if (action.payload === '') {
-        dispatch({ type: SET_MARKUP_TOOL, payload: '' });
-      }
+      dispatch({ type: SET_TOOL, payload: '' });
       break;
     }
     default:

+ 1 - 1
store/index.tsx

@@ -29,7 +29,7 @@ export const StoreProvider = ({
   children: React.ReactNode;
 }): React.ReactElement => {
   const [state, dispatch] = useReducer(reducers, initialState);
-  const enhancedDispatch = applyMiddleware(state, dispatch);
+  const enhancedDispatch = applyMiddleware(dispatch);
 
   return (
     <StateContext.Provider value={[state, enhancedDispatch]}>

+ 5 - 14
store/initialMainState.ts

@@ -1,27 +1,18 @@
 export type StateType = {
   displayMode: 'full' | 'normal';
   navbarState: 'search' | 'annotations' | 'thumbnails' | '';
-  sidebarState:
-    | 'markup-tools'
-    | 'create-form'
-    | 'watermark'
-    | 'highlight'
-    | 'freehand'
-    | 'text'
-    | 'sticky'
-    | 'shape'
-    | '';
-  markupToolState: 'highlight' | 'freehand' | 'text' | 'sticky' | 'shape' | '';
+  sidebarState: SidebarType | '';
+  toolState: ToolType | FormType | '';
   info: { token: string; id: string };
   initiated: boolean;
   isLoading: boolean;
 };
 
 export default {
-  displayMode: 'full',
+  displayMode: 'normal',
   navbarState: '',
-  sidebarState: 'highlight',
-  markupToolState: '',
+  sidebarState: '',
+  toolState: '',
   info: {},
   initiated: false,
   isLoading: false,