tldraw/state/commands/distribute.ts
2021-06-29 13:00:59 +01:00

141 lines
4.5 KiB
TypeScript

import Command from './command'
import history from '../history'
import { Data, DistributeType } from 'types'
import { getBoundsCenter, getCommonBounds } from 'utils'
import tld from 'utils/tld'
import { getShapeUtils } from 'state/shape-utils'
export default function distributeCommand(
data: Data,
type: DistributeType
): void {
const selectedShapes = tld
.getSelectedShapes(data)
.filter((shape) => !shape.isLocked)
const entries = selectedShapes.map(
(shape) => [shape.id, getShapeUtils(shape).getBounds(shape)] as const
)
const boundsForShapes = Object.fromEntries(entries)
const commonBounds = getCommonBounds(...entries.map((entry) => entry[1]))
const centers = Object.fromEntries(
selectedShapes.map((shape) => [
shape.id,
getBoundsCenter(boundsForShapes[shape.id]),
])
)
history.execute(
data,
new Command({
name: 'distribute_shapes',
category: 'canvas',
do(data) {
const { shapes } = tld.getPage(data)
const len = entries.length
switch (type) {
case DistributeType.Horizontal: {
const span = entries.reduce((a, c) => a + c[1].width, 0)
if (span > commonBounds.width) {
const left = entries.sort((a, b) => a[1].minX - b[1].minX)[0]
const right = entries.sort((a, b) => b[1].maxX - a[1].maxX)[0]
const entriesToMove = entries
.filter((a) => a !== left && a !== right)
.sort((a, b) => centers[a[0]][0] - centers[b[0]][0])
const step =
(centers[right[0]][0] - centers[left[0]][0]) / (len - 1)
const x = centers[left[0]][0] + step
for (let i = 0; i < entriesToMove.length; i++) {
const [id, bounds] = entriesToMove[i]
const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [
x + step * i - bounds.width / 2,
bounds.minY,
])
}
} else {
const entriesToMove = entries.sort(
(a, b) => centers[a[0]][0] - centers[b[0]][0]
)
let x = commonBounds.minX
const step = (commonBounds.width - span) / (len - 1)
for (let i = 0; i < entriesToMove.length - 1; i++) {
const [id, bounds] = entriesToMove[i]
const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [x, bounds.minY])
x += bounds.width + step
}
}
break
}
case DistributeType.Vertical: {
const span = entries.reduce((a, c) => a + c[1].height, 0)
if (span > commonBounds.height) {
const top = entries.sort((a, b) => a[1].minY - b[1].minY)[0]
const bottom = entries.sort((a, b) => b[1].maxY - a[1].maxY)[0]
const entriesToMove = entries
.filter((a) => a !== top && a !== bottom)
.sort((a, b) => centers[a[0]][1] - centers[b[0]][1])
const step =
(centers[bottom[0]][1] - centers[top[0]][1]) / (len - 1)
const y = centers[top[0]][1] + step
for (let i = 0; i < entriesToMove.length; i++) {
const [id, bounds] = entriesToMove[i]
const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [
bounds.minX,
y + step * i - bounds.height / 2,
])
}
} else {
const entriesToMove = entries.sort(
(a, b) => centers[a[0]][1] - centers[b[0]][1]
)
let y = commonBounds.minY
const step = (commonBounds.height - span) / (len - 1)
for (let i = 0; i < entriesToMove.length - 1; i++) {
const [id, bounds] = entriesToMove[i]
const shape = shapes[id]
getShapeUtils(shape).translateTo(shape, [bounds.minX, y])
y += bounds.height + step
}
}
break
}
}
},
undo(data) {
const { shapes } = tld.getPage(data)
for (const id in boundsForShapes) {
const shape = shapes[id]
const initialBounds = boundsForShapes[id]
getShapeUtils(shape).translateTo(shape, [
initialBounds.minX,
initialBounds.minY,
])
}
},
})
)
}