FreehandTools.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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. const position = annotations[index].obj_attr.position as PointType[][];
  79. if (!position[0]) return;
  80. if (position[0].length === 1 && annotations[index].id === uuid) {
  81. const point = position[0][0];
  82. annotations[index].obj_attr.position = [
  83. [
  84. { x: point.x - 5, y: point.y - 5 },
  85. { x: point.x + 5, y: point.y + 5 },
  86. ],
  87. ];
  88. annotations[index] = appendUserIdAndDate(annotations[index]);
  89. updateAnnots([...annotations]);
  90. }
  91. setRef(null);
  92. setUuid('');
  93. }, [annotations, uuid]);
  94. useEffect(() => {
  95. const index = annotations.length - 1;
  96. if (
  97. annotations[index] &&
  98. annotations[index].id === uuid &&
  99. cursorPosition.x &&
  100. cursorPosition.y
  101. ) {
  102. const type = annotations[index].obj_type;
  103. const position = annotations[index].obj_attr.position as PointType[][];
  104. const coordinates = parsePositionForBackend(
  105. type,
  106. { x: cursorPosition.x, y: cursorPosition.y },
  107. viewport.height,
  108. scale
  109. ) as PointType;
  110. const lastPosition = position[0].slice(-1)[0];
  111. if (
  112. coordinates.x !== lastPosition.x &&
  113. coordinates.y !== lastPosition.y
  114. ) {
  115. position[0].push(coordinates);
  116. annotations[index].obj_attr.position = position;
  117. annotations[index] = appendUserIdAndDate(annotations[index]);
  118. updateAnnots([...annotations]);
  119. }
  120. }
  121. }, [annotations, cursorPosition, uuid]);
  122. const subscribeEvent = (): void => {
  123. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  124. pdfViewer.addEventListener('mousedown', handleMouseDown);
  125. pdfViewer.addEventListener('mouseup', handleMouseUp);
  126. pdfViewer.addEventListener('touchstart', handleMouseDown);
  127. pdfViewer.addEventListener('touchend', handleMouseUp);
  128. };
  129. const unsubscribeEvent = (): void => {
  130. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  131. pdfViewer.removeEventListener('mousedown', handleMouseDown);
  132. pdfViewer.removeEventListener('mouseup', handleMouseUp);
  133. pdfViewer.removeEventListener('touchstart', handleMouseDown);
  134. pdfViewer.removeEventListener('touchend', handleMouseUp);
  135. };
  136. useEffect(() => {
  137. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  138. if (isActive && pdfViewer) {
  139. subscribeEvent();
  140. }
  141. return (): void => {
  142. if (pdfViewer) {
  143. unsubscribeEvent();
  144. }
  145. };
  146. }, [isActive, handleMouseDown, handleMouseUp]);
  147. const Label = (
  148. <Button
  149. shouldFitContainer
  150. align="left"
  151. onClick={onClick}
  152. isActive={isActive}
  153. >
  154. <Icon glyph="freehand" style={{ marginRight: '10px' }} />
  155. {title}
  156. </Button>
  157. );
  158. return (
  159. <ExpansionPanel label={Label} isActive={isActive} showBottomBorder>
  160. <InkOption
  161. type={data.type}
  162. color={data.color}
  163. opacity={data.opacity}
  164. width={data.width}
  165. setDataState={setDataState}
  166. />
  167. </ExpansionPanel>
  168. );
  169. };
  170. export default FreehandTools;