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, parsePositionForBackend, } 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; }; const Shape: React.FC = ({ title, isActive, onClick }: Props) => { const [startPosition, setStartPosition] = useState({ x: 0, y: 0 }); const [uuid, setUuid] = useState(''); const [data, setData] = useState({ shape: 'square', type: 'fill', color: '#FBB705', opacity: 35, width: 0, }); const [cursorPosition, setRef] = useCursorPosition(); const [{ viewport, scale, annotations }, dispatch] = useStore(); const { addAnnots, updateAnnots } = 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 addShape = useCallback( ( pageEle: HTMLElement, event: MouseEvent | TouchEvent, attributes: OptionPropsType, ): void => { const { shape = '', type, opacity, color, width = 0 } = attributes; const pageNum = pageEle.getAttribute('data-page-num') || 0; const coordinate = getAbsoluteCoordinate(pageEle, event); const id = uuidv4(); setUuid(id); setStartPosition(coordinate); const shapeType = ANNOTATION_TYPE[shape]; const position = convertPosition( shapeType, coordinate.x - 8, coordinate.y - 8, coordinate.x + 8, coordinate.y + 8, ); const annoteData = { id, obj_type: shapeType, obj_attr: { page: pageNum as number, position, bdcolor: color, fcolor: type === 'fill' ? color : undefined, transparency: opacity, ftransparency: type === 'fill' ? opacity : undefined, bdwidth: width, is_arrow: shape === 'arrow', }, }; const shapeAnnotation = appendUserIdAndDate( parseAnnotationObject(annoteData, viewport.height, scale), ); addAnnots([shapeAnnotation]); }, [viewport, scale, data], ); const handleMouseDown = (event: MouseEvent | TouchEvent): void => { switchPdfViewerScrollState('hidden'); const pageEle = (event.target as HTMLElement).parentNode as HTMLElement; if (pageEle.hasAttribute('data-page-num')) { addShape(pageEle, event, data); setRef(pageEle); } }; const handleMouseUp = (): void => { switchPdfViewerScrollState('scroll'); setRef(null); setUuid(''); setStartPosition({ x: 0, y: 0 }); }; const subscribeEvent = (): void => { 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(() => { const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement; if (isActive && pdfViewer) { subscribeEvent(); } return (): void => { if (pdfViewer) { unsubscribeEvent(); } }; }, [isActive, addShape]); const handleUpdate = useCallback( (start: PointType, end: PointType): void => { const index = annotations.length - 1; const { x: x1, y: y1 } = start; const { x: x2, y: y2 } = end; if (annotations[index] && annotations[index].id === uuid) { const type = annotations[index].obj_type; const position = convertPosition(type, x1, y1, x2, y2); annotations[index].obj_attr.position = parsePositionForBackend( type, position, viewport.height, scale, ); annotations[index] = appendUserIdAndDate(annotations[index]); updateAnnots([...annotations]); } }, [annotations, viewport, scale, uuid], ); useEffect(() => { if ( startPosition.x && startPosition.y && cursorPosition.x && cursorPosition.y ) { handleUpdate(startPosition, cursorPosition as PointType); } }, [startPosition, cursorPosition]); const Label = ( ); return ( ); }; export default Shape;