tldraw/state/commands/group.ts

164 lines
4.9 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-07-01 22:11:09 +00:00
import { deepClone, getCommonBounds, uniqueId } from 'utils'
2021-06-29 12:00:59 +00:00
import tld from 'utils/tld'
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-29 14:54:46 +00:00
const { currentPageId } = data
2021-06-29 14:54:46 +00:00
const oldSelectedIds = tld.getSelectedIds(data)
2021-06-04 16:08:43 +00:00
2021-06-29 12:00:59 +00:00
const initialShapes = tld
2021-06-29 14:54:46 +00:00
.getSelectedShapes(data)
2021-06-29 12:00:59 +00:00
.sort((a, b) => a.childIndex - b.childIndex)
2021-06-29 14:54:46 +00:00
.map((shape) => deepClone(shape))
2021-06-04 16:08:43 +00:00
const isAllSameParent = initialShapes.every(
(shape, i) => i === 0 || shape.parentId === initialShapes[i - 1].parentId
)
2021-06-29 14:54:46 +00:00
// 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
2021-07-01 22:11:09 +00:00
2021-06-29 14:54:46 +00:00
if (parent.children.length === initialShapes.length) {
commands.ungroup(data)
return
}
}
2021-06-04 16:08:43 +00:00
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) {
2021-06-29 14:54:46 +00:00
// Create the new group on the current page
2021-06-04 16:08:43 +00:00
newGroupParentId = currentPageId
} else {
2021-06-29 14:54:46 +00:00
// Create the new group as a child of the shapes' current parent group
newGroupParentId = parentId
2021-06-04 16:08:43 +00:00
}
} 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-07-01 22:11:09 +00:00
id: uniqueId(),
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-29 12:00:59 +00:00
const { shapes } = tld.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.includes(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
2021-06-29 12:00:59 +00:00
tld.setSelectedIds(data, [newGroupShape.id])
2021-06-04 16:08:43 +00:00
},
undo(data) {
2021-06-29 12:00:59 +00:00
const { shapes } = tld.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
2021-06-29 12:00:59 +00:00
tld.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
}
2021-06-29 12:00:59 +00:00
return getShapeDepth(data, tld.getShape(data, id).parentId, depth + 1)
2021-06-04 16:08:43 +00:00
}