FreehandTools.tsx 5.5 KB

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