diff --git a/blocksuite/affine/blocks/frame/src/frame-highlight-manager.ts b/blocksuite/affine/blocks/frame/src/frame-highlight-manager.ts index a0f021fe3d..e631591a64 100644 --- a/blocksuite/affine/blocks/frame/src/frame-highlight-manager.ts +++ b/blocksuite/affine/blocks/frame/src/frame-highlight-manager.ts @@ -5,12 +5,9 @@ import { } from '@blocksuite/affine-model'; import { type DragExtensionInitializeContext, - type ExtensionDragEndContext, - type ExtensionDragMoveContext, - type ExtensionDragStartContext, getTopElements, GfxExtensionIdentifier, - TransformExtension, + InteractivityExtension, } from '@blocksuite/std/gfx'; import { @@ -19,7 +16,7 @@ import { isFrameBlock, } from './frame-manager'; -export class FrameHighlightManager extends TransformExtension { +export class FrameHighlightManager extends InteractivityExtension { static override key = 'frame-highlight-manager'; get frameMgr() { @@ -32,57 +29,54 @@ export class FrameHighlightManager extends TransformExtension { return this.std.getOptional(OverlayIdentifier('frame')) as FrameOverlay; } - override onDragInitialize(_: DragExtensionInitializeContext): { - onDragStart?: (context: ExtensionDragStartContext) => void; - onDragMove?: (context: ExtensionDragMoveContext) => void; - onDragEnd?: (context: ExtensionDragEndContext) => void; - clear?: () => void; - } { - if (!this.frameMgr || !this.frameHighlightOverlay) { - return {}; - } + override mounted() { + this.action.onDragInitialize((_: DragExtensionInitializeContext) => { + if (!this.frameMgr || !this.frameHighlightOverlay) { + return {}; + } - let hoveredFrame: FrameBlockModel | null = null; - const { frameMgr, frameHighlightOverlay } = this; - let draggedFrames: FrameBlockModel[] = []; + let hoveredFrame: FrameBlockModel | null = null; + const { frameMgr, frameHighlightOverlay } = this; + let draggedFrames: FrameBlockModel[] = []; - return { - onDragStart(context) { - draggedFrames = context.elements - .map(elem => elem.model) - .filter(model => isFrameBlock(model)); - }, - onDragMove(context) { - const { dragLastPos } = context; + return { + onDragStart(context) { + draggedFrames = context.elements + .map(elem => elem.model) + .filter(model => isFrameBlock(model)); + }, + onDragMove(context) { + const { dragLastPos } = context; - hoveredFrame = frameMgr.getFrameFromPoint( - [dragLastPos.x, dragLastPos.y], - draggedFrames - ); + hoveredFrame = frameMgr.getFrameFromPoint( + [dragLastPos.x, dragLastPos.y], + draggedFrames + ); + + if (hoveredFrame && !hoveredFrame.isLocked()) { + frameHighlightOverlay.highlight(hoveredFrame); + } else { + frameHighlightOverlay.clear(); + } + }, + onDragEnd(context) { + const topElements = getTopElements( + context.elements.map(elem => + elem.model.group instanceof MindmapElementModel + ? elem.model.group + : elem.model + ) + ); + + if (hoveredFrame) { + frameMgr.addElementsToFrame(hoveredFrame, topElements); + } else { + topElements.forEach(elem => frameMgr.removeFromParentFrame(elem)); + } - if (hoveredFrame && !hoveredFrame.isLocked()) { - frameHighlightOverlay.highlight(hoveredFrame); - } else { frameHighlightOverlay.clear(); - } - }, - onDragEnd(context) { - const topElements = getTopElements( - context.elements.map(elem => - elem.model.group instanceof MindmapElementModel - ? elem.model.group - : elem.model - ) - ); - - if (hoveredFrame) { - frameMgr.addElementsToFrame(hoveredFrame, topElements); - } else { - topElements.forEach(elem => frameMgr.removeFromParentFrame(elem)); - } - - frameHighlightOverlay.clear(); - }, - }; + }, + }; + }); } } diff --git a/blocksuite/affine/blocks/root/src/edgeless/edgeless-builtin-spec.ts b/blocksuite/affine/blocks/root/src/edgeless/edgeless-builtin-spec.ts index 30fcdd5d25..88331a4a1d 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/edgeless-builtin-spec.ts +++ b/blocksuite/affine/blocks/root/src/edgeless/edgeless-builtin-spec.ts @@ -21,16 +21,16 @@ import { NoteTool } from '@blocksuite/affine-gfx-note'; import { ShapeTool } from '@blocksuite/affine-gfx-shape'; import { TemplateTool } from '@blocksuite/affine-gfx-template'; import { TextTool } from '@blocksuite/affine-gfx-text'; -import { ElementTransformManager } from '@blocksuite/std/gfx'; +import { InteractivityManager } from '@blocksuite/std/gfx'; import type { ExtensionType } from '@blocksuite/store'; import { EdgelessElementToolbarExtension } from './configs/toolbar'; import { EdgelessRootBlockSpec } from './edgeless-root-spec.js'; -import { DblClickAddEdgelessText } from './element-transform/dblclick-add-edgeless-text.js'; -import { SnapExtension } from './element-transform/snap-manager.js'; import { DefaultTool } from './gfx-tool/default-tool.js'; import { EmptyTool } from './gfx-tool/empty-tool.js'; import { PanTool } from './gfx-tool/pan-tool.js'; +import { DblClickAddEdgelessText } from './interact-extensions/dblclick-add-edgeless-text.js'; +import { SnapExtension } from './interact-extensions/snap-manager.js'; import { EditPropsMiddlewareBuilder } from './middlewares/base.js'; import { SnapOverlay } from './utils/snap-manager.js'; @@ -51,7 +51,7 @@ export const EdgelessToolExtension: ExtensionType[] = [ ]; export const EdgelessEditExtensions: ExtensionType[] = [ - ElementTransformManager, + InteractivityManager, ConnectorFilter, SnapExtension, MindMapDragExtension, diff --git a/blocksuite/affine/blocks/root/src/edgeless/element-transform/dblclick-add-edgeless-text.ts b/blocksuite/affine/blocks/root/src/edgeless/element-transform/dblclick-add-edgeless-text.ts deleted file mode 100644 index 961c9c9964..0000000000 --- a/blocksuite/affine/blocks/root/src/edgeless/element-transform/dblclick-add-edgeless-text.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - addText, - insertEdgelessTextCommand, -} from '@blocksuite/affine-gfx-text'; -import { - FeatureFlagService, - TelemetryProvider, -} from '@blocksuite/affine-shared/services'; -import type { PointerEventState } from '@blocksuite/std'; -import { TransformExtension } from '@blocksuite/std/gfx'; - -export class DblClickAddEdgelessText extends TransformExtension { - static override key = 'dbl-click-add-edgeless-text'; - - override dblClick(e: PointerEventState): void { - const textFlag = this.std.store - .get(FeatureFlagService) - .getFlag('enable_edgeless_text'); - const picked = this.gfx.getElementByPoint( - ...this.gfx.viewport.toModelCoord(e.x, e.y) - ); - - if (picked) { - return; - } - - if (textFlag) { - const [x, y] = this.gfx.viewport.toModelCoord(e.x, e.y); - this.std.command.exec(insertEdgelessTextCommand, { x, y }); - } else { - const edgelessView = this.std.view.getBlock( - this.std.store.root?.id || '' - ); - - if (edgelessView) { - addText(edgelessView, e); - } - } - - this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { - control: 'canvas:dbclick', - page: 'whiteboard editor', - module: 'toolbar', - segment: 'toolbar', - type: 'text', - }); - return; - } -} diff --git a/blocksuite/affine/blocks/root/src/edgeless/element-transform/snap-manager.ts b/blocksuite/affine/blocks/root/src/edgeless/element-transform/snap-manager.ts deleted file mode 100644 index d4c98df8a4..0000000000 --- a/blocksuite/affine/blocks/root/src/edgeless/element-transform/snap-manager.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { OverlayIdentifier } from '@blocksuite/affine-block-surface'; -import { MindmapElementModel } from '@blocksuite/affine-model'; -import type { Bound } from '@blocksuite/global/gfx'; -import { - type DragExtensionInitializeContext, - type ExtensionDragMoveContext, - type GfxModel, - TransformExtension, -} from '@blocksuite/std/gfx'; - -import type { SnapOverlay } from '../utils/snap-manager'; - -export class SnapExtension extends TransformExtension { - static override key = 'snap-manager'; - - get snapOverlay() { - return this.std.getOptional( - OverlayIdentifier('snap-manager') - ) as SnapOverlay; - } - - override onDragInitialize(initContext: DragExtensionInitializeContext) { - const snapOverlay = this.snapOverlay; - - if (!snapOverlay) { - return {}; - } - - let alignBound: Bound; - - return { - onDragStart() { - alignBound = snapOverlay.setMovingElements( - initContext.elements, - initContext.elements.reduce((pre, elem) => { - if (elem.group instanceof MindmapElementModel) { - pre.push(elem.group); - } - - return pre; - }, [] as GfxModel[]) - ); - }, - onDragMove(context: ExtensionDragMoveContext) { - if ( - context.elements.length === 0 || - alignBound.w === 0 || - alignBound.h === 0 - ) { - return; - } - - const currentBound = alignBound.moveDelta(context.dx, context.dy); - const alignRst = snapOverlay.align(currentBound); - - context.dx = alignRst.dx + context.dx; - context.dy = alignRst.dy + context.dy; - }, - onDragEnd() { - snapOverlay.clear(); - }, - }; - } -} diff --git a/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool.ts b/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool.ts index 133ff8c4f1..93ad2b9486 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool.ts +++ b/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool.ts @@ -22,9 +22,9 @@ import { BaseTool, getTopElements, type GfxModel, + InteractivityIdentifier, isGfxGroupCompatibleModel, type PointTestOptions, - TransformManagerIdentifier, } from '@blocksuite/std/gfx'; import { effect } from '@preact/signals-core'; @@ -196,8 +196,8 @@ export class DefaultTool extends BaseTool { return this.gfx.selection; } - get elementTransformMgr() { - return this.std.getOptional(TransformManagerIdentifier); + get interactivity() { + return this.std.getOptional(InteractivityIdentifier); } private get frameOverlay() { @@ -380,9 +380,9 @@ export class DefaultTool extends BaseTool { } if (this.dragType === DefaultModeDragType.ContentMoving) { - if (this.elementTransformMgr) { + if (this.interactivity) { this.doc.captureSync(); - this.elementTransformMgr.initializeDrag({ + this.interactivity.initializeDrag({ movingElements: this._toBeMoved, event: event.raw, onDragEnd: () => { @@ -397,12 +397,12 @@ export class DefaultTool extends BaseTool { override click(e: PointerEventState) { if (this.doc.readonly) return; - if (!this.elementTransformMgr?.dispatchOnSelected(e)) { + if (!this.interactivity?.dispatchOnSelected(e)) { this.edgelessSelectionManager.clear(); resetNativeSelection(null); } - this.elementTransformMgr?.dispatch('click', e); + this.interactivity?.dispatch('click', e); } override deactivate() { @@ -424,7 +424,7 @@ export class DefaultTool extends BaseTool { return; } - this.elementTransformMgr?.dispatch('dblclick', e); + this.interactivity?.dispatch('dblclick', e); } override dragEnd() { @@ -523,7 +523,7 @@ export class DefaultTool extends BaseTool { } override pointerDown(e: PointerEventState): void { - this.elementTransformMgr?.dispatch('pointerdown', e); + this.interactivity?.dispatch('pointerdown', e); } override pointerMove(e: PointerEventState) { @@ -542,11 +542,11 @@ export class DefaultTool extends BaseTool { this.frameOverlay.clear(); } - this.elementTransformMgr?.dispatch('pointermove', e); + this.interactivity?.dispatch('pointermove', e); } override pointerUp(e: PointerEventState) { - this.elementTransformMgr?.dispatch('pointerup', e); + this.interactivity?.dispatch('pointerup', e); } override tripleClick() {} diff --git a/blocksuite/affine/blocks/root/src/edgeless/interact-extensions/dblclick-add-edgeless-text.ts b/blocksuite/affine/blocks/root/src/edgeless/interact-extensions/dblclick-add-edgeless-text.ts new file mode 100644 index 0000000000..e0ac099c47 --- /dev/null +++ b/blocksuite/affine/blocks/root/src/edgeless/interact-extensions/dblclick-add-edgeless-text.ts @@ -0,0 +1,50 @@ +import { + addText, + insertEdgelessTextCommand, +} from '@blocksuite/affine-gfx-text'; +import { + FeatureFlagService, + TelemetryProvider, +} from '@blocksuite/affine-shared/services'; +import type { PointerEventState } from '@blocksuite/std'; +import { InteractivityExtension } from '@blocksuite/std/gfx'; + +export class DblClickAddEdgelessText extends InteractivityExtension { + static override key = 'dbl-click-add-edgeless-text'; + + override mounted() { + this.event.on('dblclick', (e: PointerEventState) => { + const textFlag = this.std.store + .get(FeatureFlagService) + .getFlag('enable_edgeless_text'); + const picked = this.gfx.getElementByPoint( + ...this.gfx.viewport.toModelCoord(e.x, e.y) + ); + + if (picked) { + return; + } + + if (textFlag) { + const [x, y] = this.gfx.viewport.toModelCoord(e.x, e.y); + this.std.command.exec(insertEdgelessTextCommand, { x, y }); + } else { + const edgelessView = this.std.view.getBlock( + this.std.store.root?.id || '' + ); + + if (edgelessView) { + addText(edgelessView, e); + } + } + + this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { + control: 'canvas:dbclick', + page: 'whiteboard editor', + module: 'toolbar', + segment: 'toolbar', + type: 'text', + }); + }); + } +} diff --git a/blocksuite/affine/blocks/root/src/edgeless/interact-extensions/snap-manager.ts b/blocksuite/affine/blocks/root/src/edgeless/interact-extensions/snap-manager.ts new file mode 100644 index 0000000000..c040518693 --- /dev/null +++ b/blocksuite/affine/blocks/root/src/edgeless/interact-extensions/snap-manager.ts @@ -0,0 +1,68 @@ +import { OverlayIdentifier } from '@blocksuite/affine-block-surface'; +import { MindmapElementModel } from '@blocksuite/affine-model'; +import type { Bound } from '@blocksuite/global/gfx'; +import { + type DragExtensionInitializeContext, + type ExtensionDragMoveContext, + type GfxModel, + InteractivityExtension, +} from '@blocksuite/std/gfx'; + +import type { SnapOverlay } from '../utils/snap-manager'; + +export class SnapExtension extends InteractivityExtension { + static override key = 'snap-manager'; + + get snapOverlay() { + return this.std.getOptional( + OverlayIdentifier('snap-manager') + ) as SnapOverlay; + } + + override mounted(): void { + this.action.onDragInitialize( + (initContext: DragExtensionInitializeContext) => { + const snapOverlay = this.snapOverlay; + + if (!snapOverlay) { + return {}; + } + + let alignBound: Bound; + + return { + onDragStart() { + alignBound = snapOverlay.setMovingElements( + initContext.elements, + initContext.elements.reduce((pre, elem) => { + if (elem.group instanceof MindmapElementModel) { + pre.push(elem.group); + } + + return pre; + }, [] as GfxModel[]) + ); + }, + onDragMove(context: ExtensionDragMoveContext) { + if ( + context.elements.length === 0 || + alignBound.w === 0 || + alignBound.h === 0 + ) { + return; + } + + const currentBound = alignBound.moveDelta(context.dx, context.dy); + const alignRst = snapOverlay.align(currentBound); + + context.dx = alignRst.dx + context.dx; + context.dy = alignRst.dy + context.dy; + }, + onDragEnd() { + snapOverlay.clear(); + }, + }; + } + ); + } +} diff --git a/blocksuite/affine/gfx/connector/src/element-transform/connector-filter.ts b/blocksuite/affine/gfx/connector/src/element-transform/connector-filter.ts index 8ec4cec8bc..0614c8d07f 100644 --- a/blocksuite/affine/gfx/connector/src/element-transform/connector-filter.ts +++ b/blocksuite/affine/gfx/connector/src/element-transform/connector-filter.ts @@ -1,39 +1,42 @@ import { ConnectorElementModel } from '@blocksuite/affine-model'; import { type DragExtensionInitializeContext, - TransformExtension, + InteractivityExtension, } from '@blocksuite/std/gfx'; -export class ConnectorFilter extends TransformExtension { +export class ConnectorFilter extends InteractivityExtension { static override key = 'connector-filter'; - override onDragInitialize(context: DragExtensionInitializeContext) { - let hasConnectorFlag = false; - const elementSet = new Set(context.elements.map(elem => elem.id)); - const elements = context.elements.filter(elem => { - if (elem instanceof ConnectorElementModel) { - const sourceElemNotFound = - elem.source.id && !elementSet.has(elem.source.id); - const targetElemNotFound = - elem.target.id && !elementSet.has(elem.target.id); + override mounted() { + this.action.onDragInitialize((context: DragExtensionInitializeContext) => { + let hasConnectorFlag = false; - // If either source or target element is not found, then remove the connector - if (sourceElemNotFound || targetElemNotFound) { - return false; + const elementSet = new Set(context.elements.map(elem => elem.id)); + const elements = context.elements.filter(elem => { + if (elem instanceof ConnectorElementModel) { + const sourceElemNotFound = + elem.source.id && !elementSet.has(elem.source.id); + const targetElemNotFound = + elem.target.id && !elementSet.has(elem.target.id); + + // If either source or target element is not found, then remove the connector + if (sourceElemNotFound || targetElemNotFound) { + return false; + } + + hasConnectorFlag = true; + return true; } - hasConnectorFlag = true; return true; + }); + + if (hasConnectorFlag) { + // connector needs to be updated first + elements.sort((a, _) => (a instanceof ConnectorElementModel ? -1 : 1)); } - return true; + return {}; }); - - if (hasConnectorFlag) { - // connector needs to be updated first - elements.sort((a, _) => (a instanceof ConnectorElementModel ? -1 : 1)); - } - - return {}; } } diff --git a/blocksuite/affine/gfx/mindmap/src/index.ts b/blocksuite/affine/gfx/mindmap/src/index.ts index 028406ac10..07ec0aa275 100644 --- a/blocksuite/affine/gfx/mindmap/src/index.ts +++ b/blocksuite/affine/gfx/mindmap/src/index.ts @@ -1,7 +1,7 @@ export * from './adapter'; export * from './element-renderer'; -export * from './element-transform'; export * from './indicator-overlay'; +export * from './interactivity'; export * from './toolbar/config'; export * from './toolbar/senior-tool'; export * from './utils'; diff --git a/blocksuite/affine/gfx/mindmap/src/element-transform/drag-utils.ts b/blocksuite/affine/gfx/mindmap/src/interactivity/drag-utils.ts similarity index 100% rename from blocksuite/affine/gfx/mindmap/src/element-transform/drag-utils.ts rename to blocksuite/affine/gfx/mindmap/src/interactivity/drag-utils.ts diff --git a/blocksuite/affine/gfx/mindmap/src/element-transform/index.ts b/blocksuite/affine/gfx/mindmap/src/interactivity/index.ts similarity index 100% rename from blocksuite/affine/gfx/mindmap/src/element-transform/index.ts rename to blocksuite/affine/gfx/mindmap/src/interactivity/index.ts diff --git a/blocksuite/affine/gfx/mindmap/src/element-transform/mind-map-drag.ts b/blocksuite/affine/gfx/mindmap/src/interactivity/mind-map-drag.ts similarity index 81% rename from blocksuite/affine/gfx/mindmap/src/element-transform/mind-map-drag.ts rename to blocksuite/affine/gfx/mindmap/src/interactivity/mind-map-drag.ts index 8fa72631e7..c14b4f6e1f 100644 --- a/blocksuite/affine/gfx/mindmap/src/element-transform/mind-map-drag.ts +++ b/blocksuite/affine/gfx/mindmap/src/interactivity/mind-map-drag.ts @@ -16,8 +16,8 @@ import { type ExtensionDragStartContext, type GfxModel, type GfxPrimitiveElementModel, + InteractivityExtension, isGfxGroupCompatibleModel, - TransformExtension, } from '@blocksuite/std/gfx'; import type { MindMapIndicatorOverlay } from '../indicator-overlay'; @@ -43,7 +43,7 @@ type DragMindMapCtx = { originalMindMapBound: Bound; }; -export class MindMapDragExtension extends TransformExtension { +export class MindMapDragExtension extends InteractivityExtension { static override key = 'mind-map-drag'; /** * The response area of the mind map is calculated in real time. @@ -376,78 +376,80 @@ export class MindMapDragExtension extends TransformExtension { }; } - override onDragInitialize(context: DragExtensionInitializeContext) { - if (isSingleMindMapNode(context.elements)) { - const mindmap = context.elements[0].group as MindmapElementModel; - const mindmapNode = mindmap.getNode(context.elements[0].id)!; - const mindmapBound = mindmap.elementBound; - const isRoot = mindmapNode === mindmap.tree; + override mounted() { + this.action.onDragInitialize((context: DragExtensionInitializeContext) => { + if (isSingleMindMapNode(context.elements)) { + const mindmap = context.elements[0].group as MindmapElementModel; + const mindmapNode = mindmap.getNode(context.elements[0].id)!; + const mindmapBound = mindmap.elementBound; + const isRoot = mindmapNode === mindmap.tree; - mindmapBound.x -= NODE_HORIZONTAL_SPACING; - mindmapBound.y -= NODE_VERTICAL_SPACING * 2; - mindmapBound.w += NODE_HORIZONTAL_SPACING * 2; - mindmapBound.h += NODE_VERTICAL_SPACING * 4; + mindmapBound.x -= NODE_HORIZONTAL_SPACING; + mindmapBound.y -= NODE_VERTICAL_SPACING * 2; + mindmapBound.w += NODE_HORIZONTAL_SPACING * 2; + mindmapBound.h += NODE_VERTICAL_SPACING * 4; - this._calcDragResponseArea(mindmap); + this._calcDragResponseArea(mindmap); - const clearDragStatus = isRoot - ? mindmap.stashTree(mindmapNode) - : this._setupDragNodeImage(mindmapNode, context.dragStartPos); - const clearOpacity = this._updateNodeOpacity(mindmap, mindmapNode); + const clearDragStatus = isRoot + ? mindmap.stashTree(mindmapNode) + : this._setupDragNodeImage(mindmapNode, context.dragStartPos); + const clearOpacity = this._updateNodeOpacity(mindmap, mindmapNode); - if (!isRoot) { - context.elements.splice(0, 1); + if (!isRoot) { + context.elements.splice(0, 1); + } + + const mindMapDragCtx: DragMindMapCtx = { + mindmap, + node: mindmapNode, + isRoot, + originalMindMapBound: mindmapBound, + }; + + return { + ...this._createManipulationHandlers(mindMapDragCtx), + clear() { + clearOpacity(); + clearDragStatus?.(); + if (!isRoot) { + context.elements.push(mindmapNode.element); + } + }, + }; } - const mindMapDragCtx: DragMindMapCtx = { - mindmap, - node: mindmapNode, - isRoot, - originalMindMapBound: mindmapBound, - }; + const mindmapNodes = new Set(); + const mindmaps = new Set(); - return { - ...this._createManipulationHandlers(mindMapDragCtx), - clear() { - clearOpacity(); - clearDragStatus?.(); - if (!isRoot) { - context.elements.push(mindmapNode.element); - } - }, - }; - } + context.elements.forEach(el => { + if (isMindmapNode(el)) { + const mindmap = + el.group instanceof MindmapElementModel + ? el.group + : (el as MindmapElementModel); - const mindmapNodes = new Set(); - const mindmaps = new Set(); + mindmaps.add(mindmap); + mindmap.childElements.forEach(child => mindmapNodes.add(child)); + } else if (isGfxGroupCompatibleModel(el)) { + el.descendantElements.forEach(desc => { + if (desc.group instanceof MindmapElementModel) { + mindmaps.add(desc.group); + desc.group.childElements.forEach(_el => mindmapNodes.add(_el)); + } + }); + } + }); - context.elements.forEach(el => { - if (isMindmapNode(el)) { - const mindmap = - el.group instanceof MindmapElementModel - ? el.group - : (el as MindmapElementModel); - - mindmaps.add(mindmap); - mindmap.childElements.forEach(child => mindmapNodes.add(child)); - } else if (isGfxGroupCompatibleModel(el)) { - el.descendantElements.forEach(desc => { - if (desc.group instanceof MindmapElementModel) { - mindmaps.add(desc.group); - desc.group.childElements.forEach(_el => mindmapNodes.add(_el)); - } + if (mindmapNodes.size > 1) { + mindmapNodes.forEach(node => context.elements.push(node)); + return this._createTranslationHandlers({ + mindmaps, + nodes: mindmapNodes, }); } + + return {}; }); - - if (mindmapNodes.size > 1) { - mindmapNodes.forEach(node => context.elements.push(node)); - return this._createTranslationHandlers({ - mindmaps, - nodes: mindmapNodes, - }); - } - - return {}; } } diff --git a/blocksuite/framework/std/src/gfx/index.ts b/blocksuite/framework/std/src/gfx/index.ts index a7db7d78cc..eec03db9b9 100644 --- a/blocksuite/framework/std/src/gfx/index.ts +++ b/blocksuite/framework/std/src/gfx/index.ts @@ -12,29 +12,26 @@ export { } from '../utils/tree.js'; export { GfxController } from './controller.js'; export type { CursorType, StandardCursor } from './cursor.js'; -export type { - DragExtensionInitializeContext, - DragInitializationOption, - ExtensionDragEndContext, - ExtensionDragMoveContext, - ExtensionDragStartContext, -} from './element-transform/drag.js'; -export { CanvasEventHandler } from './element-transform/extension/canvas-event-handler.js'; -export { - ElementTransformManager, - TransformExtension, - TransformExtensionIdentifier, - TransformManagerIdentifier, -} from './element-transform/transform-manager.js'; -export type { - DragEndContext, - DragMoveContext, - DragStartContext, -} from './element-transform/view-transform.js'; -export { type SelectedContext } from './element-transform/view-transform.js'; export { GfxExtension, GfxExtensionIdentifier } from './extension.js'; export { GridManager } from './grid.js'; export { GfxControllerIdentifier } from './identifiers.js'; +export type { + DragEndContext, + DragExtensionInitializeContext, + DragInitializationOption, + DragMoveContext, + DragStartContext, + ExtensionDragEndContext, + ExtensionDragMoveContext, + ExtensionDragStartContext, + SelectedContext, +} from './interactivity/index.js'; +export { + GfxViewEventManager, + InteractivityExtension, + InteractivityIdentifier, + InteractivityManager, +} from './interactivity/index.js'; export { LayerManager, type ReorderingDirection } from './layer.js'; export type { GfxCompatibleInterface, diff --git a/blocksuite/framework/std/src/gfx/interactivity/event.ts b/blocksuite/framework/std/src/gfx/interactivity/event.ts new file mode 100644 index 0000000000..dbbf79a0d8 --- /dev/null +++ b/blocksuite/framework/std/src/gfx/interactivity/event.ts @@ -0,0 +1,8 @@ +export type SupportedEvents = + | 'click' + | 'dblclick' + | 'pointerdown' + | 'pointerenter' + | 'pointerleave' + | 'pointermove' + | 'pointerup'; diff --git a/blocksuite/framework/std/src/gfx/interactivity/extension/base.ts b/blocksuite/framework/std/src/gfx/interactivity/extension/base.ts new file mode 100644 index 0000000000..66d950f848 --- /dev/null +++ b/blocksuite/framework/std/src/gfx/interactivity/extension/base.ts @@ -0,0 +1,141 @@ +import { type Container, createIdentifier } from '@blocksuite/global/di'; +import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; +import { Extension } from '@blocksuite/store'; + +import type { PointerEventState } from '../../../event/index.js'; +import type { GfxController } from '../../controller.js'; +import { GfxControllerIdentifier } from '../../identifiers.js'; +import type { SupportedEvents } from '../event.js'; +import type { + DragExtensionInitializeContext, + ExtensionDragEndContext, + ExtensionDragMoveContext, + ExtensionDragStartContext, +} from '../types/drag.js'; + +export const InteractivityExtensionIdentifier = + createIdentifier('interactivity-extension'); + +export class InteractivityExtension extends Extension { + static key: string; + + get std() { + return this.gfx.std; + } + + event: Omit = new InteractivityEventAPI(); + + action: Omit = new InteractivityActionAPI(); + + constructor(protected readonly gfx: GfxController) { + super(); + } + + mounted() {} + + /** + * Override this method should call `super.unmounted()` + */ + unmounted() { + this.event.destroy(); + this.action.destroy(); + } + + static override setup(di: Container) { + if (!this.key) { + throw new BlockSuiteError( + ErrorCode.ValueNotExists, + 'key is not defined in the InteractivityExtension' + ); + } + + di.add( + this as unknown as { new (gfx: GfxController): InteractivityExtension }, + [GfxControllerIdentifier] + ); + di.addImpl(InteractivityExtensionIdentifier(this.key), provider => + provider.get(this) + ); + } +} + +export class InteractivityEventAPI { + private readonly _handlersMap = new Map< + SupportedEvents, + ((evt: PointerEventState) => void)[] + >(); + + on(eventName: SupportedEvents, handler: (evt: PointerEventState) => void) { + const handlers = this._handlersMap.get(eventName) ?? []; + handlers.push(handler); + this._handlersMap.set(eventName, handlers); + + return () => { + const idx = handlers.indexOf(handler); + + if (idx > -1) { + handlers.splice(idx, 1); + } + }; + } + + emit(eventName: SupportedEvents, evt: PointerEventState) { + const handlers = this._handlersMap.get(eventName); + if (!handlers) { + return; + } + + for (const handler of handlers) { + handler(evt); + } + } + + destroy() { + this._handlersMap.clear(); + } +} + +type ActionContextMap = { + dragInitialize: { + context: DragExtensionInitializeContext; + returnType: { + onDragStart?: (context: ExtensionDragStartContext) => void; + onDragMove?: (context: ExtensionDragMoveContext) => void; + onDragEnd?: (context: ExtensionDragEndContext) => void; + clear?: () => void; + }; + }; +}; + +export class InteractivityActionAPI { + private readonly _handlers: Partial<{ + dragInitialize: Parameters[0]; + }> = {}; + + onDragInitialize( + handler: ( + ctx: ActionContextMap['dragInitialize']['context'] + ) => ActionContextMap['dragInitialize']['returnType'] + ) { + this._handlers['dragInitialize'] = handler; + + return () => { + delete this._handlers['dragInitialize']; + }; + } + + emit( + event: K, + context: ActionContextMap[K]['context'] + ): ActionContextMap[K]['returnType'] | undefined { + const handler = this._handlers[event]; + + return handler?.(context); + } + + destroy() { + for (const key in this._handlers) { + delete this._handlers[key as keyof typeof this._handlers]; + } + } +} diff --git a/blocksuite/framework/std/src/gfx/element-transform/extension/canvas-event-handler.ts b/blocksuite/framework/std/src/gfx/interactivity/gfx-view-event-handler.ts similarity index 89% rename from blocksuite/framework/std/src/gfx/element-transform/extension/canvas-event-handler.ts rename to blocksuite/framework/std/src/gfx/interactivity/gfx-view-event-handler.ts index 3292a76a45..cd90968196 100644 --- a/blocksuite/framework/std/src/gfx/element-transform/extension/canvas-event-handler.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/gfx-view-event-handler.ts @@ -1,11 +1,11 @@ import { Bound } from '@blocksuite/global/gfx'; import last from 'lodash-es/last'; -import type { PointerEventState } from '../../../event'; -import type { GfxController } from '../..'; -import type { GfxElementModelView } from '../../view/view'; +import type { PointerEventState } from '../../event'; +import type { GfxController } from '../controller.js'; +import type { GfxElementModelView } from '../view/view.js'; -export class CanvasEventHandler { +export class GfxViewEventManager { private _currentStackedElm: GfxElementModelView[] = []; private _callInReverseOrder( diff --git a/blocksuite/framework/std/src/gfx/interactivity/index.ts b/blocksuite/framework/std/src/gfx/interactivity/index.ts new file mode 100644 index 0000000000..7dd2e4639f --- /dev/null +++ b/blocksuite/framework/std/src/gfx/interactivity/index.ts @@ -0,0 +1,17 @@ +export { InteractivityExtension } from './extension/base.js'; +export { GfxViewEventManager } from './gfx-view-event-handler.js'; +export { InteractivityIdentifier, InteractivityManager } from './manager.js'; +export type { + DragExtensionInitializeContext, + DragInitializationOption, + ExtensionDragEndContext, + ExtensionDragMoveContext, + ExtensionDragStartContext, +} from './types/drag.js'; +export type { + DragEndContext, + DragMoveContext, + DragStartContext, + GfxViewTransformInterface, + SelectedContext, +} from './types/view.js'; diff --git a/blocksuite/framework/std/src/gfx/element-transform/transform-manager.ts b/blocksuite/framework/std/src/gfx/interactivity/manager.ts similarity index 70% rename from blocksuite/framework/std/src/gfx/element-transform/transform-manager.ts rename to blocksuite/framework/std/src/gfx/interactivity/manager.ts index 55e7d4c5ac..809e676cb4 100644 --- a/blocksuite/framework/std/src/gfx/element-transform/transform-manager.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/manager.ts @@ -1,39 +1,36 @@ -import { - type Container, - createIdentifier, - type ServiceIdentifier, -} from '@blocksuite/global/di'; +import { type ServiceIdentifier } from '@blocksuite/global/di'; import { DisposableGroup } from '@blocksuite/global/disposable'; -import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { Bound, Point } from '@blocksuite/global/gfx'; -import { Extension } from '@blocksuite/store'; import type { PointerEventState } from '../../event/state/pointer.js'; -import { type GfxController } from '../controller.js'; import { GfxExtension, GfxExtensionIdentifier } from '../extension.js'; -import { GfxControllerIdentifier } from '../identifiers.js'; import type { GfxModel } from '../model/model.js'; -import { type SupportedEvent } from '../view/view.js'; +import type { SupportedEvents } from './event.js'; +import { + type InteractivityActionAPI, + type InteractivityEventAPI, + InteractivityExtensionIdentifier, +} from './extension/base.js'; +import { GfxViewEventManager } from './gfx-view-event-handler.js'; import type { DragExtensionInitializeContext, DragInitializationOption, ExtensionDragEndContext, ExtensionDragMoveContext, ExtensionDragStartContext, -} from './drag.js'; -import { CanvasEventHandler } from './extension/canvas-event-handler.js'; +} from './types/drag.js'; type ExtensionPointerHandler = Exclude< - SupportedEvent, + SupportedEvents, 'pointerleave' | 'pointerenter' >; -export const TransformManagerIdentifier = GfxExtensionIdentifier( - 'element-transform-manager' -) as ServiceIdentifier; +export const InteractivityIdentifier = GfxExtensionIdentifier( + 'interactivity-manager' +) as ServiceIdentifier; const CAMEL_CASE_MAP: { - [key in ExtensionPointerHandler]: keyof CanvasEventHandler; + [key in ExtensionPointerHandler]: keyof GfxViewEventManager; } = { click: 'click', dblclick: 'dblClick', @@ -42,23 +39,29 @@ const CAMEL_CASE_MAP: { pointerup: 'pointerUp', }; -export class ElementTransformManager extends GfxExtension { - static override key = 'element-transform-manager'; +export class InteractivityManager extends GfxExtension { + static override key = 'interactivity-manager'; private readonly _disposable = new DisposableGroup(); - private canvasEventHandler = new CanvasEventHandler(this.gfx); + private canvasEventHandler = new GfxViewEventManager(this.gfx); override mounted(): void { - this.canvasEventHandler = new CanvasEventHandler(this.gfx); + this.canvasEventHandler = new GfxViewEventManager(this.gfx); + this.interactExtensions.forEach(ext => { + ext.mounted(); + }); } override unmounted(): void { this._disposable.dispose(); + this.interactExtensions.forEach(ext => { + ext.unmounted(); + }); } - get transformExtensions() { - return this.std.provider.getAll(TransformExtensionIdentifier); + get interactExtensions() { + return this.std.provider.getAll(InteractivityExtensionIdentifier); } get keyboard() { @@ -83,10 +86,10 @@ export class ElementTransformManager extends GfxExtension { this.canvasEventHandler[handlerName](evt); - const extension = this.transformExtensions; + const extensions = this.interactExtensions; - extension.forEach(ext => { - ext[handlerName]?.(evt); + extensions.forEach(ext => { + (ext.event as InteractivityEventAPI).emit(eventName, evt); }); } @@ -142,15 +145,18 @@ export class ElementTransformManager extends GfxExtension { ]) ), }; - const extension = this.transformExtensions; + const extension = this.interactExtensions; const activeExtensionHandlers = Array.from( extension.values().map(ext => { - return ext.onDragInitialize(context); + return (ext.action as InteractivityActionAPI).emit( + 'dragInitialize', + context + ); }) ); if (cancelledByExt) { - activeExtensionHandlers.forEach(handler => handler.clear?.()); + activeExtensionHandlers.forEach(handler => handler?.clear?.()); return; } @@ -198,7 +204,7 @@ export class ElementTransformManager extends GfxExtension { this._safeExecute(() => { activeExtensionHandlers.forEach(handler => - handler.onDragMove?.(moveContext) + handler?.onDragMove?.(moveContext) ); }, 'Error while executing extension `onDragMove`'); @@ -231,7 +237,7 @@ export class ElementTransformManager extends GfxExtension { this._safeExecute(() => { activeExtensionHandlers.forEach(handler => - handler.onDragEnd?.(endContext) + handler?.onDragEnd?.(endContext) ); }, 'Error while executing extension `onDragEnd` handler'); @@ -249,7 +255,7 @@ export class ElementTransformManager extends GfxExtension { }); this._safeExecute(() => { - activeExtensionHandlers.forEach(handler => handler.clear?.()); + activeExtensionHandlers.forEach(handler => handler?.clear?.()); }, 'Error while executing extension `clear` handler'); options.onDragEnd?.(); @@ -274,7 +280,7 @@ export class ElementTransformManager extends GfxExtension { this._safeExecute(() => { activeExtensionHandlers.forEach(handler => - handler.onDragStart?.(dragStartContext) + handler?.onDragStart?.(dragStartContext) ); }, 'Error while executing extension `onDragStart` handler'); }; @@ -283,58 +289,3 @@ export class ElementTransformManager extends GfxExtension { dragStart(); } } - -export const TransformExtensionIdentifier = - createIdentifier('element-transform-extension'); - -export class TransformExtension extends Extension { - static key: string; - - get std() { - return this.gfx.std; - } - - constructor(protected readonly gfx: GfxController) { - super(); - } - - mounted() {} - - unmounted() {} - - click(_: PointerEventState) {} - - dblClick(_: PointerEventState) {} - - pointerDown(_: PointerEventState) {} - - pointerMove(_: PointerEventState) {} - - pointerUp(_: PointerEventState) {} - - onDragInitialize(_: DragExtensionInitializeContext): { - onDragStart?: (context: ExtensionDragStartContext) => void; - onDragMove?: (context: ExtensionDragMoveContext) => void; - onDragEnd?: (context: ExtensionDragEndContext) => void; - clear?: () => void; - } { - return {}; - } - - static override setup(di: Container) { - if (!this.key) { - throw new BlockSuiteError( - ErrorCode.ValueNotExists, - 'key is not defined in the TransformExtension' - ); - } - - di.add( - this as unknown as { new (gfx: GfxController): TransformExtension }, - [GfxControllerIdentifier] - ); - di.addImpl(TransformExtensionIdentifier(this.key), provider => - provider.get(this) - ); - } -} diff --git a/blocksuite/framework/std/src/gfx/element-transform/drag.ts b/blocksuite/framework/std/src/gfx/interactivity/types/drag.ts similarity index 89% rename from blocksuite/framework/std/src/gfx/element-transform/drag.ts rename to blocksuite/framework/std/src/gfx/interactivity/types/drag.ts index 7816864479..c16fa427da 100644 --- a/blocksuite/framework/std/src/gfx/element-transform/drag.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/types/drag.ts @@ -1,8 +1,8 @@ import type { Bound } from '@blocksuite/global/gfx'; -import type { GfxBlockComponent } from '../../view'; -import type { GfxModel } from '../model/model'; -import type { GfxElementModelView } from '../view/view'; +import type { GfxBlockComponent } from '../../../view'; +import type { GfxModel } from '../../model/model'; +import type { GfxElementModelView } from '../../view/view'; export type DragInitializationOption = { movingElements: GfxModel[]; diff --git a/blocksuite/framework/std/src/gfx/element-transform/view-transform.ts b/blocksuite/framework/std/src/gfx/interactivity/types/view.ts similarity index 88% rename from blocksuite/framework/std/src/gfx/element-transform/view-transform.ts rename to blocksuite/framework/std/src/gfx/interactivity/types/view.ts index 8abe9e1e7d..01611b6b32 100644 --- a/blocksuite/framework/std/src/gfx/element-transform/view-transform.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/types/view.ts @@ -1,8 +1,8 @@ import type { Bound, IPoint } from '@blocksuite/global/gfx'; -import type { GfxBlockComponent } from '../../view'; -import type { GfxModel } from '../model/model'; -import type { GfxElementModelView } from '../view/view'; +import type { GfxBlockComponent } from '../../../view/element/gfx-block-component.js'; +import type { GfxModel } from '../../model/model.js'; +import type { GfxElementModelView } from '../../view/view.js'; export type DragStartContext = { /** diff --git a/blocksuite/framework/std/src/gfx/view/view.ts b/blocksuite/framework/std/src/gfx/view/view.ts index f7b8f470b1..a8752d3bba 100644 --- a/blocksuite/framework/std/src/gfx/view/view.ts +++ b/blocksuite/framework/std/src/gfx/view/view.ts @@ -6,14 +6,14 @@ import type { Extension } from '@blocksuite/store'; import type { PointerEventState } from '../../event/index.js'; import type { EditorHost } from '../../view/index.js'; +import type { GfxController } from '../index.js'; import type { DragEndContext, DragMoveContext, DragStartContext, GfxViewTransformInterface, SelectedContext, -} from '../element-transform/view-transform.js'; -import type { GfxController } from '../index.js'; +} from '../interactivity/index.js'; import type { GfxElementGeometry, PointTestOptions } from '../model/base.js'; import { GfxPrimitiveElementModel } from '../model/surface/element-model.js'; import type { GfxLocalElementModel } from '../model/surface/local-element-model.js'; diff --git a/blocksuite/framework/std/src/view/element/gfx-block-component.ts b/blocksuite/framework/std/src/view/element/gfx-block-component.ts index 1d4eee375b..660957d0ad 100644 --- a/blocksuite/framework/std/src/view/element/gfx-block-component.ts +++ b/blocksuite/framework/std/src/view/element/gfx-block-component.ts @@ -4,12 +4,12 @@ import { computed, effect, signal } from '@preact/signals-core'; import { nothing } from 'lit'; import type { BlockService } from '../../extension/index.js'; +import { GfxControllerIdentifier } from '../../gfx/identifiers.js'; import type { DragMoveContext, GfxViewTransformInterface, SelectedContext, -} from '../../gfx/element-transform/view-transform.js'; -import { GfxControllerIdentifier } from '../../gfx/identifiers.js'; +} from '../../gfx/interactivity/index.js'; import { type GfxBlockElementModel } from '../../gfx/model/gfx-block-model.js'; import { SurfaceSelection } from '../../selection/index.js'; import { BlockComponent } from './block-component.js';