FreehandTools.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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(50);
  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. freehand.obj_attr.position = [[freehand.obj_attr.position as PointType]];
  61. addAnnots([freehand], true);
  62. }
  63. }, [data, viewport, scale]);
  64. const handleMouseUp = (): void => {
  65. setRef(null);
  66. setUuid('');
  67. };
  68. useEffect(() => {
  69. const index = annotations.length - 1;
  70. if (
  71. annotations[index] && annotations[index].id === uuid
  72. && cursorPosition.x && cursorPosition.y
  73. ) {
  74. const type = annotations[index].obj_type;
  75. const { position } = annotations[index].obj_attr;
  76. const coordinates = parsePositionForBackend(
  77. type, { x: cursorPosition.x, y: cursorPosition.y }, viewport.height, scale,
  78. ) as PointType;
  79. const lastPosition = position[0].slice(-1)[0];
  80. if (
  81. coordinates.x !== lastPosition.x
  82. && coordinates.y !== lastPosition.y
  83. ) {
  84. annotations[index].obj_attr.position[0].push(coordinates);
  85. updateAnnots([...annotations]);
  86. }
  87. }
  88. }, [annotations, cursorPosition, uuid]);
  89. const subscribeEvent = (): void => {
  90. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  91. pdfViewer.addEventListener('mousedown', handleMouseDown);
  92. pdfViewer.addEventListener('mouseup', handleMouseUp);
  93. pdfViewer.addEventListener('touchstart', handleMouseDown);
  94. pdfViewer.addEventListener('touchend', handleMouseUp);
  95. };
  96. const unsubscribeEvent = (): void => {
  97. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  98. pdfViewer.removeEventListener('mousedown', handleMouseDown);
  99. pdfViewer.removeEventListener('mouseup', handleMouseUp);
  100. pdfViewer.removeEventListener('touchstart', handleMouseDown);
  101. pdfViewer.removeEventListener('touchend', handleMouseUp);
  102. };
  103. useEffect(() => {
  104. const pdfViewer = document.getElementById('pdf_viewer') as HTMLDivElement;
  105. if (isActive && pdfViewer) {
  106. subscribeEvent();
  107. }
  108. return (): void => {
  109. if (pdfViewer) {
  110. unsubscribeEvent();
  111. }
  112. };
  113. }, [isActive, handleMouseDown]);
  114. return (
  115. <ExpansionPanel
  116. label={(
  117. <Button
  118. shouldFitContainer
  119. align="left"
  120. onClick={onClick}
  121. isActive={isActive}
  122. >
  123. <Icon glyph="freehand" style={{ marginRight: '10px' }} />
  124. {title}
  125. </Button>
  126. )}
  127. isActive={isActive}
  128. showBottomBorder
  129. >
  130. <InkOption
  131. type={data.type}
  132. color={data.color}
  133. opacity={data.opacity}
  134. width={data.width}
  135. setDataState={setDataState}
  136. />
  137. </ExpansionPanel>
  138. );
  139. };
  140. export default FreehandTools;