Sfoglia il codice sorgente

fix(line module): drag and drop performance

RoyLiu 4 anni fa
parent
commit
c4e082cdd2

+ 15 - 4
.eslintrc.json

@@ -15,10 +15,16 @@
     "prettier/@typescript-eslint"
   ],
   "parser": "@typescript-eslint/parser",
-  "plugins": ["jsx-a11y", "@typescript-eslint", "prettier"],
+  "plugins": [
+    "jsx-a11y",
+    "@typescript-eslint",
+    "prettier"
+  ],
   "rules": {
     "react/display-name": "off",
-    // "@typescript-eslint/no-explicit-any": "off",
+    "@typescript-eslint/no-explicit-any": "off",
+    "@typescript-eslint/explicit-function-return-type": "off",
+    "@typescript-eslint/camelcase": "off",
     "@typescript-eslint/explicit-module-boundary-types": "off"
   },
   "settings": {
@@ -27,8 +33,13 @@
     },
     "import/resolver": {
       "node": {
-        "extensions": [".js", ".jsx", ".ts", ".tsx"]
+        "extensions": [
+          ".js",
+          ".jsx",
+          ".ts",
+          ".tsx"
+        ]
       }
     }
   }
-}
+}

+ 2 - 7
components/AnnotationList/index.tsx

@@ -45,24 +45,19 @@ const AnnotationList: React.FC<Props> = ({
             const key = `annotation_item_${index}`;
             const {
               obj_type,
-              obj_attr: { page, title, date, style },
+              obj_attr: { page, title, date, is_arrow },
             } = ele;
             const actualPage = page + 1;
             const prevAnnot = list[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={annotType}
+                type={is_arrow ? 'Arrow' : obj_type}
                 page={actualPage}
                 showPageNum={actualPage !== prevPage}
               />

+ 136 - 120
components/Line/index.tsx

@@ -1,137 +1,154 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
 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> = ({
+import { SVG } from './styled';
+
+const getActualPoint = (
+  { start, end }: LinePositionType,
+  h: number,
+  s: number,
+): LinePositionType => {
+  const x1 = start.x * s;
+  const y1 = h - start.y * s;
+  const x2 = end.x * s;
+  const y2 = h - end.y * s;
+  return {
+    start: {
+      x: x1,
+      y: y1,
+    },
+    end: {
+      x: x2,
+      y: y2,
+    },
+  };
+};
+
+const rectCalc = ({ start, end }: LinePositionType): HTMLCoordinateType => {
+  return {
+    top: start.y < end.y ? start.y : end.y,
+    left: start.x < end.x ? start.x : end.x,
+    width: Math.abs(end.x - start.x),
+    height: Math.abs(end.y - start.y),
+  };
+};
+
+const Line: React.FC<AnnotationElementPropsType> = ({
   id,
   obj_type,
-  obj_attr,
+  obj_attr: { bdcolor = '', bdwidth = 0, transparency = 0, position, is_arrow },
   scale,
   viewport,
   isCollapse,
   onUpdate,
 }: AnnotationElementPropsType) => {
-  const {
-    bdcolor = '',
-    bdwidth = 0,
-    transparency = 0,
-    position,
-    is_arrow,
-  } = obj_attr;
+  const [startPoint, setStartPoint] = useState({ x: 0, y: 0 });
+  const [endPoint, setEndPoint] = useState({ x: 0, y: 0 });
+  const [rect, setRect] = useState({ top: 0, left: 0, width: 0, height: 0 });
+  const [moving, setMoving] = useState(false);
   const uuid = uuidv4();
 
-  const getActualPoint = (
-    { start, end }: LinePositionType,
-    h: number,
-    s: number,
-  ): LinePositionType => {
-    const x1 = start.x * scale;
-    const y1 = h - start.y * s;
-    const x2 = end.x * scale;
-    const y2 = h - end.y * s;
-    return {
-      start: {
-        x: x1,
-        y: y1,
-      },
-      end: {
-        x: x2,
-        y: y2,
-      },
-    };
-  };
-
-  const pointCalc = (
-    { start, end }: LinePositionType,
-    borderWidth: number,
-  ): LinePositionType => ({
-    start: {
-      x: start.x + borderWidth,
-      y: start.y + borderWidth,
-    },
-    end: {
-      x: end.x + borderWidth,
-      y: end.y + borderWidth,
-    },
-  });
-
-  const rectCalc = (
-    { start, end }: LinePositionType,
-    h: number,
-    s: number,
-  ): HTMLCoordinateType => {
-    const startY = h - start.y * s;
-    const endY = h - end.y * s;
-
-    return {
-      top: startY > endY ? endY : startY,
-      left: start.x > end.x ? end.x * s : start.x * s,
-      width: Math.abs((end.x - start.x) * s),
-      height: Math.abs(endY - startY),
-    };
-  };
-
   const actualbdwidth = bdwidth * scale;
-  const annotRect = rectCalc(
-    position as LinePositionType,
-    viewport.height,
-    scale,
-  );
-  const { start, end } = getActualPoint(
-    position as LinePositionType,
-    viewport.height,
-    scale,
-  );
-  const { start: completeStart, end: completeEnd } = pointCalc(
-    { start, end } as LinePositionType,
-    actualbdwidth,
-  );
-
-  const actualWidth = annotRect.width + actualbdwidth + 15;
-  const actualHeight = annotRect.height + actualbdwidth + 15;
 
   const handleMove = ({
     start: moveStart,
     end: moveEnd,
   }: LinePositionType): void => {
-    const newPosition = {
-      start: {
-        x: moveStart.x,
-        y: moveStart.y,
-      },
-      end: {
-        x: moveEnd.x,
-        y: moveEnd.y,
-      },
-    };
-
-    onUpdate({
-      position: parsePositionForBackend(
-        obj_type,
-        newPosition,
-        viewport.height,
-        scale,
-      ),
-    });
+    setMoving(true);
+    setStartPoint(moveStart);
+    setEndPoint(moveEnd);
+
+    const newRect = rectCalc({ start: moveStart, end: moveEnd });
+
+    if (is_arrow) {
+      setRect({
+        top: newRect.top - actualbdwidth * 2,
+        left: newRect.left - actualbdwidth * 2,
+        width: newRect.width + actualbdwidth * 4,
+        height: newRect.height + actualbdwidth * 4,
+      });
+    } else {
+      setRect({
+        top: newRect.top - actualbdwidth / 2,
+        left: newRect.left - actualbdwidth / 2,
+        width: newRect.width + actualbdwidth,
+        height: newRect.height + actualbdwidth,
+      });
+    }
   };
 
+  const handleMouseUp = () => {
+    setMoving(false);
+  };
+
+  useEffect(() => {
+    if (!moving && startPoint.x) {
+      const newPosition = {
+        start: {
+          x: startPoint.x,
+          y: startPoint.y,
+        },
+        end: {
+          x: endPoint.x,
+          y: endPoint.y,
+        },
+      };
+
+      onUpdate({
+        position: parsePositionForBackend(
+          obj_type,
+          newPosition,
+          viewport.height,
+          scale,
+        ),
+      });
+    }
+  }, [moving, startPoint, endPoint]);
+
+  useEffect(() => {
+    const { start, end } = getActualPoint(
+      position as LinePositionType,
+      viewport.height,
+      scale,
+    );
+
+    setStartPoint(start);
+    setEndPoint(end);
+
+    const initRect = rectCalc({ start, end });
+
+    if (is_arrow) {
+      setRect({
+        top: initRect.top - actualbdwidth * 2,
+        left: initRect.left - actualbdwidth * 2,
+        width: initRect.width + actualbdwidth * 4,
+        height: initRect.height + actualbdwidth * 4,
+      });
+    } else {
+      setRect({
+        top: initRect.top - actualbdwidth / 2,
+        left: initRect.left - actualbdwidth / 2,
+        width: initRect.width + actualbdwidth,
+        height: initRect.height + actualbdwidth,
+      });
+    }
+  }, [viewport, scale]);
+
   return (
     <>
       <AnnotationContainer
         id={id}
-        top={`${annotRect.top}px`}
-        left={`${annotRect.left}px`}
-        width={`${actualWidth}px`}
-        height={`${actualHeight}px`}
+        top={`${rect.top}px`}
+        left={`${rect.left}px`}
+        width={`${rect.width}px`}
+        height={`${rect.height}px`}
       >
-        <svg
-          width={`${actualWidth}px`}
-          height={`${actualHeight}px`}
-          viewBox={`${annotRect.left} ${annotRect.top} ${actualWidth} ${actualHeight}`}
-        >
+        <SVG viewBox={`${rect.left} ${rect.top} ${rect.width} ${rect.height}`}>
           {is_arrow ? (
             <defs>
               <marker
@@ -154,28 +171,27 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
             </defs>
           ) : null}
           <line
-            x1={completeStart.x}
-            y1={completeStart.y}
-            x2={completeEnd.x}
-            y2={completeEnd.y}
+            x1={startPoint.x}
+            y1={startPoint.y}
+            x2={endPoint.x}
+            y2={endPoint.y}
             stroke={bdcolor}
             strokeWidth={actualbdwidth}
             strokeOpacity={transparency}
             markerEnd={is_arrow ? `url(#${uuid})` : ''}
           />
-        </svg>
+        </SVG>
       </AnnotationContainer>
       {!isCollapse ? (
         <Outer
-          top={annotRect.top}
-          left={annotRect.left}
-          width={actualWidth}
-          height={actualHeight}
-          start={start}
-          end={end}
-          completeStart={completeStart}
-          completeEnd={completeEnd}
+          top={rect.top}
+          left={rect.left}
+          width={rect.width}
+          height={rect.height}
+          start={startPoint}
+          end={endPoint}
           onMove={handleMove}
+          onMouseUp={handleMouseUp}
         />
       ) : (
         ''
@@ -184,4 +200,4 @@ const Note: React.SFC<AnnotationElementPropsType> = ({
   );
 };
 
-export default Note;
+export default Line;

+ 15 - 0
components/Line/styled.ts

@@ -0,0 +1,15 @@
+import styled from 'styled-components';
+
+export const SVG = styled.svg`
+  pointer-events: none;
+  user-select: none;
+  touch-action: none;
+  position: absolute;
+  left: 0;
+  top: 0;
+`;
+
+export const PolyLine = styled('polyline')<{ isCovered: boolean }>`
+  pointer-events: visiblepainted;
+  cursor: ${(props) => (props.isCovered ? 'pointer' : 'default')};
+`;

+ 16 - 17
components/OuterRectForLine/index.tsx

@@ -1,8 +1,8 @@
 import React, { useEffect, useState } from 'react';
 
-import theme from '../../helpers/theme';
 import useCursorPosition from '../../hooks/useCursorPosition';
 import { getAbsoluteCoordinate } from '../../helpers/position';
+import theme from '../../helpers/theme';
 
 import { SVG, Circle } from './styled';
 
@@ -13,11 +13,10 @@ type Props = LinePositionType & {
   height: number;
   onMove: (position: LinePositionType) => void;
   onClick?: (event: React.MouseEvent) => void;
-  completeStart: PointType;
-  completeEnd: PointType;
+  onMouseUp: () => void;
 };
 
-const MARGIN_DISTANCE = 18;
+const MARGIN_DISTANCE = 10;
 
 const initState = {
   start: { x: 0, y: 0 },
@@ -27,7 +26,7 @@ const initState = {
   clickY: 0,
 };
 
-const OuterRect: React.FC<Props> = ({
+const index: React.FC<Props> = ({
   top,
   left,
   width,
@@ -36,11 +35,10 @@ const OuterRect: React.FC<Props> = ({
   end,
   onMove,
   onClick,
-  completeStart,
-  completeEnd,
+  onMouseUp,
 }: Props) => {
   const [state, setState] = useState(initState);
-  const [cursorPosition, setRef] = useCursorPosition(20);
+  const [cursorPosition, setRef] = useCursorPosition(25);
 
   const handleMouseDown = (e: React.MouseEvent): void => {
     const operatorId = (e.target as HTMLElement).getAttribute(
@@ -61,6 +59,7 @@ const OuterRect: React.FC<Props> = ({
   const handleMouseUp = (): void => {
     setRef(null);
     setState(initState);
+    onMouseUp();
   };
 
   useEffect(() => {
@@ -68,7 +67,6 @@ const OuterRect: React.FC<Props> = ({
       if (state.operator === 'start') {
         const x1 = cursorPosition.x - (state.clickX - state.start.x);
         const y1 = cursorPosition.y - (state.clickY - state.start.y);
-
         onMove({
           start: { x: x1, y: y1 },
           end,
@@ -106,12 +104,13 @@ const OuterRect: React.FC<Props> = ({
   return (
     <SVG
       data-id="move"
-      viewBox={`${left} ${top} ${width + 10} ${height + 10}`}
+      viewBox={`${left} ${top} ${width + MARGIN_DISTANCE} 
+      ${height + MARGIN_DISTANCE}`}
       style={{
         left: `${left - MARGIN_DISTANCE}px`,
         top: `${top - MARGIN_DISTANCE}px`,
-        width: `${width + 10}px`,
-        height: `${height + 10}px`,
+        width: `${width + MARGIN_DISTANCE}px`,
+        height: `${height + MARGIN_DISTANCE}px`,
       }}
       onClick={onClick}
       onMouseDown={handleMouseDown}
@@ -120,20 +119,20 @@ const OuterRect: React.FC<Props> = ({
         data-id="start"
         onMouseDown={handleMouseDown}
         fill={theme.colors.primary}
-        cx={completeStart.x + MARGIN_DISTANCE}
-        cy={completeStart.y + MARGIN_DISTANCE}
+        cx={start.x + MARGIN_DISTANCE}
+        cy={start.y + MARGIN_DISTANCE}
         r={6}
       />
       <Circle
         data-id="end"
         onMouseDown={handleMouseDown}
         fill={theme.colors.primary}
-        cx={completeEnd.x + MARGIN_DISTANCE}
-        cy={completeEnd.y + MARGIN_DISTANCE}
+        cx={end.x + MARGIN_DISTANCE}
+        cy={end.y + MARGIN_DISTANCE}
         r={6}
       />
     </SVG>
   );
 };
 
-export default OuterRect;
+export default index;

+ 1 - 0
components/SelectBox/index.tsx

@@ -41,6 +41,7 @@ const SelectBox: React.FC<Props> = ({
 
   const handleSelect = (index: number): void => {
     setValue(options[index].content);
+    handleBlur();
 
     if (onChange) {
       onChange(options[index]);

+ 9 - 3
components/Shape/index.tsx

@@ -30,6 +30,7 @@ const Shape: React.FC<AnnotationElementPropsType> = ({
     width: 0,
     height: 0,
   });
+  const actualbdwidth = bdwidth * scale;
 
   const handleScaleOrMove = ({
     top,
@@ -54,11 +55,16 @@ const Shape: React.FC<AnnotationElementPropsType> = ({
 
   useEffect(() => {
     const rect = rectCalc(position as PositionType, viewport.height, scale);
-    setLocalePos(rect);
+    setLocalePos({
+      top: rect.top - actualbdwidth / 2,
+      left: rect.left - actualbdwidth / 2,
+      width: rect.width + actualbdwidth,
+      height: rect.height + actualbdwidth,
+    });
   }, [viewport, scale]);
 
   useEffect(() => {
-    if (!moving) {
+    if (!moving && localePos.width) {
       const newPosition = {
         top: localePos.top,
         left: localePos.left,
@@ -92,7 +98,7 @@ const Shape: React.FC<AnnotationElementPropsType> = ({
           height={localePos.height}
           bdcolor={bdcolor}
           transparency={transparency}
-          bdwidth={bdwidth * scale}
+          bdwidth={actualbdwidth}
           fcolor={fcolor}
           ftransparency={ftransparency}
         />

+ 2 - 2
components/SvgShapeElement/index.tsx

@@ -24,8 +24,8 @@ const SvgShapeElement: React.FC<Props> = ({
   <svg viewBox={`0 0 ${width} ${height}`}>
     {shape === 'Circle' ? (
       <ellipse
-        cx="50%"
-        cy="50%"
+        cx={width / 2}
+        cy={height / 2}
         rx={(width - bdwidth) / 2}
         ry={(height - bdwidth) / 2}
         stroke={bdcolor}

+ 30 - 21
containers/ShapeTool.tsx

@@ -26,10 +26,9 @@ type Props = {
 
 let shapeEle: SVGElement | null = null;
 
-const ShapeTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
+const Shape: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
   const [startPoint, setStartPoint] = useState({ x: 0, y: 0 });
   const [endPoint, setEndPoint] = useState({ x: 0, y: 0 });
-
   const [data, setData] = useState({
     page: 0,
     shape: 'square',
@@ -79,7 +78,7 @@ const ShapeTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
     }
   };
 
-  const handleMouseDown = (event: MouseEvent | TouchEvent): void => {
+  const handleMouseDown = (event: MouseEvent): void => {
     switchPdfViewerScrollState('hidden');
 
     const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
@@ -116,7 +115,12 @@ const ShapeTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
         obj_attr: {
           page: data.page as number,
           position,
-          bdcolor: data.color,
+          bdcolor:
+            data.shape === 'line' ||
+            data.shape === 'arrow' ||
+            data.type === 'border'
+              ? data.color
+              : undefined,
           fcolor: data.type === 'fill' ? data.color : undefined,
           transparency: data.opacity,
           ftransparency: data.type === 'fill' ? data.opacity : undefined,
@@ -140,16 +144,12 @@ const ShapeTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
     const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
     pdfViewer.addEventListener('mousedown', handleMouseDown);
     pdfViewer.addEventListener('mouseup', handleMouseUp);
-    pdfViewer.addEventListener('touchstart', handleMouseDown);
-    pdfViewer.addEventListener('touchend', handleMouseUp);
   };
 
   const unsubscribeEvent = (): void => {
     const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
     pdfViewer.removeEventListener('mousedown', handleMouseDown);
     pdfViewer.removeEventListener('mouseup', handleMouseUp);
-    pdfViewer.removeEventListener('touchstart', handleMouseDown);
-    pdfViewer.removeEventListener('touchend', handleMouseUp);
   };
 
   useEffect(() => {
@@ -164,7 +164,7 @@ const ShapeTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
         unsubscribeEvent();
       }
     };
-  }, [isActive, handleMouseUp]);
+  }, [isActive, handleMouseDown, handleMouseUp]);
 
   useEffect(() => {
     if (cursorPosition.x && cursorPosition.y) {
@@ -172,7 +172,7 @@ const ShapeTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
     }
   }, [cursorPosition]);
 
-  const checkSvgChild = (type: string) => {
+  const checkSvgChild = (type: string): string => {
     switch (type) {
       case 'square':
         return 'rect';
@@ -212,22 +212,31 @@ const ShapeTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
           shapeEle.setAttribute('rx', `${xRadius}`);
           shapeEle.setAttribute('ry', `${yRadius}`);
         } else {
-          const actualWidth = data.width * scale;
-          shapeEle.setAttribute('x1', `${startPoint.x + actualWidth}`);
-          shapeEle.setAttribute('y1', `${startPoint.y + actualWidth}`);
-          shapeEle.setAttribute('x2', `${endPoint.x + actualWidth}`);
-          shapeEle.setAttribute('y2', `${endPoint.y + actualWidth}`);
+          shapeEle.setAttribute('x1', `${startPoint.x}`);
+          shapeEle.setAttribute('y1', `${startPoint.y}`);
+          shapeEle.setAttribute('x2', `${endPoint.x}`);
+          shapeEle.setAttribute('y2', `${endPoint.y}`);
         }
       } else {
         shapeEle = document.createElementNS(
           'http://www.w3.org/2000/svg',
           checkSvgChild(data.shape),
         );
-        shapeEle.setAttribute('fill', data.color);
-        shapeEle.setAttribute('fill-opacity', `${data.opacity * 0.01}`);
-        shapeEle.setAttribute('stroke', data.color);
-        shapeEle.setAttribute('stroke-width', `${data.width * scale}`);
-        shapeEle.setAttribute('stroke-opacity', `${data.opacity * 0.01}`);
+
+        if (
+          data.type !== 'fill' ||
+          data.shape === 'line' ||
+          data.shape === 'arrow'
+        ) {
+          shapeEle.setAttribute('fill', 'none');
+          shapeEle.setAttribute('stroke', data.color);
+          shapeEle.setAttribute('stroke-width', `${data.width * scale}`);
+          shapeEle.setAttribute('stroke-opacity', `${data.opacity * 0.01}`);
+        } else {
+          shapeEle.setAttribute('fill', data.color);
+          shapeEle.setAttribute('fill-opacity', `${data.opacity * 0.01}`);
+        }
+
         canvas.appendChild(shapeEle);
       }
     } else if (canvas && shapeEle) {
@@ -256,4 +265,4 @@ const ShapeTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
   );
 };
 
-export default ShapeTool;
+export default Shape;

+ 2 - 2
containers/Sidebar.tsx

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
 import Typography from '../components/Typography';
 import FormTools from './FormTools';
 import MarkupTools from './MarkupTools';
-import ImageTool from './ImageTool';
+// import ImageTool from './ImageTool';
 import WatermarkTool from './WatermarkTool';
 import InsertCursor from './InsertCursor';
 
@@ -44,7 +44,7 @@ const Sidebar: React.FC = () => {
         sidebarState={sidebarState}
         onClickSidebar={handleClickSidebar}
       />
-      <ImageTool onClickSidebar={handleClickSidebar} />
+      {/* <ImageTool onClickSidebar={handleClickSidebar} /> */}
       <WatermarkTool onClickSidebar={handleClickSidebar} />
       <InsertCursor />
     </SidebarWrapper>