Pārlūkot izejas kodu

Greatly simplifies shapes

main
Steve Ruiz 4 gadus atpakaļ
vecāks
revīzija
3d52d9e9d2

+ 106
- 18
components/canvas/shape.tsx Parādīt failu

1
+import React, { useCallback, useRef } from "react"
2
+import state, { useSelector } from "state"
3
+import styled from "styles"
4
+import { getPointerEventInfo } from "utils/utils"
1
 import { memo } from "react"
5
 import { memo } from "react"
2
-import { useSelector } from "state"
3
-import { ShapeType } from "types"
4
-import Circle from "./shapes/circle"
5
-import Dot from "./shapes/dot"
6
-import Polyline from "./shapes/polyline"
7
-import Rectangle from "./shapes/rectangle"
6
+import Shapes from "lib/shapes"
8
 
7
 
9
 /*
8
 /*
10
 Gets the shape from the current page's shapes, using the
9
 Gets the shape from the current page's shapes, using the
11
 provided ID. Depending on the shape's type, return the
10
 provided ID. Depending on the shape's type, return the
12
 component for that type.
11
 component for that type.
12
+
13
+This component takes an SVG shape as its children. It handles
14
+events for the shape as well as provides indicators for hover
15
+ and selected status
13
 */
16
 */
14
 
17
 
15
 function Shape({ id }: { id: string }) {
18
 function Shape({ id }: { id: string }) {
19
+  const rGroup = useRef<SVGGElement>(null)
20
+
16
   const shape = useSelector((state) => {
21
   const shape = useSelector((state) => {
17
     const { currentPageId, document } = state.data
22
     const { currentPageId, document } = state.data
18
     return document.pages[currentPageId].shapes[id]
23
     return document.pages[currentPageId].shapes[id]
19
   })
24
   })
20
 
25
 
21
-  switch (shape.type) {
22
-    case ShapeType.Dot:
23
-      return <Dot {...shape} />
24
-    case ShapeType.Circle:
25
-      return <Circle {...shape} />
26
-    case ShapeType.Rectangle:
27
-      return <Rectangle {...shape} />
28
-    case ShapeType.Polyline:
29
-      return <Polyline {...shape} />
30
-    default:
31
-      return null
32
-  }
26
+  const isSelected = useSelector((state) => state.values.selectedIds.has(id))
27
+
28
+  const handlePointerDown = useCallback(
29
+    (e: React.PointerEvent) => {
30
+      e.stopPropagation()
31
+      rGroup.current.setPointerCapture(e.pointerId)
32
+      state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
33
+    },
34
+    [id]
35
+  )
36
+
37
+  const handlePointerUp = useCallback(
38
+    (e: React.PointerEvent) => {
39
+      e.stopPropagation()
40
+      rGroup.current.releasePointerCapture(e.pointerId)
41
+      state.send("STOPPED_POINTING_SHAPE", { id, ...getPointerEventInfo(e) })
42
+    },
43
+    [id]
44
+  )
45
+
46
+  const handlePointerEnter = useCallback(
47
+    (e: React.PointerEvent) =>
48
+      state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
49
+    [id]
50
+  )
51
+
52
+  const handlePointerLeave = useCallback(
53
+    (e: React.PointerEvent) =>
54
+      state.send("UNHOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
55
+    [id]
56
+  )
57
+
58
+  return (
59
+    <StyledGroup
60
+      ref={rGroup}
61
+      isSelected={isSelected}
62
+      transform={`translate(${shape.point})`}
63
+      onPointerDown={handlePointerDown}
64
+      onPointerUp={handlePointerUp}
65
+      onPointerEnter={handlePointerEnter}
66
+      onPointerLeave={handlePointerLeave}
67
+    >
68
+      <defs>
69
+        {Shapes[shape.type] ? Shapes[shape.type].render(shape) : null}
70
+      </defs>
71
+      <HoverIndicator as="use" xlinkHref={"#" + id} />
72
+      <use xlinkHref={"#" + id} {...shape.style} />
73
+      <Indicator as="use" xlinkHref={"#" + id} />
74
+    </StyledGroup>
75
+  )
33
 }
76
 }
34
 
77
 
78
+const Indicator = styled("path", {
79
+  fill: "none",
80
+  stroke: "transparent",
81
+  strokeWidth: "max(1, calc(2 / var(--camera-zoom)))",
82
+  pointerEvents: "none",
83
+  strokeLineCap: "round",
84
+  strokeLinejoin: "round",
85
+})
86
+
87
+const HoverIndicator = styled("path", {
88
+  fill: "none",
89
+  stroke: "transparent",
90
+  strokeWidth: "max(1, calc(8 / var(--camera-zoom)))",
91
+  pointerEvents: "all",
92
+  strokeLinecap: "round",
93
+  strokeLinejoin: "round",
94
+})
95
+
96
+const StyledGroup = styled("g", {
97
+  [`& ${HoverIndicator}`]: {
98
+    opacity: "0",
99
+  },
100
+  variants: {
101
+    isSelected: {
102
+      true: {
103
+        [`& ${Indicator}`]: {
104
+          stroke: "$selected",
105
+        },
106
+        [`&:hover ${HoverIndicator}`]: {
107
+          opacity: "1",
108
+          stroke: "$hint",
109
+        },
110
+      },
111
+      false: {
112
+        [`&:hover ${HoverIndicator}`]: {
113
+          opacity: "1",
114
+          stroke: "$hint",
115
+        },
116
+      },
117
+    },
118
+  },
119
+})
120
+
121
+export { Indicator, HoverIndicator }
122
+
35
 export default memo(Shape)
123
 export default memo(Shape)

+ 0
- 33
components/canvas/shapes/circle.tsx Parādīt failu

1
-import { CircleShape, ShapeProps } from "types"
2
-import { Indicator, HoverIndicator } from "./indicator"
3
-import ShapeGroup from "./shape-group"
4
-
5
-function BaseCircle({
6
-  radius,
7
-  fill = "#999",
8
-  stroke = "none",
9
-  strokeWidth = 0,
10
-}: ShapeProps<CircleShape>) {
11
-  return (
12
-    <>
13
-      <HoverIndicator as="circle" cx={radius} cy={radius} r={radius} />
14
-      <circle
15
-        cx={radius}
16
-        cy={radius}
17
-        r={radius}
18
-        fill={fill}
19
-        stroke={stroke}
20
-        strokeWidth={strokeWidth}
21
-      />
22
-      <Indicator as="circle" cx={radius} cy={radius} r={radius} />
23
-    </>
24
-  )
25
-}
26
-
27
-export default function Circle({ id, point, radius }: CircleShape) {
28
-  return (
29
-    <ShapeGroup id={id} point={point}>
30
-      <BaseCircle radius={radius} />
31
-    </ShapeGroup>
32
-  )
33
-}

+ 0
- 34
components/canvas/shapes/dot.tsx Parādīt failu

1
-import { Indicator, HoverIndicator } from "./indicator"
2
-import { DotShape, ShapeProps } from "types"
3
-import ShapeGroup from "./shape-group"
4
-
5
-const dotRadius = 4
6
-
7
-function BaseDot({
8
-  fill = "#999",
9
-  stroke = "none",
10
-  strokeWidth = 0,
11
-}: ShapeProps<DotShape>) {
12
-  return (
13
-    <>
14
-      <HoverIndicator as="circle" cx={dotRadius} cy={dotRadius} r={dotRadius} />
15
-      <circle
16
-        cx={dotRadius}
17
-        cy={dotRadius}
18
-        r={dotRadius}
19
-        fill={fill}
20
-        stroke={stroke}
21
-        strokeWidth={strokeWidth}
22
-      />
23
-      <Indicator as="circle" cx={dotRadius} cy={dotRadius} r={dotRadius} />
24
-    </>
25
-  )
26
-}
27
-
28
-export default function Dot({ id, point }: DotShape) {
29
-  return (
30
-    <ShapeGroup id={id} point={point}>
31
-      <BaseDot />
32
-    </ShapeGroup>
33
-  )
34
-}

+ 0
- 21
components/canvas/shapes/indicator.tsx Parādīt failu

1
-import styled from "styles"
2
-
3
-const Indicator = styled("path", {
4
-  fill: "none",
5
-  stroke: "transparent",
6
-  strokeWidth: "max(1, calc(2 / var(--camera-zoom)))",
7
-  pointerEvents: "none",
8
-  strokeLineCap: "round",
9
-  strokeLinejoin: "round",
10
-})
11
-
12
-const HoverIndicator = styled("path", {
13
-  fill: "none",
14
-  stroke: "transparent",
15
-  strokeWidth: "max(1, calc(8 / var(--camera-zoom)))",
16
-  pointerEvents: "all",
17
-  strokeLinecap: "round",
18
-  strokeLinejoin: "round",
19
-})
20
-
21
-export { Indicator, HoverIndicator }

+ 0
- 33
components/canvas/shapes/polyline.tsx Parādīt failu

1
-import { PolylineShape, ShapeProps } from "types"
2
-import { Indicator, HoverIndicator } from "./indicator"
3
-import ShapeGroup from "./shape-group"
4
-
5
-function BasePolyline({
6
-  points,
7
-  fill = "none",
8
-  stroke = "#999",
9
-  strokeWidth = 1,
10
-}: ShapeProps<PolylineShape>) {
11
-  return (
12
-    <>
13
-      <HoverIndicator as="polyline" points={points.toString()} />
14
-      <polyline
15
-        points={points.toString()}
16
-        fill={fill}
17
-        stroke={stroke}
18
-        strokeWidth={strokeWidth}
19
-        strokeLinecap="round"
20
-        strokeLinejoin="round"
21
-      />
22
-      <Indicator as="polyline" points={points.toString()} />
23
-    </>
24
-  )
25
-}
26
-
27
-export default function Polyline({ id, point, points }: PolylineShape) {
28
-  return (
29
-    <ShapeGroup id={id} point={point}>
30
-      <BasePolyline points={points} />
31
-    </ShapeGroup>
32
-  )
33
-}

+ 0
- 32
components/canvas/shapes/rectangle.tsx Parādīt failu

1
-import { RectangleShape, ShapeProps } from "types"
2
-import { HoverIndicator, Indicator } from "./indicator"
3
-import ShapeGroup from "./shape-group"
4
-
5
-function BaseRectangle({
6
-  size,
7
-  fill = "#999",
8
-  stroke = "none",
9
-  strokeWidth = 0,
10
-}: ShapeProps<RectangleShape>) {
11
-  return (
12
-    <>
13
-      <HoverIndicator as="rect" width={size[0]} height={size[1]} />
14
-      <rect
15
-        width={size[0]}
16
-        height={size[1]}
17
-        fill={fill}
18
-        stroke={stroke}
19
-        strokeWidth={strokeWidth}
20
-      />
21
-      <Indicator as="rect" width={size[0]} height={size[1]} />
22
-    </>
23
-  )
24
-}
25
-
26
-export default function Rectangle({ id, point, size }: RectangleShape) {
27
-  return (
28
-    <ShapeGroup id={id} point={point}>
29
-      <BaseRectangle size={size} />
30
-    </ShapeGroup>
31
-  )
32
-}

+ 0
- 87
components/canvas/shapes/shape-group.tsx Parādīt failu

1
-import state, { useSelector } from "state"
2
-import React, { useCallback, useRef } from "react"
3
-import { getPointerEventInfo } from "utils/utils"
4
-import { Indicator, HoverIndicator } from "./indicator"
5
-import styled from "styles"
6
-
7
-export default function ShapeGroup({
8
-  id,
9
-  children,
10
-  point,
11
-}: {
12
-  id: string
13
-  children: React.ReactNode
14
-  point: number[]
15
-}) {
16
-  const rGroup = useRef<SVGGElement>(null)
17
-  const isSelected = useSelector((state) => state.values.selectedIds.has(id))
18
-
19
-  const handlePointerDown = useCallback(
20
-    (e: React.PointerEvent) => {
21
-      e.stopPropagation()
22
-      rGroup.current.setPointerCapture(e.pointerId)
23
-      state.send("POINTED_SHAPE", { id, ...getPointerEventInfo(e) })
24
-    },
25
-    [id]
26
-  )
27
-
28
-  const handlePointerUp = useCallback(
29
-    (e: React.PointerEvent) => {
30
-      e.stopPropagation()
31
-      rGroup.current.releasePointerCapture(e.pointerId)
32
-      state.send("STOPPED_POINTING_SHAPE", { id, ...getPointerEventInfo(e) })
33
-    },
34
-    [id]
35
-  )
36
-
37
-  const handlePointerEnter = useCallback(
38
-    (e: React.PointerEvent) =>
39
-      state.send("HOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
40
-    [id]
41
-  )
42
-
43
-  const handlePointerLeave = useCallback(
44
-    (e: React.PointerEvent) =>
45
-      state.send("UNHOVERED_SHAPE", { id, ...getPointerEventInfo(e) }),
46
-    [id]
47
-  )
48
-
49
-  return (
50
-    <StyledGroup
51
-      ref={rGroup}
52
-      isSelected={isSelected}
53
-      transform={`translate(${point})`}
54
-      onPointerDown={handlePointerDown}
55
-      onPointerUp={handlePointerUp}
56
-      onPointerEnter={handlePointerEnter}
57
-      onPointerLeave={handlePointerLeave}
58
-    >
59
-      {children}
60
-    </StyledGroup>
61
-  )
62
-}
63
-
64
-const StyledGroup = styled("g", {
65
-  [`& ${HoverIndicator}`]: {
66
-    opacity: "0",
67
-  },
68
-  variants: {
69
-    isSelected: {
70
-      true: {
71
-        [`& ${Indicator}`]: {
72
-          stroke: "$selected",
73
-        },
74
-        [`&:hover ${HoverIndicator}`]: {
75
-          opacity: "1",
76
-          stroke: "$hint",
77
-        },
78
-      },
79
-      false: {
80
-        [`&:hover ${HoverIndicator}`]: {
81
-          opacity: "1",
82
-          stroke: "$hint",
83
-        },
84
-      },
85
-    },
86
-  },
87
-})

+ 1
- 3
components/status-bar.tsx Parādīt failu

12
 
12
 
13
   return (
13
   return (
14
     <StatusBarContainer>
14
     <StatusBarContainer>
15
-      <States>{active.join(" | ")}</States>
15
+      <Section>{active.join(" | ")}</Section>
16
       <Section>| {log}</Section>
16
       <Section>| {log}</Section>
17
       <Section title="Renders | Time">
17
       <Section title="Renders | Time">
18
         {count} | {time.toString().padStart(3, "0")}
18
         {count} | {time.toString().padStart(3, "0")}
45
   overflow: "hidden",
45
   overflow: "hidden",
46
 })
46
 })
47
 
47
 
48
-const States = styled("div", {})
49
-
50
 function useRenderCount() {
48
 function useRenderCount() {
51
   const rTime = useRef(Date.now())
49
   const rTime = useRef(Date.now())
52
   const rCounter = useRef(0)
50
   const rCounter = useRef(0)

+ 64
- 0
lib/shapes/circle.tsx Parādīt failu

1
+import { v4 as uuid } from "uuid"
2
+import * as vec from "utils/vec"
3
+import { BaseLibShape, CircleShape, ShapeType } from "types"
4
+
5
+const Circle: BaseLibShape<ShapeType.Circle> = {
6
+  create(props): CircleShape {
7
+    return {
8
+      id: uuid(),
9
+      type: ShapeType.Circle,
10
+      name: "Circle",
11
+      parentId: "page0",
12
+      childIndex: 0,
13
+      point: [0, 0],
14
+      radius: 20,
15
+      rotation: 0,
16
+      style: {},
17
+      ...props,
18
+    }
19
+  },
20
+
21
+  render({ id, radius }) {
22
+    return <circle id={id} cx={radius} cy={radius} r={radius} />
23
+  },
24
+
25
+  getBounds(shape) {
26
+    const {
27
+      point: [cx, cy],
28
+      radius,
29
+    } = shape
30
+
31
+    return {
32
+      minX: cx,
33
+      maxX: cx + radius * 2,
34
+      minY: cy,
35
+      maxY: cy + radius * 2,
36
+      width: radius * 2,
37
+      height: radius * 2,
38
+    }
39
+  },
40
+
41
+  hitTest(shape, test) {
42
+    return (
43
+      vec.dist(vec.addScalar(shape.point, shape.radius), test) < shape.radius
44
+    )
45
+  },
46
+
47
+  rotate(shape) {
48
+    return shape
49
+  },
50
+
51
+  translate(shape) {
52
+    return shape
53
+  },
54
+
55
+  scale(shape, scale: number) {
56
+    return shape
57
+  },
58
+
59
+  stretch(shape, scaleX: number, scaleY: number) {
60
+    return shape
61
+  },
62
+}
63
+
64
+export default Circle

+ 60
- 0
lib/shapes/dot.tsx Parādīt failu

1
+import { v4 as uuid } from "uuid"
2
+import * as vec from "utils/vec"
3
+import { BaseLibShape, DotShape, ShapeType } from "types"
4
+
5
+const Dot: BaseLibShape<ShapeType.Dot> = {
6
+  create(props): DotShape {
7
+    return {
8
+      id: uuid(),
9
+      type: ShapeType.Dot,
10
+      name: "Dot",
11
+      parentId: "page0",
12
+      childIndex: 0,
13
+      point: [0, 0],
14
+      rotation: 0,
15
+      style: {},
16
+      ...props,
17
+    }
18
+  },
19
+
20
+  render({ id }) {
21
+    return <circle id={id} cx={4} cy={4} r={4} />
22
+  },
23
+
24
+  getBounds(shape) {
25
+    const {
26
+      point: [cx, cy],
27
+    } = shape
28
+
29
+    return {
30
+      minX: cx,
31
+      maxX: cx + 4,
32
+      minY: cy,
33
+      maxY: cy + 4,
34
+      width: 4,
35
+      height: 4,
36
+    }
37
+  },
38
+
39
+  hitTest(shape, test) {
40
+    return vec.dist(shape.point, test) < 4
41
+  },
42
+
43
+  rotate(shape) {
44
+    return shape
45
+  },
46
+
47
+  translate(shape) {
48
+    return shape
49
+  },
50
+
51
+  scale(shape, scale: number) {
52
+    return shape
53
+  },
54
+
55
+  stretch(shape, scaleX: number, scaleY: number) {
56
+    return shape
57
+  },
58
+}
59
+
60
+export default Dot

+ 13
- 0
lib/shapes/index.tsx Parādīt failu

1
+import Circle from "./circle"
2
+import Dot from "./dot"
3
+import Polyline from "./polyline"
4
+import Rectangle from "./rectangle"
5
+
6
+import { ShapeType } from "types"
7
+
8
+export default {
9
+  [ShapeType.Circle]: Circle,
10
+  [ShapeType.Dot]: Dot,
11
+  [ShapeType.Polyline]: Polyline,
12
+  [ShapeType.Rectangle]: Rectangle,
13
+}

+ 69
- 0
lib/shapes/polyline.tsx Parādīt failu

1
+import { v4 as uuid } from "uuid"
2
+import * as vec from "utils/vec"
3
+import { BaseLibShape, PolylineShape, ShapeType } from "types"
4
+
5
+const Polyline: BaseLibShape<ShapeType.Polyline> = {
6
+  create(props): PolylineShape {
7
+    return {
8
+      id: uuid(),
9
+      type: ShapeType.Polyline,
10
+      name: "Polyline",
11
+      parentId: "page0",
12
+      childIndex: 0,
13
+      point: [0, 0],
14
+      points: [[0, 0]],
15
+      rotation: 0,
16
+      style: {},
17
+      ...props,
18
+    }
19
+  },
20
+
21
+  render({ id, points }) {
22
+    return <polyline id={id} points={points.toString()} />
23
+  },
24
+
25
+  getBounds(shape) {
26
+    let minX = 0
27
+    let minY = 0
28
+    let maxX = 0
29
+    let maxY = 0
30
+
31
+    for (let [x, y] of shape.points) {
32
+      minX = Math.min(x, minX)
33
+      minY = Math.min(y, minY)
34
+      maxX = Math.max(x, maxX)
35
+      maxY = Math.max(y, maxY)
36
+    }
37
+
38
+    return {
39
+      minX: minX + shape.point[0],
40
+      minY: minY + shape.point[1],
41
+      maxX: maxX + shape.point[0],
42
+      maxY: maxY + shape.point[1],
43
+      width: maxX - minX,
44
+      height: maxY - minY,
45
+    }
46
+  },
47
+
48
+  hitTest(shape) {
49
+    return true
50
+  },
51
+
52
+  rotate(shape) {
53
+    return shape
54
+  },
55
+
56
+  translate(shape) {
57
+    return shape
58
+  },
59
+
60
+  scale(shape, scale: number) {
61
+    return shape
62
+  },
63
+
64
+  stretch(shape, scaleX: number, scaleY: number) {
65
+    return shape
66
+  },
67
+}
68
+
69
+export default Polyline

+ 62
- 0
lib/shapes/rectangle.tsx Parādīt failu

1
+import { v4 as uuid } from "uuid"
2
+import * as vec from "utils/vec"
3
+import { BaseLibShape, RectangleShape, ShapeType } from "types"
4
+
5
+const Rectangle: BaseLibShape<ShapeType.Rectangle> = {
6
+  create(props): RectangleShape {
7
+    return {
8
+      id: uuid(),
9
+      type: ShapeType.Rectangle,
10
+      name: "Rectangle",
11
+      parentId: "page0",
12
+      childIndex: 0,
13
+      point: [0, 0],
14
+      size: [1, 1],
15
+      rotation: 0,
16
+      style: {},
17
+      ...props,
18
+    }
19
+  },
20
+
21
+  render({ id, size }) {
22
+    return <rect id={id} width={size[0]} height={size[1]} />
23
+  },
24
+
25
+  getBounds(shape) {
26
+    const {
27
+      point: [x, y],
28
+      size: [width, height],
29
+    } = shape
30
+
31
+    return {
32
+      minX: x,
33
+      maxX: x + width,
34
+      minY: y,
35
+      maxY: y + height,
36
+      width,
37
+      height,
38
+    }
39
+  },
40
+
41
+  hitTest(shape) {
42
+    return true
43
+  },
44
+
45
+  rotate(shape) {
46
+    return shape
47
+  },
48
+
49
+  translate(shape) {
50
+    return shape
51
+  },
52
+
53
+  scale(shape, scale: number) {
54
+    return shape
55
+  },
56
+
57
+  stretch(shape, scaleX: number, scaleY: number) {
58
+    return shape
59
+  },
60
+}
61
+
62
+export default Rectangle

+ 38
- 29
state/data.ts Parādīt failu

1
 import { Data, ShapeType } from "types"
1
 import { Data, ShapeType } from "types"
2
+import Shapes from "lib/shapes"
2
 
3
 
3
 export const defaultDocument: Data["document"] = {
4
 export const defaultDocument: Data["document"] = {
4
   pages: {
5
   pages: {
8
       name: "Page 0",
9
       name: "Page 0",
9
       childIndex: 0,
10
       childIndex: 0,
10
       shapes: {
11
       shapes: {
11
-        shape0: {
12
+        shape3: Shapes[ShapeType.Dot].create({
13
+          id: "shape3",
14
+          name: "Shape 3",
15
+          childIndex: 3,
16
+          point: [500, 100],
17
+          style: {
18
+            fill: "#aaa",
19
+            stroke: "#777",
20
+            strokeWidth: 1,
21
+          },
22
+        }),
23
+        shape0: Shapes[ShapeType.Circle].create({
12
           id: "shape0",
24
           id: "shape0",
13
-          type: ShapeType.Circle,
14
           name: "Shape 0",
25
           name: "Shape 0",
15
-          parentId: "page0",
16
           childIndex: 1,
26
           childIndex: 1,
17
           point: [100, 100],
27
           point: [100, 100],
18
           radius: 50,
28
           radius: 50,
19
-          rotation: 0,
20
-        },
21
-        shape1: {
22
-          id: "shape1",
23
-          type: ShapeType.Rectangle,
24
-          name: "Shape 1",
25
-          parentId: "page0",
26
-          childIndex: 1,
27
-          point: [300, 300],
28
-          size: [200, 200],
29
-          rotation: 0,
30
-        },
31
-        shape2: {
29
+          style: {
30
+            fill: "#aaa",
31
+            stroke: "#777",
32
+            strokeWidth: 1,
33
+          },
34
+        }),
35
+        shape2: Shapes[ShapeType.Polyline].create({
32
           id: "shape2",
36
           id: "shape2",
33
-          type: ShapeType.Polyline,
34
           name: "Shape 2",
37
           name: "Shape 2",
35
-          parentId: "page0",
36
           childIndex: 2,
38
           childIndex: 2,
37
           point: [200, 600],
39
           point: [200, 600],
38
           points: [
40
           points: [
40
             [75, 200],
42
             [75, 200],
41
             [100, 50],
43
             [100, 50],
42
           ],
44
           ],
43
-          rotation: 0,
44
-        },
45
-        shape3: {
46
-          id: "shape3",
47
-          type: ShapeType.Dot,
48
-          name: "Shape 3",
49
-          parentId: "page0",
50
-          childIndex: 3,
51
-          point: [500, 100],
52
-          rotation: 0,
53
-        },
45
+          style: {
46
+            fill: "none",
47
+            stroke: "#777",
48
+            strokeWidth: 2,
49
+          },
50
+        }),
51
+        shape1: Shapes[ShapeType.Rectangle].create({
52
+          id: "shape1",
53
+          name: "Shape 1",
54
+          childIndex: 1,
55
+          point: [300, 300],
56
+          size: [200, 200],
57
+          style: {
58
+            fill: "#aaa",
59
+            stroke: "#777",
60
+            strokeWidth: 1,
61
+          },
62
+        }),
54
       },
63
       },
55
     },
64
     },
56
   },
65
   },

+ 6
- 6
state/sessions/brush-session.ts Parādīt failu

1
 import { current } from "immer"
1
 import { current } from "immer"
2
-import { Bounds, Data, Shape, ShapeType } from "types"
2
+import { Bounds, Data, ShapeType } from "types"
3
 import BaseSession from "./base-session"
3
 import BaseSession from "./base-session"
4
-import shapeUtils from "utils/shape-utils"
4
+import Shapes from "lib/shapes"
5
 import { getBoundsFromPoints } from "utils/utils"
5
 import { getBoundsFromPoints } from "utils/utils"
6
 import * as vec from "utils/vec"
6
 import * as vec from "utils/vec"
7
 import {
7
 import {
72
         .map((shape) => {
72
         .map((shape) => {
73
           switch (shape.type) {
73
           switch (shape.type) {
74
             case ShapeType.Dot: {
74
             case ShapeType.Dot: {
75
-              const bounds = shapeUtils[shape.type].getBounds(shape)
75
+              const bounds = Shapes[shape.type].getBounds(shape)
76
 
76
 
77
               return {
77
               return {
78
                 id: shape.id,
78
                 id: shape.id,
82
               }
82
               }
83
             }
83
             }
84
             case ShapeType.Circle: {
84
             case ShapeType.Circle: {
85
-              const bounds = shapeUtils[shape.type].getBounds(shape)
85
+              const bounds = Shapes[shape.type].getBounds(shape)
86
 
86
 
87
               return {
87
               return {
88
                 id: shape.id,
88
                 id: shape.id,
96
               }
96
               }
97
             }
97
             }
98
             case ShapeType.Rectangle: {
98
             case ShapeType.Rectangle: {
99
-              const bounds = shapeUtils[shape.type].getBounds(shape)
99
+              const bounds = Shapes[shape.type].getBounds(shape)
100
 
100
 
101
               return {
101
               return {
102
                 id: shape.id,
102
                 id: shape.id,
106
               }
106
               }
107
             }
107
             }
108
             case ShapeType.Polyline: {
108
             case ShapeType.Polyline: {
109
-              const bounds = shapeUtils[shape.type].getBounds(shape)
109
+              const bounds = Shapes[shape.type].getBounds(shape)
110
               const points = shape.points.map((point) =>
110
               const points = shape.points.map((point) =>
111
                 vec.add(point, shape.point)
111
                 vec.add(point, shape.point)
112
               )
112
               )

+ 15
- 10
types.ts Parādīt failu

1
+import React from "react"
2
+
1
 export interface Data {
3
 export interface Data {
2
   camera: {
4
   camera: {
3
     point: number[]
5
     point: number[]
26
   Ellipse = "ellipse",
28
   Ellipse = "ellipse",
27
   Line = "line",
29
   Line = "line",
28
   Ray = "ray",
30
   Ray = "ray",
29
-  Polyline = "Polyline",
31
+  Polyline = "polyline",
30
   Rectangle = "rectangle",
32
   Rectangle = "rectangle",
31
   // Glob = "glob",
33
   // Glob = "glob",
32
   // Spline = "spline",
34
   // Spline = "spline",
42
   name: string
44
   name: string
43
   point: number[]
45
   point: number[]
44
   rotation: 0
46
   rotation: 0
47
+  style: Partial<React.SVGProps<SVGUseElement>>
45
 }
48
 }
46
 
49
 
47
 export interface DotShape extends BaseShape {
50
 export interface DotShape extends BaseShape {
107
   [ShapeType.Rectangle]: RectangleShape
110
   [ShapeType.Rectangle]: RectangleShape
108
 }
111
 }
109
 
112
 
110
-export interface BaseShapeStyles {
111
-  fill: string
112
-  stroke: string
113
-  strokeWidth: number
114
-}
115
-
116
 export type Difference<A, B> = A extends B ? never : A
113
 export type Difference<A, B> = A extends B ? never : A
117
 
114
 
118
 export type ShapeSpecificProps<T extends Shape> = Pick<
115
 export type ShapeSpecificProps<T extends Shape> = Pick<
120
   Difference<keyof T, keyof BaseShape>
117
   Difference<keyof T, keyof BaseShape>
121
 >
118
 >
122
 
119
 
123
-export type ShapeProps<T extends Shape> = Partial<BaseShapeStyles> &
124
-  ShapeSpecificProps<T> & { id?: Shape["id"] }
125
-
126
 export type ShapeIndicatorProps<T extends Shape> = ShapeSpecificProps<T>
120
 export type ShapeIndicatorProps<T extends Shape> = ShapeSpecificProps<T>
121
+
122
+export type BaseLibShape<K extends ShapeType> = {
123
+  create(props: Partial<Shapes[K]>): Shapes[K]
124
+  getBounds(shape: Shapes[K]): Bounds
125
+  hitTest(shape: Shapes[K], test: number[]): boolean
126
+  rotate(shape: Shapes[K]): Shapes[K]
127
+  translate(shape: Shapes[K]): Shapes[K]
128
+  scale(shape: Shapes[K], scale: number): Shapes[K]
129
+  stretch(shape: Shapes[K], scaleX: number, scaleY: number): Shapes[K]
130
+  render(shape: Shapes[K]): JSX.Element
131
+}

+ 0
- 302
utils/shape-utils.ts Parādīt failu

1
-import {
2
-  boundsCollide,
3
-  boundsContain,
4
-  pointInBounds,
5
-} from "state/sessions/brush-session"
6
-import { Bounds, ShapeType, Shapes } from "types"
7
-import { intersectCircleBounds } from "./intersections"
8
-import * as vec from "./vec"
9
-
10
-type BaseShapeUtils<K extends ShapeType> = {
11
-  getBounds(shape: Shapes[K]): Bounds
12
-  hitTest(shape: Shapes[K], test: number[]): boolean
13
-  rotate(shape: Shapes[K]): Shapes[K]
14
-  translate(shape: Shapes[K]): Shapes[K]
15
-  scale(shape: Shapes[K], scale: number): Shapes[K]
16
-  stretch(shape: Shapes[K], scaleX: number, scaleY: number): Shapes[K]
17
-}
18
-
19
-/* ----------------------- Dot ---------------------- */
20
-
21
-const DotUtils: BaseShapeUtils<ShapeType.Dot> = {
22
-  getBounds(shape) {
23
-    const {
24
-      point: [cx, cy],
25
-    } = shape
26
-
27
-    return {
28
-      minX: cx,
29
-      maxX: cx + 4,
30
-      minY: cy,
31
-      maxY: cy + 4,
32
-      width: 4,
33
-      height: 4,
34
-    }
35
-  },
36
-
37
-  hitTest(shape, test) {
38
-    return vec.dist(shape.point, test) < 4
39
-  },
40
-
41
-  rotate(shape) {
42
-    return shape
43
-  },
44
-
45
-  translate(shape) {
46
-    return shape
47
-  },
48
-
49
-  scale(shape, scale: number) {
50
-    return shape
51
-  },
52
-
53
-  stretch(shape, scaleX: number, scaleY: number) {
54
-    return shape
55
-  },
56
-}
57
-
58
-/* --------------------- Circle --------------------- */
59
-
60
-const CircleUtils: BaseShapeUtils<ShapeType.Circle> = {
61
-  getBounds(shape) {
62
-    const {
63
-      point: [cx, cy],
64
-      radius,
65
-    } = shape
66
-
67
-    return {
68
-      minX: cx,
69
-      maxX: cx + radius * 2,
70
-      minY: cy,
71
-      maxY: cy + radius * 2,
72
-      width: radius * 2,
73
-      height: radius * 2,
74
-    }
75
-  },
76
-
77
-  hitTest(shape, test) {
78
-    return (
79
-      vec.dist(vec.addScalar(shape.point, shape.radius), test) < shape.radius
80
-    )
81
-  },
82
-
83
-  rotate(shape) {
84
-    return shape
85
-  },
86
-
87
-  translate(shape) {
88
-    return shape
89
-  },
90
-
91
-  scale(shape, scale: number) {
92
-    return shape
93
-  },
94
-
95
-  stretch(shape, scaleX: number, scaleY: number) {
96
-    return shape
97
-  },
98
-}
99
-
100
-/* --------------------- Ellipse -------------------- */
101
-
102
-const EllipseUtils: BaseShapeUtils<ShapeType.Ellipse> = {
103
-  getBounds(shape) {
104
-    return {
105
-      minX: 0,
106
-      minY: 0,
107
-      maxX: 0,
108
-      maxY: 0,
109
-      width: 0,
110
-      height: 0,
111
-    }
112
-  },
113
-
114
-  hitTest(shape) {
115
-    return true
116
-  },
117
-
118
-  rotate(shape) {
119
-    return shape
120
-  },
121
-
122
-  translate(shape) {
123
-    return shape
124
-  },
125
-
126
-  scale(shape, scale: number) {
127
-    return shape
128
-  },
129
-
130
-  stretch(shape, scaleX: number, scaleY: number) {
131
-    return shape
132
-  },
133
-}
134
-
135
-/* ---------------------- Line ---------------------- */
136
-
137
-const LineUtils: BaseShapeUtils<ShapeType.Line> = {
138
-  getBounds(shape) {
139
-    return {
140
-      minX: 0,
141
-      minY: 0,
142
-      maxX: 0,
143
-      maxY: 0,
144
-      width: 0,
145
-      height: 0,
146
-    }
147
-  },
148
-
149
-  hitTest(shape) {
150
-    return true
151
-  },
152
-
153
-  rotate(shape) {
154
-    return shape
155
-  },
156
-
157
-  translate(shape) {
158
-    return shape
159
-  },
160
-
161
-  scale(shape, scale: number) {
162
-    return shape
163
-  },
164
-
165
-  stretch(shape, scaleX: number, scaleY: number) {
166
-    return shape
167
-  },
168
-}
169
-
170
-/* ----------------------- Ray ---------------------- */
171
-
172
-const RayUtils: BaseShapeUtils<ShapeType.Ray> = {
173
-  getBounds(shape) {
174
-    return {
175
-      minX: Infinity,
176
-      minY: Infinity,
177
-      maxX: Infinity,
178
-      maxY: Infinity,
179
-      width: Infinity,
180
-      height: Infinity,
181
-    }
182
-  },
183
-
184
-  hitTest(shape) {
185
-    return true
186
-  },
187
-
188
-  rotate(shape) {
189
-    return shape
190
-  },
191
-
192
-  translate(shape) {
193
-    return shape
194
-  },
195
-
196
-  scale(shape, scale: number) {
197
-    return shape
198
-  },
199
-
200
-  stretch(shape, scaleX: number, scaleY: number) {
201
-    return shape
202
-  },
203
-}
204
-
205
-/* ------------------ Line Segment ------------------ */
206
-
207
-const PolylineUtils: BaseShapeUtils<ShapeType.Polyline> = {
208
-  getBounds(shape) {
209
-    let minX = 0
210
-    let minY = 0
211
-    let maxX = 0
212
-    let maxY = 0
213
-
214
-    for (let [x, y] of shape.points) {
215
-      minX = Math.min(x, minX)
216
-      minY = Math.min(y, minY)
217
-      maxX = Math.max(x, maxX)
218
-      maxY = Math.max(y, maxY)
219
-    }
220
-
221
-    return {
222
-      minX: minX + shape.point[0],
223
-      minY: minY + shape.point[1],
224
-      maxX: maxX + shape.point[0],
225
-      maxY: maxY + shape.point[1],
226
-      width: maxX - minX,
227
-      height: maxY - minY,
228
-    }
229
-  },
230
-
231
-  hitTest(shape) {
232
-    return true
233
-  },
234
-
235
-  rotate(shape) {
236
-    return shape
237
-  },
238
-
239
-  translate(shape) {
240
-    return shape
241
-  },
242
-
243
-  scale(shape, scale: number) {
244
-    return shape
245
-  },
246
-
247
-  stretch(shape, scaleX: number, scaleY: number) {
248
-    return shape
249
-  },
250
-}
251
-
252
-/* -------------------- Rectangle ------------------- */
253
-
254
-const RectangleUtils: BaseShapeUtils<ShapeType.Rectangle> = {
255
-  getBounds(shape) {
256
-    const {
257
-      point: [x, y],
258
-      size: [width, height],
259
-    } = shape
260
-
261
-    return {
262
-      minX: x,
263
-      maxX: x + width,
264
-      minY: y,
265
-      maxY: y + height,
266
-      width,
267
-      height,
268
-    }
269
-  },
270
-
271
-  hitTest(shape) {
272
-    return true
273
-  },
274
-
275
-  rotate(shape) {
276
-    return shape
277
-  },
278
-
279
-  translate(shape) {
280
-    return shape
281
-  },
282
-
283
-  scale(shape, scale: number) {
284
-    return shape
285
-  },
286
-
287
-  stretch(shape, scaleX: number, scaleY: number) {
288
-    return shape
289
-  },
290
-}
291
-
292
-const shapeUtils: { [K in ShapeType]: BaseShapeUtils<K> } = {
293
-  [ShapeType.Dot]: DotUtils,
294
-  [ShapeType.Circle]: CircleUtils,
295
-  [ShapeType.Ellipse]: EllipseUtils,
296
-  [ShapeType.Line]: LineUtils,
297
-  [ShapeType.Ray]: RayUtils,
298
-  [ShapeType.Polyline]: PolylineUtils,
299
-  [ShapeType.Rectangle]: RectangleUtils,
300
-}
301
-
302
-export default shapeUtils

+ 4
- 2
utils/utils.ts Parādīt failu

844
   return await d.json()
844
   return await d.json()
845
 }
845
 }
846
 
846
 
847
-export function getPointerEventInfo(e: React.PointerEvent | WheelEvent) {
847
+export function getPointerEventInfo(
848
+  e: PointerEvent | React.PointerEvent | WheelEvent
849
+) {
848
   const { shiftKey, ctrlKey, metaKey, altKey } = e
850
   const { shiftKey, ctrlKey, metaKey, altKey } = e
849
   return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
851
   return { point: [e.clientX, e.clientY], shiftKey, ctrlKey, metaKey, altKey }
850
 }
852
 }
851
 
853
 
852
-export function getKeyboardEventInfo(e: React.KeyboardEvent | KeyboardEvent) {
854
+export function getKeyboardEventInfo(e: KeyboardEvent | React.KeyboardEvent) {
853
   const { shiftKey, ctrlKey, metaKey, altKey } = e
855
   const { shiftKey, ctrlKey, metaKey, altKey } = e
854
   return { key: e.key, shiftKey, ctrlKey, metaKey, altKey }
856
   return { key: e.key, shiftKey, ctrlKey, metaKey, altKey }
855
 }
857
 }

Notiek ielāde…
Atcelt
Saglabāt