Fix delete binding on shape delete bug
This commit is contained in:
parent
ab619385f3
commit
cefd294c13
2 changed files with 119 additions and 32 deletions
|
@ -1,9 +1,6 @@
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
import type { Data, TLDrawCommand, PagePartial } from '~types'
|
import type { Data, TLDrawCommand, PagePartial } from '~types'
|
||||||
|
|
||||||
// - [x] Delete shapes
|
|
||||||
// - [x] Delete bindings too
|
|
||||||
// - [x] Delete bound shapes (arrows)
|
|
||||||
// - [ ] Update parents and possibly delete parents
|
// - [ ] Update parents and possibly delete parents
|
||||||
|
|
||||||
export function deleteShapes(data: Data, ids: string[]): TLDrawCommand {
|
export function deleteShapes(data: Data, ids: string[]): TLDrawCommand {
|
||||||
|
@ -36,23 +33,30 @@ export function deleteShapes(data: Data, ids: string[]): TLDrawCommand {
|
||||||
before.bindings[binding.id] = binding
|
before.bindings[binding.id] = binding
|
||||||
after.bindings[binding.id] = undefined
|
after.bindings[binding.id] = undefined
|
||||||
|
|
||||||
// Let's also look at the bound shape...
|
// Let's also look each the bound shape...
|
||||||
const shape = TLDR.getShape(data, id, currentPageId)
|
const shape = TLDR.getShape(data, id, currentPageId)
|
||||||
|
|
||||||
// If the bound shape has a handle that references the deleted binding...
|
// If the bound shape has a handle that references the deleted binding...
|
||||||
if (shape.handles) {
|
if (shape.handles) {
|
||||||
Object.values(shape.handles)
|
Object.values(shape.handles)
|
||||||
.filter((handle) => handle.bindingId === binding.id && after.shapes[id] !== undefined)
|
.filter((handle) => handle.bindingId === binding.id)
|
||||||
.forEach((handle) => {
|
.forEach((handle) => {
|
||||||
// Otherwise, delete the reference to the deleted binding
|
// Save the binding reference in the before patch
|
||||||
before.shapes[id] = {
|
before.shapes[id] = {
|
||||||
...before.shapes[id],
|
...before.shapes[id],
|
||||||
handles: { ...before.shapes[id]?.handles, [handle.id]: { bindingId: binding.id } },
|
handles: {
|
||||||
|
...before.shapes[id]?.handles,
|
||||||
|
[handle.id]: { bindingId: binding.id },
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
after.shapes[id] = {
|
// Unless we're currently deleting the shape, remove the
|
||||||
...after.shapes[id],
|
// binding reference from the after patch
|
||||||
handles: { ...after.shapes[id]?.handles, [handle.id]: { bindingId: undefined } },
|
if (!ids.includes(id)) {
|
||||||
|
after.shapes[id] = {
|
||||||
|
...after.shapes[id],
|
||||||
|
handles: { ...after.shapes[id]?.handles, [handle.id]: { bindingId: undefined } },
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
Session,
|
Session,
|
||||||
TLDrawStatus,
|
TLDrawStatus,
|
||||||
} from '~types'
|
} from '~types'
|
||||||
import { Vec, Utils } from '@tldraw/core'
|
import { Vec, Utils, TLHandle } from '@tldraw/core'
|
||||||
import { TLDR } from '~state/tldr'
|
import { TLDR } from '~state/tldr'
|
||||||
|
|
||||||
export class ArrowSession implements Session {
|
export class ArrowSession implements Session {
|
||||||
|
@ -76,14 +76,31 @@ export class ArrowSession implements Session {
|
||||||
// If the handle changed produced no change, bail here
|
// If the handle changed produced no change, bail here
|
||||||
if (!change) return
|
if (!change) return
|
||||||
|
|
||||||
let nextBindings: Record<string, TLDrawBinding> = page.bindings
|
// If we've made it this far, the shape should be a new objet reference
|
||||||
|
// that incorporates the changes we've made due to the handle movement.
|
||||||
let nextShape = { ...shape, ...change }
|
let nextShape = { ...shape, ...change }
|
||||||
|
|
||||||
|
// If nothing changes, we want this to be the same object reference as
|
||||||
|
// before. If it does change, we'll redefine this later on.
|
||||||
|
let nextBindings: Record<string, TLDrawBinding | undefined> = page.bindings
|
||||||
|
|
||||||
|
// If the handle can bind, then we need to search bindable shapes for
|
||||||
|
// a binding. If the handle already has a binding, then we will either
|
||||||
|
// update that binding or delete it (if no binding is found).
|
||||||
if (handle.canBind) {
|
if (handle.canBind) {
|
||||||
nextBindings = { ...nextBindings }
|
// const { binding, target } = findBinding(
|
||||||
let nextBinding: ArrowBinding | undefined = undefined
|
// data,
|
||||||
let nextTarget: TLDrawShape | undefined = undefined
|
// initialShape,
|
||||||
|
// handle,
|
||||||
|
// this.newBindingId,
|
||||||
|
// this.bindableShapeIds,
|
||||||
|
// page.bindings,
|
||||||
|
// altKey,
|
||||||
|
// metaKey
|
||||||
|
// )
|
||||||
|
|
||||||
|
let binding: ArrowBinding | undefined = undefined
|
||||||
|
let target: TLDrawShape | undefined = undefined
|
||||||
|
|
||||||
// Alt key skips binding
|
// Alt key skips binding
|
||||||
if (!altKey) {
|
if (!altKey) {
|
||||||
|
@ -103,7 +120,7 @@ export class ArrowSession implements Session {
|
||||||
if (id === initialShape.id) continue
|
if (id === initialShape.id) continue
|
||||||
if (id === oppositeBinding?.toId) continue
|
if (id === oppositeBinding?.toId) continue
|
||||||
|
|
||||||
const target = TLDR.getShape(data, id, data.appState.currentPageId)
|
target = TLDR.getShape(data, id, data.appState.currentPageId)
|
||||||
|
|
||||||
const util = TLDR.getShapeUtils(target)
|
const util = TLDR.getShapeUtils(target)
|
||||||
|
|
||||||
|
@ -119,10 +136,7 @@ export class ArrowSession implements Session {
|
||||||
// Not all shapes will produce a binding point
|
// Not all shapes will produce a binding point
|
||||||
if (!bindingPoint) continue
|
if (!bindingPoint) continue
|
||||||
|
|
||||||
// Stop at the first shape that will produce a binding point
|
binding = {
|
||||||
nextTarget = target
|
|
||||||
|
|
||||||
nextBinding = {
|
|
||||||
id: this.newBindingId,
|
id: this.newBindingId,
|
||||||
type: 'arrow',
|
type: 'arrow',
|
||||||
fromId: initialShape.id,
|
fromId: initialShape.id,
|
||||||
|
@ -137,29 +151,31 @@ export class ArrowSession implements Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we didn't find a target...
|
// If we didn't find a target...
|
||||||
if (nextBinding === undefined) {
|
if (binding === undefined) {
|
||||||
this.didBind = false
|
this.didBind = false
|
||||||
|
|
||||||
if (handle.bindingId) {
|
if (handle.bindingId) {
|
||||||
nextBindings[handle.bindingId] = undefined as any
|
nextBindings = { ...nextBindings }
|
||||||
|
nextBindings[handle.bindingId] = undefined
|
||||||
}
|
}
|
||||||
nextShape.handles[handleId].bindingId = undefined
|
nextShape.handles[handleId].bindingId = undefined
|
||||||
} else if (nextTarget) {
|
} else if (target) {
|
||||||
this.didBind = true
|
this.didBind = true
|
||||||
|
nextBindings = { ...nextBindings }
|
||||||
|
|
||||||
if (handle.bindingId && handle.bindingId !== this.newBindingId) {
|
if (handle.bindingId && handle.bindingId !== this.newBindingId) {
|
||||||
nextBindings[handle.bindingId] = undefined as any
|
nextBindings[handle.bindingId] = undefined
|
||||||
nextShape.handles[handleId].bindingId = undefined
|
nextShape.handles[handleId].bindingId = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we found a new binding, add its id to the handle...
|
// If we found a new binding, add its id to the shape's handle...
|
||||||
nextShape = {
|
nextShape = {
|
||||||
...nextShape,
|
...nextShape,
|
||||||
handles: {
|
handles: {
|
||||||
...nextShape.handles,
|
...nextShape.handles,
|
||||||
[handleId]: {
|
[handleId]: {
|
||||||
...nextShape.handles[handleId],
|
...nextShape.handles[handleId],
|
||||||
bindingId: nextBinding.id,
|
bindingId: binding.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -167,16 +183,17 @@ export class ArrowSession implements Session {
|
||||||
// and add it to the page's bindings
|
// and add it to the page's bindings
|
||||||
nextBindings = {
|
nextBindings = {
|
||||||
...nextBindings,
|
...nextBindings,
|
||||||
[nextBinding.id]: nextBinding,
|
[binding.id]: binding,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now update the arrow in response to the new binding
|
// Now update the arrow in response to the new binding
|
||||||
|
const targetUtils = TLDR.getShapeUtils(target)
|
||||||
const arrowChange = TLDR.getShapeUtils(nextShape).onBindingChange(
|
const arrowChange = TLDR.getShapeUtils(nextShape).onBindingChange(
|
||||||
nextShape,
|
nextShape,
|
||||||
nextBinding,
|
binding,
|
||||||
nextTarget,
|
target,
|
||||||
TLDR.getShapeUtils(nextTarget).getBounds(nextTarget),
|
targetUtils.getBounds(target),
|
||||||
TLDR.getShapeUtils(nextTarget).getCenter(nextTarget)
|
targetUtils.getCenter(target)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (arrowChange) {
|
if (arrowChange) {
|
||||||
|
@ -298,3 +315,69 @@ export class ArrowSession implements Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findBinding(
|
||||||
|
data: Data,
|
||||||
|
shape: ArrowShape,
|
||||||
|
handle: TLHandle,
|
||||||
|
newBindingId: string,
|
||||||
|
bindableShapeIds: string[],
|
||||||
|
bindings: Record<string, TLDrawBinding | undefined>,
|
||||||
|
altKey: boolean,
|
||||||
|
metaKey: boolean
|
||||||
|
) {
|
||||||
|
let nextBinding: ArrowBinding | undefined = undefined
|
||||||
|
let nextTarget: TLDrawShape | undefined = undefined
|
||||||
|
|
||||||
|
if (!altKey) {
|
||||||
|
const oppositeHandle = shape.handles[handle.id === 'start' ? 'end' : 'start']
|
||||||
|
|
||||||
|
// Find the origin and direction of the handle
|
||||||
|
const rayOrigin = Vec.add(oppositeHandle.point, shape.point)
|
||||||
|
const rayPoint = Vec.add(handle.point, shape.point)
|
||||||
|
const rayDirection = Vec.uni(Vec.sub(rayPoint, rayOrigin))
|
||||||
|
|
||||||
|
const oppositeBinding = oppositeHandle.bindingId
|
||||||
|
? bindings[oppositeHandle.bindingId]
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
// From all bindable shapes on the page...
|
||||||
|
for (const id of bindableShapeIds) {
|
||||||
|
if (id === shape.id) continue
|
||||||
|
if (id === oppositeBinding?.toId) continue
|
||||||
|
|
||||||
|
const target = TLDR.getShape(data, id, data.appState.currentPageId)
|
||||||
|
|
||||||
|
const util = TLDR.getShapeUtils(target)
|
||||||
|
|
||||||
|
const bindingPoint = util.getBindingPoint(
|
||||||
|
target,
|
||||||
|
rayPoint,
|
||||||
|
rayOrigin,
|
||||||
|
rayDirection,
|
||||||
|
32,
|
||||||
|
metaKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// Not all shapes will produce a binding point
|
||||||
|
if (!bindingPoint) continue
|
||||||
|
|
||||||
|
// Stop at the first shape that will produce a binding point
|
||||||
|
nextTarget = target
|
||||||
|
|
||||||
|
nextBinding = {
|
||||||
|
id: newBindingId,
|
||||||
|
type: 'arrow',
|
||||||
|
fromId: shape.id,
|
||||||
|
handleId: handle.id as keyof ArrowShape['handles'],
|
||||||
|
toId: target.id,
|
||||||
|
point: Vec.round(bindingPoint.point),
|
||||||
|
distance: bindingPoint.distance,
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { target: nextTarget, binding: nextBinding }
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue