Handles groups when duplicating shapes
This commit is contained in:
parent
a1a213f9b4
commit
9f9845780f
3 changed files with 67 additions and 20 deletions
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
import { TLDrawState } from '~state'
|
import { TLDrawState } from '~state'
|
||||||
import { mockDocument } from '~test'
|
import { mockDocument } from '~test'
|
||||||
import { ArrowShape, TLDrawShapeType } from '~types'
|
import { ArrowShape, GroupShape, TLDrawShapeType } from '~types'
|
||||||
|
|
||||||
describe('Duplicate command', () => {
|
describe('Duplicate command', () => {
|
||||||
const tlstate = new TLDrawState()
|
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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,6 +6,8 @@ import type { Data, PagePartial, TLDrawCommand } from '~types'
|
||||||
export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
||||||
const { currentPageId } = data.appState
|
const { currentPageId } = data.appState
|
||||||
|
|
||||||
|
const page = TLDR.getPage(data, currentPageId)
|
||||||
|
|
||||||
const delta = Vec.div([16, 16], TLDR.getCamera(data, currentPageId).zoom)
|
const delta = Vec.div([16, 16], TLDR.getCamera(data, currentPageId).zoom)
|
||||||
|
|
||||||
const before: PagePartial = {
|
const before: PagePartial = {
|
||||||
|
@ -22,9 +24,12 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
||||||
TLDR.getShape(data, id, currentPageId)
|
TLDR.getShape(data, id, currentPageId)
|
||||||
)
|
)
|
||||||
|
|
||||||
const cloneMap: Record<string, string> = {}
|
const duplicateMap: Record<string, string> = {}
|
||||||
|
|
||||||
shapes.forEach((shape) => {
|
// Create duplicates
|
||||||
|
shapes
|
||||||
|
.filter((shape) => !ids.includes(shape.parentId))
|
||||||
|
.forEach((shape) => {
|
||||||
const id = Utils.uniqueId()
|
const id = Utils.uniqueId()
|
||||||
before.shapes[id] = undefined
|
before.shapes[id] = undefined
|
||||||
after.shapes[id] = {
|
after.shapes[id] = {
|
||||||
|
@ -32,14 +37,38 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
||||||
id,
|
id,
|
||||||
point: Vec.round(Vec.add(shape.point, delta)),
|
point: Vec.round(Vec.add(shape.point, delta)),
|
||||||
}
|
}
|
||||||
cloneMap[shape.id] = id
|
if (shape.children) {
|
||||||
|
after.shapes[id]!.children = []
|
||||||
|
}
|
||||||
|
duplicateMap[shape.id] = id
|
||||||
})
|
})
|
||||||
|
|
||||||
const page = TLDR.getPage(data, currentPageId)
|
// If the shapes have children, then duplicate those too
|
||||||
|
shapes.forEach((shape) => {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Which ids did we end up duplicating?
|
||||||
|
const duplicatedShapeIds = Object.keys(duplicateMap)
|
||||||
|
|
||||||
|
// Handle bindings that effect duplicated shapes
|
||||||
Object.values(page.bindings).forEach((binding) => {
|
Object.values(page.bindings).forEach((binding) => {
|
||||||
if (ids.includes(binding.fromId)) {
|
if (duplicatedShapeIds.includes(binding.fromId)) {
|
||||||
if (ids.includes(binding.toId)) {
|
if (duplicatedShapeIds.includes(binding.toId)) {
|
||||||
// If the binding is between two duplicating shapes then
|
// If the binding is between two duplicating shapes then
|
||||||
// duplicate the binding, too
|
// duplicate the binding, too
|
||||||
const duplicatedBindingId = Utils.uniqueId()
|
const duplicatedBindingId = Utils.uniqueId()
|
||||||
|
@ -47,8 +76,8 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
||||||
const duplicatedBinding = {
|
const duplicatedBinding = {
|
||||||
...Utils.deepClone(binding),
|
...Utils.deepClone(binding),
|
||||||
id: duplicatedBindingId,
|
id: duplicatedBindingId,
|
||||||
fromId: cloneMap[binding.fromId],
|
fromId: duplicateMap[binding.fromId],
|
||||||
toId: cloneMap[binding.toId],
|
toId: duplicateMap[binding.toId],
|
||||||
}
|
}
|
||||||
|
|
||||||
before.bindings[duplicatedBindingId] = undefined
|
before.bindings[duplicatedBindingId] = undefined
|
||||||
|
@ -65,7 +94,7 @@ export function duplicate(data: Data, ids: string[]): TLDrawCommand {
|
||||||
} else {
|
} else {
|
||||||
// If only the fromId is selected, delete the binding on
|
// If only the fromId is selected, delete the binding on
|
||||||
// the duplicated shape's handles
|
// 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) => {
|
Object.values(boundShape!.handles!).forEach((handle) => {
|
||||||
if (handle!.bindingId === binding.id) {
|
if (handle!.bindingId === binding.id) {
|
||||||
handle!.bindingId = undefined
|
handle!.bindingId = undefined
|
||||||
|
|
|
@ -178,11 +178,14 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
parentId = shape.parentId
|
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) {
|
if (parentId && parentId !== pageId) {
|
||||||
console.log('updating group', pageId, parentId, page.shapes[parentId])
|
const group = page.shapes[parentId]
|
||||||
|
if (group) {
|
||||||
groupsToUpdate.add(page.shapes[parentId] as GroupShape)
|
groupsToUpdate.add(page.shapes[parentId] as GroupShape)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// If binding is undefined, delete the binding
|
// If binding is undefined, delete the binding
|
||||||
|
|
Loading…
Reference in a new issue