RoyLiu 5 роки тому
батько
коміт
f562a6168f

+ 4 - 4
components/ColorSelector/data.ts

@@ -1,18 +1,18 @@
 export default [
   {
     key: 'lemon-yellow',
-    color: '#fcff36',
+    color: '#FCFF36',
   },
   {
     key: 'strong-pink',
-    color: '#ff1b89',
+    color: '#FF1B89',
   },
   {
     key: 'neon-green',
-    color: '#02ff36',
+    color: '#02FF36',
   },
   {
     key: 'light-blue',
-    color: '#27befd',
+    color: '#27BEFD',
   },
 ];

+ 1 - 1
components/ColorSelector/index.tsx

@@ -25,7 +25,7 @@ const ColorSelector: React.FunctionComponent<Props> = ({
         <Item
           key={ele.key}
           selected={color === ele.color}
-          onClick={(): void => { onClick(ele.color); }}
+          onMouseDown={(): void => { onClick(ele.color); }}
         >
           <Circle color={ele.color} />
         </Item>

+ 70 - 70
components/Icon/data.ts

@@ -2,76 +2,76 @@
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 import React from 'react';
 
-import Close from '../../static/icons/close.svg';
-import DownArrow from '../../static/icons/arrow00.svg';
-import Edit from '../../static/icons/navbar/rename00.svg';
-import EditActive from '../../static/icons/navbar/rename01.svg';
-import Search from '../../static/icons/navbar/Search00.svg';
-import SearchActive from '../../static/icons/navbar/Search01.svg';
-import Annotation from '../../static/icons/navbar/MyAnnotation00.svg';
-import AnnotationActive from '../../static/icons/navbar/MyAnnotation01.svg';
-import Thumbnail from '../../static/icons/navbar/Thumbnail00.svg';
-import ThumbnailActive from '../../static/icons/navbar/Thumbnail01.svg';
-import Print from '../../static/icons/navbar/Print00.svg';
-import PrintActive from '../../static/icons/navbar/Print01.svg';
-import Export from '../../static/icons/navbar/Export00.svg';
-import ExportActive from '../../static/icons/navbar/Export01.svg';
-import ZoomIn from '../../static/icons/toolbar/zoom00.svg';
-import ZoomInActive from '../../static/icons/toolbar/zoom01.svg';
-import ZoomOut from '../../static/icons/toolbar/zoomOut00.svg';
-import ZoomOutActive from '../../static/icons/toolbar/zoomOut01.svg';
-import Hand from '../../static/icons/toolbar/hand00.svg';
-import HandActive from '../../static/icons/toolbar/hand01.svg';
-import RotateLeft from '../../static/icons/toolbar/RotateLeft00.svg';
-import RotateLeftActive from '../../static/icons/toolbar/RotateLeft01.svg';
-import RotateRight from '../../static/icons/toolbar/RotateRight00.svg';
-import RotateRightActive from '../../static/icons/toolbar/RotateRight01.svg';
-import MarkupTools from '../../static/icons/sidebar/Markuptools00.svg';
-import Highlight from '../../static/icons/sidebar/Highlight.svg';
-import Freehand from '../../static/icons/sidebar/Freehand.svg';
-import Markpen from '../../static/icons/sidebar/markpen.svg';
-import Text from '../../static/icons/sidebar/Text.svg';
-import StickyNote from '../../static/icons/sidebar/StickyNote.svg';
-import Shape from '../../static/icons/sidebar/Shape.svg';
-import CreateForm from '../../static/icons/sidebar/CreatForm00.svg';
-import AddImage from '../../static/icons/sidebar/AddImage00.svg';
-import Watermark from '../../static/icons/sidebar/Watermark00.svg';
-import Underline from '../../static/icons/sidebar/Underline.svg';
-import Squiggly from '../../static/icons/sidebar/Squiggly.svg';
-import Strikeout from '../../static/icons/sidebar/Strikeout.svg';
-import ColorPicker from '../../static/icons/sidebar/Colorpicker.svg';
-import Eraser from '../../static/icons/sidebar/eraser00.svg';
-import EraserActive from '../../static/icons/sidebar/eraser01.svg';
-import Undo from '../../static/icons/sidebar/undo.svg';
-import Redo from '../../static/icons/sidebar/redo.svg';
-import Bold from '../../static/icons/sidebar/Bold00.svg';
-import BoldActive from '../../static/icons/sidebar/Bold01.svg';
-import Italic from '../../static/icons/sidebar/Italic00.svg';
-import ItalicActive from '../../static/icons/sidebar/Italic01.svg';
-import AlignCenter from '../../static/icons/sidebar/Aligncenter00.svg';
-import AlignCenterActive from '../../static/icons/sidebar/Aligncenter01.svg';
-import AlignLeft from '../../static/icons/sidebar/Alignleft00.svg';
-import AlignLeftActive from '../../static/icons/sidebar/Alignleft01.svg';
-import AlignRight from '../../static/icons/sidebar/Alignright00.svg';
-import AlignRightActive from '../../static/icons/sidebar/Alignright01.svg';
-import Circle from '../../static/icons/sidebar/Circle.svg';
-import Rectangle from '../../static/icons/sidebar/Rectangle.svg';
-import Line from '../../static/icons/sidebar/Line.svg';
-import Arrow from '../../static/icons/sidebar/Arrow.svg';
-import Border from '../../static/icons/sidebar/Border.svg';
-import Fill from '../../static/icons/sidebar/Fill.svg';
-import TextField from '../../static/icons/sidebar/Textfiled.svg';
-import Checkbox from '../../static/icons/sidebar/checkBox.svg';
-import RadioButton from '../../static/icons/sidebar/radiobutton.svg';
-import LeftBack from '../../static/icons/sidebar/LeftBack.svg';
-import RightBack from '../../static/icons/sidebar/RightBack.svg';
-import Sort from '../../static/icons/annotation/sort.svg';
-import Import from '../../static/icons/annotation/import.svg';
-import AnnotationExport from '../../static/icons/annotation/Export.svg';
-import ToolOpen from '../../static/icons/btn_ToolsOpen.svg';
-import ToolClose from '../../static/icons/btn_ToolsClose.svg';
-import Trash from '../../static/icons/toolbar/delete-00.svg';
-import RightArrow from '../../static/icons/right-arrow.svg';
+import Close from '../../public/icons/close.svg';
+import DownArrow from '../../public/icons/arrow00.svg';
+import Edit from '../../public/icons/navbar/rename00.svg';
+import EditActive from '../../public/icons/navbar/rename01.svg';
+import Search from '../../public/icons/navbar/Search00.svg';
+import SearchActive from '../../public/icons/navbar/Search01.svg';
+import Annotation from '../../public/icons/navbar/MyAnnotation00.svg';
+import AnnotationActive from '../../public/icons/navbar/MyAnnotation01.svg';
+import Thumbnail from '../../public/icons/navbar/Thumbnail00.svg';
+import ThumbnailActive from '../../public/icons/navbar/Thumbnail01.svg';
+import Print from '../../public/icons/navbar/Print00.svg';
+import PrintActive from '../../public/icons/navbar/Print01.svg';
+import Export from '../../public/icons/navbar/Export00.svg';
+import ExportActive from '../../public/icons/navbar/Export01.svg';
+import ZoomIn from '../../public/icons/toolbar/zoom00.svg';
+import ZoomInActive from '../../public/icons/toolbar/zoom01.svg';
+import ZoomOut from '../../public/icons/toolbar/zoomOut00.svg';
+import ZoomOutActive from '../../public/icons/toolbar/zoomOut01.svg';
+import Hand from '../../public/icons/toolbar/hand00.svg';
+import HandActive from '../../public/icons/toolbar/hand01.svg';
+import RotateLeft from '../../public/icons/toolbar/RotateLeft00.svg';
+import RotateLeftActive from '../../public/icons/toolbar/RotateLeft01.svg';
+import RotateRight from '../../public/icons/toolbar/RotateRight00.svg';
+import RotateRightActive from '../../public/icons/toolbar/RotateRight01.svg';
+import MarkupTools from '../../public/icons/sidebar/Markuptools00.svg';
+import Highlight from '../../public/icons/sidebar/Highlight.svg';
+import Freehand from '../../public/icons/sidebar/Freehand.svg';
+import Markpen from '../../public/icons/sidebar/markpen.svg';
+import Text from '../../public/icons/sidebar/Text.svg';
+import StickyNote from '../../public/icons/sidebar/StickyNote.svg';
+import Shape from '../../public/icons/sidebar/Shape.svg';
+import CreateForm from '../../public/icons/sidebar/CreatForm00.svg';
+import AddImage from '../../public/icons/sidebar/AddImage00.svg';
+import Watermark from '../../public/icons/sidebar/Watermark00.svg';
+import Underline from '../../public/icons/sidebar/Underline.svg';
+import Squiggly from '../../public/icons/sidebar/Squiggly.svg';
+import Strikeout from '../../public/icons/sidebar/Strikeout.svg';
+import ColorPicker from '../../public/icons/sidebar/Colorpicker.svg';
+import Eraser from '../../public/icons/sidebar/eraser00.svg';
+import EraserActive from '../../public/icons/sidebar/eraser01.svg';
+import Undo from '../../public/icons/sidebar/undo.svg';
+import Redo from '../../public/icons/sidebar/redo.svg';
+import Bold from '../../public/icons/sidebar/Bold00.svg';
+import BoldActive from '../../public/icons/sidebar/Bold01.svg';
+import Italic from '../../public/icons/sidebar/Italic00.svg';
+import ItalicActive from '../../public/icons/sidebar/Italic01.svg';
+import AlignCenter from '../../public/icons/sidebar/Aligncenter00.svg';
+import AlignCenterActive from '../../public/icons/sidebar/Aligncenter01.svg';
+import AlignLeft from '../../public/icons/sidebar/Alignleft00.svg';
+import AlignLeftActive from '../../public/icons/sidebar/Alignleft01.svg';
+import AlignRight from '../../public/icons/sidebar/Alignright00.svg';
+import AlignRightActive from '../../public/icons/sidebar/Alignright01.svg';
+import Circle from '../../public/icons/sidebar/Circle.svg';
+import Rectangle from '../../public/icons/sidebar/Rectangle.svg';
+import Line from '../../public/icons/sidebar/Line.svg';
+import Arrow from '../../public/icons/sidebar/Arrow.svg';
+import Border from '../../public/icons/sidebar/Border.svg';
+import Fill from '../../public/icons/sidebar/Fill.svg';
+import TextField from '../../public/icons/sidebar/Textfiled.svg';
+import Checkbox from '../../public/icons/sidebar/checkBox.svg';
+import RadioButton from '../../public/icons/sidebar/radiobutton.svg';
+import LeftBack from '../../public/icons/sidebar/LeftBack.svg';
+import RightBack from '../../public/icons/sidebar/RightBack.svg';
+import Sort from '../../public/icons/annotation/sort.svg';
+import Import from '../../public/icons/annotation/import.svg';
+import AnnotationExport from '../../public/icons/annotation/Export.svg';
+import ToolOpen from '../../public/icons/btn_ToolsOpen.svg';
+import ToolClose from '../../public/icons/btn_ToolsClose.svg';
+import Trash from '../../public/icons/toolbar/delete-00.svg';
+import RightArrow from '../../public/icons/right-arrow.svg';
 
 const data: {[index: string]: any} = {
   close: {

+ 0 - 3
components/Icon/index.tsx

@@ -13,7 +13,6 @@ type Props = {
   onBlur?: () => void;
   style?: {};
   isActive? : boolean;
-  tabIndex?: number;
 };
 
 const Icon: React.FunctionComponent<Props> = ({
@@ -23,7 +22,6 @@ const Icon: React.FunctionComponent<Props> = ({
   onBlur,
   style,
   isActive = false,
-  tabIndex,
 }: Props) => {
   const {
     Normal,
@@ -40,7 +38,6 @@ const Icon: React.FunctionComponent<Props> = ({
       {isActive && Hover ? <Hover data-status="active" /> : null}
       <ClickZone
         id={id}
-        tabIndex={tabIndex}
         onMouseDown={onClick}
         onBlur={onBlur}
       />

+ 29 - 19
components/Navbar/index.tsx

@@ -1,4 +1,5 @@
-import React from 'react';
+import React, { useRef, useEffect } from 'react';
+import queryString from 'query-string';
 
 import Icon from '../Icon';
 import Typography from '../Typography';
@@ -19,23 +20,32 @@ const Navbar: React.FunctionComponent<Props> = ({
   navbarState,
   children,
   displayMode,
-}: Props) => (
-  <Wrapper isHidden={displayMode === 'full'}>
-    <Typography variant="title">Title</Typography>
-    <Icon glyph="edit" />
-    <Separator />
-    {
-      data.btnGroup.map(ele => (
-        <Icon
-          key={ele.key}
-          glyph={ele.content}
-          isActive={navbarState === ele.content}
-          onClick={(): void => { onClick(ele.content); }}
-        />
-      ))
-    }
-    {children}
-  </Wrapper>
-);
+}: Props) => {
+  const fileName = useRef('');
+
+  useEffect(() => {
+    const parsed = queryString.parse(window.location.search);
+    fileName.current = atob(parsed.token as string);
+  }, []);
+
+  return (
+    <Wrapper isHidden={displayMode === 'full'}>
+      <Typography variant="title">{fileName.current}</Typography>
+      {/* <Icon glyph="edit" /> */}
+      <Separator />
+      {
+        data.btnGroup.map(ele => (
+          <Icon
+            key={ele.key}
+            glyph={ele.content}
+            isActive={navbarState === ele.content}
+            onClick={(): void => { onClick(ele.content); }}
+          />
+        ))
+      }
+      {children}
+    </Wrapper>
+  );
+};
 
 export default Navbar;

+ 10 - 7
components/Page/index.tsx

@@ -27,16 +27,16 @@ const PageView: React.FunctionComponent<Props> = ({
   annotations = [],
 }: Props) => {
   const rootEle = useRef<HTMLDivElement | null>(null);
-  let page: any = null;
+  let pdfPage: any = null;
 
   const renderPage = async (): Promise<any> => {
     if (getPage) {
-      page = await getPage();
+      pdfPage = await getPage();
 
       if (rootEle.current) {
         await renderPdfPage({
           rootEle: rootEle.current,
-          page,
+          pdfPage,
           viewport,
         });
       }
@@ -46,13 +46,13 @@ const PageView: React.FunctionComponent<Props> = ({
   useEffect(() => {
     if (renderingState === 'RENDERING') {
       renderPage();
-    } else if (page && renderingState === 'LOADING') {
-      page.cleanup();
+    } else if (pdfPage && renderingState === 'LOADING') {
+      pdfPage.cleanup();
     }
   }, [renderingState, viewport]);
 
   useEffect(() => (): void => {
-    if (page) page.cleanup();
+    if (pdfPage) pdfPage.cleanup();
   }, []);
 
   return (
@@ -66,7 +66,10 @@ const PageView: React.FunctionComponent<Props> = ({
     >
       {
         renderingState === 'LOADING' ? (
-          <LoadingLayer />
+          <>
+            <LoadingLayer />
+            <TextLayer data-id="text-layer" />
+          </>
         ) : (
           <>
             <div>

+ 4 - 0
components/Page/styled.ts

@@ -12,6 +12,10 @@ export const PageWrapper = styled('div')<{width: number; height: number; rotatio
   height: ${props => props.height}px;
   transform: rotate(${props => props.rotation}deg);
   background-color: white;
+
+  &:first-of-type {
+    margin-top: 40px;
+  }
 `;
 
 export const PdfCanvas = styled.canvas`

+ 3 - 8
components/ThumbnailViewer/index.tsx

@@ -1,4 +1,5 @@
 import React, { useEffect, useState, useRef } from 'react';
+import _ from 'lodash';
 
 import Icon from '../Icon';
 import Drawer from '../Drawer';
@@ -45,7 +46,7 @@ const Thumbnails: React.FunctionComponent<Props> = ({
           key={`thumbnail_${i}`}
           pageNum={i}
           getPdfImage={getPdfImage}
-          renderingState={[1, 2, 3, 4, 5].includes(i) ? 'RENDERING' : 'LOADING'}
+          renderingState={_.range(1, 6).includes(i) ? 'RENDERING' : 'LOADING'}
         />
       );
       eleContent.push(component);
@@ -54,13 +55,7 @@ const Thumbnails: React.FunctionComponent<Props> = ({
   };
 
   const updateThumbnails = (): void => {
-    const renderingIndexQueue = [
-      scrollIndex - 1,
-      scrollIndex,
-      scrollIndex + 1,
-      scrollIndex + 2,
-      scrollIndex + 3,
-    ];
+    const renderingIndexQueue = _.range(scrollIndex - 1, scrollIndex + 3);
     let index = scrollIndex - 5;
     const end = scrollIndex + 5;
 

+ 3 - 3
components/Toolbar/index.tsx

@@ -49,9 +49,9 @@ const Toolbar: React.FunctionComponent<Props> = ({
 
   const handleScaleSelect = async (selected: SelectOptionType): Promise<any> => {
     if (selected.value === 'fit') {
-      const screenHeight = window.screen.height - 200;
-      const originPdfHeight = viewport.height / scale;
-      const rate = screenHeight / originPdfHeight;
+      const screenWidth = window.document.body.offsetWidth - 276;
+      const originPdfWidth = viewport.width / scale;
+      const rate = screenWidth / originPdfWidth;
       changeScale(rate);
     } else {
       changeScale(scaleCheck(selected.value as number));

+ 1 - 0
components/Typography/index.tsx

@@ -7,6 +7,7 @@ type Props = {
   light?: boolean;
   children: string | number;
   style?: {};
+  align?: 'left' | 'center' | 'right';
 };
 
 const Typography: React.FunctionComponent<Props> = ({

+ 6 - 4
components/Typography/styled.ts

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

+ 1 - 3
components/Viewer/styled.ts

@@ -4,17 +4,15 @@ export const Container = styled('div')<{isFull: boolean}>`
   position: fixed;
   left: ${props => (props.isFull ? 0 : '267px')};
   right: 0;
-  top: ${props => (props.isFull ? 0 : '60px')};
+  top: ${props => (props.isFull ? '-34px' : '60px')};
   bottom: 0;
   overflow: scroll;
   text-align: center;
-  padding: 20px;
 
   transition: all 225ms cubic-bezier(0, 0, 0.2, 1) 0ms;
 `;
 
 export const Wrapper = styled('div')<{width: number}>`
-  padding: 20px 0;
   display: inline-flex;
   flex-direction: column;
   width: ${props => props.width}px;

+ 1 - 1
containers/HighlightTools/index.tsx

@@ -23,7 +23,7 @@ const Highlight: React.FunctionComponent<Props> = ({
   isActive,
   onClick,
 }: Props) => {
-  const [color, setColor] = useState('#fcff36');
+  const [color, setColor] = useState('#FCFF36');
   const [type, setType] = useState('highlight');
   const [opacity, setOpacity] = useState(40);
 

+ 22 - 12
containers/Navbar.tsx

@@ -1,30 +1,40 @@
 import React from 'react';
+import queryString from 'query-string';
 
 import useStore from '../store';
 import useActions from '../actions';
 
 import NavbarComponent from '../components/Navbar';
 import Search from './Search';
-import AnnotationList from '../components/AnnotationList';
 import Thumbnails from './Thumbnails';
+import AnnotationList from './AnnotationList';
+import { downloadFileWithUri } from '../helpers/utility';
 
 const Navbar: React.FunctionComponent = () => {
-  const [{ navbarState, displayMode }, dispatch] = useStore();
+  const [{
+    navbarState,
+    displayMode,
+  }, dispatch] = useStore();
   const { setNavbar } = useActions(dispatch);
 
   const onClick = (state: string): void => {
-    if (state === navbarState) {
-      setNavbar('');
-    } else {
-      setNavbar(state);
+    switch (state) {
+      case 'export': {
+        const parsed = queryString.parse(window.location.search);
+        const fileName = atob(parsed.token as string);
+        const path = `/api/v1/output.pdf?f=${parsed.token}`;
+        downloadFileWithUri(fileName, path);
+        break;
+      }
+      case navbarState:
+        setNavbar('');
+        break;
+      default:
+        setNavbar(state);
+        break;
     }
   };
 
-  const transferProps = (role: string): any => ({
-    isActive: navbarState === role,
-    close: (): void => { onClick(''); },
-  });
-
   return (
     <NavbarComponent
       onClick={onClick}
@@ -32,7 +42,7 @@ const Navbar: React.FunctionComponent = () => {
       displayMode={displayMode}
     >
       <Search />
-      <AnnotationList {...transferProps('annotations')} />
+      <AnnotationList />
       <Thumbnails />
     </NavbarComponent>
   );

+ 5 - 3
containers/PdfPages.tsx

@@ -1,6 +1,7 @@
 import React, {
   useEffect, useState, useRef,
 } from 'react';
+import _ from 'lodash';
 
 import { AnnotationType, ScrollStateType } from '../constants/type';
 import Page from '../components/Page';
@@ -34,7 +35,8 @@ const PdfPages: React.FunctionComponent<Props> = ({
   const getAnnotations = (arr: AnnotationType[], pageNum: number): any[] => {
     const result: any[] = [];
     arr.forEach((ele: AnnotationType, index: number) => {
-      if (ele.obj_attr?.page === pageNum) {
+      const page = ele.obj_attr ? ele.obj_attr.page as number + 1 : 0;
+      if (page === pageNum) {
         result.push(<Annotation key={`annotations_${pageNum + index}`} scale={scale} index={index} {...ele} />);
       }
     });
@@ -50,7 +52,7 @@ const PdfPages: React.FunctionComponent<Props> = ({
         <Page
           key={`page-${i}`}
           pageNum={i}
-          renderingState={[1, 2, 3].includes(i) ? 'RENDERING' : 'LOADING'}
+          renderingState={_.range(1, 4).includes(i) ? 'RENDERING' : 'LOADING'}
           viewport={viewport}
           getPage={(): Promise<any> => getPdfPage(pdf, i)}
           rotation={rotation}
@@ -64,7 +66,7 @@ const PdfPages: React.FunctionComponent<Props> = ({
   };
 
   const updatePages = (): void => {
-    const renderingIndexQueue = [currentPage - 1, currentPage, currentPage + 1];
+    const renderingIndexQueue = _.range(currentPage - 1, currentPage + 2);
     let index = currentPage - 4;
     const end = currentPage + 3;
 

+ 10 - 12
containers/PdfViewer.tsx

@@ -8,7 +8,7 @@ import PdfPages from './PdfPages';
 import { fetchPdf } from '../helpers/pdf';
 import { ProgressType, ScrollStateType } from '../constants/type';
 import { scrollIntoView } from '../helpers/utility';
-import { parseAnnotationFromXml, fetchXfdf } from '../helpers/annotation';
+import { parseAnnotationFromXml } from '../helpers/annotation';
 
 import useActions from '../actions';
 import useStore from '../store';
@@ -53,14 +53,10 @@ const PdfViewer: React.FunctionComponent = () => {
     const parsed = queryString.parse(window.location.search);
     const result = await initialPdfFile(parsed.token as string);
 
-    fetchXfdf(parsed.token as string).then((res) => {
-      if (res) {
-        const annot = parseAnnotationFromXml(res);
-        addAnnotation(annot, false);
-      }
-    });
-
     if (result.data) {
+      const annot = parseAnnotationFromXml(result.data.xfdf);
+      addAnnotation(annot, false);
+
       setInfo({
         token: parsed.token,
         id: result.data.transaction_id,
@@ -79,9 +75,10 @@ const PdfViewer: React.FunctionComponent = () => {
 
       iViewport.width = Math.round(iViewport.width);
       iViewport.height = Math.round(iViewport.height);
-      const screenwidth = window.screen.width - 200;
-      const originPdfWidth = iViewport.width / 1;
-      const rate = screenwidth / originPdfWidth;
+      const screenWidth = window.document.body.offsetWidth - 5;
+      const originPdfWidth = iViewport.width;
+      const rate = (screenWidth / originPdfWidth).toFixed(2);
+
       changeScale(rate);
     }
   };
@@ -102,7 +99,8 @@ const PdfViewer: React.FunctionComponent = () => {
 
   const scrollToUpdate = (state: ScrollStateType): void => {
     const ele: HTMLDivElement = document.getElementById('page_1') as HTMLDivElement;
-    const page = Math.round((state.lastY + ele.offsetHeight / 1.4) / (ele.offsetHeight + 40));
+    const page = Math.round((state.lastY + ele.offsetHeight / 1.4) / (ele.offsetHeight + 50));
+
     if (page !== currentPageRef.current) {
       setCurrentPage(page);
     }

+ 20 - 5
containers/Search.tsx

@@ -1,5 +1,7 @@
 /* eslint-disable @typescript-eslint/no-use-before-define */
-import React, { useState, useEffect, useRef } from 'react';
+import React, {
+  useState, useEffect, useRef,
+} from 'react';
 
 import useActions from '../actions';
 import useStore from '../store';
@@ -71,7 +73,7 @@ const Search: React.FunctionComponent = () => {
   };
 
   const cleanMatch = (): void => {
-    if (queryString.current) {
+    if (queryString.current && pageMatches.current[pageIndex.current]) {
       const pageMatch = pageMatches.current[pageIndex.current][matchIndex.current];
       const textContentItem = textContentItems.current[pageIndex.current];
       const { begin, end } = convertMatches(queryString.current, pageMatch, textContentItem);
@@ -88,7 +90,10 @@ const Search: React.FunctionComponent = () => {
   };
 
   const startText = (begin: Record<string, any>): void => {
-    textDiv.current[begin.divIdx].textContent = '';
+    if (textDiv.current[begin.divIdx]) {
+      textDiv.current[begin.divIdx].textContent = '';
+    }
+
     appendTextToDiv(begin.divIdx, 0, begin.offset, false);
   };
 
@@ -145,7 +150,10 @@ const Search: React.FunctionComponent = () => {
   };
 
   const prevMatch = (): void => {
-    const numPageMatches = pageMatches.current[pageIndex.current].length;
+    let numPageMatches = 0;
+    if (pageMatches.current[pageIndex.current]) {
+      numPageMatches = pageMatches.current[pageIndex.current].length;
+    }
 
     if (numPageMatches && matchIndex.current - 1 >= 0) {
       matchIndex.current -= 1;
@@ -156,7 +164,11 @@ const Search: React.FunctionComponent = () => {
   };
 
   const nextMatch = (): void => {
-    const numPageMatches = pageMatches.current[pageIndex.current].length;
+    let numPageMatches = 0;
+
+    if (pageMatches.current[pageIndex.current]) {
+      numPageMatches = pageMatches.current[pageIndex.current].length;
+    }
 
     if (numPageMatches && matchIndex.current + 1 < numPageMatches) {
       matchIndex.current += 1;
@@ -199,6 +211,9 @@ const Search: React.FunctionComponent = () => {
       await extractPdfText();
     }
 
+    pageIndex.current = 0;
+    matchIndex.current = 0;
+
     const query = val.toLowerCase();
     queryString.current = query;
     for (let i = 0; i < totalPage; i += 1) {

+ 1 - 8
global/styled.js

@@ -1,5 +1,5 @@
 /* eslint-disable consistent-return */
-import { createGlobalStyle, css } from 'styled-components';
+import { createGlobalStyle } from 'styled-components';
 
 export const GlobalStyle = createGlobalStyle`
   html, body {
@@ -12,13 +12,6 @@ export const GlobalStyle = createGlobalStyle`
   div {
     box-sizing: border-box;
   }
-
-  ${({ lang }) => {
-    if (lang === 'zh-tw') return css`@import url('https://fonts.googleapis.com/css?family=Noto+Sans+TC:300,400,700&display=swap');`;
-    if (lang === 'zh-cn') return css`@import url('https://fonts.googleapis.com/css?family=Noto+Sans+SC:300,400,700&display=swap');`;
-    if (lang === 'ja') return css`@import url('https://fonts.googleapis.com/css?family=Noto+Sans+JP:300,400,700&display=swap');`;
-    if (lang === 'en') return css`@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,700&display=swap');`;
-  }}
   
   &:lang(en) {
     font-family: 'Open Sans', sans-serif;

+ 12 - 7
helpers/utility.ts

@@ -19,6 +19,7 @@ export const watchScroll = (
     down: true,
     lastX: element.scrollLeft,
     lastY: element.scrollTop,
+    subscriber: {},
   };
 
   const debounceScroll = (): void => {
@@ -45,11 +46,13 @@ export const watchScroll = (
     });
   };
 
-  fromEvent(element, 'scroll').pipe(
+  const subscriber = fromEvent(element, 'scroll').pipe(
     throttleTime(200),
     auditTime(300),
   ).subscribe(debounceScroll);
 
+  state.subscriber = subscriber;
+
   return state;
 };
 
@@ -104,10 +107,12 @@ export const hexToRgb = (hex: string): any => {
   } : null;
 };
 
-export const chunk = (arr: any[], chunkSize: number): any[] => {
-  const R = [];
-  for (let i = 0, len = arr.length; i < len; i += chunkSize) {
-    R.push(arr.slice(i, i + chunkSize));
-  }
-  return R;
+export const downloadFileWithUri = (name: string, uri: string): void => {
+  const ele = document.createElement('a');
+  ele.download = name;
+  ele.href = uri;
+  document.body.appendChild(ele);
+  ele.click();
+  document.body.removeChild(ele);
+  ele.remove();
 };

+ 4 - 2
hooks/useCursorPosition.ts

@@ -39,10 +39,12 @@ const useCursorPosition = (): [CursorPosition, (element: HTMLElement | null) =>
         clientX, clientY, screenX, screenY, pageX, pageY,
       } = e;
       const rect = element.getBoundingClientRect();
+      const offsetX = window.pageXOffset || window.scrollX || 0;
+      const offsetY = window.pageYOffset || window.scrollY || 0;
 
       setState((prev: CursorPosition) => ({
-        x: pageX - rect.left - (window.pageXOffset || window.scrollX),
-        y: pageY - rect.top - (window.pageYOffset || window.scrollY),
+        x: pageX - rect.left - offsetX,
+        y: pageY - rect.top - offsetY,
         clientX,
         clientY,
         screenX,

+ 3 - 3
middleware/index.ts

@@ -1,6 +1,6 @@
 /* eslint-disable @typescript-eslint/camelcase */
 import { Dispatch } from 'react';
-import { CHANGE_SCALE, TOGGLE_DISPLAY_MODE } from '../constants/actionTypes';
+import { CHANGE_SCALE, TOGGLE_DISPLAY_MODE } from '../../constants/actionTypes';
 
 const applyMiddleware = (
   state: any, dispatch: Dispatch<any>,
@@ -11,9 +11,9 @@ const applyMiddleware = (
   switch (action.type) {
     case TOGGLE_DISPLAY_MODE: {
       if (action.payload === 'full') {
-        const screenwidth = window.screen.width - 200;
+        const screenWidth = window.document.body.offsetWidth - 5;
         const originPdfWidth = state.viewport.width / state.scale;
-        const rate = screenwidth / originPdfWidth;
+        const rate = (screenWidth / originPdfWidth).toFixed(2);
         dispatch({ type: CHANGE_SCALE, payload: rate });
       } else {
         dispatch({ type: CHANGE_SCALE, payload: 1 });

+ 1 - 1
store/index.tsx

@@ -4,7 +4,7 @@ import initialMainState, { StateType as MainStateType } from './initialMainState
 import initialPdfState, { StateType as PdfStateType } from './initialPdfState';
 
 import reducers from '../reducers';
-import applyMiddleware from '../middleware';
+import applyMiddleware from '../reducers/middleware';
 
 type StateType = MainStateType & PdfStateType