Browse Source

Starts implementing locked shapes

main
Steve Ruiz 4 years ago
parent
commit
6118dfcc2c

+ 31
- 11
components/canvas/bounds/bounding-box.tsx View File

@@ -1,7 +1,7 @@
1 1
 import * as React from 'react'
2 2
 import { Edge, Corner } from 'types'
3 3
 import { useSelector } from 'state'
4
-import { getSelectedShapes, isMobile } from 'utils/utils'
4
+import { getPage, getSelectedShapes, isMobile } from 'utils/utils'
5 5
 
6 6
 import CenterHandle from './center-handle'
7 7
 import CornerHandle from './corner-handle'
@@ -13,10 +13,18 @@ export default function Bounds() {
13 13
   const isSelecting = useSelector((s) => s.isIn('selecting'))
14 14
   const zoom = useSelector((s) => s.data.camera.zoom)
15 15
   const bounds = useSelector((s) => s.values.selectedBounds)
16
+
16 17
   const rotation = useSelector(({ data }) =>
17 18
     data.selectedIds.size === 1 ? getSelectedShapes(data)[0].rotation : 0
18 19
   )
19 20
 
21
+  const isAllLocked = useSelector((s) => {
22
+    const page = getPage(s.data)
23
+    return Array.from(s.data.selectedIds.values()).every(
24
+      (id) => page.shapes[id].isLocked
25
+    )
26
+  })
27
+
20 28
   if (!bounds) return null
21 29
   if (!isSelecting) return null
22 30
 
@@ -31,16 +39,28 @@ export default function Bounds() {
31 39
         ${(bounds.minY + bounds.maxY) / 2})
32 40
         translate(${bounds.minX},${bounds.minY})`}
33 41
     >
34
-      <CenterHandle bounds={bounds} />
35
-      <EdgeHandle size={size} bounds={bounds} edge={Edge.Top} />
36
-      <EdgeHandle size={size} bounds={bounds} edge={Edge.Right} />
37
-      <EdgeHandle size={size} bounds={bounds} edge={Edge.Bottom} />
38
-      <EdgeHandle size={size} bounds={bounds} edge={Edge.Left} />
39
-      <CornerHandle size={size} bounds={bounds} corner={Corner.TopLeft} />
40
-      <CornerHandle size={size} bounds={bounds} corner={Corner.TopRight} />
41
-      <CornerHandle size={size} bounds={bounds} corner={Corner.BottomRight} />
42
-      <CornerHandle size={size} bounds={bounds} corner={Corner.BottomLeft} />
43
-      <RotateHandle size={size} bounds={bounds} />
42
+      <CenterHandle bounds={bounds} isLocked={isAllLocked} />
43
+      {!isAllLocked && (
44
+        <>
45
+          <EdgeHandle size={size} bounds={bounds} edge={Edge.Top} />
46
+          <EdgeHandle size={size} bounds={bounds} edge={Edge.Right} />
47
+          <EdgeHandle size={size} bounds={bounds} edge={Edge.Bottom} />
48
+          <EdgeHandle size={size} bounds={bounds} edge={Edge.Left} />
49
+          <CornerHandle size={size} bounds={bounds} corner={Corner.TopLeft} />
50
+          <CornerHandle size={size} bounds={bounds} corner={Corner.TopRight} />
51
+          <CornerHandle
52
+            size={size}
53
+            bounds={bounds}
54
+            corner={Corner.BottomRight}
55
+          />
56
+          <CornerHandle
57
+            size={size}
58
+            bounds={bounds}
59
+            corner={Corner.BottomLeft}
60
+          />
61
+          <RotateHandle size={size} bounds={bounds} />
62
+        </>
63
+      )}
44 64
     </g>
45 65
   )
46 66
 }

+ 22
- 6
components/canvas/bounds/center-handle.tsx View File

@@ -1,18 +1,34 @@
1
-import styled from "styles"
2
-import { Bounds } from "types"
1
+import styled from 'styles'
2
+import { Bounds } from 'types'
3 3
 
4
-export default function CenterHandle({ bounds }: { bounds: Bounds }) {
4
+export default function CenterHandle({
5
+  bounds,
6
+  isLocked,
7
+}: {
8
+  bounds: Bounds
9
+  isLocked: boolean
10
+}) {
5 11
   return (
6 12
     <StyledBounds
7 13
       width={bounds.width}
8 14
       height={bounds.height}
9 15
       pointerEvents="none"
16
+      isLocked={isLocked}
10 17
     />
11 18
   )
12 19
 }
13 20
 
14
-const StyledBounds = styled("rect", {
15
-  fill: "none",
16
-  stroke: "$bounds",
21
+const StyledBounds = styled('rect', {
22
+  fill: 'none',
23
+  stroke: '$bounds',
17 24
   zStrokeWidth: 2,
25
+
26
+  variants: {
27
+    isLocked: {
28
+      true: {
29
+        zStrokeWidth: 1,
30
+        zDash: 2,
31
+      },
32
+    },
33
+  },
18 34
 })

+ 10
- 0
components/canvas/selected.tsx View File

@@ -43,6 +43,7 @@ export function ShapeOutline({ id }: { id: string }) {
43 43
       as="use"
44 44
       href={'#' + id}
45 45
       transform={transform}
46
+      isLocked={shape.isLocked}
46 47
       {...events}
47 48
     />
48 49
   )
@@ -55,4 +56,13 @@ const Indicator = styled('path', {
55 56
   stroke: '$selected',
56 57
   fill: 'transparent',
57 58
   pointerEvents: 'all',
59
+
60
+  variants: {
61
+    isLocked: {
62
+      true: {
63
+        zDash: 2,
64
+      },
65
+      false: {},
66
+    },
67
+  },
58 68
 })

+ 19
- 21
state/commands/transform.ts View File

@@ -1,59 +1,57 @@
1
-import Command from "./command"
2
-import history from "../history"
3
-import { Data, Corner, Edge } from "types"
4
-import { TransformSnapshot } from "state/sessions/transform-session"
5
-import { getShapeUtils } from "lib/shape-utils"
6
-import { getPage } from "utils/utils"
1
+import Command from './command'
2
+import history from '../history'
3
+import { Data } from 'types'
4
+import { TransformSnapshot } from 'state/sessions/transform-session'
5
+import { getShapeUtils } from 'lib/shape-utils'
6
+import { getPage } from 'utils/utils'
7 7
 
8 8
 export default function transformCommand(
9 9
   data: Data,
10 10
   before: TransformSnapshot,
11
-  after: TransformSnapshot,
12
-  scaleX: number,
13
-  scaleY: number
11
+  after: TransformSnapshot
14 12
 ) {
15 13
   history.execute(
16 14
     data,
17 15
     new Command({
18
-      name: "translate_shapes",
19
-      category: "canvas",
16
+      name: 'translate_shapes',
17
+      category: 'canvas',
20 18
       do(data) {
21
-        const { type, selectedIds } = after
19
+        const { type, shapeBounds } = after
22 20
 
23 21
         const { shapes } = getPage(data)
24 22
 
25
-        selectedIds.forEach((id) => {
23
+        for (let id in shapeBounds) {
26 24
           const { initialShape, initialShapeBounds, transformOrigin } =
27
-            after.shapeBounds[id]
25
+            shapeBounds[id]
28 26
           const shape = shapes[id]
29 27
 
30 28
           getShapeUtils(shape).transform(shape, initialShapeBounds, {
31 29
             type,
32 30
             initialShape,
31
+            transformOrigin,
33 32
             scaleX: 1,
34 33
             scaleY: 1,
35
-            transformOrigin,
36 34
           })
37
-        })
35
+        }
38 36
       },
39 37
       undo(data) {
40
-        const { type, selectedIds } = before
38
+        const { type, shapeBounds } = before
41 39
 
42 40
         const { shapes } = getPage(data)
43 41
 
44
-        selectedIds.forEach((id) => {
42
+        for (let id in shapeBounds) {
45 43
           const { initialShape, initialShapeBounds, transformOrigin } =
46
-            before.shapeBounds[id]
44
+            shapeBounds[id]
47 45
           const shape = shapes[id]
48 46
 
49 47
           getShapeUtils(shape).transform(shape, initialShapeBounds, {
50 48
             type,
51 49
             initialShape,
50
+            transformOrigin,
52 51
             scaleX: 1,
53 52
             scaleY: 1,
54
-            transformOrigin,
55 53
           })
56
-        })
54
+        }
57 55
       },
58 56
     })
59 57
   )

+ 8
- 8
state/commands/translate.ts View File

@@ -1,9 +1,9 @@
1
-import Command from "./command"
2
-import history from "../history"
3
-import { TranslateSnapshot } from "state/sessions/translate-session"
4
-import { Data } from "types"
5
-import { getPage } from "utils/utils"
6
-import { getShapeUtils } from "lib/shape-utils"
1
+import Command from './command'
2
+import history from '../history'
3
+import { TranslateSnapshot } from 'state/sessions/translate-session'
4
+import { Data } from 'types'
5
+import { getPage } from 'utils/utils'
6
+import { getShapeUtils } from 'lib/shape-utils'
7 7
 
8 8
 export default function translateCommand(
9 9
   data: Data,
@@ -14,8 +14,8 @@ export default function translateCommand(
14 14
   history.execute(
15 15
     data,
16 16
     new Command({
17
-      name: isCloning ? "clone_shapes" : "translate_shapes",
18
-      category: "canvas",
17
+      name: isCloning ? 'clone_shapes' : 'translate_shapes',
18
+      category: 'canvas',
19 19
       manualSelection: true,
20 20
       do(data, initial) {
21 21
         if (initial) return

+ 29
- 31
state/sessions/transform-session.ts View File

@@ -1,18 +1,19 @@
1
-import { Data, Edge, Corner } from "types"
2
-import * as vec from "utils/vec"
3
-import BaseSession from "./base-session"
4
-import commands from "state/commands"
5
-import { current } from "immer"
6
-import { getShapeUtils } from "lib/shape-utils"
1
+import { Data, Edge, Corner } from 'types'
2
+import * as vec from 'utils/vec'
3
+import BaseSession from './base-session'
4
+import commands from 'state/commands'
5
+import { current } from 'immer'
6
+import { getShapeUtils } from 'lib/shape-utils'
7 7
 import {
8 8
   getBoundsCenter,
9 9
   getBoundsFromPoints,
10 10
   getCommonBounds,
11 11
   getPage,
12 12
   getRelativeTransformedBoundingBox,
13
+  getSelectedShapes,
13 14
   getShapes,
14 15
   getTransformedBoundingBox,
15
-} from "utils/utils"
16
+} from 'utils/utils'
16 17
 
17 18
 export default class TransformSession extends BaseSession {
18 19
   scaleX = 1
@@ -31,7 +32,7 @@ export default class TransformSession extends BaseSession {
31 32
   update(data: Data, point: number[], isAspectRatioLocked = false) {
32 33
     const { transformType } = this
33 34
 
34
-    const { selectedIds, shapeBounds, initialBounds } = this.snapshot
35
+    const { shapeBounds, initialBounds } = this.snapshot
35 36
 
36 37
     const { shapes } = getPage(data)
37 38
 
@@ -48,7 +49,7 @@ export default class TransformSession extends BaseSession {
48 49
 
49 50
     // Now work backward to calculate a new bounding box for each of the shapes.
50 51
 
51
-    selectedIds.forEach((id) => {
52
+    for (let id in shapeBounds) {
52 53
       const { initialShape, initialShapeBounds, transformOrigin } =
53 54
         shapeBounds[id]
54 55
 
@@ -69,15 +70,15 @@ export default class TransformSession extends BaseSession {
69 70
         scaleY: this.scaleY,
70 71
         transformOrigin,
71 72
       })
72
-    })
73
+    }
73 74
   }
74 75
 
75 76
   cancel(data: Data) {
76
-    const { currentPageId, selectedIds, shapeBounds } = this.snapshot
77
+    const { currentPageId, shapeBounds } = this.snapshot
77 78
 
78 79
     const page = getPage(data, currentPageId)
79 80
 
80
-    selectedIds.forEach((id) => {
81
+    for (let id in shapeBounds) {
81 82
       const shape = page.shapes[id]
82 83
 
83 84
       const { initialShape, initialShapeBounds, transformOrigin } =
@@ -90,35 +91,33 @@ export default class TransformSession extends BaseSession {
90 91
         scaleY: 1,
91 92
         transformOrigin,
92 93
       })
93
-    })
94
+    }
94 95
   }
95 96
 
96 97
   complete(data: Data) {
97 98
     commands.transform(
98 99
       data,
99 100
       this.snapshot,
100
-      getTransformSnapshot(data, this.transformType),
101
-      this.scaleX,
102
-      this.scaleY
101
+      getTransformSnapshot(data, this.transformType)
103 102
     )
104 103
   }
105 104
 }
106 105
 
107 106
 export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
108
-  const {
109
-    document: { pages },
110
-    selectedIds,
111
-    currentPageId,
112
-  } = current(data)
107
+  const cData = current(data)
108
+  const { currentPageId } = cData
113 109
 
114
-  const pageShapes = pages[currentPageId].shapes
110
+  const initialShapes = getSelectedShapes(cData).filter(
111
+    (shape) => !shape.isLocked
112
+  )
113
+  const hasShapes = initialShapes.length > 0
115 114
 
116 115
   // A mapping of selected shapes and their bounds
117 116
   const shapesBounds = Object.fromEntries(
118
-    Array.from(selectedIds.values()).map((id) => {
119
-      const shape = pageShapes[id]
120
-      return [shape.id, getShapeUtils(shape).getBounds(shape)]
121
-    })
117
+    initialShapes.map((shape) => [
118
+      shape.id,
119
+      getShapeUtils(shape).getBounds(shape),
120
+    ])
122 121
   )
123 122
 
124 123
   const boundsArr = Object.values(shapesBounds)
@@ -132,20 +131,19 @@ export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
132 131
   // positions of the shape's bounds within the common bounds shape.
133 132
   return {
134 133
     type: transformType,
134
+    hasShapes,
135 135
     currentPageId,
136
-    selectedIds: new Set(selectedIds),
137 136
     initialBounds: bounds,
138 137
     shapeBounds: Object.fromEntries(
139
-      Array.from(selectedIds.values()).map((id) => {
140
-        const shape = pageShapes[id]
141
-        const initialShapeBounds = shapesBounds[id]
138
+      initialShapes.map((shape) => {
139
+        const initialShapeBounds = shapesBounds[shape.id]
142 140
         const ic = getBoundsCenter(initialShapeBounds)
143 141
 
144 142
         let ix = (ic[0] - initialInnerBounds.minX) / initialInnerBounds.width
145 143
         let iy = (ic[1] - initialInnerBounds.minY) / initialInnerBounds.height
146 144
 
147 145
         return [
148
-          id,
146
+          shape.id,
149 147
           {
150 148
             initialShape: shape,
151 149
             initialShapeBounds,

+ 13
- 9
state/sessions/translate-session.ts View File

@@ -1,11 +1,11 @@
1
-import { Data } from "types"
2
-import * as vec from "utils/vec"
3
-import BaseSession from "./base-session"
4
-import commands from "state/commands"
5
-import { current } from "immer"
6
-import { v4 as uuid } from "uuid"
7
-import { getChildIndexAbove, getPage, getSelectedShapes } from "utils/utils"
8
-import { getShapeUtils } from "lib/shape-utils"
1
+import { Data } from 'types'
2
+import * as vec from 'utils/vec'
3
+import BaseSession from './base-session'
4
+import commands from 'state/commands'
5
+import { current } from 'immer'
6
+import { v4 as uuid } from 'uuid'
7
+import { getChildIndexAbove, getPage, getSelectedShapes } from 'utils/utils'
8
+import { getShapeUtils } from 'lib/shape-utils'
9 9
 
10 10
 export default class TranslateSession extends BaseSession {
11 11
   delta = [0, 0]
@@ -89,6 +89,8 @@ export default class TranslateSession extends BaseSession {
89 89
   }
90 90
 
91 91
   complete(data: Data) {
92
+    if (!this.snapshot.hasShapes) return
93
+
92 94
     commands.translate(
93 95
       data,
94 96
       this.snapshot,
@@ -100,7 +102,8 @@ export default class TranslateSession extends BaseSession {
100 102
 
101 103
 export function getTranslateSnapshot(data: Data) {
102 104
   const cData = current(data)
103
-  const shapes = getSelectedShapes(cData)
105
+  const shapes = getSelectedShapes(cData).filter((shape) => !shape.isLocked)
106
+  const hasShapes = shapes.length > 0
104 107
 
105 108
   return {
106 109
     currentPageId: data.currentPageId,
@@ -110,6 +113,7 @@ export function getTranslateSnapshot(data: Data) {
110 113
       id: uuid(),
111 114
       childIndex: getChildIndexAbove(cData, shape.id),
112 115
     })),
116
+    hasShapes,
113 117
   }
114 118
 }
115 119
 

+ 1
- 1
state/state.ts View File

@@ -141,7 +141,6 @@ const state = createState({
141 141
             UNDO: 'undo',
142 142
             REDO: 'redo',
143 143
             SAVED_CODE: 'saveCode',
144
-            CANCELLED: 'clearSelectedIds',
145 144
             DELETED: 'deleteSelectedIds',
146 145
             STARTED_PINCHING: { to: 'pinching' },
147 146
             INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
@@ -159,6 +158,7 @@ const state = createState({
159 158
           states: {
160 159
             notPointing: {
161 160
               on: {
161
+                CANCELLED: 'clearSelectedIds',
162 162
                 POINTED_CANVAS: { to: 'brushSelecting' },
163 163
                 POINTED_BOUNDS: { to: 'pointingBounds' },
164 164
                 POINTED_BOUNDS_HANDLE: {

+ 5
- 0
styles/stitches.config.ts View File

@@ -43,6 +43,11 @@ const { styled, global, css, theme, getCssString } = createCss({
43 43
     transitions: {},
44 44
   },
45 45
   utils: {
46
+    zDash: () => (value: number) => {
47
+      return {
48
+        strokeDasharray: `calc(${value}px / var(--camera-zoom)) calc(${value}px / var(--camera-zoom))`,
49
+      }
50
+    },
46 51
     zStrokeWidth: () => (value: number | number[]) => {
47 52
       if (Array.isArray(value)) {
48 53
         // const [val, min, max] = value

Loading…
Cancel
Save