diff --git a/src/components/MarkInputs/Signature.module.scss b/src/components/MarkInputs/Signature.module.scss new file mode 100644 index 0000000..fe8deec --- /dev/null +++ b/src/components/MarkInputs/Signature.module.scss @@ -0,0 +1,44 @@ +@import '../../styles/colors.scss'; + +$padding: 5px; + +.wrapper { + display: flex; + justify-content: center; + align-items: center; + padding: $padding; +} + +.relative { + position: relative; +} + +.canvas { + outline: 1px solid black; + background-color: $body-background-color; + cursor: crosshair; + + // Disable panning/zooming when touching canvas element + -ms-touch-action: none; + touch-action: none; + -webkit-user-select: none; + user-select: none; +} + +.absolute { + position: absolute; + inset: 0; + pointer-events: none; +} + +.reset { + cursor: pointer; + position: absolute; + top: 0; + right: $padding; + color: $primary-main; + + &:hover { + color: $primary-dark; + } +} diff --git a/src/components/MarkInputs/Signature.tsx b/src/components/MarkInputs/Signature.tsx new file mode 100644 index 0000000..106a847 --- /dev/null +++ b/src/components/MarkInputs/Signature.tsx @@ -0,0 +1,101 @@ +import { useRef, useState } from 'react' +import { MarkInputProps } from '../../types/mark' +import { getOptimizedPaths, optimizeSVG } from '../../utils' +import { faEraser } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import styles from './Signature.module.scss' +import { MarkRenderSignature } from '../MarkRender/Signature' + +export const MarkInputSignature = ({ + value, + handler, + userMark +}: MarkInputProps) => { + const location = userMark?.mark.location + + const canvasRef = useRef(null) + const [drawing, setDrawing] = useState(false) + const [paths, setPaths] = useState(value ? JSON.parse(value) : []) + + function update() { + if (location && paths) { + if (paths.length) { + const optimizedSvg = optimizeSVG(location, paths) + const extractedPaths = getOptimizedPaths(optimizedSvg) + handler(JSON.stringify(extractedPaths)) + } else { + handler('') + } + } + } + + const handlePointerDown = (event: React.PointerEvent) => { + const rect = event.currentTarget.getBoundingClientRect() + const x = event.clientX - rect.left + const y = event.clientY - rect.top + + const ctx = canvasRef.current?.getContext('2d') + ctx?.beginPath() + ctx?.moveTo(x, y) + setPaths([...paths, `M ${x} ${y}`]) + setDrawing(true) + } + const handlePointerUp = () => { + setDrawing(false) + update() + const ctx = canvasRef.current?.getContext('2d') + ctx?.clearRect(0, 0, canvasRef.current!.width, canvasRef.current!.height) + } + const handlePointerMove = (event: React.PointerEvent) => { + if (!drawing) return + const ctx = canvasRef.current?.getContext('2d') + const rect = canvasRef.current?.getBoundingClientRect() + const x = event.clientX - rect!.left + const y = event.clientY - rect!.top + + ctx?.lineTo(x, y) + ctx?.stroke() + + // Collect the path data + setPaths((prevPaths) => { + const newPaths = [...prevPaths] + newPaths[newPaths.length - 1] += ` L ${x} ${y}` + return newPaths + }) + } + + const handleReset = () => { + setPaths([]) + setDrawing(false) + update() + const ctx = canvasRef.current?.getContext('2d') + ctx?.clearRect(0, 0, canvasRef.current!.width, canvasRef.current!.height) + } + + return ( + <> +
+
+ + {typeof userMark?.mark !== 'undefined' && ( +
+ +
+ )} +
+ +
+
+
+ + ) +} diff --git a/src/components/MarkRender/Signature.tsx b/src/components/MarkRender/Signature.tsx new file mode 100644 index 0000000..6236edc --- /dev/null +++ b/src/components/MarkRender/Signature.tsx @@ -0,0 +1,13 @@ +import { MarkRenderProps } from '../../types/mark' + +export const MarkRenderSignature = ({ value, mark }: MarkRenderProps) => { + const paths = value ? JSON.parse(value) : [] + + return ( + + {paths.map((path: string) => ( + + ))} + + ) +} diff --git a/src/components/getMarkComponents.tsx b/src/components/getMarkComponents.tsx new file mode 100644 index 0000000..507f388 --- /dev/null +++ b/src/components/getMarkComponents.tsx @@ -0,0 +1,16 @@ +import { MarkType } from '../types/drawing' +import { MarkConfigs } from '../types/mark' +import { MarkInputSignature } from './MarkInputs/Signature' +import { MarkInputText } from './MarkInputs/Text' +import { MarkRenderSignature } from './MarkRender/Signature' + +export const MARK_TYPE_CONFIG: MarkConfigs = { + [MarkType.TEXT]: { + input: MarkInputText, + render: ({ value }) => <>{value} + }, + [MarkType.SIGNATURE]: { + input: MarkInputSignature, + render: MarkRenderSignature + } +} diff --git a/src/types/mark.ts b/src/types/mark.ts index df733d6..ec1c162 100644 --- a/src/types/mark.ts +++ b/src/types/mark.ts @@ -28,3 +28,24 @@ export interface MarkRect { width: number height: number } + +export interface MarkInputProps { + value: string + handler: (value: string) => void + placeholder?: string + userMark?: CurrentUserMark +} + +export interface MarkRenderProps { + value?: string + mark: Mark +} + +export interface MarkConfig { + input: React.FC + render?: React.FC +} + +export type MarkConfigs = { + [key in MarkType]?: MarkConfig +}