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 */
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in a new issue