mirror of https://github.com/logseq/logseq
chore: add distribute method
parent
e902780f19
commit
09fd3a752f
|
@ -1,5 +1,5 @@
|
||||||
import { useApp } from '@tldraw/react'
|
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 { observer } from 'mobx-react-lite'
|
||||||
import { TablerIcon } from '../icons'
|
import { TablerIcon } from '../icons'
|
||||||
import { Button } from '../Button'
|
import { Button } from '../Button'
|
||||||
|
@ -46,14 +46,14 @@ export const ContextMenu = observer(function ContextMenu({
|
||||||
<Button title="Align center horizontally" onClick={() => runAndTransition(() => app.align(AlignType.CenterHorizontal))}><TablerIcon name="layout-align-center" /></Button>
|
<Button title="Align center horizontally" onClick={() => runAndTransition(() => app.align(AlignType.CenterHorizontal))}><TablerIcon name="layout-align-center" /></Button>
|
||||||
<Button title="Align right" onClick={() => runAndTransition(() => app.align(AlignType.Right))}><TablerIcon name="layout-align-right" /></Button>
|
<Button title="Align right" onClick={() => runAndTransition(() => app.align(AlignType.Right))}><TablerIcon name="layout-align-right" /></Button>
|
||||||
<Separator.Root className="tl-toolbar-separator" orientation="vertical" />
|
<Separator.Root className="tl-toolbar-separator" orientation="vertical" />
|
||||||
<Button title="Distribute horizontally"><TablerIcon name="layout-distribute-horizontal" /></Button>
|
<Button title="Distribute horizontally" onClick={() => runAndTransition(() => app.distribute(DistributeType.Horizontal))}><TablerIcon name="layout-distribute-vertical" /></Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="tl-menu-button-row pt-0">
|
<div className="tl-menu-button-row pt-0">
|
||||||
<Button title="Align top" onClick={() => runAndTransition(() => app.align(AlignType.Top))}><TablerIcon name="layout-align-top" /></Button>
|
<Button title="Align top" onClick={() => runAndTransition(() => app.align(AlignType.Top))}><TablerIcon name="layout-align-top" /></Button>
|
||||||
<Button title="Align center vertically" onClick={() => runAndTransition(() => app.align(AlignType.CenterVertical))}><TablerIcon name="layout-align-middle" /></Button>
|
<Button title="Align center vertically" onClick={() => runAndTransition(() => app.align(AlignType.CenterVertical))}><TablerIcon name="layout-align-middle" /></Button>
|
||||||
<Button title="Align bottom" onClick={() => runAndTransition(() => app.align(AlignType.Bottom))}><TablerIcon name="layout-align-bottom" /></Button>
|
<Button title="Align bottom" onClick={() => runAndTransition(() => app.align(AlignType.Bottom))}><TablerIcon name="layout-align-bottom" /></Button>
|
||||||
<Separator.Root className="tl-toolbar-separator" orientation="vertical" />
|
<Separator.Root className="tl-toolbar-separator" orientation="vertical" />
|
||||||
<Button title="Distribute vertically"><TablerIcon name="layout-distribute-vertical" /></Button>
|
<Button title="Distribute vertically" onClick={() => runAndTransition(() => app.distribute(DistributeType.Vertical ))}><TablerIcon name="layout-distribute-horizontal" /></Button>
|
||||||
</div>
|
</div>
|
||||||
<ReactContextMenu.Separator className="menu-separator" />
|
<ReactContextMenu.Separator className="menu-separator" />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -17,7 +17,7 @@ import type {
|
||||||
TLEvents,
|
TLEvents,
|
||||||
TLHandle,
|
TLHandle,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
import { AlignType } from '../../types'
|
import { AlignType, DistributeType } from '../../types'
|
||||||
import { KeyUtils, BoundsUtils, isNonNullable, createNewLineBinding } from '../../utils'
|
import { KeyUtils, BoundsUtils, isNonNullable, createNewLineBinding } from '../../utils'
|
||||||
import type { TLShape, TLShapeConstructor, TLShapeModel } from '../shapes'
|
import type { TLShape, TLShapeConstructor, TLShapeModel } from '../shapes'
|
||||||
import { TLApi } from '../TLApi'
|
import { TLApi } from '../TLApi'
|
||||||
|
@ -420,6 +420,113 @@ export class TLApp<
|
||||||
return this
|
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 --------------------- */
|
/* --------------------- Assets --------------------- */
|
||||||
|
|
||||||
@observable assets: Record<string, TLAsset> = {}
|
@observable assets: Record<string, TLAsset> = {}
|
||||||
|
|
Loading…
Reference in New Issue