|
|
@@ -3,100 +3,108 @@ import type { Data, TLDrawCommand } from '~types'
|
|
3
|
3
|
import { TLDR } from '~state/tldr'
|
|
4
|
4
|
import type { Patch } from 'rko'
|
|
5
|
5
|
|
|
6
|
|
-export function ungroup(data: Data, groupId: string, pageId: string): TLDrawCommand | undefined {
|
|
|
6
|
+export function ungroup(
|
|
|
7
|
+ data: Data,
|
|
|
8
|
+ selectedIds: string[],
|
|
|
9
|
+ groupShapes: GroupShape[],
|
|
|
10
|
+ pageId: string
|
|
|
11
|
+): TLDrawCommand | undefined {
|
|
7
|
12
|
const beforeShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
|
8
|
13
|
const afterShapes: Record<string, Patch<TLDrawShape | undefined>> = {}
|
|
9
|
14
|
|
|
10
|
15
|
const beforeBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
|
11
|
16
|
const afterBindings: Record<string, Patch<TLDrawBinding | undefined>> = {}
|
|
12
|
17
|
|
|
13
|
|
- // The group shape
|
|
14
|
|
- const groupShape = TLDR.getShape<GroupShape>(data, groupId, pageId)
|
|
15
|
|
-
|
|
16
|
|
- const idsToUngroup = groupShape.children
|
|
17
|
|
- const shapesToUngroup: TLDrawShape[] = []
|
|
18
|
|
- const deletedGroupIds: string[] = []
|
|
19
|
|
-
|
|
20
|
|
- // Collect all of the shapes to group (and their ids)
|
|
21
|
|
- for (const id of idsToUngroup) {
|
|
22
|
|
- const shape = TLDR.getShape(data, id, pageId)
|
|
23
|
|
- shapesToUngroup.push(shape)
|
|
24
|
|
- }
|
|
|
18
|
+ const beforeSelectedIds = selectedIds
|
|
|
19
|
+ const afterSelectedIds = selectedIds.filter((id) => !groupShapes.find((shape) => shape.id === id))
|
|
25
|
20
|
|
|
26
|
|
- // We'll start placing the shapes at this childIndex
|
|
27
|
|
- const startingChildIndex = groupShape.childIndex
|
|
|
21
|
+ // The group shape
|
|
|
22
|
+ groupShapes.forEach((groupShape) => {
|
|
|
23
|
+ const shapesToReparent: TLDrawShape[] = []
|
|
|
24
|
+ const deletedGroupIds: string[] = []
|
|
|
25
|
+
|
|
|
26
|
+ // Remove the group shape in the next state
|
|
|
27
|
+ beforeShapes[groupShape.id] = groupShape
|
|
|
28
|
+ afterShapes[groupShape.id] = undefined
|
|
|
29
|
+
|
|
|
30
|
+ // Select its children in the next state
|
|
|
31
|
+ groupShape.children.forEach((id) => {
|
|
|
32
|
+ afterSelectedIds.push(id)
|
|
|
33
|
+ const shape = TLDR.getShape(data, id, pageId)
|
|
|
34
|
+ shapesToReparent.push(shape)
|
|
|
35
|
+ })
|
|
28
|
36
|
|
|
29
|
|
- // And we'll need to fit them under this child index
|
|
30
|
|
- const endingChildIndex = TLDR.getChildIndexAbove(data, groupShape.id, pageId)
|
|
|
37
|
+ // We'll start placing the shapes at this childIndex
|
|
|
38
|
+ const startingChildIndex = groupShape.childIndex
|
|
31
|
39
|
|
|
32
|
|
- const step = (endingChildIndex - startingChildIndex) / shapesToUngroup.length
|
|
|
40
|
+ // And we'll need to fit them under this child index
|
|
|
41
|
+ const endingChildIndex = TLDR.getChildIndexAbove(data, groupShape.id, pageId)
|
|
33
|
42
|
|
|
34
|
|
- // An array of shapes in order by their child index
|
|
35
|
|
- const sortedShapes = shapesToUngroup.sort((a, b) => a.childIndex - b.childIndex)
|
|
|
43
|
+ const step = (endingChildIndex - startingChildIndex) / shapesToReparent.length
|
|
36
|
44
|
|
|
37
|
|
- // Remove the group shape
|
|
38
|
|
- beforeShapes[groupId] = groupShape
|
|
39
|
|
- afterShapes[groupId] = undefined
|
|
|
45
|
+ // An array of shapes in order by their child index
|
|
|
46
|
+ const sortedShapes = shapesToReparent.sort((a, b) => a.childIndex - b.childIndex)
|
|
40
|
47
|
|
|
41
|
|
- // Reparent shapes to the page
|
|
42
|
|
- sortedShapes.forEach((shape, index) => {
|
|
43
|
|
- beforeShapes[shape.id] = {
|
|
44
|
|
- parentId: shape.parentId,
|
|
45
|
|
- childIndex: shape.childIndex,
|
|
46
|
|
- }
|
|
|
48
|
+ // Reparent shapes to the page
|
|
|
49
|
+ sortedShapes.forEach((shape, index) => {
|
|
|
50
|
+ beforeShapes[shape.id] = {
|
|
|
51
|
+ parentId: shape.parentId,
|
|
|
52
|
+ childIndex: shape.childIndex,
|
|
|
53
|
+ }
|
|
47
|
54
|
|
|
48
|
|
- afterShapes[shape.id] = {
|
|
49
|
|
- parentId: pageId,
|
|
50
|
|
- childIndex: startingChildIndex + step * index,
|
|
51
|
|
- }
|
|
52
|
|
- })
|
|
|
55
|
+ afterShapes[shape.id] = {
|
|
|
56
|
+ parentId: pageId,
|
|
|
57
|
+ childIndex: startingChildIndex + step * index,
|
|
|
58
|
+ }
|
|
|
59
|
+ })
|
|
53
|
60
|
|
|
54
|
|
- const page = TLDR.getPage(data, pageId)
|
|
55
|
|
-
|
|
56
|
|
- // We also need to delete bindings that reference the deleted shapes
|
|
57
|
|
- Object.values(page.bindings)
|
|
58
|
|
- .filter((binding) => binding.toId === groupId || binding.fromId === groupId)
|
|
59
|
|
- .forEach((binding) => {
|
|
60
|
|
- for (const id of [binding.toId, binding.fromId]) {
|
|
61
|
|
- // If the binding references the deleted group...
|
|
62
|
|
- if (afterShapes[id] === undefined) {
|
|
63
|
|
- // Delete the binding
|
|
64
|
|
- beforeBindings[binding.id] = binding
|
|
65
|
|
- afterBindings[binding.id] = undefined
|
|
66
|
|
-
|
|
67
|
|
- // Let's also look each the bound shape...
|
|
68
|
|
- const shape = TLDR.getShape(data, id, pageId)
|
|
69
|
|
-
|
|
70
|
|
- // If the bound shape has a handle that references the deleted binding...
|
|
71
|
|
- if (shape.handles) {
|
|
72
|
|
- Object.values(shape.handles)
|
|
73
|
|
- .filter((handle) => handle.bindingId === binding.id)
|
|
74
|
|
- .forEach((handle) => {
|
|
75
|
|
- // Save the binding reference in the before patch
|
|
76
|
|
- beforeShapes[id] = {
|
|
77
|
|
- ...beforeShapes[id],
|
|
78
|
|
- handles: {
|
|
79
|
|
- ...beforeShapes[id]?.handles,
|
|
80
|
|
- [handle.id]: { bindingId: binding.id },
|
|
81
|
|
- },
|
|
82
|
|
- }
|
|
83
|
|
-
|
|
84
|
|
- // Unless we're currently deleting the shape, remove the
|
|
85
|
|
- // binding reference from the after patch
|
|
86
|
|
- if (!deletedGroupIds.includes(id)) {
|
|
87
|
|
- afterShapes[id] = {
|
|
88
|
|
- ...afterShapes[id],
|
|
|
61
|
+ const page = TLDR.getPage(data, pageId)
|
|
|
62
|
+
|
|
|
63
|
+ // We also need to delete bindings that reference the deleted shapes
|
|
|
64
|
+ Object.values(page.bindings)
|
|
|
65
|
+ .filter((binding) => binding.toId === groupShape.id || binding.fromId === groupShape.id)
|
|
|
66
|
+ .forEach((binding) => {
|
|
|
67
|
+ for (const id of [binding.toId, binding.fromId]) {
|
|
|
68
|
+ // If the binding references the deleted group...
|
|
|
69
|
+ if (afterShapes[id] === undefined) {
|
|
|
70
|
+ // Delete the binding
|
|
|
71
|
+ beforeBindings[binding.id] = binding
|
|
|
72
|
+ afterBindings[binding.id] = undefined
|
|
|
73
|
+
|
|
|
74
|
+ // Let's also look each the bound shape...
|
|
|
75
|
+ const shape = TLDR.getShape(data, id, pageId)
|
|
|
76
|
+
|
|
|
77
|
+ // If the bound shape has a handle that references the deleted binding...
|
|
|
78
|
+ if (shape.handles) {
|
|
|
79
|
+ Object.values(shape.handles)
|
|
|
80
|
+ .filter((handle) => handle.bindingId === binding.id)
|
|
|
81
|
+ .forEach((handle) => {
|
|
|
82
|
+ // Save the binding reference in the before patch
|
|
|
83
|
+ beforeShapes[id] = {
|
|
|
84
|
+ ...beforeShapes[id],
|
|
89
|
85
|
handles: {
|
|
90
|
|
- ...afterShapes[id]?.handles,
|
|
91
|
|
- [handle.id]: { bindingId: undefined },
|
|
|
86
|
+ ...beforeShapes[id]?.handles,
|
|
|
87
|
+ [handle.id]: { bindingId: binding.id },
|
|
92
|
88
|
},
|
|
93
|
89
|
}
|
|
94
|
|
- }
|
|
95
|
|
- })
|
|
|
90
|
+
|
|
|
91
|
+ // Unless we're currently deleting the shape, remove the
|
|
|
92
|
+ // binding reference from the after patch
|
|
|
93
|
+ if (!deletedGroupIds.includes(id)) {
|
|
|
94
|
+ afterShapes[id] = {
|
|
|
95
|
+ ...afterShapes[id],
|
|
|
96
|
+ handles: {
|
|
|
97
|
+ ...afterShapes[id]?.handles,
|
|
|
98
|
+ [handle.id]: { bindingId: undefined },
|
|
|
99
|
+ },
|
|
|
100
|
+ }
|
|
|
101
|
+ }
|
|
|
102
|
+ })
|
|
|
103
|
+ }
|
|
96
|
104
|
}
|
|
97
|
105
|
}
|
|
98
|
|
- }
|
|
99
|
|
- })
|
|
|
106
|
+ })
|
|
|
107
|
+ })
|
|
100
|
108
|
|
|
101
|
109
|
return {
|
|
102
|
110
|
id: 'ungroup',
|
|
|
@@ -110,7 +118,7 @@ export function ungroup(data: Data, groupId: string, pageId: string): TLDrawComm
|
|
110
|
118
|
},
|
|
111
|
119
|
pageStates: {
|
|
112
|
120
|
[pageId]: {
|
|
113
|
|
- selectedIds: [groupId],
|
|
|
121
|
+ selectedIds: beforeSelectedIds,
|
|
114
|
122
|
},
|
|
115
|
123
|
},
|
|
116
|
124
|
},
|
|
|
@@ -125,7 +133,7 @@ export function ungroup(data: Data, groupId: string, pageId: string): TLDrawComm
|
|
125
|
133
|
},
|
|
126
|
134
|
pageStates: {
|
|
127
|
135
|
[pageId]: {
|
|
128
|
|
- selectedIds: idsToUngroup,
|
|
|
136
|
+ selectedIds: afterSelectedIds,
|
|
129
|
137
|
},
|
|
130
|
138
|
},
|
|
131
|
139
|
},
|