Pārlūkot izejas kodu

Adds bounds

main
Steve Ruiz 4 gadus atpakaļ
vecāks
revīzija
89d9ddcb1d

+ 33
- 1
components/canvas/bounds-bg.tsx Parādīt failu

1
-export default function BoundsBg() {}
1
+import state, { useSelector } from "state"
2
+import styled from "styles"
3
+
4
+export default function BoundsBg() {
5
+  const bounds = useSelector((state) => state.values.selectedBounds)
6
+
7
+  if (!bounds) return null
8
+
9
+  const { minX, minY, width, height } = bounds
10
+
11
+  return (
12
+    <StyledBoundsBg
13
+      x={minX}
14
+      y={minY}
15
+      width={width}
16
+      height={height}
17
+      onPointerDown={(e) => {
18
+        if (e.buttons !== 1) return
19
+        state.send("POINTED_BOUNDS", {
20
+          shiftKey: e.shiftKey,
21
+          optionKey: e.altKey,
22
+          metaKey: e.metaKey || e.ctrlKey,
23
+          ctrlKey: e.ctrlKey,
24
+          buttons: e.buttons,
25
+        })
26
+      }}
27
+    />
28
+  )
29
+}
30
+
31
+const StyledBoundsBg = styled("rect", {
32
+  fill: "$boundsBg",
33
+})

+ 297
- 1
components/canvas/bounds.tsx Parādīt failu

1
-export default function Bounds() {}
1
+import state, { useSelector } from "state"
2
+import { motion } from "framer-motion"
3
+import styled from "styles"
4
+
5
+export default function Bounds() {
6
+  const bounds = useSelector((state) => state.values.selectedBounds)
7
+  const isBrushing = useSelector((state) => state.isIn("brushSelecting"))
8
+  const zoom = useSelector((state) => state.data.camera.zoom)
9
+
10
+  if (!bounds) return null
11
+
12
+  const { minX, minY, maxX, maxY, width, height } = bounds
13
+
14
+  const p = 4 / zoom
15
+  const cp = p * 2
16
+
17
+  return (
18
+    <g pointerEvents={isBrushing ? "none" : "all"}>
19
+      <StyledBounds
20
+        x={minX}
21
+        y={minY}
22
+        width={width}
23
+        height={height}
24
+        pointerEvents="none"
25
+      />
26
+      <Corner
27
+        x={minX}
28
+        y={minY}
29
+        corner={0}
30
+        width={cp}
31
+        height={cp}
32
+        cursor="nwse-resize"
33
+      />
34
+      <Corner
35
+        x={maxX}
36
+        y={minY}
37
+        corner={1}
38
+        width={cp}
39
+        height={cp}
40
+        cursor="nesw-resize"
41
+      />
42
+      <Corner
43
+        x={maxX}
44
+        y={maxY}
45
+        corner={2}
46
+        width={cp}
47
+        height={cp}
48
+        cursor="nwse-resize"
49
+      />
50
+      <Corner
51
+        x={minX}
52
+        y={maxY}
53
+        corner={3}
54
+        width={cp}
55
+        height={cp}
56
+        cursor="nesw-resize"
57
+      />
58
+      <EdgeHorizontal
59
+        x={minX + p}
60
+        y={minY}
61
+        width={Math.max(0, width - p * 2)}
62
+        height={p}
63
+        onSelect={(e) => {
64
+          e.stopPropagation()
65
+          if (e.buttons !== 1) return
66
+          state.send("POINTED_BOUNDS_EDGE", {
67
+            edge: 0,
68
+            shiftKey: e.shiftKey,
69
+            optionKey: e.altKey,
70
+            metaKey: e.metaKey,
71
+            ctrlKey: e.ctrlKey,
72
+            buttons: e.buttons,
73
+          })
74
+          document.body.style.cursor = "ns-resize"
75
+        }}
76
+      />
77
+      <EdgeVertical
78
+        x={maxX}
79
+        y={minY + p}
80
+        width={p}
81
+        height={Math.max(0, height - p * 2)}
82
+        onSelect={(e) => {
83
+          e.stopPropagation()
84
+          if (e.buttons !== 1) return
85
+          state.send("POINTED_BOUNDS_EDGE", {
86
+            edge: 1,
87
+            shiftKey: e.shiftKey,
88
+            optionKey: e.altKey,
89
+            metaKey: e.metaKey,
90
+            ctrlKey: e.ctrlKey,
91
+            buttons: e.buttons,
92
+          })
93
+          document.body.style.cursor = "ew-resize"
94
+        }}
95
+      />
96
+      <EdgeHorizontal
97
+        x={minX + p}
98
+        y={maxY}
99
+        width={Math.max(0, width - p * 2)}
100
+        height={p}
101
+        onSelect={(e) => {
102
+          e.stopPropagation()
103
+          if (e.buttons !== 1) return
104
+          state.send("POINTED_BOUNDS_EDGE", {
105
+            edge: 2,
106
+            shiftKey: e.shiftKey,
107
+            optionKey: e.altKey,
108
+            metaKey: e.metaKey,
109
+            ctrlKey: e.ctrlKey,
110
+            buttons: e.buttons,
111
+          })
112
+          document.body.style.cursor = "ns-resize"
113
+        }}
114
+      />
115
+      <EdgeVertical
116
+        x={minX}
117
+        y={minY + p}
118
+        width={p}
119
+        height={Math.max(0, height - p * 2)}
120
+        onSelect={(e) => {
121
+          e.stopPropagation()
122
+          if (e.buttons !== 1) return
123
+          state.send("POINTED_BOUNDS_EDGE", {
124
+            edge: 3,
125
+            shiftKey: e.shiftKey,
126
+            optionKey: e.altKey,
127
+            metaKey: e.metaKey,
128
+            ctrlKey: e.ctrlKey,
129
+            buttons: e.buttons,
130
+          })
131
+          document.body.style.cursor = "ew-resize"
132
+        }}
133
+      />
134
+    </g>
135
+  )
136
+}
137
+
138
+function Corner({
139
+  x,
140
+  y,
141
+  width,
142
+  height,
143
+  cursor,
144
+  onHover,
145
+  corner,
146
+}: {
147
+  x: number
148
+  y: number
149
+  width: number
150
+  height: number
151
+  cursor: string
152
+  corner: number
153
+  onHover?: () => void
154
+}) {
155
+  const isTop = corner === 0 || corner === 1
156
+  const isLeft = corner === 0 || corner === 3
157
+  return (
158
+    <g>
159
+      <motion.rect
160
+        x={x + width * (isLeft ? -1.25 : -0.5)} // + width * 2 * transformOffset[0]}
161
+        y={y + width * (isTop ? -1.25 : -0.5)} // + height * 2 * transformOffset[1]}
162
+        width={width * 1.75}
163
+        height={height * 1.75}
164
+        onPanEnd={restoreCursor}
165
+        onTap={restoreCursor}
166
+        onPointerDown={(e) => {
167
+          e.stopPropagation()
168
+          if (e.buttons !== 1) return
169
+          state.send("POINTED_ROTATE_CORNER", {
170
+            corner,
171
+            shiftKey: e.shiftKey,
172
+            optionKey: e.altKey,
173
+            metaKey: e.metaKey,
174
+            ctrlKey: e.ctrlKey,
175
+            buttons: e.buttons,
176
+          })
177
+          document.body.style.cursor = "grabbing"
178
+        }}
179
+        style={{ cursor: "grab" }}
180
+        fill="transparent"
181
+      />
182
+      <StyledCorner
183
+        x={x + width * -0.5}
184
+        y={y + height * -0.5}
185
+        width={width}
186
+        height={height}
187
+        onPointerEnter={onHover}
188
+        onPointerDown={(e) => {
189
+          e.stopPropagation()
190
+          if (e.buttons !== 1) return
191
+          state.send("POINTED_BOUNDS_CORNER", {
192
+            corner,
193
+            shiftKey: e.shiftKey,
194
+            optionKey: e.altKey,
195
+            metaKey: e.metaKey,
196
+            ctrlKey: e.ctrlKey,
197
+            buttons: e.buttons,
198
+          })
199
+          document.body.style.cursor = "nesw-resize"
200
+        }}
201
+        onPanEnd={restoreCursor}
202
+        onTap={restoreCursor}
203
+        style={{ cursor }}
204
+        className="strokewidth-ui stroke-bounds fill-corner"
205
+      />
206
+    </g>
207
+  )
208
+}
209
+
210
+function EdgeHorizontal({
211
+  x,
212
+  y,
213
+  width,
214
+  height,
215
+  onHover,
216
+  onSelect,
217
+}: {
218
+  x: number
219
+  y: number
220
+  width: number
221
+  height: number
222
+  onHover?: () => void
223
+  onSelect?: (e: React.PointerEvent) => void
224
+}) {
225
+  return (
226
+    <StyledEdge
227
+      x={x}
228
+      y={y - height / 2}
229
+      width={width}
230
+      height={height}
231
+      onPointerEnter={onHover}
232
+      onPointerDown={onSelect}
233
+      onPanEnd={restoreCursor}
234
+      onTap={restoreCursor}
235
+      style={{ cursor: "ns-resize" }}
236
+      direction="horizontal"
237
+    />
238
+  )
239
+}
240
+
241
+function EdgeVertical({
242
+  x,
243
+  y,
244
+  width,
245
+  height,
246
+  onHover,
247
+  onSelect,
248
+}: {
249
+  x: number
250
+  y: number
251
+  width: number
252
+  height: number
253
+  onHover?: () => void
254
+  onSelect?: (e: React.PointerEvent) => void
255
+}) {
256
+  return (
257
+    <StyledEdge
258
+      x={x - width / 2}
259
+      y={y}
260
+      width={width}
261
+      height={height}
262
+      onPointerEnter={onHover}
263
+      onPointerDown={onSelect}
264
+      onPanEnd={restoreCursor}
265
+      onTap={restoreCursor}
266
+      direction="vertical"
267
+    />
268
+  )
269
+}
270
+
271
+function restoreCursor() {
272
+  document.body.style.cursor = "default"
273
+  state.send("STOPPED_POINTING")
274
+}
275
+
276
+const StyledEdge = styled(motion.rect, {
277
+  stroke: "none",
278
+  fill: "none",
279
+  variant: {
280
+    direction: {
281
+      horizontal: { cursor: "ns-resize" },
282
+      vertical: { cursor: "ew-resize" },
283
+    },
284
+  },
285
+})
286
+
287
+const StyledCorner = styled(motion.rect, {
288
+  stroke: "$bounds",
289
+  fill: "#fff",
290
+  zStrokeWidth: 2,
291
+})
292
+
293
+const StyledBounds = styled("rect", {
294
+  fill: "none",
295
+  stroke: "$bounds",
296
+  zStrokeWidth: 2,
297
+})

+ 4
- 0
components/canvas/canvas.tsx Parādīt failu

6
 import Page from "./page"
6
 import Page from "./page"
7
 import Brush from "./brush"
7
 import Brush from "./brush"
8
 import state from "state"
8
 import state from "state"
9
+import Bounds from "./bounds"
10
+import BoundsBg from "./bounds-bg"
9
 
11
 
10
 export default function Canvas() {
12
 export default function Canvas() {
11
   const rCanvas = useRef<SVGSVGElement>(null)
13
   const rCanvas = useRef<SVGSVGElement>(null)
37
       onPointerUp={handlePointerUp}
39
       onPointerUp={handlePointerUp}
38
     >
40
     >
39
       <MainGroup ref={rGroup}>
41
       <MainGroup ref={rGroup}>
42
+        <BoundsBg />
40
         <Page />
43
         <Page />
44
+        <Bounds />
41
         <Brush />
45
         <Brush />
42
       </MainGroup>
46
       </MainGroup>
43
     </MainSVG>
47
     </MainSVG>

+ 6
- 17
components/canvas/shape.tsx Parādīt failu

1
-import React, { useCallback, useRef } from "react"
1
+import React, { useCallback, useRef, memo } from "react"
2
 import state, { useSelector } from "state"
2
 import state, { useSelector } from "state"
3
-import styled from "styles"
4
 import { getPointerEventInfo } from "utils/utils"
3
 import { getPointerEventInfo } from "utils/utils"
5
-import { memo } from "react"
6
-import Shapes from "lib/shapes"
7
-
8
-/*
9
-Gets the shape from the current page's shapes, using the
10
-provided ID. Depending on the shape's type, return the
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
16
-*/
4
+import shapes from "lib/shapes"
5
+import styled from "styles"
17
 
6
 
18
 function Shape({ id }: { id: string }) {
7
 function Shape({ id }: { id: string }) {
19
   const rGroup = useRef<SVGGElement>(null)
8
   const rGroup = useRef<SVGGElement>(null)
66
       onPointerLeave={handlePointerLeave}
55
       onPointerLeave={handlePointerLeave}
67
     >
56
     >
68
       <defs>
57
       <defs>
69
-        {Shapes[shape.type] ? Shapes[shape.type].render(shape) : null}
58
+        {shapes[shape.type] ? shapes[shape.type].render(shape) : null}
70
       </defs>
59
       </defs>
71
       <HoverIndicator as="use" xlinkHref={"#" + id} />
60
       <HoverIndicator as="use" xlinkHref={"#" + id} />
72
       <use xlinkHref={"#" + id} {...shape.style} />
61
       <use xlinkHref={"#" + id} {...shape.style} />
78
 const Indicator = styled("path", {
67
 const Indicator = styled("path", {
79
   fill: "none",
68
   fill: "none",
80
   stroke: "transparent",
69
   stroke: "transparent",
81
-  strokeWidth: "max(1, calc(2 / var(--camera-zoom)))",
70
+  zStrokeWidth: 1,
82
   pointerEvents: "none",
71
   pointerEvents: "none",
83
   strokeLineCap: "round",
72
   strokeLineCap: "round",
84
   strokeLinejoin: "round",
73
   strokeLinejoin: "round",
87
 const HoverIndicator = styled("path", {
76
 const HoverIndicator = styled("path", {
88
   fill: "none",
77
   fill: "none",
89
   stroke: "transparent",
78
   stroke: "transparent",
90
-  strokeWidth: "max(1, calc(8 / var(--camera-zoom)))",
79
+  zStrokeWidth: 8,
91
   pointerEvents: "all",
80
   pointerEvents: "all",
92
   strokeLinecap: "round",
81
   strokeLinecap: "round",
93
   strokeLinejoin: "round",
82
   strokeLinejoin: "round",

+ 14
- 6
lib/shapes/circle.tsx Parādīt failu

1
 import { v4 as uuid } from "uuid"
1
 import { v4 as uuid } from "uuid"
2
 import * as vec from "utils/vec"
2
 import * as vec from "utils/vec"
3
 import { BaseLibShape, CircleShape, ShapeType } from "types"
3
 import { BaseLibShape, CircleShape, ShapeType } from "types"
4
+import { boundsCache } from "./index"
4
 
5
 
5
 const Circle: BaseLibShape<ShapeType.Circle> = {
6
 const Circle: BaseLibShape<ShapeType.Circle> = {
6
   create(props): CircleShape {
7
   create(props): CircleShape {
23
   },
24
   },
24
 
25
 
25
   getBounds(shape) {
26
   getBounds(shape) {
27
+    if (boundsCache.has(shape)) {
28
+      return boundsCache.get(shape)
29
+    }
30
+
26
     const {
31
     const {
27
-      point: [cx, cy],
32
+      point: [x, y],
28
       radius,
33
       radius,
29
     } = shape
34
     } = shape
30
 
35
 
31
-    return {
32
-      minX: cx,
33
-      maxX: cx + radius * 2,
34
-      minY: cy,
35
-      maxY: cy + radius * 2,
36
+    const bounds = {
37
+      minX: x,
38
+      maxX: x + radius * 2,
39
+      minY: y,
40
+      maxY: y + radius * 2,
36
       width: radius * 2,
41
       width: radius * 2,
37
       height: radius * 2,
42
       height: radius * 2,
38
     }
43
     }
44
+
45
+    boundsCache.set(shape, bounds)
46
+    return bounds
39
   },
47
   },
40
 
48
 
41
   hitTest(shape, test) {
49
   hitTest(shape, test) {

+ 16
- 8
lib/shapes/dot.tsx Parādīt failu

1
 import { v4 as uuid } from "uuid"
1
 import { v4 as uuid } from "uuid"
2
 import * as vec from "utils/vec"
2
 import * as vec from "utils/vec"
3
 import { BaseLibShape, DotShape, ShapeType } from "types"
3
 import { BaseLibShape, DotShape, ShapeType } from "types"
4
+import { boundsCache } from "./index"
4
 
5
 
5
 const Dot: BaseLibShape<ShapeType.Dot> = {
6
 const Dot: BaseLibShape<ShapeType.Dot> = {
6
   create(props): DotShape {
7
   create(props): DotShape {
22
   },
23
   },
23
 
24
 
24
   getBounds(shape) {
25
   getBounds(shape) {
26
+    if (boundsCache.has(shape)) {
27
+      return boundsCache.get(shape)
28
+    }
29
+
25
     const {
30
     const {
26
-      point: [cx, cy],
31
+      point: [x, y],
27
     } = shape
32
     } = shape
28
 
33
 
29
-    return {
30
-      minX: cx,
31
-      maxX: cx + 4,
32
-      minY: cy,
33
-      maxY: cy + 4,
34
-      width: 4,
35
-      height: 4,
34
+    const bounds = {
35
+      minX: x,
36
+      maxX: x + 8,
37
+      minY: y,
38
+      maxY: y + 8,
39
+      width: 8,
40
+      height: 8,
36
     }
41
     }
42
+
43
+    boundsCache.set(shape, bounds)
44
+    return bounds
37
   },
45
   },
38
 
46
 
39
   hitTest(shape, test) {
47
   hitTest(shape, test) {

+ 6
- 2
lib/shapes/index.tsx Parādīt failu

3
 import Polyline from "./polyline"
3
 import Polyline from "./polyline"
4
 import Rectangle from "./rectangle"
4
 import Rectangle from "./rectangle"
5
 
5
 
6
-import { ShapeType } from "types"
6
+import { Bounds, Shape, ShapeType } from "types"
7
 
7
 
8
-export default {
8
+export const boundsCache = new WeakMap<Shape, Bounds>([])
9
+
10
+const shapes = {
9
   [ShapeType.Circle]: Circle,
11
   [ShapeType.Circle]: Circle,
10
   [ShapeType.Dot]: Dot,
12
   [ShapeType.Dot]: Dot,
11
   [ShapeType.Polyline]: Polyline,
13
   [ShapeType.Polyline]: Polyline,
12
   [ShapeType.Rectangle]: Rectangle,
14
   [ShapeType.Rectangle]: Rectangle,
13
 }
15
 }
16
+
17
+export default shapes

+ 9
- 1
lib/shapes/polyline.tsx Parādīt failu

1
 import { v4 as uuid } from "uuid"
1
 import { v4 as uuid } from "uuid"
2
 import * as vec from "utils/vec"
2
 import * as vec from "utils/vec"
3
 import { BaseLibShape, PolylineShape, ShapeType } from "types"
3
 import { BaseLibShape, PolylineShape, ShapeType } from "types"
4
+import { boundsCache } from "./index"
4
 
5
 
5
 const Polyline: BaseLibShape<ShapeType.Polyline> = {
6
 const Polyline: BaseLibShape<ShapeType.Polyline> = {
6
   create(props): PolylineShape {
7
   create(props): PolylineShape {
23
   },
24
   },
24
 
25
 
25
   getBounds(shape) {
26
   getBounds(shape) {
27
+    if (boundsCache.has(shape)) {
28
+      return boundsCache.get(shape)
29
+    }
30
+
26
     let minX = 0
31
     let minX = 0
27
     let minY = 0
32
     let minY = 0
28
     let maxX = 0
33
     let maxX = 0
35
       maxY = Math.max(y, maxY)
40
       maxY = Math.max(y, maxY)
36
     }
41
     }
37
 
42
 
38
-    return {
43
+    const bounds = {
39
       minX: minX + shape.point[0],
44
       minX: minX + shape.point[0],
40
       minY: minY + shape.point[1],
45
       minY: minY + shape.point[1],
41
       maxX: maxX + shape.point[0],
46
       maxX: maxX + shape.point[0],
43
       width: maxX - minX,
48
       width: maxX - minX,
44
       height: maxY - minY,
49
       height: maxY - minY,
45
     }
50
     }
51
+
52
+    boundsCache.set(shape, bounds)
53
+    return bounds
46
   },
54
   },
47
 
55
 
48
   hitTest(shape) {
56
   hitTest(shape) {

+ 9
- 1
lib/shapes/rectangle.tsx Parādīt failu

1
 import { v4 as uuid } from "uuid"
1
 import { v4 as uuid } from "uuid"
2
 import * as vec from "utils/vec"
2
 import * as vec from "utils/vec"
3
 import { BaseLibShape, RectangleShape, ShapeType } from "types"
3
 import { BaseLibShape, RectangleShape, ShapeType } from "types"
4
+import { boundsCache } from "./index"
4
 
5
 
5
 const Rectangle: BaseLibShape<ShapeType.Rectangle> = {
6
 const Rectangle: BaseLibShape<ShapeType.Rectangle> = {
6
   create(props): RectangleShape {
7
   create(props): RectangleShape {
23
   },
24
   },
24
 
25
 
25
   getBounds(shape) {
26
   getBounds(shape) {
27
+    if (boundsCache.has(shape)) {
28
+      return boundsCache.get(shape)
29
+    }
30
+
26
     const {
31
     const {
27
       point: [x, y],
32
       point: [x, y],
28
       size: [width, height],
33
       size: [width, height],
29
     } = shape
34
     } = shape
30
 
35
 
31
-    return {
36
+    const bounds = {
32
       minX: x,
37
       minX: x,
33
       maxX: x + width,
38
       maxX: x + width,
34
       minY: y,
39
       minY: y,
36
       width,
41
       width,
37
       height,
42
       height,
38
     }
43
     }
44
+
45
+    boundsCache.set(shape, bounds)
46
+    return bounds
39
   },
47
   },
40
 
48
 
41
   hitTest(shape) {
49
   hitTest(shape) {

+ 1
- 0
package.json Parādīt failu

11
     "@state-designer/react": "^1.7.1",
11
     "@state-designer/react": "^1.7.1",
12
     "@stitches/react": "^0.1.9",
12
     "@stitches/react": "^0.1.9",
13
     "@types/uuid": "^8.3.0",
13
     "@types/uuid": "^8.3.0",
14
+    "framer-motion": "^4.1.16",
14
     "next": "10.2.0",
15
     "next": "10.2.0",
15
     "perfect-freehand": "^0.4.7",
16
     "perfect-freehand": "^0.4.7",
16
     "react": "17.0.2",
17
     "react": "17.0.2",

+ 39
- 2
state/state.ts Parādīt failu

1
 import { createSelectorHook, createState } from "@state-designer/react"
1
 import { createSelectorHook, createState } from "@state-designer/react"
2
-import { clamp, screenToWorld } from "utils/utils"
2
+import { clamp, getCommonBounds, screenToWorld } from "utils/utils"
3
 import * as vec from "utils/vec"
3
 import * as vec from "utils/vec"
4
-import { Data } from "types"
4
+import { Bounds, Data, Shape, ShapeType } from "types"
5
 import { defaultDocument } from "./data"
5
 import { defaultDocument } from "./data"
6
+import Shapes from "lib/shapes"
6
 import * as Sessions from "./sessions"
7
 import * as Sessions from "./sessions"
7
 
8
 
8
 const initialData: Data = {
9
 const initialData: Data = {
131
     selectedIds(data) {
132
     selectedIds(data) {
132
       return new Set(data.selectedIds)
133
       return new Set(data.selectedIds)
133
     },
134
     },
135
+    selectedBounds(data) {
136
+      const {
137
+        selectedIds,
138
+        currentPageId,
139
+        document: { pages },
140
+      } = data
141
+
142
+      return getCommonBounds(
143
+        ...Array.from(selectedIds.values())
144
+          .map((id) => {
145
+            const shape = pages[currentPageId].shapes[id]
146
+
147
+            switch (shape.type) {
148
+              case ShapeType.Dot: {
149
+                return Shapes[shape.type].getBounds(shape)
150
+              }
151
+              case ShapeType.Circle: {
152
+                return Shapes[shape.type].getBounds(shape)
153
+              }
154
+              case ShapeType.Line: {
155
+                return Shapes[shape.type].getBounds(shape)
156
+              }
157
+              case ShapeType.Polyline: {
158
+                return Shapes[shape.type].getBounds(shape)
159
+              }
160
+              case ShapeType.Rectangle: {
161
+                return Shapes[shape.type].getBounds(shape)
162
+              }
163
+              default: {
164
+                return null
165
+              }
166
+            }
167
+          })
168
+          .filter(Boolean)
169
+      )
170
+    },
134
   },
171
   },
135
 })
172
 })
136
 
173
 

+ 7
- 0
styles/stitches.config.ts Parādīt failu

10
       brushStroke: "rgba(0,0,0,.5)",
10
       brushStroke: "rgba(0,0,0,.5)",
11
       hint: "rgba(66, 133, 244, 0.200)",
11
       hint: "rgba(66, 133, 244, 0.200)",
12
       selected: "rgba(66, 133, 244, 1.000)",
12
       selected: "rgba(66, 133, 244, 1.000)",
13
+      bounds: "rgba(65, 132, 244, 1.000)",
14
+      boundsBg: "rgba(65, 132, 244, 0.100)",
13
     },
15
     },
14
     space: {},
16
     space: {},
15
     fontSizes: {
17
     fontSizes: {
33
     zIndices: {},
35
     zIndices: {},
34
     transitions: {},
36
     transitions: {},
35
   },
37
   },
38
+  utils: {
39
+    zStrokeWidth: () => (value: number) => ({
40
+      strokeWidth: `calc(${value}px / var(--camera-zoom))`,
41
+    }),
42
+  },
36
 })
43
 })
37
 
44
 
38
 const light = theme({})
45
 const light = theme({})

+ 34
- 1
utils/utils.ts Parādīt failu

1
-import { Data } from "types"
1
+import { Data, Bounds } from "types"
2
 import * as svg from "./svg"
2
 import * as svg from "./svg"
3
 import * as vec from "./vec"
3
 import * as vec from "./vec"
4
 
4
 
6
   return vec.sub(vec.div(point, data.camera.zoom), data.camera.point)
6
   return vec.sub(vec.div(point, data.camera.zoom), data.camera.point)
7
 }
7
 }
8
 
8
 
9
+/**
10
+ * Get a bounding box that includes two bounding boxes.
11
+ * @param a Bounding box
12
+ * @param b Bounding box
13
+ * @returns
14
+ */
15
+export function getExpandedBounds(a: Bounds, b: Bounds) {
16
+  const minX = Math.min(a.minX, b.minX),
17
+    minY = Math.min(a.minY, b.minY),
18
+    maxX = Math.max(a.maxX, b.maxX),
19
+    maxY = Math.max(a.maxY, b.maxY),
20
+    width = Math.abs(maxX - minX),
21
+    height = Math.abs(maxY - minY)
22
+
23
+  return { minX, minY, maxX, maxY, width, height }
24
+}
25
+
26
+/**
27
+ * Get the common bounds of a group of bounds.
28
+ * @returns
29
+ */
30
+export function getCommonBounds(...b: Bounds[]) {
31
+  if (b.length < 2) return b[0]
32
+
33
+  let bounds = b[0]
34
+
35
+  for (let i = 1; i < b.length; i++) {
36
+    bounds = getExpandedBounds(bounds, b[i])
37
+  }
38
+
39
+  return bounds
40
+}
41
+
9
 export function getBoundsFromPoints(a: number[], b: number[]) {
42
 export function getBoundsFromPoints(a: number[], b: number[]) {
10
   const minX = Math.min(a[0], b[0])
43
   const minX = Math.min(a[0], b[0])
11
   const maxX = Math.max(a[0], b[0])
44
   const maxX = Math.max(a[0], b[0])

+ 56
- 1
yarn.lock Parādīt failu

939
     exec-sh "^0.3.2"
939
     exec-sh "^0.3.2"
940
     minimist "^1.2.0"
940
     minimist "^1.2.0"
941
 
941
 
942
+"@emotion/is-prop-valid@^0.8.2":
943
+  version "0.8.8"
944
+  resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
945
+  integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
946
+  dependencies:
947
+    "@emotion/memoize" "0.7.4"
948
+
949
+"@emotion/memoize@0.7.4":
950
+  version "0.7.4"
951
+  resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
952
+  integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
953
+
942
 "@hapi/accept@5.0.1":
954
 "@hapi/accept@5.0.1":
943
   version "5.0.1"
955
   version "5.0.1"
944
   resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.1.tgz#068553e867f0f63225a506ed74e899441af53e10"
956
   resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.1.tgz#068553e867f0f63225a506ed74e899441af53e10"
3402
   dependencies:
3414
   dependencies:
3403
     map-cache "^0.2.2"
3415
     map-cache "^0.2.2"
3404
 
3416
 
3417
+framer-motion@^4.1.16:
3418
+  version "4.1.16"
3419
+  resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-4.1.16.tgz#dc715334847d0a146acf47f61019222d0d1c46c9"
3420
+  integrity sha512-sEc3UI3oncwE+RUzdd86TxbmpEaX/Ki/T0AmFYSsbxEqGZ3feLvzGL7BJlkhERIyyuAC9+OzI4BnhJM0GSUAMA==
3421
+  dependencies:
3422
+    framesync "5.3.0"
3423
+    hey-listen "^1.0.8"
3424
+    popmotion "9.3.6"
3425
+    style-value-types "4.1.4"
3426
+    tslib "^2.1.0"
3427
+  optionalDependencies:
3428
+    "@emotion/is-prop-valid" "^0.8.2"
3429
+
3430
+framesync@5.3.0:
3431
+  version "5.3.0"
3432
+  resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.3.0.tgz#0ecfc955e8f5a6ddc8fdb0cc024070947e1a0d9b"
3433
+  integrity sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==
3434
+  dependencies:
3435
+    tslib "^2.1.0"
3436
+
3405
 fs-extra@8.1.0:
3437
 fs-extra@8.1.0:
3406
   version "8.1.0"
3438
   version "8.1.0"
3407
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
3439
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
3652
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
3684
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
3653
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
3685
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
3654
 
3686
 
3687
+hey-listen@^1.0.8:
3688
+  version "1.0.8"
3689
+  resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
3690
+  integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
3691
+
3655
 hmac-drbg@^1.0.1:
3692
 hmac-drbg@^1.0.1:
3656
   version "1.0.1"
3693
   version "1.0.1"
3657
   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
3694
   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
5622
   dependencies:
5659
   dependencies:
5623
     ts-pnp "^1.1.6"
5660
     ts-pnp "^1.1.6"
5624
 
5661
 
5662
+popmotion@9.3.6:
5663
+  version "9.3.6"
5664
+  resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.3.6.tgz#b5236fa28f242aff3871b9e23721f093133248d1"
5665
+  integrity sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==
5666
+  dependencies:
5667
+    framesync "5.3.0"
5668
+    hey-listen "^1.0.8"
5669
+    style-value-types "4.1.4"
5670
+    tslib "^2.1.0"
5671
+
5625
 posix-character-classes@^0.1.0:
5672
 posix-character-classes@^0.1.0:
5626
   version "0.1.1"
5673
   version "0.1.1"
5627
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
5674
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
6734
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
6781
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
6735
   integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
6782
   integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
6736
 
6783
 
6784
+style-value-types@4.1.4:
6785
+  version "4.1.4"
6786
+  resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.4.tgz#80f37cb4fb024d6394087403dfb275e8bb627e75"
6787
+  integrity sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==
6788
+  dependencies:
6789
+    hey-listen "^1.0.8"
6790
+    tslib "^2.1.0"
6791
+
6737
 styled-jsx@3.3.2:
6792
 styled-jsx@3.3.2:
6738
   version "3.3.2"
6793
   version "3.3.2"
6739
   resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-3.3.2.tgz#2474601a26670a6049fb4d3f94bd91695b3ce018"
6794
   resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-3.3.2.tgz#2474601a26670a6049fb4d3f94bd91695b3ce018"
7051
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
7106
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
7052
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
7107
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
7053
 
7108
 
7054
-tslib@^2.0.3:
7109
+tslib@^2.0.3, tslib@^2.1.0:
7055
   version "2.2.0"
7110
   version "2.2.0"
7056
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
7111
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
7057
   integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
7112
   integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==

Notiek ielāde…
Atcelt
Saglabāt