123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- 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<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',
- 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 = (
- <Button
- shouldFitContainer
- align="left"
- onClick={onClick}
- isActive={isActive}
- >
- <Icon glyph="shape" style={{ marginRight: '10px' }} />
- {title}
- </Button>
- );
- return (
- <ExpansionPanel isActive={isActive} label={Label}>
- <ShapeOption {...data} setDataState={setDataState} />
- </ExpansionPanel>
- );
- };
- export default Shape;
|