From 27d07a6e24391c82c30b0aec32e6b8decb1dd7ab Mon Sep 17 00:00:00 2001 From: Saul-Mirone Date: Tue, 25 Mar 2025 12:09:24 +0000 Subject: [PATCH] feat(editor): add command for edgeless clipboard (#11173) --- .../src/edgeless/clipboard/canvas.ts | 117 +++++++ .../src/edgeless/clipboard/clipboard.ts | 297 +----------------- .../src/edgeless/clipboard/command.ts | 220 +++++++++++++ .../src/edgeless/gfx-tool/default-tool.ts | 20 +- .../blocks/block-root/src/edgeless/index.ts | 1 + .../src/edgeless/utils/clipboard-utils.ts | 17 +- 6 files changed, 369 insertions(+), 303 deletions(-) create mode 100644 blocksuite/affine/blocks/block-root/src/edgeless/clipboard/canvas.ts create mode 100644 blocksuite/affine/blocks/block-root/src/edgeless/clipboard/command.ts diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/canvas.ts b/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/canvas.ts new file mode 100644 index 0000000000..ea370dc209 --- /dev/null +++ b/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/canvas.ts @@ -0,0 +1,117 @@ +import { + CanvasElementType, + type ClipboardConfigCreationContext, + EdgelessCRUDIdentifier, +} from '@blocksuite/affine-block-surface'; +import type { Connection } from '@blocksuite/affine-model'; +import { TelemetryProvider } from '@blocksuite/affine-shared/services'; +import type { BlockStdScope } from '@blocksuite/block-std'; +import type { + GfxPrimitiveElementModel, + SerializedElement, +} from '@blocksuite/block-std/gfx'; +import { Bound, type SerializedXYWH, Vec } from '@blocksuite/global/gfx'; +import * as Y from 'yjs'; + +const { GROUP, MINDMAP, CONNECTOR } = CanvasElementType; + +export function createCanvasElement( + std: BlockStdScope, + clipboardData: SerializedElement, + context: ClipboardConfigCreationContext, + newXYWH: SerializedXYWH +) { + if (clipboardData.type === GROUP) { + const yMap = new Y.Map(); + const children = clipboardData.children ?? {}; + + for (const [key, value] of Object.entries(children)) { + const newKey = context.oldToNewIdMap.get(key); + if (!newKey) { + console.error( + `Copy failed: cannot find the copied child in group, key: ${key}` + ); + return null; + } + yMap.set(newKey, value); + } + clipboardData.children = yMap; + clipboardData.xywh = newXYWH; + } else if (clipboardData.type === MINDMAP) { + const yMap = new Y.Map(); + const children = clipboardData.children ?? {}; + + for (const [oldKey, oldValue] of Object.entries(children)) { + const newKey = context.oldToNewIdMap.get(oldKey); + const newValue = { + ...oldValue, + }; + if (!newKey) { + console.error( + `Copy failed: cannot find the copied node in mind map, key: ${oldKey}` + ); + return null; + } + + if (oldValue.parent) { + const newParent = context.oldToNewIdMap.get(oldValue.parent); + if (!newParent) { + console.error( + `Copy failed: cannot find the copied node in mind map, parent: ${oldValue.parent}` + ); + return null; + } + newValue.parent = newParent; + } + + yMap.set(newKey, newValue); + } + clipboardData.children = yMap; + } else if (clipboardData.type === CONNECTOR) { + const source = clipboardData.source as Connection; + const target = clipboardData.target as Connection; + + const oldBound = Bound.deserialize(clipboardData.xywh); + const newBound = Bound.deserialize(newXYWH); + const offset = Vec.sub([newBound.x, newBound.y], [oldBound.x, oldBound.y]); + + if (source.id) { + source.id = context.oldToNewIdMap.get(source.id) ?? source.id; + } else if (source.position) { + source.position = Vec.add(source.position, offset); + } + + if (target.id) { + target.id = context.oldToNewIdMap.get(target.id) ?? target.id; + } else if (target.position) { + target.position = Vec.add(target.position, offset); + } + } else { + clipboardData.xywh = newXYWH; + } + + clipboardData.lockedBySelf = false; + + const crud = std.get(EdgelessCRUDIdentifier); + + const id = crud.addElement( + clipboardData.type as CanvasElementType, + clipboardData + ); + if (!id) { + return null; + } + std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { + control: 'canvas:paste', + page: 'whiteboard editor', + module: 'toolbar', + segment: 'toolbar', + type: clipboardData.type as string, + }); + const element = crud.getElementById(id) as GfxPrimitiveElementModel; + if (!element) { + console.error(`Copy failed: cannot find the copied element, id: ${id}`); + return null; + } + return element; +} diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts b/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts index 2416cc55df..e8901a47f5 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/clipboard.ts @@ -2,16 +2,12 @@ import { addAttachments } from '@blocksuite/affine-block-attachment'; import { EdgelessFrameManagerIdentifier } from '@blocksuite/affine-block-frame'; import { addImages } from '@blocksuite/affine-block-image'; import { - CanvasElementType, - type ClipboardConfigCreationContext, - EdgelessClipboardConfigIdentifier, EdgelessCRUDIdentifier, ExportManager, getSurfaceComponent, - SurfaceGroupLikeModel, TextUtils, } from '@blocksuite/affine-block-surface'; -import type { Connection, ShapeElementModel } from '@blocksuite/affine-model'; +import type { ShapeElementModel } from '@blocksuite/affine-model'; import { BookmarkStyles, DEFAULT_NOTE_HEIGHT, @@ -48,12 +44,9 @@ import type { import { compareLayer, type GfxBlockElementModel, - type GfxCompatibleProps, GfxControllerIdentifier, - type GfxModel, type GfxPrimitiveElementModel, type SerializedElement, - SortOrder, } from '@blocksuite/block-std/gfx'; import { DisposableGroup } from '@blocksuite/global/disposable'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; @@ -62,23 +55,16 @@ import { getCommonBound, type IBound, type IVec, - type SerializedXYWH, Vec, } from '@blocksuite/global/gfx'; -import { assertType } from '@blocksuite/global/utils'; -import { - type BlockSnapshot, - BlockSnapshotSchema, - type SliceSnapshot, -} from '@blocksuite/store'; +import { type BlockSnapshot, type SliceSnapshot } from '@blocksuite/store'; import * as Y from 'yjs'; import { PageClipboard } from '../../clipboard/index.js'; import { getSortedCloneElements } from '../utils/clone-utils.js'; import { isCanvasElementWithText } from '../utils/query.js'; +import { createElementsFromClipboardDataCommand } from './command.js'; import { - createNewPresentationIndexes, - edgelessElementsBoundFromRawData, isPureFileInClipboard, prepareClipboardData, tryGetSvgFromClipboard, @@ -86,7 +72,6 @@ import { const BLOCKSUITE_SURFACE = 'blocksuite/surface'; -const { GROUP, MINDMAP, CONNECTOR } = CanvasElementType; const IMAGE_PADDING = 5; // for rotated shapes some padding is needed interface CanvasExportOptions { @@ -397,107 +382,6 @@ export class EdgelessClipboardController extends PageClipboard { } } - private _createCanvasElement( - clipboardData: SerializedElement, - context: ClipboardConfigCreationContext, - newXYWH: SerializedXYWH - ): GfxPrimitiveElementModel | null { - if (clipboardData.type === GROUP) { - const yMap = new Y.Map(); - const children = clipboardData.children ?? {}; - - for (const [key, value] of Object.entries(children)) { - const newKey = context.oldToNewIdMap.get(key); - if (!newKey) { - console.error( - `Copy failed: cannot find the copied child in group, key: ${key}` - ); - return null; - } - yMap.set(newKey, value); - } - clipboardData.children = yMap; - clipboardData.xywh = newXYWH; - } else if (clipboardData.type === MINDMAP) { - const yMap = new Y.Map(); - const children = clipboardData.children ?? {}; - - for (const [oldKey, oldValue] of Object.entries(children)) { - const newKey = context.oldToNewIdMap.get(oldKey); - const newValue = { - ...oldValue, - }; - if (!newKey) { - console.error( - `Copy failed: cannot find the copied node in mind map, key: ${oldKey}` - ); - return null; - } - - if (oldValue.parent) { - const newParent = context.oldToNewIdMap.get(oldValue.parent); - if (!newParent) { - console.error( - `Copy failed: cannot find the copied node in mind map, parent: ${oldValue.parent}` - ); - return null; - } - newValue.parent = newParent; - } - - yMap.set(newKey, newValue); - } - clipboardData.children = yMap; - } else if (clipboardData.type === CONNECTOR) { - const source = clipboardData.source as Connection; - const target = clipboardData.target as Connection; - - const oldBound = Bound.deserialize(clipboardData.xywh); - const newBound = Bound.deserialize(newXYWH); - const offset = Vec.sub( - [newBound.x, newBound.y], - [oldBound.x, oldBound.y] - ); - - if (source.id) { - source.id = context.oldToNewIdMap.get(source.id) ?? source.id; - } else if (source.position) { - source.position = Vec.add(source.position, offset); - } - - if (target.id) { - target.id = context.oldToNewIdMap.get(target.id) ?? target.id; - } else if (target.position) { - target.position = Vec.add(target.position, offset); - } - } else { - clipboardData.xywh = newXYWH; - } - - clipboardData.lockedBySelf = false; - - const id = this.crud.addElement( - clipboardData.type as CanvasElementType, - clipboardData - ); - if (!id) { - return null; - } - this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { - control: 'canvas:paste', - page: 'whiteboard editor', - module: 'toolbar', - segment: 'toolbar', - type: clipboardData.type as string, - }); - const element = this.crud.getElementById(id) as GfxPrimitiveElementModel; - if (!element) { - console.error(`Copy failed: cannot find the copied element, id: ${id}`); - return null; - } - return element; - } - private async _edgelessToCanvas( bound: IBound, nodes?: GfxBlockElementModel[], @@ -680,8 +564,14 @@ export class EdgelessClipboardController extends PageClipboard { private async _pasteShapesAndBlocks( elementsRawData: (SerializedElement | BlockSnapshot)[] ) { - const { canvasElements, blockModels } = - await this.createElementsFromClipboardData(elementsRawData); + const [_, { createdElementsPromise }] = this.std.command.exec( + createElementsFromClipboardDataCommand, + { + elementsRawData, + } + ); + if (!createdElementsPromise) return; + const { canvasElements, blockModels } = await createdElementsPromise; this._emitSelectionChangeAfterPaste( canvasElements.map(ele => ele.id), blockModels.map(block => block.id) @@ -761,51 +651,6 @@ export class EdgelessClipboardController extends PageClipboard { }); } - private _updatePastedElementsIndex( - elements: GfxModel[], - originalIndexes: Map - ) { - function compare(a: GfxModel, b: GfxModel) { - if (a instanceof SurfaceGroupLikeModel && a.hasDescendant(b)) { - return SortOrder.BEFORE; - } else if (b instanceof SurfaceGroupLikeModel && b.hasDescendant(a)) { - return SortOrder.AFTER; - } else { - const aGroups = a.groups as SurfaceGroupLikeModel[]; - const bGroups = b.groups as SurfaceGroupLikeModel[]; - - let i = 1; - let aGroup: GfxModel | undefined = aGroups.at(-i); - let bGroup: GfxModel | undefined = bGroups.at(-i); - - while (aGroup === bGroup && aGroup) { - ++i; - aGroup = aGroups.at(-i); - bGroup = bGroups.at(-i); - } - - aGroup = aGroup ?? a; - bGroup = bGroup ?? b; - - return originalIndexes.get(aGroup.id) === originalIndexes.get(bGroup.id) - ? SortOrder.SAME - : originalIndexes.get(aGroup.id)! < originalIndexes.get(bGroup.id)! - ? SortOrder.BEFORE - : SortOrder.AFTER; - } - } - - const idxGenerator = this.gfx.layer.createIndexGenerator(); - const sortedElements = elements.sort(compare); - sortedElements.forEach(ele => { - const newIndex = idxGenerator(); - - this.crud.updateElement(ele.id, { - index: newIndex, - }); - }); - } - copy() { document.dispatchEvent( new Event('copy', { @@ -815,126 +660,6 @@ export class EdgelessClipboardController extends PageClipboard { ); } - async createElementsFromClipboardData( - elementsRawData: (SerializedElement | BlockSnapshot)[], - pasteCenter?: IVec - ) { - let oldCommonBound, pasteX, pasteY; - { - const lastMousePos = this.toolManager.lastMousePos$.peek(); - pasteCenter = - pasteCenter ?? - this.gfx.viewport.toModelCoord(lastMousePos.x, lastMousePos.y); - const [modelX, modelY] = pasteCenter; - oldCommonBound = edgelessElementsBoundFromRawData(elementsRawData); - - pasteX = modelX - oldCommonBound.w / 2; - pasteY = modelY - oldCommonBound.h / 2; - } - - const getNewXYWH = (oldXYWH: SerializedXYWH) => { - const oldBound = Bound.deserialize(oldXYWH); - return new Bound( - oldBound.x + pasteX - oldCommonBound.x, - oldBound.y + pasteY - oldCommonBound.y, - oldBound.w, - oldBound.h - ).serialize(); - }; - - // create blocks and canvas elements - - const context: ClipboardConfigCreationContext = { - oldToNewIdMap: new Map(), - originalIndexes: new Map(), - newPresentationIndexes: createNewPresentationIndexes( - elementsRawData, - this.std - ), - }; - - const blockModels: GfxBlockElementModel[] = []; - const canvasElements: GfxPrimitiveElementModel[] = []; - const allElements: GfxModel[] = []; - - for (const data of elementsRawData) { - const { data: blockSnapshot } = BlockSnapshotSchema.safeParse(data); - if (blockSnapshot) { - const oldId = blockSnapshot.id; - - const config = this.std.getOptional( - EdgelessClipboardConfigIdentifier(blockSnapshot.flavour) - ); - if (!config) continue; - - if (typeof blockSnapshot.props.index !== 'string') { - console.error(`Block(id: ${oldId}) does not have index property`); - continue; - } - const originalIndex = (blockSnapshot.props as GfxCompatibleProps).index; - - if (typeof blockSnapshot.props.xywh !== 'string') { - console.error(`Block(id: ${oldId}) does not have xywh property`); - continue; - } - - assertType(blockSnapshot.props); - - blockSnapshot.props.xywh = getNewXYWH( - blockSnapshot.props.xywh as SerializedXYWH - ); - blockSnapshot.props.lockedBySelf = false; - - const newId = await config.createBlock(blockSnapshot, context); - if (!newId) continue; - - const block = this.doc.getBlock(newId); - if (!block) continue; - - assertType(block.model); - blockModels.push(block.model); - allElements.push(block.model); - context.oldToNewIdMap.set(oldId, newId); - context.originalIndexes.set(oldId, originalIndex); - } else { - assertType(data); - const oldId = data.id; - - const element = this._createCanvasElement( - data, - context, - getNewXYWH(data.xywh) - ); - - if (!element) continue; - - canvasElements.push(element); - allElements.push(element); - - context.oldToNewIdMap.set(oldId, element.id); - context.originalIndexes.set(oldId, element.index); - } - } - - // remap old id to new id for the original index - const oldIds = [...context.originalIndexes.keys()]; - oldIds.forEach(oldId => { - const newId = context.oldToNewIdMap.get(oldId); - const originalIndex = context.originalIndexes.get(oldId); - if (newId && originalIndex) { - context.originalIndexes.set(newId, originalIndex); - context.originalIndexes.delete(oldId); - } - }); - - this._updatePastedElementsIndex(allElements, context.originalIndexes); - - return { - canvasElements: canvasElements, - blockModels: blockModels, - }; - } - override mounted() { if (!navigator.clipboard) { console.error( diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/command.ts b/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/command.ts new file mode 100644 index 0000000000..3617c2c1da --- /dev/null +++ b/blocksuite/affine/blocks/block-root/src/edgeless/clipboard/command.ts @@ -0,0 +1,220 @@ +import { + type ClipboardConfigCreationContext, + EdgelessClipboardConfigIdentifier, + EdgelessCRUDIdentifier, + SurfaceGroupLikeModel, +} from '@blocksuite/affine-block-surface'; +import type { BlockStdScope, Command } from '@blocksuite/block-std'; +import { + type GfxBlockElementModel, + type GfxCompatibleProps, + GfxControllerIdentifier, + type GfxModel, + type GfxPrimitiveElementModel, + type SerializedElement, + SortOrder, +} from '@blocksuite/block-std/gfx'; +import { Bound, type IVec, type SerializedXYWH } from '@blocksuite/global/gfx'; +import { assertType } from '@blocksuite/global/utils'; +import { type BlockSnapshot, BlockSnapshotSchema } from '@blocksuite/store'; + +import { createCanvasElement } from './canvas'; +import { + createNewPresentationIndexes, + edgelessElementsBoundFromRawData, +} from './utils'; + +interface Input { + elementsRawData: (SerializedElement | BlockSnapshot)[]; + pasteCenter?: IVec; +} + +type CreatedElements = { + canvasElements: GfxPrimitiveElementModel[]; + blockModels: GfxBlockElementModel[]; +}; + +interface Output { + createdElementsPromise: Promise; +} + +export const createElementsFromClipboardDataCommand: Command = ( + ctx, + next +) => { + const { std, elementsRawData } = ctx; + let { pasteCenter } = ctx; + + const gfx = std.get(GfxControllerIdentifier); + const toolManager = gfx.tool; + + const runner = async (): Promise => { + let oldCommonBound, pasteX, pasteY; + { + const lastMousePos = toolManager.lastMousePos$.peek(); + pasteCenter = + pasteCenter ?? + gfx.viewport.toModelCoord(lastMousePos.x, lastMousePos.y); + const [modelX, modelY] = pasteCenter; + oldCommonBound = edgelessElementsBoundFromRawData(elementsRawData); + + pasteX = modelX - oldCommonBound.w / 2; + pasteY = modelY - oldCommonBound.h / 2; + } + + const getNewXYWH = (oldXYWH: SerializedXYWH) => { + const oldBound = Bound.deserialize(oldXYWH); + return new Bound( + oldBound.x + pasteX - oldCommonBound.x, + oldBound.y + pasteY - oldCommonBound.y, + oldBound.w, + oldBound.h + ).serialize(); + }; + + // create blocks and canvas elements + + const context: ClipboardConfigCreationContext = { + oldToNewIdMap: new Map(), + originalIndexes: new Map(), + newPresentationIndexes: createNewPresentationIndexes( + elementsRawData, + std + ), + }; + + const blockModels: GfxBlockElementModel[] = []; + const canvasElements: GfxPrimitiveElementModel[] = []; + const allElements: GfxModel[] = []; + + for (const data of elementsRawData) { + const { data: blockSnapshot } = BlockSnapshotSchema.safeParse(data); + if (blockSnapshot) { + const oldId = blockSnapshot.id; + + const config = std.getOptional( + EdgelessClipboardConfigIdentifier(blockSnapshot.flavour) + ); + if (!config) continue; + + if (typeof blockSnapshot.props.index !== 'string') { + console.error(`Block(id: ${oldId}) does not have index property`); + continue; + } + const originalIndex = (blockSnapshot.props as GfxCompatibleProps).index; + + if (typeof blockSnapshot.props.xywh !== 'string') { + console.error(`Block(id: ${oldId}) does not have xywh property`); + continue; + } + + assertType(blockSnapshot.props); + + blockSnapshot.props.xywh = getNewXYWH( + blockSnapshot.props.xywh as SerializedXYWH + ); + blockSnapshot.props.lockedBySelf = false; + + const newId = await config.createBlock(blockSnapshot, context); + if (!newId) continue; + + const block = std.store.getBlock(newId); + if (!block) continue; + + assertType(block.model); + blockModels.push(block.model); + allElements.push(block.model); + context.oldToNewIdMap.set(oldId, newId); + context.originalIndexes.set(oldId, originalIndex); + } else { + assertType(data); + const oldId = data.id; + + const element = createCanvasElement( + std, + data, + context, + getNewXYWH(data.xywh) + ); + + if (!element) continue; + + canvasElements.push(element); + allElements.push(element); + + context.oldToNewIdMap.set(oldId, element.id); + context.originalIndexes.set(oldId, element.index); + } + } + + // remap old id to new id for the original index + const oldIds = [...context.originalIndexes.keys()]; + oldIds.forEach(oldId => { + const newId = context.oldToNewIdMap.get(oldId); + const originalIndex = context.originalIndexes.get(oldId); + if (newId && originalIndex) { + context.originalIndexes.set(newId, originalIndex); + context.originalIndexes.delete(oldId); + } + }); + + updatePastedElementsIndex(std, allElements, context.originalIndexes); + + return { + canvasElements: canvasElements, + blockModels: blockModels, + }; + }; + + return next({ + createdElementsPromise: runner(), + }); +}; + +function updatePastedElementsIndex( + std: BlockStdScope, + elements: GfxModel[], + originalIndexes: Map +) { + const gfx = std.get(GfxControllerIdentifier); + const crud = std.get(EdgelessCRUDIdentifier); + function compare(a: GfxModel, b: GfxModel) { + if (a instanceof SurfaceGroupLikeModel && a.hasDescendant(b)) { + return SortOrder.BEFORE; + } else if (b instanceof SurfaceGroupLikeModel && b.hasDescendant(a)) { + return SortOrder.AFTER; + } else { + const aGroups = a.groups as SurfaceGroupLikeModel[]; + const bGroups = b.groups as SurfaceGroupLikeModel[]; + + let i = 1; + let aGroup: GfxModel | undefined = aGroups.at(-i); + let bGroup: GfxModel | undefined = bGroups.at(-i); + + while (aGroup === bGroup && aGroup) { + ++i; + aGroup = aGroups.at(-i); + bGroup = bGroups.at(-i); + } + + aGroup = aGroup ?? a; + bGroup = bGroup ?? b; + + return originalIndexes.get(aGroup.id) === originalIndexes.get(bGroup.id) + ? SortOrder.SAME + : originalIndexes.get(aGroup.id)! < originalIndexes.get(bGroup.id)! + ? SortOrder.BEFORE + : SortOrder.AFTER; + } + } + + const idxGenerator = gfx.layer.createIndexGenerator(); + const sortedElements = elements.sort(compare); + sortedElements.forEach(ele => { + const newIndex = idxGenerator(); + + crud.updateElement(ele.id, { + index: newIndex, + }); + }); +} diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts b/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts index 1cd70e7e95..eb8faf067c 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/gfx-tool/default-tool.ts @@ -49,7 +49,7 @@ import { effect } from '@preact/signals-core'; import clamp from 'lodash-es/clamp'; import last from 'lodash-es/last'; -import { EdgelessClipboardController } from '../clipboard/clipboard.js'; +import { createElementsFromClipboardDataCommand } from '../clipboard/command.js'; import { prepareCloneData } from '../utils/clone-utils.js'; import { calPanDelta } from '../utils/panning-utils.js'; import { isCanvasElement, isEdgelessTextBlock } from '../utils/query.js'; @@ -238,18 +238,18 @@ export class DefaultTool extends BaseTool { private async _cloneContent() { if (!this._edgeless) return; - const clipboardController = this.std.getOptional( - EdgelessClipboardController - ); - if (!clipboardController) return; const snapshot = prepareCloneData(this._toBeMoved, this.std); const bound = getCommonBoundWithRotation(this._toBeMoved); - const { canvasElements, blockModels } = - await clipboardController.createElementsFromClipboardData( - snapshot, - bound.center - ); + const [_, { createdElementsPromise }] = this.std.command.exec( + createElementsFromClipboardDataCommand, + { + elementsRawData: snapshot, + pasteCenter: bound.center, + } + ); + if (!createdElementsPromise) return; + const { canvasElements, blockModels } = await createdElementsPromise; this._toBeMoved = [...canvasElements, ...blockModels]; this.edgelessSelectionManager.set({ diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/index.ts b/blocksuite/affine/blocks/block-root/src/edgeless/index.ts index 698ac73c9d..bdd6869aa7 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/index.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/index.ts @@ -1,4 +1,5 @@ export * from './clipboard/clipboard'; +export * from './clipboard/command'; export { EdgelessTemplatePanel } from './components/toolbar/template/template-panel.js'; export * from './components/toolbar/template/template-type.js'; export * from './edgeless-root-block.js'; diff --git a/blocksuite/affine/blocks/block-root/src/edgeless/utils/clipboard-utils.ts b/blocksuite/affine/blocks/block-root/src/edgeless/utils/clipboard-utils.ts index f65bddf09e..7698c50741 100644 --- a/blocksuite/affine/blocks/block-root/src/edgeless/utils/clipboard-utils.ts +++ b/blocksuite/affine/blocks/block-root/src/edgeless/utils/clipboard-utils.ts @@ -20,7 +20,7 @@ import { import { getCommonBoundWithRotation } from '@blocksuite/global/gfx'; import groupBy from 'lodash-es/groupBy'; -import { EdgelessClipboardController } from '../clipboard/clipboard.js'; +import { createElementsFromClipboardDataCommand } from '../clipboard/command.js'; import { getSortedCloneElements, prepareCloneData } from './clone-utils.js'; import { isEdgelessTextBlock, @@ -34,7 +34,6 @@ export async function duplicate( elements: GfxModel[], select = true ) { - const edgelessClipboard = edgeless.std.get(EdgelessClipboardController); const gfx = edgeless.std.get(GfxControllerIdentifier); const surface = getSurfaceComponent(edgeless.std); @@ -45,11 +44,15 @@ export async function duplicate( totalBound.x += totalBound.w + offset; const snapshot = prepareCloneData(copyElements, edgeless.std); - const { canvasElements, blockModels } = - await edgelessClipboard.createElementsFromClipboardData( - snapshot, - totalBound.center - ); + const [_, { createdElementsPromise }] = edgeless.std.command.exec( + createElementsFromClipboardDataCommand, + { + elementsRawData: snapshot, + pasteCenter: totalBound.center, + } + ); + if (!createdElementsPromise) return; + const { canvasElements, blockModels } = await createdElementsPromise; const newElements = [...canvasElements, ...blockModels];