123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- 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 {
- 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<Props> = ({ title, isActive, onClick }: Props) => {
- const [cursorPosition, setRef] = useCursorPosition(30);
- const [path, setPath] = useState<PointType[]>([]);
- 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 | TouchEvent): 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';
- setData((current) => ({
- ...current,
- page: parseInt(pageNum, 10),
- }));
- }
- };
- 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) {
- 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.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);
- 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 (pdfViewer) {
- if (isActive) {
- subscribeEvent();
- } else {
- unsubscribeEvent();
- }
- }
- return (): void => {
- if (pdfViewer) {
- unsubscribeEvent();
- }
- };
- }, [isActive, handleMouseUp]);
- const Label = (
- <Button
- shouldFitContainer
- align="left"
- onClick={onClick}
- isActive={isActive}
- >
- <Icon glyph="freehand" style={{ marginRight: '10px' }} />
- {title}
- </Button>
- );
- return (
- <ExpansionPanel label={Label} isActive={isActive} showBottomBorder>
- <InkOption
- type={data.type}
- color={data.color}
- opacity={data.opacity}
- width={data.width}
- setDataState={setDataState}
- />
- </ExpansionPanel>
- );
- };
- export default FreehandTool;
|