tldraw/state/commands/group.ts
2021-06-04 17:08:43 +01:00

136 lines
3.8 KiB
TypeScript

import Command from './command'
import history from '../history'
import { Data, GroupShape, Shape, ShapeType } from 'types'
import {
getCommonBounds,
getPage,
getSelectedShapes,
getShape,
} from 'utils/utils'
import { current } from 'immer'
import { createShape, getShapeUtils } from 'lib/shape-utils'
import { PropsOfType } from 'types'
import { v4 as uuid } from 'uuid'
import commands from '.'
export default function groupCommand(data: Data) {
const cData = current(data)
const { currentPageId, selectedIds } = cData
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
let newGroupShape: GroupShape
let oldGroupShape: GroupShape
const selectedShapeIds = initialShapes.map((s) => s.id)
const parentIds = Array.from(
new Set(initialShapes.map((s) => s.parentId)).values()
)
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) {
// !
// !
// !
// Hey! We're not going any further. We need to ungroup those shapes.
commands.ungroup(data)
return
} else {
newGroupParentId = parentId
}
}
} else {
// Find the least-deep parent among the shapes and add the group as a child
let minDepth = Infinity
for (let parentId of initialShapes.map((shape) => shape.parentId)) {
const depth = getShapeDepth(data, parentId)
if (depth < minDepth) {
minDepth = depth
newGroupParentId = parentId
}
}
}
newGroupShape = createShape(ShapeType.Group, {
parentId: newGroupParentId,
point: [commonBounds.minX, commonBounds.minY],
size: [commonBounds.width, commonBounds.height],
children: selectedShapeIds,
})
history.execute(
data,
new Command({
name: 'group_shapes',
category: 'canvas',
do(data) {
const { shapes } = getPage(data, currentPageId)
// Remove shapes from old parents
for (const parentId of parentIds) {
if (parentId === currentPageId) continue
const shape = shapes[parentId] as GroupShape
getShapeUtils(shape).setProperty(
shape,
'children',
shape.children.filter((id) => !selectedIds.has(id))
)
}
shapes[newGroupShape.id] = newGroupShape
data.selectedIds.clear()
data.selectedIds.add(newGroupShape.id)
initialShapes.forEach(({ id }, i) => {
const shape = shapes[id]
getShapeUtils(shape)
.setProperty(shape, 'parentId', newGroupShape.id)
.setProperty(shape, 'childIndex', i)
})
},
undo(data) {
const { shapes } = getPage(data, currentPageId)
data.selectedIds.clear()
delete shapes[newGroupShape.id]
initialShapes.forEach(({ id, parentId, childIndex }, i) => {
data.selectedIds.add(id)
const shape = shapes[id]
getShapeUtils(shape)
.setProperty(shape, 'parentId', parentId)
.setProperty(shape, 'childIndex', childIndex)
})
},
})
)
}
function getShapeDepth(data: Data, id: string, depth = 0) {
if (id === data.currentPageId) {
return depth
}
return getShapeDepth(data, getShape(data, id).parentId, depth + 1)
}