| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 | 
							- import React, { useRef, memo, useEffect } from 'react'
 - import state, { useSelector } from 'state'
 - import styled from 'styles'
 - import { getShapeUtils } from 'lib/shape-utils'
 - import { getBoundsCenter, getPage, isMobile } from 'utils/utils'
 - import { ShapeStyles, ShapeType } from 'types'
 - import useShapeEvents from 'hooks/useShapeEvents'
 - import vec from 'utils/vec'
 - import { getShapeStyle } from 'lib/shape-styles'
 - import ContextMenu from 'components/canvas/context-menu/context-menu'
 - 
 - const isMobileDevice = isMobile()
 - 
 - interface ShapeProps {
 -   id: string
 -   isSelecting: boolean
 -   parentPoint: number[]
 - }
 - 
 - function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
 -   const rGroup = useRef<SVGGElement>(null)
 -   const rFocusable = useRef<HTMLTextAreaElement>(null)
 - 
 -   const isEditing = useSelector((s) => s.data.editingId === id)
 - 
 -   const shape = useSelector((s) => getPage(s.data).shapes[id])
 - 
 -   const events = useShapeEvents(id, getShapeUtils(shape)?.isParent, rGroup)
 - 
 -   useEffect(() => {
 -     if (isEditing) {
 -       setTimeout(() => {
 -         const elm = rFocusable.current
 -         if (!elm) return
 -         elm.focus()
 -       }, 0)
 -     }
 -   }, [isEditing])
 - 
 -   // This is a problem with deleted shapes. The hooks in this component
 -   // may sometimes run before the hook in the Page component, which means
 -   // a deleted shape will still be pulled here before the page component
 -   // detects the change and pulls this component.
 -   if (!shape) return null
 - 
 -   const style = getShapeStyle(shape.style)
 -   const shapeUtils = getShapeUtils(shape)
 - 
 -   const { isShy, isParent, isForeignObject } = shapeUtils
 - 
 -   const bounds = shapeUtils.getBounds(shape)
 -   const center = shapeUtils.getCenter(shape)
 -   const rotation = shape.rotation * (180 / Math.PI)
 - 
 -   const transform = `
 -     translate(${vec.neg(parentPoint)})
 -     rotate(${rotation}, ${center})
 -     translate(${shape.point})
 -   `
 - 
 -   return (
 -     <StyledGroup
 -       ref={rGroup}
 -       transform={transform}
 -       device={isMobileDevice ? 'mobile' : 'desktop'}
 -     >
 -       {isSelecting && !isShy && (
 -         <>
 -           {isForeignObject ? (
 -             <HoverIndicator
 -               as="rect"
 -               width={bounds.width}
 -               height={bounds.height}
 -               strokeWidth={1.5}
 -               variant={'ghost'}
 -               {...events}
 -             />
 -           ) : (
 -             <HoverIndicator
 -               as="use"
 -               href={'#' + id}
 -               strokeWidth={+style.strokeWidth + 4}
 -               variant={getShapeUtils(shape).canStyleFill ? 'filled' : 'hollow'}
 -               {...events}
 -             />
 -           )}
 -         </>
 -       )}
 - 
 -       {!shape.isHidden &&
 -         (isForeignObject ? (
 -           shapeUtils.render(shape, { isEditing, ref: rFocusable })
 -         ) : (
 -           <RealShape
 -             isParent={isParent}
 -             id={id}
 -             style={style}
 -             isEditing={isEditing}
 -           />
 -         ))}
 - 
 -       {isParent &&
 -         shape.children.map((shapeId) => (
 -           <Shape
 -             key={shapeId}
 -             id={shapeId}
 -             isSelecting={isSelecting}
 -             parentPoint={shape.point}
 -           />
 -         ))}
 -     </StyledGroup>
 -   )
 - }
 - 
 - interface RealShapeProps {
 -   id: string
 -   style: Partial<React.SVGProps<SVGUseElement>>
 -   isParent: boolean
 -   isEditing: boolean
 - }
 - 
 - const RealShape = memo(function RealShape({
 -   id,
 -   style,
 -   isParent,
 - }: RealShapeProps) {
 -   return <StyledShape as="use" data-shy={isParent} href={'#' + id} {...style} />
 - })
 - 
 - const StyledShape = styled('path', {
 -   strokeLinecap: 'round',
 -   strokeLinejoin: 'round',
 -   pointerEvents: 'none',
 - })
 - 
 - const HoverIndicator = styled('path', {
 -   stroke: '$selected',
 -   strokeLinecap: 'round',
 -   strokeLinejoin: 'round',
 -   fill: 'transparent',
 -   filter: 'url(#expand)',
 -   variants: {
 -     variant: {
 -       ghost: {
 -         pointerEvents: 'all',
 -         filter: 'none',
 -         opacity: 0,
 -       },
 -       hollow: {
 -         pointerEvents: 'stroke',
 -       },
 -       filled: {
 -         pointerEvents: 'all',
 -       },
 -     },
 -   },
 - })
 - 
 - const StyledGroup = styled('g', {
 -   outline: 'none',
 -   [`& *[data-shy="true"]`]: {
 -     opacity: '0',
 -   },
 -   [`& ${HoverIndicator}`]: {
 -     opacity: '0',
 -   },
 -   variants: {
 -     device: {
 -       mobile: {},
 -       desktop: {},
 -     },
 -     isSelected: {
 -       true: {
 -         [`& *[data-shy="true"]`]: {
 -           opacity: '1',
 -         },
 -         [`& ${HoverIndicator}`]: {
 -           opacity: '0.2',
 -         },
 -       },
 -       false: {
 -         [`& ${HoverIndicator}`]: {
 -           opacity: '0',
 -         },
 -       },
 -     },
 -   },
 -   compoundVariants: [
 -     {
 -       device: 'desktop',
 -       isSelected: 'false',
 -       css: {
 -         [`&:hover ${HoverIndicator}`]: {
 -           opacity: '0.16',
 -         },
 -         [`&:hover *[data-shy="true"]`]: {
 -           opacity: '1',
 -         },
 -       },
 -     },
 -     {
 -       device: 'desktop',
 -       isSelected: 'true',
 -       css: {
 -         [`&:hover ${HoverIndicator}`]: {
 -           opacity: '0.3',
 -         },
 -         [`&:active ${HoverIndicator}`]: {
 -           opacity: '0.3',
 -         },
 -       },
 -     },
 -   ],
 - })
 - 
 - function Label({ children }: { children: React.ReactNode }) {
 -   return (
 -     <text
 -       y={4}
 -       x={4}
 -       fontSize={12}
 -       fill="black"
 -       stroke="none"
 -       alignmentBaseline="text-before-edge"
 -       pointerEvents="none"
 -     >
 -       {children}
 -     </text>
 -   )
 - }
 - 
 - function pp(n: number[]) {
 -   return '[' + n.map((v) => v.toFixed(1)).join(', ') + ']'
 - }
 - 
 - export { HoverIndicator }
 - 
 - export default memo(Shape)
 
 
  |