tldraw/state/commands/group.ts
2021-07-09 09:59:43 +01:00

163 lines
4.9 KiB
TypeScript

import Command from './command'
import history from '../history'
import { Data, GroupShape, ShapeType } from 'types'
import { deepClone, getCommonBounds, uniqueId } from 'utils'
import tld from 'utils/tld'
import { createShape, getShapeUtils } from 'state/shape-utils'
import commands from '.'
export default function groupCommand(data: Data): void {
const { currentPageId } = data
const oldSelectedIds = tld.getSelectedIds(data)
const initialShapes = tld
.getSelectedShapes(data)
.sort((a, b) => a.childIndex - b.childIndex)
.map((shape) => deepClone(shape))
const isAllSameParent = initialShapes.every(
(shape, i) => i === 0 || shape.parentId === initialShapes[i - 1].parentId
)
// Do we need to ungroup the selected shapes shapes, rather than group them?
if (isAllSameParent && initialShapes[0]?.parentId !== currentPageId) {
const parent = tld.getShape(data, initialShapes[0]?.parentId) as GroupShape
if (parent.children.length === initialShapes.length) {
commands.ungroup(data)
return
}
}
let newGroupParentId: string
const initialShapeIds = initialShapes.map((s) => s.id)
const commonBounds = getCommonBounds(
...initialShapes.map((shape) =>
getShapeUtils(shape).getRotatedBounds(shape)
)
)
if (isAllSameParent) {
const parentId = initialShapes[0].parentId
if (parentId === currentPageId) {
// Create the new group on the current page
newGroupParentId = currentPageId
} else {
// Create the new group as a child of the shapes' current parent group
newGroupParentId = parentId
}
} else {
// Find the least-deep parent among the shapes and add the group as a child
let minDepth = Infinity
for (const parentId of initialShapes.map((shape) => shape.parentId)) {
const depth = getShapeDepth(data, parentId)
if (depth < minDepth) {
minDepth = depth
newGroupParentId = parentId
}
}
}
const newGroupShape = createShape(ShapeType.Group, {
id: uniqueId(),
parentId: newGroupParentId,
point: [commonBounds.minX, commonBounds.minY],
size: [commonBounds.width, commonBounds.height],
children: initialShapeIds,
childIndex: initialShapes[0].childIndex,
})
history.execute(
data,
new Command({
name: 'group_shapes',
category: 'canvas',
manualSelection: true,
do(data) {
const { shapes } = tld.getPage(data)
// Create the new group
shapes[newGroupShape.id] = newGroupShape
// Assign the group to its new parent
if (newGroupParentId !== data.currentPageId) {
const parent = shapes[newGroupParentId]
getShapeUtils(parent).setProperty(parent, 'children', [
...parent.children,
newGroupShape.id,
])
}
// 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.includes(id))
)
}
// Assign the shape to its new parent, with its new childIndex
const shape = shapes[initialShape.id]
getShapeUtils(shape)
.setProperty(shape, 'childIndex', i)
.setProperty(shape, 'parentId', newGroupShape.id)
})
tld.setSelectedIds(data, [newGroupShape.id])
},
undo(data) {
const { shapes } = tld.getPage(data)
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 }) => {
const shape = shapes[id]
getShapeUtils(shape)
.setProperty(shape, 'parentId', parentId)
.setProperty(shape, 'childIndex', childIndex)
if (parentId !== data.currentPageId) {
const parent = shapes[parentId]
getShapeUtils(parent).setProperty(parent, 'children', [
...parent.children,
id,
])
}
})
// Delete the group
delete shapes[newGroupShape.id]
// Reselect the children of the group
tld.setSelectedIds(data, initialShapeIds)
},
})
)
}
function getShapeDepth(data: Data, id: string, depth = 0) {
if (id === data.currentPageId) {
return depth
}
return getShapeDepth(data, tld.getShape(data, id).parentId, depth + 1)
}