Handles groups when duplicating shapes

This commit is contained in:
Steve Ruiz 2021-09-02 14:25:17 +01:00
parent a1a213f9b4
commit 9f9845780f
3 changed files with 67 additions and 20 deletions

View file

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { TLDrawState } from '~state'
import { mockDocument } from '~test'
import { ArrowShape, TLDrawShapeType } from '~types'
import { ArrowShape, GroupShape, TLDrawShapeType } from '~types'
describe('Duplicate command', () => {
const tlstate = new TLDrawState()
@ -116,8 +116,23 @@ describe('Duplicate command', () => {
)
})
it.todo('updates the arrow when bound on both sides')
it('duplicates grouped shapes', () => {
tlstate.loadDocument(mockDocument)
tlstate.group(['rect1', 'rect2'], 'newGroup').select('newGroup')
it.todo('snaps the bend to zero when dragging the bend handle toward the center')
const beforeShapeIds = Object.keys(tlstate.page.shapes)
tlstate.duplicate()
expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length + 3)
tlstate.undo()
expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length)
tlstate.redo()
expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length + 3)
})
})
})

View file

@ -6,6 +6,8 @@ import type { Data, PagePartial, TLDrawCommand } from '~types'
export function duplicate(data: Data, ids: string[]): TLDrawCommand {
const { currentPageId } = data.appState
const page = TLDR.getPage(data, currentPageId)
const delta = Vec.div([16, 16], TLDR.getCamera(data, currentPageId).zoom)
const before: PagePartial = {
@ -22,24 +24,51 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
TLDR.getShape(data, id, currentPageId)
)
const cloneMap: Record<string, string> = {}
const duplicateMap: Record<string, string> = {}
// Create duplicates
shapes
.filter((shape) => !ids.includes(shape.parentId))
.forEach((shape) => {
const id = Utils.uniqueId()
before.shapes[id] = undefined
after.shapes[id] = {
...Utils.deepClone(shape),
id,
point: Vec.round(Vec.add(shape.point, delta)),
}
if (shape.children) {
after.shapes[id]!.children = []
}
duplicateMap[shape.id] = id
})
// If the shapes have children, then duplicate those too
shapes.forEach((shape) => {
const id = Utils.uniqueId()
before.shapes[id] = undefined
after.shapes[id] = {
...Utils.deepClone(shape),
id,
point: Vec.round(Vec.add(shape.point, delta)),
if (shape.children) {
shape.children.forEach((childId) => {
const child = TLDR.getShape(data, childId, currentPageId)
const duplicatedId = Utils.uniqueId()
const duplicatedParentId = duplicateMap[shape.id]
before.shapes[duplicatedId] = undefined
after.shapes[duplicatedId] = {
...Utils.deepClone(child),
id: duplicatedId,
parentId: duplicatedParentId,
}
duplicateMap[childId] = duplicatedId
after.shapes[duplicateMap[shape.id]]?.children?.push(duplicatedId)
})
}
cloneMap[shape.id] = id
})
const page = TLDR.getPage(data, currentPageId)
// Which ids did we end up duplicating?
const duplicatedShapeIds = Object.keys(duplicateMap)
// Handle bindings that effect duplicated shapes
Object.values(page.bindings).forEach((binding) => {
if (ids.includes(binding.fromId)) {
if (ids.includes(binding.toId)) {
if (duplicatedShapeIds.includes(binding.fromId)) {
if (duplicatedShapeIds.includes(binding.toId)) {
// If the binding is between two duplicating shapes then
// duplicate the binding, too
const duplicatedBindingId = Utils.uniqueId()
@ -47,8 +76,8 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
const duplicatedBinding = {
...Utils.deepClone(binding),
id: duplicatedBindingId,
fromId: cloneMap[binding.fromId],
toId: cloneMap[binding.toId],
fromId: duplicateMap[binding.fromId],
toId: duplicateMap[binding.toId],
}
before.bindings[duplicatedBindingId] = undefined
@ -65,7 +94,7 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
} else {
// If only the fromId is selected, delete the binding on
// the duplicated shape's handles
const boundShape = after.shapes[cloneMap[binding.fromId]]
const boundShape = after.shapes[duplicateMap[binding.fromId]]
Object.values(boundShape!.handles!).forEach((handle) => {
if (handle!.bindingId === binding.id) {
handle!.bindingId = undefined

View file

@ -178,10 +178,13 @@ export class TLDrawState extends StateManager<Data> {
parentId = shape.parentId
}
// If the shape is the child of a group, update the group
// If the shape is the child of a group, then update the group
// (unless the group is being deleted too)
if (parentId && parentId !== pageId) {
console.log('updating group', pageId, parentId, page.shapes[parentId])
groupsToUpdate.add(page.shapes[parentId] as GroupShape)
const group = page.shapes[parentId]
if (group) {
groupsToUpdate.add(page.shapes[parentId] as GroupShape)
}
}
})