FreehandTools.tsx 5.0 KB

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