123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- import React, { useCallback, useRef, memo } from "react"
- import state, { useSelector } from "state"
- import inputs from "state/inputs"
- import styled from "styles"
- import { getShapeUtils } from "lib/shape-utils"
- import { getPage } from "utils/utils"
-
- function Shape({ id }: { id: string }) {
- const rGroup = useRef<SVGGElement>(null)
-
- const isHovered = useSelector((state) => state.data.hoveredId === id)
-
- const isSelected = useSelector((state) => state.values.selectedIds.has(id))
-
- const shape = useSelector(({ data }) => getPage(data).shapes[id])
-
- const handlePointerDown = useCallback(
- (e: React.PointerEvent) => {
- e.stopPropagation()
- rGroup.current.setPointerCapture(e.pointerId)
- state.send("POINTED_SHAPE", inputs.pointerDown(e, id))
- },
- [id]
- )
-
- const handlePointerUp = useCallback(
- (e: React.PointerEvent) => {
- e.stopPropagation()
- rGroup.current.releasePointerCapture(e.pointerId)
- state.send("STOPPED_POINTING", inputs.pointerUp(e))
- },
- [id]
- )
-
- const handlePointerEnter = useCallback(
- (e: React.PointerEvent) => {
- state.send("HOVERED_SHAPE", inputs.pointerEnter(e, id))
- },
- [id, shape]
- )
-
- const handlePointerMove = useCallback(
- (e: React.PointerEvent) => {
- state.send("MOVED_OVER_SHAPE", inputs.pointerEnter(e, id))
- },
- [id, shape]
- )
-
- const handlePointerLeave = useCallback(
- () => state.send("UNHOVERED_SHAPE", { target: id }),
- [id]
- )
-
- // 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
-
- return (
- <StyledGroup
- ref={rGroup}
- isHovered={isHovered}
- isSelected={isSelected}
- transform={`rotate(${shape.rotation * (180 / Math.PI)},${getShapeUtils(
- shape
- ).getCenter(shape)}) translate(${shape.point})`}
- onPointerDown={handlePointerDown}
- onPointerUp={handlePointerUp}
- onPointerEnter={handlePointerEnter}
- onPointerLeave={handlePointerLeave}
- onPointerMove={handlePointerMove}
- >
- <defs>{getShapeUtils(shape).render(shape)}</defs>
- <HoverIndicator as="use" xlinkHref={"#" + id} />
- <MainShape as="use" xlinkHref={"#" + id} {...shape.style} />
- <Indicator as="use" xlinkHref={"#" + id} />
- </StyledGroup>
- )
- }
-
- const MainShape = styled("use", {
- zStrokeWidth: 1,
- })
-
- const Indicator = styled("path", {
- fill: "none",
- stroke: "transparent",
- zStrokeWidth: 1,
- pointerEvents: "none",
- strokeLineCap: "round",
- strokeLinejoin: "round",
- })
-
- const HoverIndicator = styled("path", {
- fill: "none",
- stroke: "transparent",
- zStrokeWidth: [8, 4],
- pointerEvents: "all",
- strokeLinecap: "round",
- strokeLinejoin: "round",
- transform: "all .2s",
- })
-
- const StyledGroup = styled("g", {
- [`& ${HoverIndicator}`]: {
- opacity: "0",
- },
- variants: {
- isSelected: {
- true: {
- [`& ${Indicator}`]: {
- stroke: "$selected",
- },
- },
- false: {},
- },
- isHovered: {
- true: {},
- false: {},
- },
- },
- compoundVariants: [
- {
- isSelected: true,
- isHovered: true,
- css: {
- [`& ${HoverIndicator}`]: {
- opacity: "1",
- stroke: "$hint",
- zStrokeWidth: [8, 4],
- },
- },
- },
- {
- isSelected: true,
- isHovered: false,
- css: {
- [`& ${HoverIndicator}`]: {
- opacity: "1",
- stroke: "$hint",
- zStrokeWidth: [6, 3],
- },
- },
- },
- {
- isSelected: false,
- isHovered: true,
- css: {
- [`& ${HoverIndicator}`]: {
- opacity: "1",
- stroke: "$hint",
- zStrokeWidth: [8, 4],
- },
- },
- },
- ],
- })
-
- export { Indicator, HoverIndicator }
-
- export default memo(Shape)
|