Fix binding bugs

This commit is contained in:
Steve Ruiz 2021-09-04 22:12:07 +01:00
parent 010a09ff19
commit 13a40d1b9c
6 changed files with 103 additions and 86 deletions

View file

@ -112,4 +112,6 @@ describe('Delete command', () => {
expect(tlstate.getShape('newGroup')).toBeUndefined() expect(tlstate.getShape('newGroup')).toBeUndefined()
}) })
}) })
it.todo('Does not delete uneffected bindings.')
}) })

View file

@ -158,4 +158,6 @@ describe('Duplicate command', () => {
expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length + 1) expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length + 1)
}) })
}) })
it.todo('Does not delete uneffected bindings.')
}) })

View file

@ -83,46 +83,48 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
}) })
// Which ids did we end up duplicating? // Which ids did we end up duplicating?
const duplicatedShapeIds = Object.keys(duplicateMap) const dupedShapeIds = new Set(Object.keys(duplicateMap))
// Handle bindings that effect duplicated shapes // Handle bindings that effect duplicated shapes
Object.values(page.bindings).forEach((binding) => { Object.values(page.bindings)
if (duplicatedShapeIds.includes(binding.fromId)) { .filter((binding) => dupedShapeIds.has(binding.fromId) || dupedShapeIds.has(binding.toId))
if (duplicatedShapeIds.includes(binding.toId)) { .forEach((binding) => {
// If the binding is between two duplicating shapes then if (dupedShapeIds.has(binding.fromId)) {
// duplicate the binding, too if (dupedShapeIds.has(binding.toId)) {
const duplicatedBindingId = Utils.uniqueId() // If the binding is between two duplicating shapes then
// duplicate the binding, too
const duplicatedBindingId = Utils.uniqueId()
const duplicatedBinding = { const duplicatedBinding = {
...Utils.deepClone(binding), ...Utils.deepClone(binding),
id: duplicatedBindingId, id: duplicatedBindingId,
fromId: duplicateMap[binding.fromId], fromId: duplicateMap[binding.fromId],
toId: duplicateMap[binding.toId], toId: duplicateMap[binding.toId],
}
before.bindings[duplicatedBindingId] = undefined
after.bindings[duplicatedBindingId] = duplicatedBinding
// Change the duplicated shape's handle so that it reference
// the duplicated binding
const boundShape = after.shapes[duplicatedBinding.fromId]
Object.values(boundShape!.handles!).forEach((handle) => {
if (handle!.bindingId === binding.id) {
handle!.bindingId = duplicatedBindingId
}
})
} else {
// If only the fromId is selected, delete the binding on
// the duplicated shape's handles
const boundShape = after.shapes[duplicateMap[binding.fromId]]
Object.values(boundShape!.handles!).forEach((handle) => {
if (handle!.bindingId === binding.id) {
handle!.bindingId = undefined
}
})
} }
before.bindings[duplicatedBindingId] = undefined
after.bindings[duplicatedBindingId] = duplicatedBinding
// Change the duplicated shape's handle so that it reference
// the duplicated binding
const boundShape = after.shapes[duplicatedBinding.fromId]
Object.values(boundShape!.handles!).forEach((handle) => {
if (handle!.bindingId === binding.id) {
handle!.bindingId = duplicatedBindingId
}
})
} else {
// If only the fromId is selected, delete the binding on
// the duplicated shape's handles
const boundShape = after.shapes[duplicateMap[binding.fromId]]
Object.values(boundShape!.handles!).forEach((handle) => {
if (handle!.bindingId === binding.id) {
handle!.bindingId = undefined
}
})
} }
} })
})
return { return {
id: 'duplicate', id: 'duplicate',
@ -142,7 +144,9 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
[currentPageId]: after, [currentPageId]: after,
}, },
pageStates: { pageStates: {
[currentPageId]: { selectedIds: duplicatedShapeIds.map((id) => duplicateMap[id]) }, [currentPageId]: {
selectedIds: Array.from(dupedShapeIds.values()).map((id) => duplicateMap[id]),
},
}, },
}, },
}, },

View file

@ -247,4 +247,6 @@ describe('Move to page command', () => {
]) ])
}) })
}) })
it.todo('Does not delete uneffected bindings.')
}) })

View file

@ -52,44 +52,49 @@ export function removeShapesFromPage(data: Data, ids: string[], pageId: string)
const page = TLDR.getPage(data, pageId) const page = TLDR.getPage(data, pageId)
// We also need to delete bindings that reference the deleted shapes // We also need to delete bindings that reference the deleted shapes
Object.values(page.bindings).forEach((binding) => { Object.values(page.bindings)
for (const id of [binding.toId, binding.fromId]) { .filter((binding) => deletedIds.has(binding.fromId) || deletedIds.has(binding.toId))
// If the binding references a deleted shape... .forEach((binding) => {
if (after.shapes[id] === undefined) { for (const id of [binding.toId, binding.fromId]) {
// Delete this binding // If the binding references a deleted shape...
before.bindings[binding.id] = binding if (after.shapes[id] === undefined) {
after.bindings[binding.id] = undefined // Delete this binding
before.bindings[binding.id] = binding
after.bindings[binding.id] = undefined
// Let's also look each the bound shape... // Let's also look each the bound shape...
const shape = TLDR.getShape(data, id, pageId) const shape = TLDR.getShape(data, id, pageId)
// 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) .filter((handle) => handle.bindingId === binding.id)
.forEach((handle) => { .forEach((handle) => {
// Save the binding reference in the before patch // Save the binding reference in the before patch
before.shapes[id] = { before.shapes[id] = {
...before.shapes[id], ...before.shapes[id],
handles: { handles: {
...before.shapes[id]?.handles, ...before.shapes[id]?.handles,
[handle.id]: { bindingId: binding.id }, [handle.id]: { bindingId: binding.id },
}, },
}
// Unless we're currently deleting the shape, remove the
// binding reference from the after patch
if (!deletedIds.has(id)) {
after.shapes[id] = {
...after.shapes[id],
handles: { ...after.shapes[id]?.handles, [handle.id]: { bindingId: undefined } },
} }
}
}) // Unless we're currently deleting the shape, remove the
// binding reference from the after patch
if (!deletedIds.has(id)) {
after.shapes[id] = {
...after.shapes[id],
handles: {
...after.shapes[id]?.handles,
[handle.id]: { bindingId: undefined },
},
}
}
})
}
} }
} }
} })
})
return { before, after } return { before, after }
} }

View file

@ -382,30 +382,32 @@ export function getTranslateSnapshot(data: Data) {
// Potentially confusing name here: these are the ids of the // Potentially confusing name here: these are the ids of the
// original shapes that were cloned, not their clones' ids. // original shapes that were cloned, not their clones' ids.
const clonedShapeIds = Object.keys(cloneMap) const clonedShapeIds = new Set(Object.keys(cloneMap))
const bindingsToDelete: TLDrawBinding[] = [] const bindingsToDelete: TLDrawBinding[] = []
// Create cloned bindings for shapes where both to and from shapes are selected // Create cloned bindings for shapes where both to and from shapes are selected
// (if the user clones, then we will create a new binding for the clones) // (if the user clones, then we will create a new binding for the clones)
Object.values(page.bindings).forEach((binding) => { Object.values(page.bindings)
if (clonedShapeIds.includes(binding.fromId)) { .filter((binding) => clonedShapeIds.has(binding.fromId) || clonedShapeIds.has(binding.toId))
if (clonedShapeIds.includes(binding.toId)) { .forEach((binding) => {
const cloneId = Utils.uniqueId() if (clonedShapeIds.has(binding.fromId)) {
const cloneBinding = { if (clonedShapeIds.has(binding.toId)) {
...Utils.deepClone(binding), const cloneId = Utils.uniqueId()
id: cloneId, const cloneBinding = {
fromId: cloneMap[binding.fromId] || binding.fromId, ...Utils.deepClone(binding),
toId: cloneMap[binding.toId] || binding.toId, id: cloneId,
} fromId: cloneMap[binding.fromId] || binding.fromId,
toId: cloneMap[binding.toId] || binding.toId,
}
clonedBindingsMap[binding.id] = cloneId clonedBindingsMap[binding.id] = cloneId
clonedBindings.push(cloneBinding) clonedBindings.push(cloneBinding)
} else { } else {
bindingsToDelete.push(binding) bindingsToDelete.push(binding)
}
} }
} })
})
// Assign new binding ids to clones (or delete them!) // Assign new binding ids to clones (or delete them!)
clones.forEach((clone) => { clones.forEach((clone) => {