refactor: migrate elements clone from default-tool to extension (#11703)
### Changed - Add `onRequestElementsClone` action to allow extension to define how to clone gfx elements
This commit is contained in:
@@ -29,6 +29,7 @@ import { EdgelessRootBlockSpec } from './edgeless-root-spec.js';
|
|||||||
import { DefaultTool } from './gfx-tool/default-tool.js';
|
import { DefaultTool } from './gfx-tool/default-tool.js';
|
||||||
import { EmptyTool } from './gfx-tool/empty-tool.js';
|
import { EmptyTool } from './gfx-tool/empty-tool.js';
|
||||||
import { PanTool } from './gfx-tool/pan-tool.js';
|
import { PanTool } from './gfx-tool/pan-tool.js';
|
||||||
|
import { AltCloneExtension } from './interact-extensions/clone-ext.js';
|
||||||
import { DblClickAddEdgelessText } from './interact-extensions/dblclick-add-edgeless-text.js';
|
import { DblClickAddEdgelessText } from './interact-extensions/dblclick-add-edgeless-text.js';
|
||||||
import { SnapExtension } from './interact-extensions/snap-manager.js';
|
import { SnapExtension } from './interact-extensions/snap-manager.js';
|
||||||
import { EditPropsMiddlewareBuilder } from './middlewares/base.js';
|
import { EditPropsMiddlewareBuilder } from './middlewares/base.js';
|
||||||
@@ -63,6 +64,7 @@ export const EdgelessBuiltInManager: ExtensionType[] = [
|
|||||||
ConnectionOverlay,
|
ConnectionOverlay,
|
||||||
MindMapIndicatorOverlay,
|
MindMapIndicatorOverlay,
|
||||||
SnapOverlay,
|
SnapOverlay,
|
||||||
|
AltCloneExtension,
|
||||||
EditPropsMiddlewareBuilder,
|
EditPropsMiddlewareBuilder,
|
||||||
EdgelessElementToolbarExtension,
|
EdgelessElementToolbarExtension,
|
||||||
].flat();
|
].flat();
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import {
|
|||||||
import { resetNativeSelection } from '@blocksuite/affine-shared/utils';
|
import { resetNativeSelection } from '@blocksuite/affine-shared/utils';
|
||||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||||
import type { IVec } from '@blocksuite/global/gfx';
|
import type { IVec } from '@blocksuite/global/gfx';
|
||||||
import { Bound, getCommonBoundWithRotation, Vec } from '@blocksuite/global/gfx';
|
import { Bound, Vec } from '@blocksuite/global/gfx';
|
||||||
import type { BlockComponent, PointerEventState } from '@blocksuite/std';
|
import type { PointerEventState } from '@blocksuite/std';
|
||||||
import {
|
import {
|
||||||
BaseTool,
|
BaseTool,
|
||||||
getTopElements,
|
getTopElements,
|
||||||
@@ -28,8 +28,6 @@ import {
|
|||||||
} from '@blocksuite/std/gfx';
|
} from '@blocksuite/std/gfx';
|
||||||
import { effect } from '@preact/signals-core';
|
import { effect } from '@preact/signals-core';
|
||||||
|
|
||||||
import { createElementsFromClipboardDataCommand } from '../clipboard/command.js';
|
|
||||||
import { prepareCloneData } from '../utils/clone-utils.js';
|
|
||||||
import { calPanDelta } from '../utils/panning-utils.js';
|
import { calPanDelta } from '../utils/panning-utils.js';
|
||||||
import { isCanvasElement } from '../utils/query.js';
|
import { isCanvasElement } from '../utils/query.js';
|
||||||
import { DefaultModeDragType } from './default-tool-ext/ext.js';
|
import { DefaultModeDragType } from './default-tool-ext/ext.js';
|
||||||
@@ -170,10 +168,6 @@ export class DefaultTool extends BaseTool {
|
|||||||
|
|
||||||
enableHover = true;
|
enableHover = true;
|
||||||
|
|
||||||
private get _edgeless(): BlockComponent | null {
|
|
||||||
return this.std.view.getBlock(this.doc.root!.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the end position of the dragging area in the model coordinate
|
* Get the end position of the dragging area in the model coordinate
|
||||||
*/
|
*/
|
||||||
@@ -205,22 +199,13 @@ export class DefaultTool extends BaseTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _cloneContent() {
|
private async _cloneContent() {
|
||||||
if (!this._edgeless) return;
|
const clonedResult = await this.interactivity?.requestElementsClone({
|
||||||
|
elements: this._toBeMoved,
|
||||||
|
});
|
||||||
|
|
||||||
const snapshot = prepareCloneData(this._toBeMoved, this.std);
|
if (!clonedResult) return;
|
||||||
|
|
||||||
const bound = getCommonBoundWithRotation(this._toBeMoved);
|
this._toBeMoved = clonedResult.elements;
|
||||||
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({
|
this.edgelessSelectionManager.set({
|
||||||
elements: this._toBeMoved.map(e => e.id),
|
elements: this._toBeMoved.map(e => e.id),
|
||||||
editing: false,
|
editing: false,
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { getCommonBoundWithRotation } from '@blocksuite/global/gfx';
|
||||||
|
import { type GfxModel, InteractivityExtension } from '@blocksuite/std/gfx';
|
||||||
|
|
||||||
|
import { createElementsFromClipboardDataCommand } from '../clipboard/command.js';
|
||||||
|
import { prepareCloneData } from '../utils/clone-utils.js';
|
||||||
|
|
||||||
|
export class AltCloneExtension extends InteractivityExtension {
|
||||||
|
static override key = 'alt-clone';
|
||||||
|
|
||||||
|
override mounted(): void {
|
||||||
|
this.action.onRequestElementsClone(async context => {
|
||||||
|
const { elements: elementsToClone } = context;
|
||||||
|
const snapshot = prepareCloneData(elementsToClone, this.std);
|
||||||
|
|
||||||
|
const bound = getCommonBoundWithRotation(elementsToClone);
|
||||||
|
const [_, { createdElementsPromise }] = this.std.command.exec(
|
||||||
|
createElementsFromClipboardDataCommand,
|
||||||
|
{
|
||||||
|
elementsRawData: snapshot,
|
||||||
|
pasteCenter: bound.center,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!createdElementsPromise) return;
|
||||||
|
const { canvasElements, blockModels } = await createdElementsPromise;
|
||||||
|
|
||||||
|
return {
|
||||||
|
elements: [...canvasElements, ...blockModels] as GfxModel[],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,9 @@ import { Extension } from '@blocksuite/store';
|
|||||||
import type { PointerEventState } from '../../../event/index.js';
|
import type { PointerEventState } from '../../../event/index.js';
|
||||||
import type { GfxController } from '../../controller.js';
|
import type { GfxController } from '../../controller.js';
|
||||||
import { GfxControllerIdentifier } from '../../identifiers.js';
|
import { GfxControllerIdentifier } from '../../identifiers.js';
|
||||||
|
import type { GfxModel } from '../../model/model.js';
|
||||||
import type { SupportedEvents } from '../event.js';
|
import type { SupportedEvents } from '../event.js';
|
||||||
|
import type { ExtensionElementsCloneContext } from '../types/clone.js';
|
||||||
import type {
|
import type {
|
||||||
DragExtensionInitializeContext,
|
DragExtensionInitializeContext,
|
||||||
ExtensionDragEndContext,
|
ExtensionDragEndContext,
|
||||||
@@ -105,11 +107,22 @@ type ActionContextMap = {
|
|||||||
clear?: () => void;
|
clear?: () => void;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
elementsClone: {
|
||||||
|
context: ExtensionElementsCloneContext;
|
||||||
|
returnType: Promise<
|
||||||
|
| {
|
||||||
|
elements: GfxModel[];
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export class InteractivityActionAPI {
|
export class InteractivityActionAPI {
|
||||||
private readonly _handlers: Partial<{
|
private readonly _handlers: Partial<{
|
||||||
dragInitialize: Parameters<InteractivityActionAPI['onDragInitialize']>[0];
|
[K in keyof ActionContextMap]: (
|
||||||
|
ctx: ActionContextMap[K]['context']
|
||||||
|
) => ActionContextMap[K]['returnType'];
|
||||||
}> = {};
|
}> = {};
|
||||||
|
|
||||||
onDragInitialize(
|
onDragInitialize(
|
||||||
@@ -124,6 +137,18 @@ export class InteractivityActionAPI {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRequestElementsClone(
|
||||||
|
handler: (
|
||||||
|
ctx: ActionContextMap['elementsClone']['context']
|
||||||
|
) => ActionContextMap['elementsClone']['returnType']
|
||||||
|
) {
|
||||||
|
this._handlers['elementsClone'] = handler;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
return delete this._handlers['elementsClone'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
emit<K extends keyof ActionContextMap>(
|
emit<K extends keyof ActionContextMap>(
|
||||||
event: K,
|
event: K,
|
||||||
context: ActionContextMap[K]['context']
|
context: ActionContextMap[K]['context']
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
InteractivityExtensionIdentifier,
|
InteractivityExtensionIdentifier,
|
||||||
} from './extension/base.js';
|
} from './extension/base.js';
|
||||||
import { GfxViewEventManager } from './gfx-view-event-handler.js';
|
import { GfxViewEventManager } from './gfx-view-event-handler.js';
|
||||||
|
import type { RequestElementsCloneContext } from './types/clone.js';
|
||||||
import type {
|
import type {
|
||||||
DragExtensionInitializeContext,
|
DragExtensionInitializeContext,
|
||||||
DragInitializationOption,
|
DragInitializationOption,
|
||||||
@@ -288,4 +289,21 @@ export class InteractivityManager extends GfxExtension {
|
|||||||
listenEvent();
|
listenEvent();
|
||||||
dragStart();
|
dragStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestElementsClone(options: RequestElementsCloneContext) {
|
||||||
|
const extensions = this.interactExtensions;
|
||||||
|
|
||||||
|
for (let ext of extensions.values()) {
|
||||||
|
const cloneData = (ext.action as InteractivityActionAPI).emit(
|
||||||
|
'elementsClone',
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cloneData) {
|
||||||
|
return cloneData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { GfxModel } from '../../model/model';
|
||||||
|
|
||||||
|
export type ExtensionElementsCloneContext = {
|
||||||
|
elements: GfxModel[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RequestElementsCloneContext = ExtensionElementsCloneContext;
|
||||||
Reference in New Issue
Block a user