PenShape.tsx 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. /* eslint-disable @typescript-eslint/no-explicit-any */
  2. import { getStroke } from 'perfect-freehand'
  3. import { SvgPathUtils, TLDrawShape, TLDrawShapeProps, getComputedColor } from '@tldraw/core'
  4. import { SVGContainer, TLComponentProps } from '@tldraw/react'
  5. import { observer } from 'mobx-react-lite'
  6. import { computed, makeObservable } from 'mobx'
  7. import { CustomStyleProps, withClampedStyles } from './style-props'
  8. export interface PenShapeProps extends TLDrawShapeProps, CustomStyleProps {
  9. type: 'pen'
  10. }
  11. export class PenShape extends TLDrawShape<PenShapeProps> {
  12. constructor(props = {} as Partial<PenShapeProps>) {
  13. super(props)
  14. makeObservable(this)
  15. }
  16. static id = 'pen'
  17. static defaultProps: PenShapeProps = {
  18. id: 'pen',
  19. parentId: 'page',
  20. type: 'pen',
  21. point: [0, 0],
  22. points: [],
  23. isComplete: false,
  24. stroke: '',
  25. fill: 's',
  26. noFill: false,
  27. strokeType: 'line',
  28. strokeWidth: 2,
  29. opacity: 1,
  30. }
  31. @computed get pointsPath() {
  32. const {
  33. props: { points, isComplete, strokeWidth },
  34. } = this
  35. if (points.length < 2) {
  36. return `M -4, 0
  37. a 4,4 0 1,0 8,0
  38. a 4,4 0 1,0 -8,0`
  39. }
  40. const stroke = getStroke(points, { size: 4 + strokeWidth * 2, last: isComplete })
  41. return SvgPathUtils.getCurvedPathForPolygon(stroke)
  42. }
  43. ReactComponent = observer(({ events, isErasing }: TLComponentProps) => {
  44. const {
  45. pointsPath,
  46. props: { stroke, strokeWidth, opacity },
  47. } = this
  48. return (
  49. <SVGContainer {...events} opacity={isErasing ? 0.2 : opacity}>
  50. <path
  51. d={pointsPath}
  52. strokeWidth={strokeWidth}
  53. stroke={getComputedColor(stroke, 'stroke')}
  54. fill={getComputedColor(stroke, 'stroke')}
  55. pointerEvents="all"
  56. />
  57. </SVGContainer>
  58. )
  59. })
  60. ReactIndicator = observer(() => {
  61. const { pointsPath } = this
  62. return <path d={pointsPath} />
  63. })
  64. validateProps = (props: Partial<PenShapeProps>) => {
  65. props = withClampedStyles(this, props)
  66. if (props.strokeWidth !== undefined) props.strokeWidth = Math.max(props.strokeWidth, 1)
  67. return props
  68. }
  69. }