Преглед на файлове

Fixes nesting groups

main
Steve Ruiz преди 4 години
родител
ревизия
a52e91459f

+ 0
- 1
components/canvas/page.tsx Целия файл

@@ -29,7 +29,6 @@ export default function Page() {
29 29
           id={shapeId}
30 30
           isSelecting={isSelecting}
31 31
           parentPoint={noOffset}
32
-          parentRotation={0}
33 32
         />
34 33
       ))}
35 34
     </g>

+ 20
- 8
components/canvas/shape.tsx Целия файл

@@ -3,7 +3,7 @@ import { useSelector } from 'state'
3 3
 import styled from 'styles'
4 4
 import { getShapeUtils } from 'lib/shape-utils'
5 5
 import { getPage } from 'utils/utils'
6
-import { ShapeType } from 'types'
6
+import { ShapeStyles, ShapeType } from 'types'
7 7
 import useShapeEvents from 'hooks/useShapeEvents'
8 8
 import * as vec from 'utils/vec'
9 9
 import { getShapeStyle } from 'lib/shape-styles'
@@ -12,10 +12,9 @@ interface ShapeProps {
12 12
   id: string
13 13
   isSelecting: boolean
14 14
   parentPoint: number[]
15
-  parentRotation: number
16 15
 }
17 16
 
18
-function Shape({ id, isSelecting, parentPoint, parentRotation }: ShapeProps) {
17
+function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
19 18
   const shape = useSelector(({ data }) => getPage(data).shapes[id])
20 19
 
21 20
   const rGroup = useRef<SVGGElement>(null)
@@ -26,7 +25,9 @@ function Shape({ id, isSelecting, parentPoint, parentRotation }: ShapeProps) {
26 25
   // may sometimes run before the hook in the Page component, which means
27 26
   // a deleted shape will still be pulled here before the page component
28 27
   // detects the change and pulls this component.
29
-  if (!shape) return null
28
+  if (!shape) {
29
+    return null
30
+  }
30 31
 
31 32
   const isGroup = shape.type === ShapeType.Group
32 33
 
@@ -50,9 +51,7 @@ function Shape({ id, isSelecting, parentPoint, parentRotation }: ShapeProps) {
50 51
           {...events}
51 52
         />
52 53
       )}
53
-      {!shape.isHidden && (
54
-        <StyledShape as="use" data-shy={isGroup} href={'#' + id} {...style} />
55
-      )}
54
+      {!shape.isHidden && <ReadShape isGroup={isGroup} id={id} style={style} />}
56 55
       {isGroup &&
57 56
         shape.children.map((shapeId) => (
58 57
           <Shape
@@ -60,13 +59,26 @@ function Shape({ id, isSelecting, parentPoint, parentRotation }: ShapeProps) {
60 59
             id={shapeId}
61 60
             isSelecting={isSelecting}
62 61
             parentPoint={shape.point}
63
-            parentRotation={shape.rotation}
64 62
           />
65 63
         ))}
66 64
     </StyledGroup>
67 65
   )
68 66
 }
69 67
 
68
+interface RealShapeProps {
69
+  isGroup: boolean
70
+  id: string
71
+  style: Partial<React.SVGProps<SVGUseElement>>
72
+}
73
+
74
+const ReadShape = memo(function RealShape({
75
+  isGroup,
76
+  id,
77
+  style,
78
+}: RealShapeProps) {
79
+  return <StyledShape as="use" data-shy={isGroup} href={'#' + id} {...style} />
80
+})
81
+
70 82
 const StyledShape = styled('path', {
71 83
   strokeLinecap: 'round',
72 84
   strokeLinejoin: 'round',

+ 1
- 1
lib/shape-utils/group.tsx Целия файл

@@ -28,7 +28,7 @@ const group = registerShapeUtils<GroupShape>({
28 28
       id: uuid(),
29 29
       type: ShapeType.Group,
30 30
       isGenerated: false,
31
-      name: 'Rectangle',
31
+      name: 'Group',
32 32
       parentId: 'page0',
33 33
       childIndex: 0,
34 34
       point: [0, 0],

+ 62
- 26
state/commands/group.ts Целия файл

@@ -27,9 +27,9 @@ export default function groupCommand(data: Data) {
27 27
 
28 28
   let newGroupParentId: string
29 29
   let newGroupShape: GroupShape
30
-  let oldGroupShape: GroupShape
30
+  let newGroupChildIndex: number
31 31
 
32
-  const selectedShapeIds = initialShapes.map((s) => s.id)
32
+  const initialShapeIds = initialShapes.map((s) => s.id)
33 33
 
34 34
   const parentIds = Array.from(
35 35
     new Set(initialShapes.map((s) => s.parentId)).values()
@@ -50,13 +50,11 @@ export default function groupCommand(data: Data) {
50 50
       const parent = getShape(data, parentId) as GroupShape
51 51
 
52 52
       if (parent.children.length === initialShapes.length) {
53
-        // !
54
-        // !
55
-        // !
56
-        // Hey! We're not going any further. We need to ungroup those shapes.
53
+        // !!! Hey! We're not going any further. We need to ungroup those shapes.
57 54
         commands.ungroup(data)
58 55
         return
59 56
       } else {
57
+        // Make the group inside of the current group
60 58
         newGroupParentId = parentId
61 59
       }
62 60
     }
@@ -77,7 +75,8 @@ export default function groupCommand(data: Data) {
77 75
     parentId: newGroupParentId,
78 76
     point: [commonBounds.minX, commonBounds.minY],
79 77
     size: [commonBounds.width, commonBounds.height],
80
-    children: selectedShapeIds,
78
+    children: initialShapeIds,
79
+    childIndex: initialShapes[0].childIndex,
81 80
   })
82 81
 
83 82
   history.execute(
@@ -85,43 +84,80 @@ export default function groupCommand(data: Data) {
85 84
     new Command({
86 85
       name: 'group_shapes',
87 86
       category: 'canvas',
87
+      manualSelection: true,
88 88
       do(data) {
89 89
         const { shapes } = getPage(data, currentPageId)
90 90
 
91
-        // Remove shapes from old parents
92
-        for (const parentId of parentIds) {
93
-          if (parentId === currentPageId) continue
91
+        // Create the new group
92
+        shapes[newGroupShape.id] = newGroupShape
94 93
 
95
-          const shape = shapes[parentId] as GroupShape
96
-          getShapeUtils(shape).setProperty(
97
-            shape,
98
-            'children',
99
-            shape.children.filter((id) => !selectedIds.has(id))
100
-          )
94
+        // Assign the group to its new parent
95
+        if (newGroupParentId !== data.currentPageId) {
96
+          const parent = shapes[newGroupParentId]
97
+          getShapeUtils(parent).setProperty(parent, 'children', [
98
+            ...parent.children,
99
+            newGroupShape.id,
100
+          ])
101 101
         }
102 102
 
103
-        shapes[newGroupShape.id] = newGroupShape
104
-        data.selectedIds.clear()
105
-        data.selectedIds.add(newGroupShape.id)
106
-        initialShapes.forEach(({ id }, i) => {
107
-          const shape = shapes[id]
103
+        // Assign the shapes to their new parent
104
+        initialShapes.forEach((initialShape, i) => {
105
+          // Remove shape from its old parent
106
+          if (initialShape.parentId !== currentPageId) {
107
+            const oldParent = shapes[initialShape.parentId] as GroupShape
108
+            getShapeUtils(oldParent).setProperty(
109
+              oldParent,
110
+              'children',
111
+              oldParent.children.filter((id) => !selectedIds.has(id))
112
+            )
113
+          }
114
+
115
+          // Assign the shape to its new parent, with its new childIndex
116
+          const shape = shapes[initialShape.id]
108 117
           getShapeUtils(shape)
109
-            .setProperty(shape, 'parentId', newGroupShape.id)
110 118
             .setProperty(shape, 'childIndex', i)
119
+            .setProperty(shape, 'parentId', newGroupShape.id)
111 120
         })
121
+
122
+        data.selectedIds.clear()
123
+        data.selectedIds.add(newGroupShape.id)
112 124
       },
113 125
       undo(data) {
114 126
         const { shapes } = getPage(data, currentPageId)
115
-        data.selectedIds.clear()
116 127
 
117
-        delete shapes[newGroupShape.id]
118
-        initialShapes.forEach(({ id, parentId, childIndex }, i) => {
119
-          data.selectedIds.add(id)
128
+        const group = shapes[newGroupShape.id]
129
+
130
+        // remove the group from its parent
131
+        if (group.parentId !== data.currentPageId) {
132
+          const parent = shapes[group.parentId]
133
+          getShapeUtils(parent).setProperty(
134
+            parent,
135
+            'children',
136
+            parent.children.filter((id) => id !== newGroupShape.id)
137
+          )
138
+        }
139
+
140
+        // Move the shapes back to their previous parent / childIndex
141
+        initialShapes.forEach(({ id, parentId, childIndex }) => {
120 142
           const shape = shapes[id]
121 143
           getShapeUtils(shape)
122 144
             .setProperty(shape, 'parentId', parentId)
123 145
             .setProperty(shape, 'childIndex', childIndex)
146
+
147
+          if (parentId !== data.currentPageId) {
148
+            const parent = shapes[parentId]
149
+            getShapeUtils(parent).setProperty(parent, 'children', [
150
+              ...parent.children,
151
+              id,
152
+            ])
153
+          }
124 154
         })
155
+
156
+        // Delete the group
157
+        delete shapes[newGroupShape.id]
158
+
159
+        // Reselect the children of the group
160
+        data.selectedIds = new Set(initialShapeIds)
125 161
       },
126 162
     })
127 163
   )

+ 5
- 13
state/commands/transform-single.ts Целия файл

@@ -33,26 +33,18 @@ export default function transformSingleCommand(
33 33
         updateParents(data, [id])
34 34
       },
35 35
       undo(data) {
36
-        const { id, type, initialShapeBounds } = before
36
+        const { id, initialShape } = before
37 37
 
38 38
         const { shapes } = getPage(data, before.currentPageId)
39 39
 
40
-        data.selectedIds.clear()
41
-
42 40
         if (isCreating) {
41
+          data.selectedIds.clear()
43 42
           delete shapes[id]
44 43
         } else {
45
-          const shape = shapes[id]
46
-          data.selectedIds.add(id)
47
-
48
-          getShapeUtils(shape).transform(shape, initialShapeBounds, {
49
-            type,
50
-            initialShape: after.initialShape,
51
-            scaleX: 1,
52
-            scaleY: 1,
53
-            transformOrigin: [0.5, 0.5],
54
-          })
44
+          const page = getPage(data)
45
+          page.shapes[id] = initialShape
55 46
           updateParents(data, [id])
47
+          data.selectedIds = new Set([id])
56 48
         }
57 49
       },
58 50
     })

+ 19
- 16
state/commands/transform.ts Целия файл

@@ -18,8 +18,6 @@ export default function transformCommand(
18 18
       name: 'translate_shapes',
19 19
       category: 'canvas',
20 20
       do(data, isInitial) {
21
-        if (isInitial) return
22
-
23 21
         const { type, shapeBounds } = after
24 22
 
25 23
         const { shapes } = getPage(data)
@@ -27,15 +25,18 @@ export default function transformCommand(
27 25
         for (let id in shapeBounds) {
28 26
           const { initialShape, initialShapeBounds, transformOrigin } =
29 27
             shapeBounds[id]
28
+
30 29
           const shape = shapes[id]
31 30
 
32
-          getShapeUtils(shape).transform(shape, initialShapeBounds, {
33
-            type,
34
-            initialShape,
35
-            transformOrigin,
36
-            scaleX,
37
-            scaleY,
38
-          })
31
+          getShapeUtils(shape)
32
+            .transform(shape, initialShapeBounds, {
33
+              type,
34
+              initialShape,
35
+              transformOrigin,
36
+              scaleX,
37
+              scaleY,
38
+            })
39
+            .onSessionComplete(shape)
39 40
         }
40 41
 
41 42
         updateParents(data, Object.keys(shapeBounds))
@@ -50,13 +51,15 @@ export default function transformCommand(
50 51
             shapeBounds[id]
51 52
           const shape = shapes[id]
52 53
 
53
-          getShapeUtils(shape).transform(shape, initialShapeBounds, {
54
-            type,
55
-            initialShape,
56
-            transformOrigin,
57
-            scaleX: scaleX < 0 ? scaleX * -1 : scaleX,
58
-            scaleY: scaleX < 0 ? scaleX * -1 : scaleX,
59
-          })
54
+          getShapeUtils(shape)
55
+            .transform(shape, initialShapeBounds, {
56
+              type,
57
+              initialShape,
58
+              transformOrigin,
59
+              scaleX: scaleX < 0 ? scaleX * -1 : scaleX,
60
+              scaleY: scaleX < 0 ? scaleX * -1 : scaleX,
61
+            })
62
+            .onSessionComplete(shape)
60 63
         }
61 64
 
62 65
         updateParents(data, Object.keys(shapeBounds))

+ 5
- 3
state/commands/translate.ts Целия файл

@@ -2,7 +2,7 @@ import Command from './command'
2 2
 import history from '../history'
3 3
 import { TranslateSnapshot } from 'state/sessions/translate-session'
4 4
 import { Data, GroupShape, Shape, ShapeType } from 'types'
5
-import { getPage, updateParents } from 'utils/utils'
5
+import { getDocumentBranch, getPage, updateParents } from 'utils/utils'
6 6
 import { getShapeUtils } from 'lib/shape-utils'
7 7
 import { v4 as uuid } from 'uuid'
8 8
 
@@ -43,8 +43,10 @@ export default function translateCommand(
43 43
 
44 44
         // Move shapes (these initialShapes will include clones if any)
45 45
         for (const { id, point } of initialShapes) {
46
-          const shape = shapes[id]
47
-          getShapeUtils(shape).translateTo(shape, point)
46
+          getDocumentBranch(data, id).forEach((id) => {
47
+            const shape = shapes[id]
48
+            getShapeUtils(shape).translateTo(shape, point)
49
+          })
48 50
         }
49 51
 
50 52
         // Set selected shapes

+ 3
- 11
state/sessions/transform-single-session.ts Целия файл

@@ -67,18 +67,10 @@ export default class TransformSingleSession extends BaseSession {
67 67
   }
68 68
 
69 69
   cancel(data: Data) {
70
-    const { id, initialShape, initialShapeBounds, currentPageId } =
71
-      this.snapshot
72
-
73
-    const shape = getShape(data, id, currentPageId)
70
+    const { id, initialShape } = this.snapshot
74 71
 
75
-    getShapeUtils(shape).transform(shape, initialShapeBounds, {
76
-      initialShape,
77
-      type: this.transformType,
78
-      scaleX: this.scaleX,
79
-      scaleY: this.scaleY,
80
-      transformOrigin: [0.5, 0.5],
81
-    })
72
+    const page = getPage(data)
73
+    page.shapes[id] = initialShape
82 74
 
83 75
     updateParents(data, [id])
84 76
   }

+ 22
- 22
state/sessions/translate-session.ts Целия файл

@@ -6,6 +6,7 @@ import { current } from 'immer'
6 6
 import { v4 as uuid } from 'uuid'
7 7
 import {
8 8
   getChildIndexAbove,
9
+  getDocumentBranch,
9 10
   getPage,
10 11
   getSelectedShapes,
11 12
   updateParents,
@@ -14,6 +15,7 @@ import { getShapeUtils } from 'lib/shape-utils'
14 15
 
15 16
 export default class TranslateSession extends BaseSession {
16 17
   delta = [0, 0]
18
+  prev = [0, 0]
17 19
   origin: number[]
18 20
   snapshot: TranslateSnapshot
19 21
   isCloning = false
@@ -30,6 +32,9 @@ export default class TranslateSession extends BaseSession {
30 32
     const { shapes } = getPage(data, currentPageId)
31 33
 
32 34
     const delta = vec.vec(this.origin, point)
35
+    const trueDelta = vec.sub(delta, this.prev)
36
+    this.delta = delta
37
+    this.prev = delta
33 38
 
34 39
     if (isAligned) {
35 40
       if (Math.abs(delta[0]) < Math.abs(delta[1])) {
@@ -92,17 +97,10 @@ export default class TranslateSession extends BaseSession {
92 97
       }
93 98
 
94 99
       for (const initialShape of initialShapes) {
95
-        const shape = shapes[initialShape.id]
96
-        const next = vec.add(initialShape.point, delta)
97
-        const deltaForShape = vec.sub(next, shape.point)
98
-        getShapeUtils(shape).translateTo(shape, next)
99
-
100
-        if (shape.type === ShapeType.Group) {
101
-          for (let childId of shape.children) {
102
-            const childShape = shapes[childId]
103
-            getShapeUtils(childShape).translateBy(childShape, deltaForShape)
104
-          }
105
-        }
100
+        getDocumentBranch(data, initialShape.id).forEach((id) => {
101
+          const shape = shapes[id]
102
+          getShapeUtils(shape).translateBy(shape, trueDelta)
103
+        })
106 104
       }
107 105
 
108 106
       updateParents(
@@ -117,17 +115,11 @@ export default class TranslateSession extends BaseSession {
117 115
       this.snapshot
118 116
     const { shapes } = getPage(data, currentPageId)
119 117
 
120
-    for (const { id, point } of initialShapes) {
121
-      const shape = shapes[id]
122
-      const deltaForShape = vec.sub(point, shape.point)
123
-      getShapeUtils(shape).translateTo(shape, point)
124
-
125
-      if (shape.type === ShapeType.Group) {
126
-        for (let childId of shape.children) {
127
-          const childShape = shapes[childId]
128
-          getShapeUtils(childShape).translateBy(childShape, deltaForShape)
129
-        }
130
-      }
118
+    for (const { id } of initialShapes) {
119
+      getDocumentBranch(data, id).forEach((id) => {
120
+        const shape = shapes[id]
121
+        getShapeUtils(shape).translateBy(shape, vec.neg(this.delta))
122
+      })
131 123
     }
132 124
 
133 125
     for (const { id } of clones) {
@@ -159,6 +151,14 @@ export default class TranslateSession extends BaseSession {
159 151
 
160 152
 export function getTranslateSnapshot(data: Data) {
161 153
   const cData = current(data)
154
+
155
+  // Get selected shapes
156
+  // Filter out the locked shapes
157
+  // Collect the branch children for each remaining shape
158
+  // Filter out doubles using a set
159
+  // End up with an array of ids for all of the shapes that will change
160
+  // Map into shapes from data snapshot
161
+
162 162
   const page = getPage(cData)
163 163
   const selectedShapes = getSelectedShapes(cData).filter(
164 164
     (shape) => !shape.isLocked

Loading…
Отказ
Запис