FreehandTools.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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. event.preventDefault();
  49. const pageEle = (event.target as HTMLElement).parentNode as HTMLElement;
  50. switchPdfViewerScrollState('hidden');
  51. if (pageEle.hasAttribute('data-page-num')) {
  52. setRef(pageEle);
  53. const pageNum = pageEle.getAttribute('data-page-num') || 0;
  54. const coordinate = getAbsoluteCoordinate(pageEle, event);
  55. const id = uuidv4();
  56. setUuid(id);
  57. const annotData = {
  58. id,
  59. obj_type: ANNOTATION_TYPE.ink,
  60. obj_attr: {
  61. page: pageNum as number,
  62. bdcolor: data.color,
  63. bdwidth: data.width,
  64. position: [[coordinate]],
  65. transparency: data.opacity,
  66. },
  67. };
  68. const freehand = appendUserIdAndDate(
  69. parseAnnotationObject(annotData, viewport.height, scale)
  70. );
  71. addAnnots([freehand]);
  72. }
  73. },
  74. [data, viewport, scale]
  75. );
  76. const handleMouseUp = useCallback((): void => {
  77. switchPdfViewerScrollState('scroll');
  78. const index = annotations.length - 1;
  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. }, [annotations, uuid]);
  95. useEffect(() => {
  96. const index = annotations.length - 1;
  97. if (
  98. annotations[index] &&
  99. annotations[index].id === uuid &&
  100. cursorPosition.x &&
  101. cursorPosition.y
  102. ) {
  103. const type = annotations[index].obj_type;
  104. const position = annotations[index].obj_attr.position as PointType[][];
  105. const coordinates = parsePositionForBackend(
  106. type,
  107. { x: cursorPosition.x, y: cursorPosition.y },
  108. viewport.height,
  109. scale
  110. ) as PointType;
  111. const lastPosition = position[0].slice(-1)[0];
  112. if (
  113. coordinates.x !== lastPosition.x &&
  114. coordinates.y !== lastPosition.y
  115. ) {
  116. position[0].push(coordinates);
  117. annotations[index].obj_attr.position = position;
  118. annotations[index] = appendUserIdAndDate(annotations[index]);
  119. updateAnnots([...annotations]);
  120. }
  121. }
  122. }, [annotations, cursorPosition, uuid]);
  123. const subscribeEvent = (): void => {
  124. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  125. pdfViewer.addEventListener('mousedown', handleMouseDown);
  126. pdfViewer.addEventListener('mouseup', handleMouseUp);
  127. pdfViewer.addEventListener('touchstart', handleMouseDown);
  128. pdfViewer.addEventListener('touchend', handleMouseUp);
  129. };
  130. const unsubscribeEvent = (): void => {
  131. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  132. pdfViewer.removeEventListener('mousedown', handleMouseDown);
  133. pdfViewer.removeEventListener('mouseup', handleMouseUp);
  134. pdfViewer.removeEventListener('touchstart', handleMouseDown);
  135. pdfViewer.removeEventListener('touchend', handleMouseUp);
  136. };
  137. useEffect(() => {
  138. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  139. if (isActive && pdfViewer) {
  140. subscribeEvent();
  141. }
  142. return (): void => {
  143. if (pdfViewer) {
  144. unsubscribeEvent();
  145. }
  146. };
  147. }, [isActive, handleMouseDown, handleMouseUp]);
  148. const Label = (
  149. <Button
  150. shouldFitContainer
  151. align="left"
  152. onClick={onClick}
  153. isActive={isActive}
  154. >
  155. <Icon glyph="freehand" style={{ marginRight: '10px' }} />
  156. {title}
  157. </Button>
  158. );
  159. return (
  160. <ExpansionPanel label={Label} isActive={isActive} showBottomBorder>
  161. <InkOption
  162. type={data.type}
  163. color={data.color}
  164. opacity={data.opacity}
  165. width={data.width}
  166. setDataState={setDataState}
  167. />
  168. </ExpansionPanel>
  169. );
  170. };
  171. export default FreehandTools;