import React, { useEffect, useState, useCallback } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { ANNOTATION_TYPE } from '../constants'; import Button from '../components/Button'; import ExpansionPanel from '../components/ExpansionPanel'; import Icon from '../components/Icon'; import ShapeOption from '../components/ShapeOption'; import { getAbsoluteCoordinate } from '../helpers/position'; import { parseAnnotationObject, appendUserIdAndDate, } from '../helpers/annotation'; import { switchPdfViewerScrollState } from '../helpers/pdf'; import useCursorPosition from '../hooks/useCursorPosition'; import useActions from '../actions'; import useStore from '../store'; type Props = { title: string; isActive: boolean; onClick: () => void; }; let shapeEle: SVGElement | null = null; const Shape: React.FC = ({ 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', type: 'fill', color: '#FBB705', opacity: 35, width: 0, }); const [cursorPosition, setRef] = useCursorPosition(30); const [{ viewport, scale }, dispatch] = useStore(); const { addAnnots } = useActions(dispatch); const setDataState = (obj: OptionPropsType): void => { setData((prev) => ({ ...prev, ...obj, })); }; const convertPosition = ( type: string, x1: number, y1: number, x2: number, y2: number, ): PositionType | LinePositionType => { switch (type) { case 'Line': return { start: { x: x1, y: y1, }, end: { x: x2, y: y2, }, }; default: return { top: y2 > y1 ? y1 : y2, left: x2 > x1 ? x1 : x2, right: x2 > x1 ? x2 : x1, bottom: y2 > y1 ? y2 : y1, }; } }; const handleMouseDown = (event: MouseEvent): void => { switchPdfViewerScrollState('hidden'); const pageEle = (event.target as HTMLElement).parentNode as HTMLElement; if (pageEle.hasAttribute('data-page-num')) { setRef(pageEle); const pageNum = pageEle.getAttribute('data-page-num') || '0'; const coordinate = getAbsoluteCoordinate(pageEle, event); setData((current) => ({ ...current, page: parseInt(pageNum, 10), })); setStartPoint(coordinate); } }; const handleMouseUp = useCallback((): void => { switchPdfViewerScrollState('scroll'); const shapeType = ANNOTATION_TYPE[data.shape]; const id = uuidv4(); if (startPoint.x !== endPoint.x && endPoint.y) { const position = convertPosition( shapeType, startPoint.x, startPoint.y, endPoint.x, endPoint.y, ); const annoteData = { id, obj_type: shapeType, obj_attr: { page: data.page as number, position, 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, bdwidth: data.width, is_arrow: data.shape === 'arrow', }, }; const shapeAnnotation = appendUserIdAndDate( parseAnnotationObject(annoteData, viewport.height, scale), ); addAnnots([shapeAnnotation]); setRef(null); setStartPoint({ x: 0, y: 0 }); setEndPoint({ x: 0, y: 0 }); } }, [startPoint, endPoint, viewport, data, scale]); const subscribeEvent = (): void => { const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement; pdfViewer.addEventListener('mousedown', handleMouseDown); pdfViewer.addEventListener('mouseup', handleMouseUp); }; const unsubscribeEvent = (): void => { const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement; pdfViewer.removeEventListener('mousedown', handleMouseDown); pdfViewer.removeEventListener('mouseup', handleMouseUp); }; useEffect(() => { const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement; if (pdfViewer) { if (isActive) { subscribeEvent(); } else { unsubscribeEvent(); } } return (): void => { if (pdfViewer) { unsubscribeEvent(); } }; }, [isActive, handleMouseDown, handleMouseUp]); useEffect(() => { if (cursorPosition.x && cursorPosition.y) { setEndPoint({ x: cursorPosition.x, y: cursorPosition.y }); } }, [cursorPosition]); const checkSvgChild = (type: string): string => { switch (type) { case 'square': return 'rect'; case 'circle': return 'ellipse'; default: return 'line'; } }; useEffect(() => { const pageEle = document.getElementById(`page_${data.page}`); const canvas = pageEle?.getElementsByClassName('canvas')[0] as HTMLElement; if (endPoint.x && endPoint.y) { if (shapeEle) { const { top, left, right, bottom } = convertPosition( ANNOTATION_TYPE[data.shape], startPoint.x, startPoint.y, endPoint.x, endPoint.y, ) as PositionType; if (data.shape === 'square') { shapeEle.setAttribute('x', `${left}`); shapeEle.setAttribute('y', `${top}`); shapeEle.setAttribute('width', `${right - left}`); shapeEle.setAttribute('height', `${bottom - top}`); } else if (data.shape === 'circle') { const xRadius = (right - left) / 2; const yRadius = (bottom - top) / 2; shapeEle.setAttribute('cx', `${left + xRadius}`); shapeEle.setAttribute('cy', `${top + yRadius}`); shapeEle.setAttribute('rx', `${xRadius}`); shapeEle.setAttribute('ry', `${yRadius}`); } else { 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), ); 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) { canvas.removeChild(shapeEle); shapeEle = null; } }, [startPoint, endPoint, data, scale]); const Label = ( ); return ( ); }; export default Shape;