import React, { useState, useEffect, useCallback } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { ANNOTATION_TYPE } from '../constants'; import Icon from '../components/Icon'; import Button from '../components/Button'; import ExpansionPanel from '../components/ExpansionPanel'; import InkOption from '../components/InkOption'; import { svgPath, bezierCommand, controlPoint, line, } from '../helpers/svgBezierCurve'; 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 pathEle: any = null; const FreehandTool: React.FC = ({ title, isActive, onClick }: Props) => { const [cursorPosition, setRef] = useCursorPosition(30); const [path, setPath] = useState([]); const [data, setData] = useState({ page: 0, type: 'pen', opacity: 100, color: '#FF0000', width: 3, }); const [{ viewport, scale }, dispatch] = useStore(); const { addAnnots } = useActions(dispatch); const setDataState = (obj: OptionPropsType): void => { setData((prev) => ({ ...prev, ...obj, })); }; const handleMouseDown = (event: MouseEvent): void => { const pageEle = (event.target as HTMLElement).parentNode as HTMLElement; switchPdfViewerScrollState('hidden'); 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), })); setPath([coordinate]); } }; const handleMouseUp = useCallback((): void => { switchPdfViewerScrollState('scroll'); const id = uuidv4(); if (path.length) { const defaultPoints = [ { x: path[0].x - 3, y: path[0].y - 3 }, { x: path[0].x + 3, y: path[0].y + 3 }, ]; const annotData = { id, obj_type: ANNOTATION_TYPE.ink, obj_attr: { page: data.page as number, bdcolor: data.color, bdwidth: data.width, position: path.length === 1 ? [defaultPoints] : [path], transparency: data.opacity, }, }; const freehand = appendUserIdAndDate( parseAnnotationObject(annotData, viewport.height, scale), ); addAnnots([freehand]); setRef(null); setPath([]); } }, [path, data, viewport, scale]); useEffect(() => { if (cursorPosition.x && cursorPosition.y) { const coordinates = { x: cursorPosition.x, y: cursorPosition.y, } as PointType; setPath((current) => { return [...current, coordinates]; }); } }, [cursorPosition]); /** * 1. draw to canvas when mouse move * 2. trigger mouse up to remove path element */ useEffect(() => { const pageEle = document.getElementById(`page_${data.page}`); const canvas = pageEle?.getElementsByClassName('canvas')[0] as HTMLElement; if (path.length) { if (pageEle && canvas) { canvas.style.display = 'block'; if (pathEle) { const d = svgPath(path, bezierCommand(controlPoint(line, 0.2))); pathEle.setAttribute('d', d); } else { pathEle = document.createElementNS( 'http://www.w3.org/2000/svg', 'path', ); pathEle.setAttribute('fill', 'none'); pathEle.setAttribute('stroke', data.color); pathEle.setAttribute('stroke-width', data.width * scale); pathEle.setAttribute('stroke-opacity', data.opacity * 0.01); canvas.appendChild(pathEle); } } } else if (canvas && pathEle) { canvas.style.display = 'none'; canvas.removeChild(pathEle); pathEle = null; } }, [path, 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 (isActive && pdfViewer) { subscribeEvent(); } return (): void => { if (pdfViewer) { unsubscribeEvent(); } }; }, [isActive, handleMouseUp]); const Label = ( ); return ( ); }; export default FreehandTool;