FreehandTools.tsx 5.1 KB

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