kopia lustrzana https://github.com/Tldraw/Tldraw
110 wiersze
3.6 KiB
TypeScript
110 wiersze
3.6 KiB
TypeScript
import type { Action, CustomBinding } from 'state/constants'
|
|
import { getShapeUtils, Shape } from 'shapes'
|
|
import { mutables } from 'state/mutables'
|
|
import { nanoid } from 'nanoid'
|
|
import { TLPointerInfo, Utils } from '@tldraw/core'
|
|
import type { ArrowShape } from 'shapes/arrow'
|
|
import Vec from '@tldraw/vec'
|
|
|
|
export const translateHandle: Action = (data, payload: TLPointerInfo) => {
|
|
const { initialPoint, snapshot, pointedHandleId } = mutables
|
|
|
|
if (!pointedHandleId) return
|
|
|
|
let delta = Vec.sub(mutables.currentPoint, initialPoint)
|
|
|
|
data.pageState.selectedIds.forEach((id) => {
|
|
const initialShape = snapshot.page.shapes[id] as ArrowShape
|
|
|
|
const shape = data.page.shapes[id] as ArrowShape
|
|
|
|
if (payload.shiftKey) {
|
|
const A = initialShape.handles[pointedHandleId === 'start' ? 'end' : 'start'].point
|
|
const B = initialShape.handles[pointedHandleId].point
|
|
const C = Vec.add(B, delta)
|
|
const angle = Vec.angle(A, C)
|
|
const adjusted = Vec.rotWith(C, A, Utils.snapAngleToSegments(angle, 24) - angle)
|
|
delta = Vec.add(delta, Vec.sub(adjusted, C))
|
|
}
|
|
|
|
const handlePoints = {
|
|
start: [...initialShape.handles.start.point],
|
|
end: [...initialShape.handles.end.point],
|
|
}
|
|
|
|
handlePoints[pointedHandleId] = Vec.add(handlePoints[pointedHandleId], delta)
|
|
|
|
// Create binding
|
|
|
|
const oppositeHandleId = pointedHandleId === 'start' ? 'end' : 'start'
|
|
const oppositeHandle = shape.handles[oppositeHandleId]
|
|
const handlePoint = Vec.add(handlePoints[pointedHandleId], initialShape.point)
|
|
|
|
let minDistance = Infinity
|
|
let toShape: Shape | undefined
|
|
const oppositeBindingTargetId = Object.values(data.page.bindings).find(
|
|
(binding) => binding.fromId === shape.id && binding.handleId === oppositeHandleId
|
|
)?.toId
|
|
|
|
if (!payload.metaKey) {
|
|
// Find colliding shape with center nearest to point
|
|
Object.values(data.page.shapes)
|
|
.filter(
|
|
(shape) =>
|
|
!data.pageState.selectedIds.includes(shape.id) && shape.id !== oppositeBindingTargetId
|
|
)
|
|
.forEach((potentialTarget) => {
|
|
const utils = getShapeUtils(potentialTarget)
|
|
|
|
if (!utils.canBind) return
|
|
|
|
const bounds = utils.getBounds(potentialTarget)
|
|
|
|
if (Utils.pointInBounds(handlePoint, bounds)) {
|
|
const dist = Vec.dist(handlePoint, utils.getCenter(potentialTarget))
|
|
if (dist < minDistance) {
|
|
minDistance = dist
|
|
toShape = potentialTarget
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const oldBinding = Object.values(data.page.bindings).find(
|
|
(binding) => binding.fromId === shape.id && binding.handleId === pointedHandleId
|
|
)
|
|
|
|
// If we have a binding target
|
|
if (toShape) {
|
|
if (!oldBinding || oldBinding.toId !== toShape.id) {
|
|
if (oldBinding) {
|
|
delete data.page.bindings[oldBinding.id]
|
|
}
|
|
|
|
// Create a new binding between shape and toShape
|
|
const binding: CustomBinding = {
|
|
id: nanoid(),
|
|
fromId: shape.id,
|
|
toId: toShape.id,
|
|
handleId: pointedHandleId,
|
|
}
|
|
|
|
data.page.bindings[binding.id] = binding
|
|
}
|
|
|
|
// The `updateBoundShapes` action will take it from here.
|
|
return
|
|
}
|
|
|
|
// If we didn't find a toShape, clear out the old binding (if present)
|
|
if (oldBinding) {
|
|
delete data.page.bindings[oldBinding.id]
|
|
}
|
|
|
|
const offset = Utils.getCommonTopLeft([handlePoints.start, handlePoints.end])
|
|
shape.handles.start.point = Vec.sub(handlePoints.start, offset)
|
|
shape.handles.end.point = Vec.sub(handlePoints.end, offset)
|
|
shape.point = Vec.add(initialShape.point, offset)
|
|
})
|
|
}
|