FreehandTool.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import React, { useState, useEffect, useCallback } from 'react';
  2. import { v4 as uuidv4 } from 'uuid';
  3. import { ANNOTATION_TYPE } from '../constants';
  4. import Icon from '../components/Icon';
  5. import Button from '../components/Button';
  6. import ExpansionPanel from '../components/ExpansionPanel';
  7. import InkOption from '../components/InkOption';
  8. import {
  9. getAbsoluteCoordinate,
  10. parsePositionForBackend,
  11. } from '../helpers/position';
  12. import {
  13. parseAnnotationObject,
  14. appendUserIdAndDate,
  15. } from '../helpers/annotation';
  16. import { switchPdfViewerScrollState } from '../helpers/pdf';
  17. import useCursorPosition from '../hooks/useCursorPosition';
  18. import useActions from '../actions';
  19. import useStore from '../store';
  20. type Props = {
  21. title: string;
  22. isActive: boolean;
  23. onClick: () => void;
  24. };
  25. const FreehandTool: React.FC<Props> = ({ title, isActive, onClick }: Props) => {
  26. const [cursorPosition, setRef] = useCursorPosition(40);
  27. const [uuid, setUuid] = useState('');
  28. const [data, setData] = useState({
  29. type: 'pen',
  30. opacity: 100,
  31. color: '#FF0000',
  32. width: 3,
  33. });
  34. const [{ viewport, scale, annotations }, dispatch] = useStore();
  35. const { addAnnots, updateAnnots } = useActions(dispatch);
  36. const setDataState = (obj: OptionPropsType): void => {
  37. setData(prev => ({
  38. ...prev,
  39. ...obj,
  40. }));
  41. };
  42. const handleMouseDown = useCallback(
  43. (event: MouseEvent | TouchEvent): void => {
  44. const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
  45. switchPdfViewerScrollState('hidden');
  46. if (pageEle.hasAttribute('data-page-num')) {
  47. setRef(pageEle);
  48. const pageNum = pageEle.getAttribute('data-page-num') || 0;
  49. const coordinate = getAbsoluteCoordinate(pageEle, event);
  50. const id = uuidv4();
  51. setUuid(id);
  52. const annotData = {
  53. id,
  54. obj_type: ANNOTATION_TYPE.ink,
  55. obj_attr: {
  56. page: pageNum as number,
  57. bdcolor: data.color,
  58. bdwidth: data.width,
  59. position: [[coordinate]],
  60. transparency: data.opacity,
  61. },
  62. };
  63. const freehand = appendUserIdAndDate(
  64. parseAnnotationObject(annotData, viewport.height, scale)
  65. );
  66. addAnnots([freehand]);
  67. }
  68. },
  69. [data, viewport, scale]
  70. );
  71. const handleMouseUp = useCallback((): void => {
  72. switchPdfViewerScrollState('scroll');
  73. const index = annotations.length - 1;
  74. if (annotations[index]) {
  75. const position = annotations[index].obj_attr.position as PointType[][];
  76. if (!position[0]) return;
  77. if (position[0].length === 1 && annotations[index].id === uuid) {
  78. const point = position[0][0];
  79. annotations[index].obj_attr.position = [
  80. [
  81. { x: point.x - 5, y: point.y - 5 },
  82. { x: point.x + 5, y: point.y + 5 },
  83. ],
  84. ];
  85. annotations[index] = appendUserIdAndDate(annotations[index]);
  86. updateAnnots([...annotations]);
  87. }
  88. setRef(null);
  89. setUuid('');
  90. }
  91. }, [annotations, uuid]);
  92. useEffect(() => {
  93. const index = annotations.length - 1;
  94. if (
  95. annotations[index] &&
  96. annotations[index].id === uuid &&
  97. cursorPosition.x &&
  98. cursorPosition.y
  99. ) {
  100. const type = annotations[index].obj_type;
  101. const position = annotations[index].obj_attr.position as PointType[][];
  102. const coordinates = parsePositionForBackend(
  103. type,
  104. { x: cursorPosition.x, y: cursorPosition.y },
  105. viewport.height,
  106. scale
  107. ) as PointType;
  108. const lastPosition = position[0].slice(-1)[0];
  109. if (
  110. coordinates.x !== lastPosition.x &&
  111. coordinates.y !== lastPosition.y
  112. ) {
  113. position[0].push(coordinates);
  114. annotations[index].obj_attr.position = position;
  115. annotations[index] = appendUserIdAndDate(annotations[index]);
  116. updateAnnots([...annotations]);
  117. }
  118. }
  119. }, [annotations, cursorPosition, uuid]);
  120. const subscribeEvent = (): void => {
  121. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  122. pdfViewer.addEventListener('mousedown', handleMouseDown);
  123. pdfViewer.addEventListener('mouseup', handleMouseUp);
  124. pdfViewer.addEventListener('touchstart', handleMouseDown);
  125. pdfViewer.addEventListener('touchend', handleMouseUp);
  126. };
  127. const unsubscribeEvent = (): void => {
  128. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  129. pdfViewer.removeEventListener('mousedown', handleMouseDown);
  130. pdfViewer.removeEventListener('mouseup', handleMouseUp);
  131. pdfViewer.removeEventListener('touchstart', handleMouseDown);
  132. pdfViewer.removeEventListener('touchend', handleMouseUp);
  133. };
  134. useEffect(() => {
  135. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  136. if (isActive && pdfViewer) {
  137. subscribeEvent();
  138. }
  139. return (): void => {
  140. if (pdfViewer) {
  141. unsubscribeEvent();
  142. }
  143. };
  144. }, [isActive, handleMouseDown, handleMouseUp]);
  145. const Label = (
  146. <Button
  147. shouldFitContainer
  148. align="left"
  149. onClick={onClick}
  150. isActive={isActive}
  151. >
  152. <Icon glyph="freehand" style={{ marginRight: '10px' }} />
  153. {title}
  154. </Button>
  155. );
  156. return (
  157. <ExpansionPanel label={Label} isActive={isActive} showBottomBorder>
  158. <InkOption
  159. type={data.type}
  160. color={data.color}
  161. opacity={data.opacity}
  162. width={data.width}
  163. setDataState={setDataState}
  164. />
  165. </ExpansionPanel>
  166. );
  167. };
  168. export default FreehandTool;