|
|
@@ -17,7 +17,7 @@ import type {
|
|
|
TLEvents,
|
|
|
TLHandle,
|
|
|
} from '../../types'
|
|
|
-import { AlignType } from '../../types'
|
|
|
+import { AlignType, DistributeType } from '../../types'
|
|
|
import { KeyUtils, BoundsUtils, isNonNullable, createNewLineBinding } from '../../utils'
|
|
|
import type { TLShape, TLShapeConstructor, TLShapeModel } from '../shapes'
|
|
|
import { TLApi } from '../TLApi'
|
|
|
@@ -420,6 +420,113 @@ export class TLApp<
|
|
|
return this
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ distribute = (type: DistributeType, shapes: S[] = this.selectedShapesArray): this => {
|
|
|
+ if (shapes.length < 2) return this
|
|
|
+
|
|
|
+ const deltaMap = Object.fromEntries(this.getDistributions(shapes, type).map((d) => [d.id, d]))
|
|
|
+
|
|
|
+ shapes.forEach(shape => shape.update(deltaMap[shape.id] ? { point: deltaMap[shape.id].next } : shape))
|
|
|
+
|
|
|
+ this.persist()
|
|
|
+ return this
|
|
|
+ }
|
|
|
+
|
|
|
+ getDistributions = (shapes: TLShape[], type: DistributeType) => {
|
|
|
+ const entries = shapes.map((shape) => {
|
|
|
+ const bounds = shape.getBounds();
|
|
|
+ return {
|
|
|
+ id: shape.id,
|
|
|
+ point: [bounds.minX, bounds.minY],
|
|
|
+ bounds: bounds,
|
|
|
+ center: shape.getCenter(),
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const len = entries.length
|
|
|
+ const commonBounds = BoundsUtils.getCommonBounds(entries.map(({ bounds }) => bounds))
|
|
|
+
|
|
|
+ const results: { id: string; prev: number[]; next: number[] }[] = []
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+ case DistributeType.Horizontal: {
|
|
|
+ const span = entries.reduce((a, c) => a + c.bounds.width, 0)
|
|
|
+
|
|
|
+ if (span > commonBounds.width) {
|
|
|
+ const left = entries.sort((a, b) => a.bounds.minX - b.bounds.minX)[0]
|
|
|
+
|
|
|
+ const right = entries.sort((a, b) => b.bounds.maxX - a.bounds.maxX)[0]
|
|
|
+
|
|
|
+ const entriesToMove = entries
|
|
|
+ .filter((a) => a !== left && a !== right)
|
|
|
+ .sort((a, b) => a.center[0] - b.center[0])
|
|
|
+
|
|
|
+ const step = (right.center[0] - left.center[0]) / (len - 1)
|
|
|
+
|
|
|
+ const x = left.center[0] + step
|
|
|
+
|
|
|
+ entriesToMove.forEach(({ id, point, bounds }, i) => {
|
|
|
+ results.push({
|
|
|
+ id,
|
|
|
+ prev: point,
|
|
|
+ next: [x + step * i - bounds.width / 2, bounds.minY],
|
|
|
+ })
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ const entriesToMove = entries.sort((a, b) => a.center[0] - b.center[0])
|
|
|
+
|
|
|
+ let x = commonBounds.minX
|
|
|
+ const step = (commonBounds.width - span) / (len - 1)
|
|
|
+
|
|
|
+ entriesToMove.forEach(({ id, point, bounds }) => {
|
|
|
+ results.push({ id, prev: point, next: [x, bounds.minY] })
|
|
|
+ x += bounds.width + step
|
|
|
+ })
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ case DistributeType.Vertical: {
|
|
|
+ const span = entries.reduce((a, c) => a + c.bounds.height, 0)
|
|
|
+
|
|
|
+ if (span > commonBounds.height) {
|
|
|
+ const top = entries.sort((a, b) => a.bounds.minY - b.bounds.minY)[0]
|
|
|
+
|
|
|
+ const bottom = entries.sort((a, b) => b.bounds.maxY - a.bounds.maxY)[0]
|
|
|
+
|
|
|
+ const entriesToMove = entries
|
|
|
+ .filter((a) => a !== top && a !== bottom)
|
|
|
+ .sort((a, b) => a.center[1] - b.center[1])
|
|
|
+
|
|
|
+ const step = (bottom.center[1] - top.center[1]) / (len - 1)
|
|
|
+
|
|
|
+ const y = top.center[1] + step
|
|
|
+
|
|
|
+ entriesToMove.forEach(({ id, point, bounds }, i) => {
|
|
|
+ results.push({
|
|
|
+ id,
|
|
|
+ prev: point,
|
|
|
+ next: [bounds.minX, y + step * i - bounds.height / 2],
|
|
|
+ })
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ const entriesToMove = entries.sort((a, b) => a.center[1] - b.center[1])
|
|
|
+
|
|
|
+ let y = commonBounds.minY
|
|
|
+ const step = (commonBounds.height - span) / (len - 1)
|
|
|
+
|
|
|
+ entriesToMove.forEach(({ id, point, bounds }) => {
|
|
|
+ results.push({ id, prev: point, next: [bounds.minX, y] })
|
|
|
+ y += bounds.height + step
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return results
|
|
|
+ }
|
|
|
+
|
|
|
/* --------------------- Assets --------------------- */
|
|
|
|
|
|
@observable assets: Record<string, TLAsset> = {}
|