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
 import * as React from 'react'
1
 import * as React from 'react'
2
 import { Edge, Corner } from 'types'
2
 import { Edge, Corner } from 'types'
3
 import { useSelector } from 'state'
3
 import { useSelector } from 'state'
4
-import { getSelectedShapes, isMobile } from 'utils/utils'
4
+import { getPage, getSelectedShapes, isMobile } from 'utils/utils'
5
 
5
 
6
 import CenterHandle from './center-handle'
6
 import CenterHandle from './center-handle'
7
 import CornerHandle from './corner-handle'
7
 import CornerHandle from './corner-handle'
13
   const isSelecting = useSelector((s) => s.isIn('selecting'))
13
   const isSelecting = useSelector((s) => s.isIn('selecting'))
14
   const zoom = useSelector((s) => s.data.camera.zoom)
14
   const zoom = useSelector((s) => s.data.camera.zoom)
15
   const bounds = useSelector((s) => s.values.selectedBounds)
15
   const bounds = useSelector((s) => s.values.selectedBounds)
16
+
16
   const rotation = useSelector(({ data }) =>
17
   const rotation = useSelector(({ data }) =>
17
     data.selectedIds.size === 1 ? getSelectedShapes(data)[0].rotation : 0
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
   if (!bounds) return null
28
   if (!bounds) return null
21
   if (!isSelecting) return null
29
   if (!isSelecting) return null
22
 
30
 
31
         ${(bounds.minY + bounds.maxY) / 2})
39
         ${(bounds.minY + bounds.maxY) / 2})
32
         translate(${bounds.minX},${bounds.minY})`}
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
     </g>
64
     </g>
45
   )
65
   )
46
 }
66
 }

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

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
   return (
11
   return (
6
     <StyledBounds
12
     <StyledBounds
7
       width={bounds.width}
13
       width={bounds.width}
8
       height={bounds.height}
14
       height={bounds.height}
9
       pointerEvents="none"
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
   zStrokeWidth: 2,
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
       as="use"
43
       as="use"
44
       href={'#' + id}
44
       href={'#' + id}
45
       transform={transform}
45
       transform={transform}
46
+      isLocked={shape.isLocked}
46
       {...events}
47
       {...events}
47
     />
48
     />
48
   )
49
   )
55
   stroke: '$selected',
56
   stroke: '$selected',
56
   fill: 'transparent',
57
   fill: 'transparent',
57
   pointerEvents: 'all',
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
-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
 export default function transformCommand(
8
 export default function transformCommand(
9
   data: Data,
9
   data: Data,
10
   before: TransformSnapshot,
10
   before: TransformSnapshot,
11
-  after: TransformSnapshot,
12
-  scaleX: number,
13
-  scaleY: number
11
+  after: TransformSnapshot
14
 ) {
12
 ) {
15
   history.execute(
13
   history.execute(
16
     data,
14
     data,
17
     new Command({
15
     new Command({
18
-      name: "translate_shapes",
19
-      category: "canvas",
16
+      name: 'translate_shapes',
17
+      category: 'canvas',
20
       do(data) {
18
       do(data) {
21
-        const { type, selectedIds } = after
19
+        const { type, shapeBounds } = after
22
 
20
 
23
         const { shapes } = getPage(data)
21
         const { shapes } = getPage(data)
24
 
22
 
25
-        selectedIds.forEach((id) => {
23
+        for (let id in shapeBounds) {
26
           const { initialShape, initialShapeBounds, transformOrigin } =
24
           const { initialShape, initialShapeBounds, transformOrigin } =
27
-            after.shapeBounds[id]
25
+            shapeBounds[id]
28
           const shape = shapes[id]
26
           const shape = shapes[id]
29
 
27
 
30
           getShapeUtils(shape).transform(shape, initialShapeBounds, {
28
           getShapeUtils(shape).transform(shape, initialShapeBounds, {
31
             type,
29
             type,
32
             initialShape,
30
             initialShape,
31
+            transformOrigin,
33
             scaleX: 1,
32
             scaleX: 1,
34
             scaleY: 1,
33
             scaleY: 1,
35
-            transformOrigin,
36
           })
34
           })
37
-        })
35
+        }
38
       },
36
       },
39
       undo(data) {
37
       undo(data) {
40
-        const { type, selectedIds } = before
38
+        const { type, shapeBounds } = before
41
 
39
 
42
         const { shapes } = getPage(data)
40
         const { shapes } = getPage(data)
43
 
41
 
44
-        selectedIds.forEach((id) => {
42
+        for (let id in shapeBounds) {
45
           const { initialShape, initialShapeBounds, transformOrigin } =
43
           const { initialShape, initialShapeBounds, transformOrigin } =
46
-            before.shapeBounds[id]
44
+            shapeBounds[id]
47
           const shape = shapes[id]
45
           const shape = shapes[id]
48
 
46
 
49
           getShapeUtils(shape).transform(shape, initialShapeBounds, {
47
           getShapeUtils(shape).transform(shape, initialShapeBounds, {
50
             type,
48
             type,
51
             initialShape,
49
             initialShape,
50
+            transformOrigin,
52
             scaleX: 1,
51
             scaleX: 1,
53
             scaleY: 1,
52
             scaleY: 1,
54
-            transformOrigin,
55
           })
53
           })
56
-        })
54
+        }
57
       },
55
       },
58
     })
56
     })
59
   )
57
   )

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

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
 export default function translateCommand(
8
 export default function translateCommand(
9
   data: Data,
9
   data: Data,
14
   history.execute(
14
   history.execute(
15
     data,
15
     data,
16
     new Command({
16
     new Command({
17
-      name: isCloning ? "clone_shapes" : "translate_shapes",
18
-      category: "canvas",
17
+      name: isCloning ? 'clone_shapes' : 'translate_shapes',
18
+      category: 'canvas',
19
       manualSelection: true,
19
       manualSelection: true,
20
       do(data, initial) {
20
       do(data, initial) {
21
         if (initial) return
21
         if (initial) return

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

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
 import {
7
 import {
8
   getBoundsCenter,
8
   getBoundsCenter,
9
   getBoundsFromPoints,
9
   getBoundsFromPoints,
10
   getCommonBounds,
10
   getCommonBounds,
11
   getPage,
11
   getPage,
12
   getRelativeTransformedBoundingBox,
12
   getRelativeTransformedBoundingBox,
13
+  getSelectedShapes,
13
   getShapes,
14
   getShapes,
14
   getTransformedBoundingBox,
15
   getTransformedBoundingBox,
15
-} from "utils/utils"
16
+} from 'utils/utils'
16
 
17
 
17
 export default class TransformSession extends BaseSession {
18
 export default class TransformSession extends BaseSession {
18
   scaleX = 1
19
   scaleX = 1
31
   update(data: Data, point: number[], isAspectRatioLocked = false) {
32
   update(data: Data, point: number[], isAspectRatioLocked = false) {
32
     const { transformType } = this
33
     const { transformType } = this
33
 
34
 
34
-    const { selectedIds, shapeBounds, initialBounds } = this.snapshot
35
+    const { shapeBounds, initialBounds } = this.snapshot
35
 
36
 
36
     const { shapes } = getPage(data)
37
     const { shapes } = getPage(data)
37
 
38
 
48
 
49
 
49
     // Now work backward to calculate a new bounding box for each of the shapes.
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
       const { initialShape, initialShapeBounds, transformOrigin } =
53
       const { initialShape, initialShapeBounds, transformOrigin } =
53
         shapeBounds[id]
54
         shapeBounds[id]
54
 
55
 
69
         scaleY: this.scaleY,
70
         scaleY: this.scaleY,
70
         transformOrigin,
71
         transformOrigin,
71
       })
72
       })
72
-    })
73
+    }
73
   }
74
   }
74
 
75
 
75
   cancel(data: Data) {
76
   cancel(data: Data) {
76
-    const { currentPageId, selectedIds, shapeBounds } = this.snapshot
77
+    const { currentPageId, shapeBounds } = this.snapshot
77
 
78
 
78
     const page = getPage(data, currentPageId)
79
     const page = getPage(data, currentPageId)
79
 
80
 
80
-    selectedIds.forEach((id) => {
81
+    for (let id in shapeBounds) {
81
       const shape = page.shapes[id]
82
       const shape = page.shapes[id]
82
 
83
 
83
       const { initialShape, initialShapeBounds, transformOrigin } =
84
       const { initialShape, initialShapeBounds, transformOrigin } =
90
         scaleY: 1,
91
         scaleY: 1,
91
         transformOrigin,
92
         transformOrigin,
92
       })
93
       })
93
-    })
94
+    }
94
   }
95
   }
95
 
96
 
96
   complete(data: Data) {
97
   complete(data: Data) {
97
     commands.transform(
98
     commands.transform(
98
       data,
99
       data,
99
       this.snapshot,
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
 export function getTransformSnapshot(data: Data, transformType: Edge | Corner) {
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
   // A mapping of selected shapes and their bounds
115
   // A mapping of selected shapes and their bounds
117
   const shapesBounds = Object.fromEntries(
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
   const boundsArr = Object.values(shapesBounds)
123
   const boundsArr = Object.values(shapesBounds)
132
   // positions of the shape's bounds within the common bounds shape.
131
   // positions of the shape's bounds within the common bounds shape.
133
   return {
132
   return {
134
     type: transformType,
133
     type: transformType,
134
+    hasShapes,
135
     currentPageId,
135
     currentPageId,
136
-    selectedIds: new Set(selectedIds),
137
     initialBounds: bounds,
136
     initialBounds: bounds,
138
     shapeBounds: Object.fromEntries(
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
         const ic = getBoundsCenter(initialShapeBounds)
140
         const ic = getBoundsCenter(initialShapeBounds)
143
 
141
 
144
         let ix = (ic[0] - initialInnerBounds.minX) / initialInnerBounds.width
142
         let ix = (ic[0] - initialInnerBounds.minX) / initialInnerBounds.width
145
         let iy = (ic[1] - initialInnerBounds.minY) / initialInnerBounds.height
143
         let iy = (ic[1] - initialInnerBounds.minY) / initialInnerBounds.height
146
 
144
 
147
         return [
145
         return [
148
-          id,
146
+          shape.id,
149
           {
147
           {
150
             initialShape: shape,
148
             initialShape: shape,
151
             initialShapeBounds,
149
             initialShapeBounds,

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

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
 export default class TranslateSession extends BaseSession {
10
 export default class TranslateSession extends BaseSession {
11
   delta = [0, 0]
11
   delta = [0, 0]
89
   }
89
   }
90
 
90
 
91
   complete(data: Data) {
91
   complete(data: Data) {
92
+    if (!this.snapshot.hasShapes) return
93
+
92
     commands.translate(
94
     commands.translate(
93
       data,
95
       data,
94
       this.snapshot,
96
       this.snapshot,
100
 
102
 
101
 export function getTranslateSnapshot(data: Data) {
103
 export function getTranslateSnapshot(data: Data) {
102
   const cData = current(data)
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
   return {
108
   return {
106
     currentPageId: data.currentPageId,
109
     currentPageId: data.currentPageId,
110
       id: uuid(),
113
       id: uuid(),
111
       childIndex: getChildIndexAbove(cData, shape.id),
114
       childIndex: getChildIndexAbove(cData, shape.id),
112
     })),
115
     })),
116
+    hasShapes,
113
   }
117
   }
114
 }
118
 }
115
 
119
 

+ 1
- 1
state/state.ts View File

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

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

43
     transitions: {},
43
     transitions: {},
44
   },
44
   },
45
   utils: {
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
     zStrokeWidth: () => (value: number | number[]) => {
51
     zStrokeWidth: () => (value: number | number[]) => {
47
       if (Array.isArray(value)) {
52
       if (Array.isArray(value)) {
48
         // const [val, min, max] = value
53
         // const [val, min, max] = value

Loading…
Cancel
Save