From 09fd3a752f9406dcc4b1d626b02ec1b3c2f0109c Mon Sep 17 00:00:00 2001 From: Konstantinos Kaloutas Date: Wed, 21 Sep 2022 14:08:48 +0300 Subject: [PATCH] chore: add distribute method --- .../components/ContextMenu/ContextMenu.tsx | 6 +- tldraw/packages/core/src/lib/TLApp/TLApp.ts | 109 +++++++++++++++++- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx b/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx index 8b3a8a2e1..f434eca25 100644 --- a/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx +++ b/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx @@ -1,5 +1,5 @@ import { useApp } from '@tldraw/react' -import { MOD_KEY, AlignType } from '@tldraw/core' +import { MOD_KEY, AlignType, DistributeType } from '@tldraw/core' import { observer } from 'mobx-react-lite' import { TablerIcon } from '../icons' import { Button } from '../Button' @@ -46,14 +46,14 @@ export const ContextMenu = observer(function ContextMenu({ - +
- +
diff --git a/tldraw/packages/core/src/lib/TLApp/TLApp.ts b/tldraw/packages/core/src/lib/TLApp/TLApp.ts index d12dd1a46..0731bbb65 100644 --- a/tldraw/packages/core/src/lib/TLApp/TLApp.ts +++ b/tldraw/packages/core/src/lib/TLApp/TLApp.ts @@ -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 = {}