tldraw/state/commands/group.ts

168 lines
4.8 KiB
TypeScript
Raw Normal View History

2021-06-04 16:08:43 +00:00
import Command from './command'
import history from '../history'
2021-06-21 21:35:28 +00:00
import { Data, GroupShape, ShapeType } from 'types'
2021-06-04 16:08:43 +00:00
import {
getCommonBounds,
getPage,
getSelectedIds,
2021-06-04 16:08:43 +00:00
getSelectedShapes,
getShape,
setSelectedIds,
2021-06-24 08:18:14 +00:00
} from 'utils'
2021-06-04 16:08:43 +00:00
import { current } from 'immer'
2021-06-21 21:35:28 +00:00
import { createShape, getShapeUtils } from 'state/shape-utils'
2021-06-04 16:08:43 +00:00
import commands from '.'
2021-06-21 21:35:28 +00:00
export default function groupCommand(data: Data): void {
2021-06-04 16:08:43 +00:00
const cData = current(data)
const { currentPageId } = cData
const oldSelectedIds = getSelectedIds(cData)
2021-06-04 16:08:43 +00:00
const initialShapes = getSelectedShapes(cData).sort(
(a, b) => a.childIndex - b.childIndex
)
const isAllSameParent = initialShapes.every(
(shape, i) => i === 0 || shape.parentId === initialShapes[i - 1].parentId
)
let newGroupParentId: string
2021-06-05 19:36:46 +00:00
const initialShapeIds = initialShapes.map((s) => s.id)
2021-06-04 16:08:43 +00:00
const commonBounds = getCommonBounds(
...initialShapes.map((shape) =>
getShapeUtils(shape).getRotatedBounds(shape)
)
)
if (isAllSameParent) {
const parentId = initialShapes[0].parentId
if (parentId === currentPageId) {
newGroupParentId = currentPageId
} else {
// Are all of the parent's children selected?
const parent = getShape(data, parentId) as GroupShape
if (parent.children.length === initialShapes.length) {
2021-06-05 19:36:46 +00:00
// !!! Hey! We're not going any further. We need to ungroup those shapes.
2021-06-04 16:08:43 +00:00
commands.ungroup(data)
return
} else {
2021-06-05 19:36:46 +00:00
// Make the group inside of the current group
2021-06-04 16:08:43 +00:00
newGroupParentId = parentId
}
}
} else {
// Find the least-deep parent among the shapes and add the group as a child
let minDepth = Infinity
2021-06-21 21:35:28 +00:00
for (const parentId of initialShapes.map((shape) => shape.parentId)) {
2021-06-04 16:08:43 +00:00
const depth = getShapeDepth(data, parentId)
if (depth < minDepth) {
minDepth = depth
newGroupParentId = parentId
}
}
}
2021-06-21 21:35:28 +00:00
const newGroupShape = createShape(ShapeType.Group, {
2021-06-04 16:08:43 +00:00
parentId: newGroupParentId,
point: [commonBounds.minX, commonBounds.minY],
size: [commonBounds.width, commonBounds.height],
2021-06-05 19:36:46 +00:00
children: initialShapeIds,
childIndex: initialShapes[0].childIndex,
2021-06-04 16:08:43 +00:00
})
history.execute(
data,
new Command({
name: 'group_shapes',
category: 'canvas',
2021-06-05 19:36:46 +00:00
manualSelection: true,
2021-06-04 16:08:43 +00:00
do(data) {
2021-06-24 12:34:43 +00:00
const { shapes } = getPage(data)
2021-06-04 16:08:43 +00:00
2021-06-05 19:36:46 +00:00
// Create the new group
shapes[newGroupShape.id] = newGroupShape
2021-06-04 16:08:43 +00:00
2021-06-05 19:36:46 +00:00
// Assign the group to its new parent
if (newGroupParentId !== data.currentPageId) {
const parent = shapes[newGroupParentId]
getShapeUtils(parent).setProperty(parent, 'children', [
...parent.children,
newGroupShape.id,
])
2021-06-04 16:08:43 +00:00
}
2021-06-05 19:36:46 +00:00
// Assign the shapes to their new parent
initialShapes.forEach((initialShape, i) => {
// Remove shape from its old parent
if (initialShape.parentId !== currentPageId) {
const oldParent = shapes[initialShape.parentId] as GroupShape
getShapeUtils(oldParent).setProperty(
oldParent,
'children',
oldParent.children.filter((id) => !oldSelectedIds.has(id))
2021-06-05 19:36:46 +00:00
)
}
// Assign the shape to its new parent, with its new childIndex
const shape = shapes[initialShape.id]
2021-06-04 16:08:43 +00:00
getShapeUtils(shape)
.setProperty(shape, 'childIndex', i)
2021-06-05 19:36:46 +00:00
.setProperty(shape, 'parentId', newGroupShape.id)
2021-06-04 16:08:43 +00:00
})
2021-06-05 19:36:46 +00:00
setSelectedIds(data, [newGroupShape.id])
2021-06-04 16:08:43 +00:00
},
undo(data) {
2021-06-24 12:34:43 +00:00
const { shapes } = getPage(data)
2021-06-04 16:08:43 +00:00
2021-06-05 19:36:46 +00:00
const group = shapes[newGroupShape.id]
// remove the group from its parent
if (group.parentId !== data.currentPageId) {
const parent = shapes[group.parentId]
getShapeUtils(parent).setProperty(
parent,
'children',
parent.children.filter((id) => id !== newGroupShape.id)
)
}
// Move the shapes back to their previous parent / childIndex
initialShapes.forEach(({ id, parentId, childIndex }) => {
2021-06-04 16:08:43 +00:00
const shape = shapes[id]
getShapeUtils(shape)
.setProperty(shape, 'parentId', parentId)
.setProperty(shape, 'childIndex', childIndex)
2021-06-05 19:36:46 +00:00
if (parentId !== data.currentPageId) {
const parent = shapes[parentId]
getShapeUtils(parent).setProperty(parent, 'children', [
...parent.children,
id,
])
}
2021-06-04 16:08:43 +00:00
})
2021-06-05 19:36:46 +00:00
// Delete the group
delete shapes[newGroupShape.id]
// Reselect the children of the group
setSelectedIds(data, initialShapeIds)
2021-06-04 16:08:43 +00:00
},
})
)
}
function getShapeDepth(data: Data, id: string, depth = 0) {
if (id === data.currentPageId) {
return depth
}
return getShapeDepth(data, getShape(data, id).parentId, depth + 1)
}