|
|
@@ -19,116 +19,127 @@ import type { Patch } from 'rko'
|
|
19
|
19
|
export class GridSession implements Session {
|
|
20
|
20
|
type = SessionType.Grid
|
|
21
|
21
|
status = TLDrawStatus.Translating
|
|
22
|
|
- delta = [0, 0]
|
|
23
|
|
- prev = [0, 0]
|
|
24
|
22
|
origin: number[]
|
|
25
|
23
|
shape: TLDrawShape
|
|
26
|
|
- isCloning = false
|
|
27
|
|
- clones: TLDrawShape[] = []
|
|
28
|
24
|
bounds: TLBounds
|
|
29
|
25
|
initialSelectedIds: string[]
|
|
30
|
|
- grid: string[][]
|
|
|
26
|
+ initialSiblings?: string[]
|
|
|
27
|
+ grid: Record<string, string> = {}
|
|
31
|
28
|
columns = 1
|
|
32
|
29
|
rows = 1
|
|
|
30
|
+ isCopying = false
|
|
33
|
31
|
|
|
34
|
32
|
constructor(data: Data, id: string, pageId: string, point: number[]) {
|
|
35
|
33
|
this.origin = point
|
|
36
|
34
|
this.shape = TLDR.getShape(data, id, pageId)
|
|
37
|
|
- this.grid = [[this.shape.id]]
|
|
|
35
|
+ this.grid['0_0'] = this.shape.id
|
|
38
|
36
|
this.bounds = TLDR.getBounds(this.shape)
|
|
39
|
37
|
this.initialSelectedIds = TLDR.getSelectedIds(data, pageId)
|
|
|
38
|
+ if (this.shape.parentId !== pageId) {
|
|
|
39
|
+ this.initialSiblings = TLDR.getShape(data, this.shape.parentId, pageId).children?.filter(
|
|
|
40
|
+ (id) => id !== this.shape.id
|
|
|
41
|
+ )
|
|
|
42
|
+ }
|
|
40
|
43
|
}
|
|
41
|
44
|
|
|
42
|
45
|
start = () => void null
|
|
43
|
46
|
|
|
44
|
|
- getClone = (point: number[]) => {
|
|
|
47
|
+ getClone = (point: number[], copy: boolean) => {
|
|
45
|
48
|
const clone = {
|
|
46
|
49
|
...this.shape,
|
|
47
|
50
|
id: Utils.uniqueId(),
|
|
48
|
51
|
point,
|
|
49
|
52
|
}
|
|
50
|
|
- if (clone.type === TLDrawShapeType.Sticky) {
|
|
51
|
|
- clone.text = ''
|
|
|
53
|
+
|
|
|
54
|
+ if (!copy) {
|
|
|
55
|
+ if (clone.type === TLDrawShapeType.Sticky) {
|
|
|
56
|
+ clone.text = ''
|
|
|
57
|
+ }
|
|
52
|
58
|
}
|
|
|
59
|
+
|
|
53
|
60
|
return clone
|
|
54
|
61
|
}
|
|
55
|
62
|
|
|
56
|
|
- update = (data: Data, point: number[], shiftKey: boolean, altKey: boolean) => {
|
|
57
|
|
- const { currentPageId } = data.appState
|
|
58
|
|
-
|
|
|
63
|
+ update = (data: Data, point: number[], shiftKey: boolean, altKey: boolean, metaKey: boolean) => {
|
|
59
|
64
|
const nextShapes: Patch<Record<string, TLDrawShape>> = {}
|
|
60
|
65
|
|
|
61
|
66
|
const nextPageState: Patch<TLPageState> = {}
|
|
62
|
67
|
|
|
63
|
|
- const delta = Vec.sub(point, this.origin)
|
|
|
68
|
+ const center = Utils.getBoundsCenter(this.bounds)
|
|
|
69
|
+
|
|
|
70
|
+ const offset = Vec.sub(point, center)
|
|
64
|
71
|
|
|
65
|
72
|
if (shiftKey) {
|
|
66
|
|
- if (Math.abs(delta[0]) < Math.abs(delta[1])) {
|
|
67
|
|
- delta[0] = 0
|
|
|
73
|
+ if (Math.abs(offset[0]) < Math.abs(offset[1])) {
|
|
|
74
|
+ offset[0] = 0
|
|
68
|
75
|
} else {
|
|
69
|
|
- delta[1] = 0
|
|
|
76
|
+ offset[1] = 0
|
|
70
|
77
|
}
|
|
71
|
78
|
}
|
|
|
79
|
+ // use the distance from center to determine the grid
|
|
72
|
80
|
|
|
73
|
|
- this.delta = delta
|
|
74
|
|
-
|
|
75
|
|
- this.prev = delta
|
|
76
|
|
-
|
|
77
|
|
- const startX = this.shape.point[0]
|
|
78
|
|
- const startY = this.shape.point[1]
|
|
79
|
81
|
const gapX = this.bounds.width + 32
|
|
80
|
82
|
const gapY = this.bounds.height + 32
|
|
81
|
83
|
|
|
82
|
|
- const columns = Math.max(
|
|
83
|
|
- 1,
|
|
84
|
|
- Math.floor(Math.abs(this.delta[0] + this.bounds.width / 2) / gapX + 1)
|
|
85
|
|
- )
|
|
86
|
|
-
|
|
87
|
|
- const rows = Math.max(
|
|
88
|
|
- 1,
|
|
89
|
|
- Math.floor(Math.abs(this.delta[1] + this.bounds.height / 2) / gapY + 1)
|
|
90
|
|
- )
|
|
91
|
|
-
|
|
92
|
|
- console.log(rows, columns)
|
|
93
|
|
-
|
|
94
|
|
- // if (columns > this.columns) {
|
|
95
|
|
- // for (let x = this.columns; x < columns; x++) {
|
|
96
|
|
- // this.grid.forEach((row, y) => {
|
|
97
|
|
- // const clone = this.getClone([startX + x * gapX, startY + y * gapY])
|
|
98
|
|
- // row.push(clone.id)
|
|
99
|
|
- // nextShapes[clone.id] = clone
|
|
100
|
|
- // })
|
|
101
|
|
- // }
|
|
102
|
|
- // } else if (columns < this.columns) {
|
|
103
|
|
- // this.grid.forEach((row) => {
|
|
104
|
|
- // for (let x = this.columns; x > columns; x--) {
|
|
105
|
|
- // const id = row.pop()
|
|
106
|
|
- // if (id) nextShapes[id] = undefined
|
|
107
|
|
- // }
|
|
108
|
|
- // })
|
|
109
|
|
- // }
|
|
110
|
|
-
|
|
111
|
|
- // this.columns = columns
|
|
112
|
|
-
|
|
113
|
|
- // if (rows > this.rows) {
|
|
114
|
|
- // for (let y = this.rows; y < rows; y++) {
|
|
115
|
|
- // const row: string[] = []
|
|
116
|
|
- // for (let x = 0; x < this.columns; x++) {
|
|
117
|
|
- // const clone = this.getClone([startX + x * gapX, startY + y * gapY])
|
|
118
|
|
- // row.push(clone.id)
|
|
119
|
|
- // nextShapes[clone.id] = clone
|
|
120
|
|
- // }
|
|
121
|
|
- // this.grid.push(row)
|
|
122
|
|
- // }
|
|
123
|
|
- // } else if (rows < this.rows) {
|
|
124
|
|
- // for (let y = this.rows; y > rows; y--) {
|
|
125
|
|
- // const row = this.grid[y - 1]
|
|
126
|
|
- // row.forEach((id) => (nextShapes[id] = undefined))
|
|
127
|
|
- // this.grid.pop()
|
|
128
|
|
- // }
|
|
129
|
|
- // }
|
|
130
|
|
-
|
|
131
|
|
- // this.rows = rows
|
|
|
84
|
+ const columns = Math.ceil(offset[0] / gapX)
|
|
|
85
|
+ const rows = Math.ceil(offset[1] / gapY)
|
|
|
86
|
+
|
|
|
87
|
+ const minX = Math.min(columns, 0)
|
|
|
88
|
+ const minY = Math.min(rows, 0)
|
|
|
89
|
+ const maxX = Math.max(columns, 1)
|
|
|
90
|
+ const maxY = Math.max(rows, 1)
|
|
|
91
|
+
|
|
|
92
|
+ const inGrid = new Set<string>()
|
|
|
93
|
+
|
|
|
94
|
+ const isCopying = altKey
|
|
|
95
|
+
|
|
|
96
|
+ if (isCopying !== this.isCopying) {
|
|
|
97
|
+ // Recreate shapes copying
|
|
|
98
|
+ Object.values(this.grid)
|
|
|
99
|
+ .filter((id) => id !== this.shape.id)
|
|
|
100
|
+ .forEach((id) => (nextShapes[id] = undefined))
|
|
|
101
|
+
|
|
|
102
|
+ this.grid = { '0_0': this.shape.id }
|
|
|
103
|
+
|
|
|
104
|
+ this.isCopying = isCopying
|
|
|
105
|
+ }
|
|
|
106
|
+
|
|
|
107
|
+ // Go through grid, adding items in positions
|
|
|
108
|
+ // that aren't already filled.
|
|
|
109
|
+ for (let x = minX; x < maxX; x++) {
|
|
|
110
|
+ for (let y = minY; y < maxY; y++) {
|
|
|
111
|
+ const position = `${x}_${y}`
|
|
|
112
|
+
|
|
|
113
|
+ inGrid.add(position)
|
|
|
114
|
+
|
|
|
115
|
+ if (this.grid[position]) continue
|
|
|
116
|
+
|
|
|
117
|
+ if (x === 0 && y === 0) continue
|
|
|
118
|
+
|
|
|
119
|
+ const clone = this.getClone(Vec.add(this.shape.point, [x * gapX, y * gapY]), isCopying)
|
|
|
120
|
+
|
|
|
121
|
+ nextShapes[clone.id] = clone
|
|
|
122
|
+
|
|
|
123
|
+ this.grid[position] = clone.id
|
|
|
124
|
+ }
|
|
|
125
|
+ }
|
|
|
126
|
+
|
|
|
127
|
+ // Remove any other items from the grid
|
|
|
128
|
+ Object.entries(this.grid).forEach(([position, id]) => {
|
|
|
129
|
+ if (!inGrid.has(position)) {
|
|
|
130
|
+ nextShapes[id] = undefined
|
|
|
131
|
+ delete this.grid[position]
|
|
|
132
|
+ }
|
|
|
133
|
+ })
|
|
|
134
|
+
|
|
|
135
|
+ if (Object.values(nextShapes).length === 0) return
|
|
|
136
|
+
|
|
|
137
|
+ // Add shapes to parent id
|
|
|
138
|
+ if (this.initialSiblings) {
|
|
|
139
|
+ nextShapes[this.shape.parentId] = {
|
|
|
140
|
+ children: [...this.initialSiblings, ...Object.values(this.grid)],
|
|
|
141
|
+ }
|
|
|
142
|
+ }
|
|
132
|
143
|
|
|
133
|
144
|
return {
|
|
134
|
145
|
document: {
|
|
|
@@ -145,39 +156,40 @@ export class GridSession implements Session {
|
|
145
|
156
|
}
|
|
146
|
157
|
|
|
147
|
158
|
cancel = (data: Data) => {
|
|
148
|
|
- const nextBindings: Record<string, Partial<TLDrawBinding> | undefined> = {}
|
|
149
|
159
|
const nextShapes: Record<string, Partial<TLDrawShape> | undefined> = {}
|
|
150
|
|
- const nextPageState: Partial<TLPageState> = {}
|
|
151
|
|
-
|
|
152
|
|
- // Put initial shapes back to where they started
|
|
153
|
|
- nextShapes[this.shape.id] = { ...nextShapes[this.shape.id], point: this.shape.point }
|
|
154
|
160
|
|
|
155
|
161
|
// Delete clones
|
|
156
|
|
- this.grid.forEach((row) =>
|
|
157
|
|
- row.forEach((id) => {
|
|
158
|
|
- nextShapes[id] = undefined
|
|
159
|
|
- // TODO: Remove shape from parent if grouped
|
|
160
|
|
- })
|
|
161
|
|
- )
|
|
|
162
|
+ Object.values(this.grid).forEach((id) => {
|
|
|
163
|
+ nextShapes[id] = undefined
|
|
|
164
|
+ // TODO: Remove from parent if grouped
|
|
|
165
|
+ })
|
|
162
|
166
|
|
|
163
|
|
- nextPageState.selectedIds = [this.shape.id]
|
|
|
167
|
+ // Put back the initial shape
|
|
|
168
|
+ nextShapes[this.shape.id] = { ...nextShapes[this.shape.id], point: this.shape.point }
|
|
|
169
|
+
|
|
|
170
|
+ if (this.initialSiblings) {
|
|
|
171
|
+ nextShapes[this.shape.parentId] = {
|
|
|
172
|
+ children: [...this.initialSiblings, this.shape.id],
|
|
|
173
|
+ }
|
|
|
174
|
+ }
|
|
164
|
175
|
|
|
165
|
176
|
return {
|
|
166
|
177
|
document: {
|
|
167
|
178
|
pages: {
|
|
168
|
179
|
[data.appState.currentPageId]: {
|
|
169
|
180
|
shapes: nextShapes,
|
|
170
|
|
- bindings: nextBindings,
|
|
171
|
181
|
},
|
|
172
|
182
|
},
|
|
173
|
183
|
pageStates: {
|
|
174
|
|
- [data.appState.currentPageId]: nextPageState,
|
|
|
184
|
+ [data.appState.currentPageId]: {
|
|
|
185
|
+ selectedIds: [this.shape.id],
|
|
|
186
|
+ },
|
|
175
|
187
|
},
|
|
176
|
188
|
},
|
|
177
|
189
|
}
|
|
178
|
190
|
}
|
|
179
|
191
|
|
|
180
|
|
- complete(data: Data): TLDrawCommand {
|
|
|
192
|
+ complete(data: Data) {
|
|
181
|
193
|
const pageId = data.appState.currentPageId
|
|
182
|
194
|
|
|
183
|
195
|
const beforeShapes: Patch<Record<string, TLDrawShape>> = {}
|
|
|
@@ -186,17 +198,28 @@ export class GridSession implements Session {
|
|
186
|
198
|
|
|
187
|
199
|
const afterSelectedIds: string[] = []
|
|
188
|
200
|
|
|
189
|
|
- this.grid.forEach((row) =>
|
|
190
|
|
- row.forEach((id) => {
|
|
191
|
|
- beforeShapes[id] = undefined
|
|
192
|
|
- afterShapes[id] = TLDR.getShape(data, id, pageId)
|
|
193
|
|
- afterSelectedIds.push(id)
|
|
194
|
|
- // TODO: Add shape to parent if grouped
|
|
195
|
|
- })
|
|
196
|
|
- )
|
|
|
201
|
+ Object.values(this.grid).forEach((id) => {
|
|
|
202
|
+ beforeShapes[id] = undefined
|
|
|
203
|
+ afterShapes[id] = TLDR.getShape(data, id, pageId)
|
|
|
204
|
+ afterSelectedIds.push(id)
|
|
|
205
|
+ // TODO: Add shape to parent if grouped
|
|
|
206
|
+ })
|
|
197
|
207
|
|
|
198
|
208
|
beforeShapes[this.shape.id] = this.shape
|
|
199
|
|
- afterShapes[this.shape.id] = this.shape
|
|
|
209
|
+
|
|
|
210
|
+ // Add shapes to parent id
|
|
|
211
|
+ if (this.initialSiblings) {
|
|
|
212
|
+ beforeShapes[this.shape.parentId] = {
|
|
|
213
|
+ children: [...this.initialSiblings, this.shape.id],
|
|
|
214
|
+ }
|
|
|
215
|
+
|
|
|
216
|
+ afterShapes[this.shape.parentId] = {
|
|
|
217
|
+ children: [...this.initialSiblings, ...Object.values(this.grid)],
|
|
|
218
|
+ }
|
|
|
219
|
+ }
|
|
|
220
|
+
|
|
|
221
|
+ // If no new shapes have been created, bail
|
|
|
222
|
+ if (afterSelectedIds.length === 1) return
|
|
200
|
223
|
|
|
201
|
224
|
return {
|
|
202
|
225
|
id: 'grid',
|