mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 23:07:02 +08:00
feat: add ElementTransformManager for edgeless element basic manipulation (#10824)
### Overview:
We've been working with some legacy code in the default-tool and edgeless-selected-rect modules, which are responsible for fundamental operations like moving, resizing, and rotating elements. Currently, these operations are hardcoded, making it challenging to extend functionalities without diving deep into the code.
### What's Changing:
Introducing `ElementTransformManager` to streamline the handling of basic transformations (move, resize, rotate) while allowing the business logic to dictate when these actions occur.
Providing two ways to extend the transformations behaviour:
- Extends inside element view definition: Elements can decide how to handle move/resize events, such as enforcing size constraints.
- Extension mechanism provided by this manager: Adjust or completely override default drag behaviors, like snapping elements into alignment.
### Code Examples:
Delegate element movement to TransformManager:
```typescript
class DefaultTool {
override dragStart(event) {
if(this.dragType === DragType.ContentMoving) {
const transformManager = this.std.get(TransformManagerIdentifier);
transformManager.startDrag({ selectedElements, event });
}
}
}
```
Enforce minimum width inside view definition:
```typescript
class EdgelessNoteBlock extends GfxBlockComponent {
onResizeDelta({ dw, dh }) {
const bound = this.model.elementBound;
bound.w = Math.min(MAX_WIDTH, bound.w + dw);
bound.h = Math.min(MAX_HEIGHT, bound.h + dh);
this.model.xywh = bound.serialize();
}
}
```
Use extension to implement element snapping:
```typescript
import { TransformerExtension } from '@blocksuite/std/gfx';
// Just extends the TransformerExtension
class SnapManager extends TransformerExtension {
static override key = 'snap-manager';
onDragInitialize() {
return {
onDragMove(context) {
const { dx, dy } = this.getAlignmentMoveDistance(context.elements);
context.dx = dx;
context.dy = dy;
}
}
}
}
```
### Others
The migration will be divided into several PRs. This PR mostly focus on refactoring elements movement part of `default-tool`.
- Delegate elements movement to `TransformManager`
- Rewrite the default tool extension into `TransformManager` extension
- Add drag handler interface to gfx view (both `GfxBlockComponent` and `GfxElementModelView`) to allow element to define how it gonna react on drag
This commit is contained in:
@@ -1,14 +1,22 @@
|
||||
import { PresentTool } from '@blocksuite/affine-block-frame';
|
||||
import { ConnectionOverlay } from '@blocksuite/affine-block-surface';
|
||||
import { TextTool } from '@blocksuite/affine-gfx-text';
|
||||
import {
|
||||
CanvasEventHandler,
|
||||
ElementTransformManager,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { EdgelessElementToolbarExtension } from './configs/toolbar';
|
||||
import { EdgelessRootBlockSpec } from './edgeless-root-spec.js';
|
||||
import { ConnectorFilter } from './element-transform/connector-filter.js';
|
||||
import { FrameHighlightManager } from './element-transform/frame-highlight-manager.js';
|
||||
import { MindMapDragExtension } from './element-transform/mind-map-drag.js';
|
||||
import { SnapExtension } from './element-transform/snap-manager.js';
|
||||
import { MindMapIndicatorOverlay } from './element-transform/utils/indicator-overlay.js';
|
||||
import { BrushTool } from './gfx-tool/brush-tool.js';
|
||||
import { ConnectorTool } from './gfx-tool/connector-tool.js';
|
||||
import { DefaultTool } from './gfx-tool/default-tool.js';
|
||||
import { MindMapIndicatorOverlay } from './gfx-tool/default-tool-ext/mind-map-ext/indicator-overlay.js';
|
||||
import { EmptyTool } from './gfx-tool/empty-tool.js';
|
||||
import { EraserTool } from './gfx-tool/eraser-tool.js';
|
||||
import { FrameTool } from './gfx-tool/frame-tool.js';
|
||||
@@ -18,7 +26,7 @@ import { PanTool } from './gfx-tool/pan-tool.js';
|
||||
import { ShapeTool } from './gfx-tool/shape-tool.js';
|
||||
import { TemplateTool } from './gfx-tool/template-tool.js';
|
||||
import { EditPropsMiddlewareBuilder } from './middlewares/base.js';
|
||||
import { SnapManager } from './utils/snap-manager.js';
|
||||
import { SnapOverlay } from './utils/snap-manager.js';
|
||||
|
||||
export const EdgelessToolExtension: ExtensionType[] = [
|
||||
DefaultTool,
|
||||
@@ -36,10 +44,19 @@ export const EdgelessToolExtension: ExtensionType[] = [
|
||||
PresentTool,
|
||||
];
|
||||
|
||||
export const EdgelessEditExtensions: ExtensionType[] = [
|
||||
ElementTransformManager,
|
||||
ConnectorFilter,
|
||||
SnapExtension,
|
||||
CanvasEventHandler,
|
||||
MindMapDragExtension,
|
||||
FrameHighlightManager,
|
||||
];
|
||||
|
||||
export const EdgelessBuiltInManager: ExtensionType[] = [
|
||||
ConnectionOverlay,
|
||||
MindMapIndicatorOverlay,
|
||||
SnapManager,
|
||||
SnapOverlay,
|
||||
EditPropsMiddlewareBuilder,
|
||||
EdgelessElementToolbarExtension,
|
||||
].flat();
|
||||
@@ -48,4 +65,5 @@ export const EdgelessBuiltInSpecs: ExtensionType[] = [
|
||||
EdgelessRootBlockSpec,
|
||||
EdgelessToolExtension,
|
||||
EdgelessBuiltInManager,
|
||||
EdgelessEditExtensions,
|
||||
].flat();
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ConnectorElementModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
type DragExtensionInitializeContext,
|
||||
TransformExtension,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
|
||||
export class ConnectorFilter extends TransformExtension {
|
||||
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);
|
||||
|
||||
// If either source or target element is not found, then remove the connector
|
||||
if (sourceElemNotFound || targetElemNotFound) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hasConnectorFlag = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (hasConnectorFlag) {
|
||||
// connector needs to be updated first
|
||||
elements.sort((a, _) => (a instanceof ConnectorElementModel ? -1 : 1));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
type EdgelessFrameManager,
|
||||
type FrameOverlay,
|
||||
isFrameBlock,
|
||||
} from '@blocksuite/affine-block-frame';
|
||||
import { OverlayIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
type FrameBlockModel,
|
||||
MindmapElementModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
type DragExtensionInitializeContext,
|
||||
type ExtensionDragEndContext,
|
||||
type ExtensionDragMoveContext,
|
||||
type ExtensionDragStartContext,
|
||||
getTopElements,
|
||||
GfxExtensionIdentifier,
|
||||
TransformExtension,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
|
||||
export class FrameHighlightManager extends TransformExtension {
|
||||
static override key = 'frame-highlight-manager';
|
||||
|
||||
get frameMgr() {
|
||||
return this.std.getOptional(
|
||||
GfxExtensionIdentifier('frame-manager')
|
||||
) as EdgelessFrameManager;
|
||||
}
|
||||
|
||||
get frameHighlightOverlay() {
|
||||
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 {};
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
frameHighlightOverlay.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -11,39 +11,41 @@ import {
|
||||
MindmapElementModel,
|
||||
type MindmapNode,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { PointerEventState } from '@blocksuite/block-std';
|
||||
import {
|
||||
type DragExtensionInitializeContext,
|
||||
type ExtensionDragEndContext,
|
||||
type ExtensionDragMoveContext,
|
||||
type ExtensionDragStartContext,
|
||||
type GfxModel,
|
||||
type GfxPrimitiveElementModel,
|
||||
isGfxGroupCompatibleModel,
|
||||
TransformExtension,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import type { Bound, IVec } from '@blocksuite/global/gfx';
|
||||
|
||||
import { isSingleMindMapNode } from '../../../utils/mindmap.js';
|
||||
import { isMindmapNode } from '../../../utils/query.js';
|
||||
import { DefaultModeDragType, DefaultToolExt, type DragState } from '../ext.js';
|
||||
import { calculateResponseArea } from './drag-utils.js';
|
||||
import type { MindMapIndicatorOverlay } from './indicator-overlay.js';
|
||||
import { isSingleMindMapNode } from '../utils/mindmap';
|
||||
import { isMindmapNode } from '../utils/query';
|
||||
import { calculateResponseArea } from './utils/drag-utils';
|
||||
import type { MindMapIndicatorOverlay } from './utils/indicator-overlay';
|
||||
|
||||
type DragMindMapCtx = {
|
||||
mindmap: MindmapElementModel;
|
||||
node: MindmapNode;
|
||||
clear: () => void;
|
||||
/**
|
||||
* Whether the dragged node is the root node of the mind map
|
||||
*/
|
||||
isRoot: boolean;
|
||||
originalMindMapBound: Bound;
|
||||
startPoint: PointerEventState;
|
||||
};
|
||||
|
||||
export class MindMapExt extends DefaultToolExt {
|
||||
export class MindMapDragExtension extends TransformExtension {
|
||||
static override key = 'mind-map-drag';
|
||||
/**
|
||||
* The response area of the mind map is calculated in real time.
|
||||
* It only needs to be calculated once when the mind map is dragged.
|
||||
*/
|
||||
private readonly _responseAreaUpdated = new Set<MindmapElementModel>();
|
||||
|
||||
override supportedDragTypes: DefaultModeDragType[] = [
|
||||
DefaultModeDragType.ContentMoving,
|
||||
];
|
||||
|
||||
private get _indicatorOverlay() {
|
||||
return this.std.getOptional(
|
||||
OverlayIdentifier('mindmap-indicator')
|
||||
@@ -62,9 +64,8 @@ export class MindMapExt extends DefaultToolExt {
|
||||
* @returns
|
||||
*/
|
||||
private _createManipulationHandlers(dragMindMapCtx: DragMindMapCtx): {
|
||||
dragStart?: (evt: PointerEventState) => void;
|
||||
dragMove?: (evt: PointerEventState) => void;
|
||||
dragEnd?: (evt: PointerEventState) => void;
|
||||
onDragMove?: (context: ExtensionDragMoveContext) => void;
|
||||
onDragEnd?: (context: ExtensionDragEndContext) => void;
|
||||
} {
|
||||
let hoveredCtx: {
|
||||
mindmap: MindmapElementModel | null;
|
||||
@@ -75,8 +76,8 @@ export class MindMapExt extends DefaultToolExt {
|
||||
} | null = null;
|
||||
|
||||
return {
|
||||
dragMove: (_: PointerEventState) => {
|
||||
const [x, y] = this.defaultTool.dragLastPos;
|
||||
onDragMove: (context: ExtensionDragMoveContext) => {
|
||||
const { x, y } = context.dragLastPos;
|
||||
const hoveredMindMap = this._getHoveredMindMap([x, y], dragMindMapCtx);
|
||||
const indicator = this._indicatorOverlay;
|
||||
|
||||
@@ -166,18 +167,15 @@ export class MindMapExt extends DefaultToolExt {
|
||||
}
|
||||
}
|
||||
},
|
||||
dragEnd: (e: PointerEventState) => {
|
||||
onDragEnd: (dragEndContext: ExtensionDragEndContext) => {
|
||||
if (hoveredCtx?.merge) {
|
||||
hoveredCtx.merge();
|
||||
} else {
|
||||
hoveredCtx?.abort?.();
|
||||
|
||||
if (hoveredCtx?.detach) {
|
||||
const [startX, startY] = this.gfx.viewport.toModelCoord(
|
||||
dragMindMapCtx.startPoint.x,
|
||||
dragMindMapCtx.startPoint.y
|
||||
);
|
||||
const [endX, endY] = this.gfx.viewport.toModelCoord(e.x, e.y);
|
||||
const { x: startX, y: startY } = dragEndContext.dragStartPos;
|
||||
const { x: endX, y: endY } = dragEndContext.dragLastPos;
|
||||
|
||||
dragMindMapCtx.node.element.xywh =
|
||||
dragMindMapCtx.node.element.elementBound
|
||||
@@ -204,7 +202,6 @@ export class MindMapExt extends DefaultToolExt {
|
||||
}
|
||||
|
||||
hoveredCtx = null;
|
||||
dragMindMapCtx.clear();
|
||||
this._responseAreaUpdated.clear();
|
||||
},
|
||||
};
|
||||
@@ -213,24 +210,21 @@ export class MindMapExt extends DefaultToolExt {
|
||||
/**
|
||||
* Create handlers that can translate entire mind map
|
||||
*/
|
||||
private _createTranslationHandlers(
|
||||
_: DragState,
|
||||
ctx: {
|
||||
mindmaps: Set<MindmapElementModel>;
|
||||
nodes: Set<GfxModel>;
|
||||
}
|
||||
): {
|
||||
dragStart?: (evt: PointerEventState) => void;
|
||||
dragMove?: (evt: PointerEventState) => void;
|
||||
dragEnd?: (evt: PointerEventState) => void;
|
||||
private _createTranslationHandlers(ctx: {
|
||||
mindmaps: Set<MindmapElementModel>;
|
||||
nodes: Set<GfxModel>;
|
||||
}): {
|
||||
onDragStart?: (context: ExtensionDragStartContext) => void;
|
||||
onDragMove?: (context: ExtensionDragMoveContext) => void;
|
||||
onDragEnd?: (context: ExtensionDragEndContext) => void;
|
||||
} {
|
||||
return {
|
||||
dragStart: (_: PointerEventState) => {
|
||||
onDragStart: () => {
|
||||
ctx.nodes.forEach(node => {
|
||||
node.stash('xywh');
|
||||
});
|
||||
},
|
||||
dragEnd: (_: PointerEventState) => {
|
||||
onDragEnd: () => {
|
||||
ctx.mindmaps.forEach(mindmap => {
|
||||
mindmap.layout();
|
||||
});
|
||||
@@ -317,7 +311,7 @@ export class MindMapExt extends DefaultToolExt {
|
||||
|
||||
private _setupDragNodeImage(
|
||||
mindmapNode: MindmapNode,
|
||||
event: PointerEventState
|
||||
pos: { x: number; y: number }
|
||||
) {
|
||||
const surfaceBlock = this.gfx.surfaceComponent as SurfaceBlockComponent;
|
||||
const renderer = surfaceBlock?.renderer;
|
||||
@@ -329,7 +323,6 @@ export class MindMapExt extends DefaultToolExt {
|
||||
|
||||
const nodeBound = mindmapNode.element.elementBound;
|
||||
|
||||
const pos = this.gfx.viewport.toModelCoord(event.x, event.y);
|
||||
const canvas = renderer.getCanvasByBound(
|
||||
mindmapNode.element.elementBound,
|
||||
[mindmapNode.element],
|
||||
@@ -338,7 +331,7 @@ export class MindMapExt extends DefaultToolExt {
|
||||
false
|
||||
);
|
||||
|
||||
indicatorOverlay.dragNodePos = [nodeBound.x - pos[0], nodeBound.y - pos[1]];
|
||||
indicatorOverlay.dragNodePos = [nodeBound.x - pos.x, nodeBound.y - pos.y];
|
||||
indicatorOverlay.dragNodeImage = canvas;
|
||||
|
||||
return () => {
|
||||
@@ -385,14 +378,10 @@ export class MindMapExt extends DefaultToolExt {
|
||||
};
|
||||
}
|
||||
|
||||
override initDrag(dragState: DragState) {
|
||||
if (dragState.dragType !== DefaultModeDragType.ContentMoving) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (isSingleMindMapNode(dragState.movedElements)) {
|
||||
const mindmap = dragState.movedElements[0].group as MindmapElementModel;
|
||||
const mindmapNode = mindmap.getNode(dragState.movedElements[0].id)!;
|
||||
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;
|
||||
|
||||
@@ -405,34 +394,36 @@ export class MindMapExt extends DefaultToolExt {
|
||||
|
||||
const clearDragStatus = isRoot
|
||||
? mindmap.stashTree(mindmapNode)
|
||||
: this._setupDragNodeImage(mindmapNode, dragState.event);
|
||||
: this._setupDragNodeImage(mindmapNode, context.dragStartPos);
|
||||
const clearOpacity = this._updateNodeOpacity(mindmap, mindmapNode);
|
||||
|
||||
if (!isRoot) {
|
||||
dragState.movedElements.splice(0, 1);
|
||||
context.elements.splice(0, 1);
|
||||
}
|
||||
|
||||
const mindMapDragCtx: DragMindMapCtx = {
|
||||
mindmap,
|
||||
node: mindmapNode,
|
||||
isRoot,
|
||||
clear: () => {
|
||||
originalMindMapBound: mindmapBound,
|
||||
};
|
||||
|
||||
return {
|
||||
...this._createManipulationHandlers(mindMapDragCtx),
|
||||
clear() {
|
||||
clearOpacity();
|
||||
clearDragStatus?.();
|
||||
if (!isRoot) {
|
||||
dragState.movedElements.push(mindmapNode.element);
|
||||
context.elements.push(mindmapNode.element);
|
||||
}
|
||||
},
|
||||
originalMindMapBound: mindmapBound,
|
||||
startPoint: dragState.event,
|
||||
};
|
||||
|
||||
return this._createManipulationHandlers(mindMapDragCtx);
|
||||
}
|
||||
|
||||
const mindmapNodes = new Set<GfxModel>();
|
||||
const mindmaps = new Set<MindmapElementModel>();
|
||||
dragState.movedElements.forEach(el => {
|
||||
|
||||
context.elements.forEach(el => {
|
||||
if (isMindmapNode(el)) {
|
||||
const mindmap =
|
||||
el.group instanceof MindmapElementModel
|
||||
@@ -452,8 +443,8 @@ export class MindMapExt extends DefaultToolExt {
|
||||
});
|
||||
|
||||
if (mindmapNodes.size > 1) {
|
||||
mindmapNodes.forEach(node => dragState.movedElements.push(node));
|
||||
return this._createTranslationHandlers(dragState, {
|
||||
mindmapNodes.forEach(node => context.elements.push(node));
|
||||
return this._createTranslationHandlers({
|
||||
mindmaps,
|
||||
nodes: mindmapNodes,
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { OverlayIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import { MindmapElementModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
type DragExtensionInitializeContext,
|
||||
type ExtensionDragMoveContext,
|
||||
type GfxModel,
|
||||
TransformExtension,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import type { Bound } from '@blocksuite/global/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();
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import type { PointerEventState } from '@blocksuite/block-std';
|
||||
import type { GfxElementModelView } from '@blocksuite/block-std/gfx';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
import last from 'lodash-es/last';
|
||||
|
||||
import { DefaultModeDragType, DefaultToolExt } from './ext.js';
|
||||
|
||||
export class CanvasElementEventExt extends DefaultToolExt {
|
||||
private _currentStackedElm: GfxElementModelView[] = [];
|
||||
|
||||
override supportedDragTypes: DefaultModeDragType[] = [
|
||||
DefaultModeDragType.None,
|
||||
];
|
||||
|
||||
private _callInReverseOrder(
|
||||
callback: (view: GfxElementModelView) => void,
|
||||
arr = this._currentStackedElm
|
||||
) {
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
const view = arr[i];
|
||||
|
||||
callback(view);
|
||||
}
|
||||
}
|
||||
|
||||
override click(_evt: PointerEventState): void {
|
||||
last(this._currentStackedElm)?.dispatch('click', _evt);
|
||||
}
|
||||
|
||||
override dblClick(_evt: PointerEventState): void {
|
||||
last(this._currentStackedElm)?.dispatch('dblclick', _evt);
|
||||
}
|
||||
|
||||
override pointerDown(_evt: PointerEventState): void {
|
||||
last(this._currentStackedElm)?.dispatch('pointerdown', _evt);
|
||||
}
|
||||
|
||||
override pointerMove(_evt: PointerEventState): void {
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(_evt.x, _evt.y);
|
||||
const hoveredElmViews = this.gfx.grid
|
||||
.search(new Bound(x, y, 1, 1), {
|
||||
filter: ['canvas', 'local'],
|
||||
})
|
||||
.map(model => this.gfx.view.get(model)) as GfxElementModelView[];
|
||||
const currentStackedViews = new Set(this._currentStackedElm);
|
||||
const visited = new Set<GfxElementModelView>();
|
||||
|
||||
this._callInReverseOrder(view => {
|
||||
if (currentStackedViews.has(view)) {
|
||||
visited.add(view);
|
||||
view.dispatch('pointermove', _evt);
|
||||
} else {
|
||||
view.dispatch('pointerenter', _evt);
|
||||
}
|
||||
}, hoveredElmViews);
|
||||
this._callInReverseOrder(
|
||||
view => !visited.has(view) && view.dispatch('pointerleave', _evt)
|
||||
);
|
||||
this._currentStackedElm = hoveredElmViews;
|
||||
}
|
||||
|
||||
override pointerUp(_evt: PointerEventState): void {
|
||||
last(this._currentStackedElm)?.dispatch('pointerup', _evt);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,4 @@
|
||||
import type { PointerEventState } from '@blocksuite/block-std';
|
||||
import type { GfxModel } from '@blocksuite/block-std/gfx';
|
||||
|
||||
import type { DefaultTool } from '../default-tool.js';
|
||||
|
||||
export enum DefaultModeDragType {
|
||||
/** press alt/option key to clone selected */
|
||||
AltCloning = 'alt-cloning',
|
||||
/** Moving connector label */
|
||||
ConnectorLabelMoving = 'connector-label-moving',
|
||||
/** Moving selected contents */
|
||||
@@ -19,45 +12,3 @@ export enum DefaultModeDragType {
|
||||
/** Expanding the dragging area, select the content covered inside */
|
||||
Selecting = 'selecting',
|
||||
}
|
||||
|
||||
export type DragState = {
|
||||
movedElements: GfxModel[];
|
||||
dragType: DefaultModeDragType;
|
||||
event: PointerEventState;
|
||||
};
|
||||
|
||||
export class DefaultToolExt {
|
||||
readonly supportedDragTypes: DefaultModeDragType[] = [];
|
||||
|
||||
get gfx() {
|
||||
return this.defaultTool.gfx;
|
||||
}
|
||||
|
||||
get std() {
|
||||
return this.defaultTool.std;
|
||||
}
|
||||
|
||||
constructor(protected defaultTool: DefaultTool) {}
|
||||
|
||||
click(_evt: PointerEventState) {}
|
||||
|
||||
dblClick(_evt: PointerEventState) {}
|
||||
|
||||
initDrag(_: DragState): {
|
||||
dragStart?: (evt: PointerEventState) => void;
|
||||
dragMove?: (evt: PointerEventState) => void;
|
||||
dragEnd?: (evt: PointerEventState) => void;
|
||||
} {
|
||||
return {};
|
||||
}
|
||||
|
||||
mounted() {}
|
||||
|
||||
pointerDown(_evt: PointerEventState) {}
|
||||
|
||||
pointerMove(_evt: PointerEventState) {}
|
||||
|
||||
pointerUp(_evt: PointerEventState) {}
|
||||
|
||||
unmounted() {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { insertEdgelessTextCommand } from '@blocksuite/affine-block-edgeless-text';
|
||||
import {
|
||||
EdgelessFrameManagerIdentifier,
|
||||
type FrameOverlay,
|
||||
isFrameBlock,
|
||||
} from '@blocksuite/affine-block-frame';
|
||||
@@ -12,7 +11,6 @@ import {
|
||||
import { addText, mountTextElementEditor } from '@blocksuite/affine-gfx-text';
|
||||
import type {
|
||||
EdgelessTextBlockModel,
|
||||
FrameBlockModel,
|
||||
NoteBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
@@ -35,11 +33,10 @@ import type { PointerEventState } from '@blocksuite/block-std';
|
||||
import {
|
||||
BaseTool,
|
||||
getTopElements,
|
||||
type GfxBlockElementModel,
|
||||
type GfxModel,
|
||||
type GfxPrimitiveElementModel,
|
||||
isGfxGroupCompatibleModel,
|
||||
type PointTestOptions,
|
||||
TransformManagerIdentifier,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import type { IVec } from '@blocksuite/global/gfx';
|
||||
@@ -53,25 +50,19 @@ import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js';
|
||||
import { prepareCloneData } from '../utils/clone-utils.js';
|
||||
import { calPanDelta } from '../utils/panning-utils.js';
|
||||
import { isCanvasElement, isEdgelessTextBlock } from '../utils/query.js';
|
||||
import type { SnapManager } from '../utils/snap-manager.js';
|
||||
import {
|
||||
mountConnectorLabelEditor,
|
||||
mountFrameTitleEditor,
|
||||
mountGroupTitleEditor,
|
||||
mountShapeTextEditor,
|
||||
} from '../utils/text.js';
|
||||
import { CanvasElementEventExt } from './default-tool-ext/event-ext.js';
|
||||
import type { DefaultToolExt } from './default-tool-ext/ext.js';
|
||||
import { DefaultModeDragType } from './default-tool-ext/ext.js';
|
||||
import { MindMapExt } from './default-tool-ext/mind-map-ext/mind-map-ext.js';
|
||||
|
||||
export class DefaultTool extends BaseTool {
|
||||
static override toolName: string = 'default';
|
||||
|
||||
private _accumulateDelta: IVec = [0, 0];
|
||||
|
||||
private _alignBound = new Bound();
|
||||
|
||||
private _autoPanTimer: number | null = null;
|
||||
|
||||
private readonly _clearDisposable = () => {
|
||||
@@ -84,42 +75,19 @@ export class DefaultTool extends BaseTool {
|
||||
private readonly _clearSelectingState = () => {
|
||||
this._stopAutoPanning();
|
||||
this._clearDisposable();
|
||||
|
||||
this._wheeling = false;
|
||||
};
|
||||
|
||||
private _disposables: DisposableGroup | null = null;
|
||||
|
||||
private _extHandlers: {
|
||||
dragStart?: (evt: PointerEventState) => void;
|
||||
dragMove?: (evt: PointerEventState) => void;
|
||||
dragEnd?: (evt: PointerEventState) => void;
|
||||
}[] = [];
|
||||
|
||||
private _exts: DefaultToolExt[] = [];
|
||||
|
||||
private _hoveredFrame: FrameBlockModel | null = null;
|
||||
|
||||
// Do not select the text, when click again after activating the note.
|
||||
private _isDoubleClickedOnMask = false;
|
||||
|
||||
private _lock = false;
|
||||
|
||||
private readonly _panViewport = (delta: IVec) => {
|
||||
this._accumulateDelta[0] += delta[0];
|
||||
this._accumulateDelta[1] += delta[1];
|
||||
this.gfx.viewport.applyDeltaCenter(delta[0], delta[1]);
|
||||
};
|
||||
|
||||
private readonly _pendingUpdates = new Map<
|
||||
GfxBlockElementModel | GfxPrimitiveElementModel,
|
||||
Partial<GfxBlockElementModel>
|
||||
>();
|
||||
|
||||
private _rafId: number | null = null;
|
||||
|
||||
private _selectedBounds: Bound[] = [];
|
||||
|
||||
// For moving the connector label
|
||||
private _selectedConnector: ConnectorElementModel | null = null;
|
||||
|
||||
@@ -219,8 +187,6 @@ export class DefaultTool extends BaseTool {
|
||||
});
|
||||
};
|
||||
|
||||
private _wheeling = false;
|
||||
|
||||
dragType = DefaultModeDragType.None;
|
||||
|
||||
enableHover = true;
|
||||
@@ -231,16 +197,6 @@ export class DefaultTool extends BaseTool {
|
||||
return (block as EdgelessRootBlockComponent) ?? null;
|
||||
}
|
||||
|
||||
private get _frameMgr() {
|
||||
return this.std.get(EdgelessFrameManagerIdentifier);
|
||||
}
|
||||
|
||||
private get _supportedExts() {
|
||||
return this._exts.filter(ext =>
|
||||
ext.supportedDragTypes.includes(this.dragType)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end position of the dragging area in the model coordinate
|
||||
*/
|
||||
@@ -263,12 +219,12 @@ export class DefaultTool extends BaseTool {
|
||||
return this.gfx.selection;
|
||||
}
|
||||
|
||||
private get frameOverlay() {
|
||||
return this.std.get(OverlayIdentifier('frame')) as FrameOverlay;
|
||||
get elementTransformMgr() {
|
||||
return this.std.getOptional(TransformManagerIdentifier);
|
||||
}
|
||||
|
||||
get snapOverlay() {
|
||||
return this.std.get(OverlayIdentifier('snap-manager')) as SnapManager;
|
||||
private get frameOverlay() {
|
||||
return this.std.get(OverlayIdentifier('frame')) as FrameOverlay;
|
||||
}
|
||||
|
||||
private _addEmptyParagraphBlock(
|
||||
@@ -285,8 +241,6 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
|
||||
private async _cloneContent() {
|
||||
this._lock = true;
|
||||
|
||||
if (!this._edgeless) return;
|
||||
|
||||
const clipboardController = this._edgeless?.clipboardController;
|
||||
@@ -374,91 +328,6 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
}
|
||||
|
||||
private _filterConnectedConnector() {
|
||||
this._toBeMoved = this._toBeMoved.filter(ele => {
|
||||
// eslint-disable-next-line sonarjs/no-collapsible-if
|
||||
if (
|
||||
ele instanceof ConnectorElementModel &&
|
||||
ele.source?.id &&
|
||||
ele.target?.id
|
||||
) {
|
||||
if (
|
||||
this._toBeMoved.some(e => e.id === ele.source.id) &&
|
||||
this._toBeMoved.some(e => e.id === ele.target.id)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private _isDraggable(element: GfxModel) {
|
||||
return !(
|
||||
element instanceof ConnectorElementModel &&
|
||||
!ConnectorUtils.isConnectorAndBindingsAllSelected(
|
||||
element,
|
||||
this._toBeMoved
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _moveContent(
|
||||
[dx, dy]: IVec,
|
||||
alignBound: Bound,
|
||||
shifted?: boolean,
|
||||
shouldClone?: boolean
|
||||
) {
|
||||
alignBound.x += dx;
|
||||
alignBound.y += dy;
|
||||
|
||||
const alignRst = this.snapOverlay.align(alignBound);
|
||||
const delta = [dx + alignRst.dx, dy + alignRst.dy];
|
||||
|
||||
if (shifted) {
|
||||
const angle = Math.abs(Math.atan2(delta[1], delta[0]));
|
||||
const direction =
|
||||
angle < Math.PI / 4 || angle > 3 * (Math.PI / 4) ? 'x' : 'y';
|
||||
delta[direction === 'x' ? 1 : 0] = 0;
|
||||
}
|
||||
|
||||
this._toBeMoved.forEach((element, index) => {
|
||||
const isGraphicElement = isCanvasElement(element);
|
||||
|
||||
if (isGraphicElement && !this._isDraggable(element)) return;
|
||||
|
||||
let bound = this._selectedBounds[index];
|
||||
if (shouldClone) bound = bound.clone();
|
||||
|
||||
bound.x += delta[0];
|
||||
bound.y += delta[1];
|
||||
|
||||
if (isGraphicElement) {
|
||||
if (!this._lock) {
|
||||
this._lock = true;
|
||||
this.doc.captureSync();
|
||||
}
|
||||
|
||||
if (element instanceof ConnectorElementModel) {
|
||||
element.moveTo(bound);
|
||||
}
|
||||
}
|
||||
|
||||
this._scheduleUpdate(element, {
|
||||
xywh: bound.serialize(),
|
||||
});
|
||||
});
|
||||
|
||||
this._hoveredFrame = this._frameMgr.getFrameFromPoint(
|
||||
this.dragLastPos,
|
||||
this._toBeMoved.filter(ele => isFrameBlock(ele))
|
||||
);
|
||||
|
||||
this._hoveredFrame && !this._hoveredFrame.isLocked()
|
||||
? this.frameOverlay.highlight(this._hoveredFrame)
|
||||
: this.frameOverlay.clear();
|
||||
}
|
||||
|
||||
private _moveLabel(delta: IVec) {
|
||||
const connector = this._selectedConnector;
|
||||
let bounds = this._selectedConnectorLabelBounds;
|
||||
@@ -535,62 +404,15 @@ export class DefaultTool extends BaseTool {
|
||||
return tryGetLockedAncestor(result);
|
||||
}
|
||||
|
||||
private _scheduleUpdate(
|
||||
element: GfxBlockElementModel | GfxPrimitiveElementModel,
|
||||
updates: Partial<GfxBlockElementModel>
|
||||
) {
|
||||
this._pendingUpdates.set(element, updates);
|
||||
|
||||
if (this._rafId !== null) return;
|
||||
|
||||
this._rafId = requestAnimationFrame(() => {
|
||||
this._pendingUpdates.forEach((updates, element) => {
|
||||
this.gfx.updateElement(element, updates);
|
||||
});
|
||||
this._pendingUpdates.clear();
|
||||
this._rafId = null;
|
||||
});
|
||||
}
|
||||
|
||||
private initializeDragState(
|
||||
dragType: DefaultModeDragType,
|
||||
event: PointerEventState
|
||||
) {
|
||||
this.dragType = dragType;
|
||||
|
||||
const mindmaps: MindmapElementModel[] = this._toBeMoved.reduce(
|
||||
(pre, elem) => {
|
||||
if (
|
||||
elem.group instanceof MindmapElementModel &&
|
||||
!pre.includes(elem.group)
|
||||
) {
|
||||
pre.push(elem.group);
|
||||
}
|
||||
|
||||
return pre;
|
||||
},
|
||||
[] as MindmapElementModel[]
|
||||
);
|
||||
|
||||
this._alignBound = this.snapOverlay.setMovingElements(this._toBeMoved, [
|
||||
...mindmaps,
|
||||
...mindmaps.flatMap(m => m.childElements),
|
||||
]);
|
||||
|
||||
this._clearDisposable();
|
||||
this._disposables = new DisposableGroup();
|
||||
|
||||
const ctx = {
|
||||
movedElements: this._toBeMoved,
|
||||
dragType,
|
||||
event,
|
||||
};
|
||||
|
||||
this._extHandlers = this._supportedExts.map(ext => ext.initDrag(ctx));
|
||||
this._selectedBounds = this._toBeMoved.map(element =>
|
||||
Bound.deserialize(element.xywh)
|
||||
);
|
||||
|
||||
// If the drag type is selecting, set up the dragging area disposable group
|
||||
// If the viewport updates when dragging, should update the dragging area and selection
|
||||
if (this.dragType === DefaultModeDragType.Selecting) {
|
||||
@@ -609,36 +431,16 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
|
||||
if (this.dragType === DefaultModeDragType.ContentMoving) {
|
||||
this._disposables.add(
|
||||
this.gfx.viewport.viewportMoved.subscribe(delta => {
|
||||
if (
|
||||
this.dragType === DefaultModeDragType.ContentMoving &&
|
||||
this.controller.dragging$.peek() &&
|
||||
!this._autoPanTimer
|
||||
) {
|
||||
if (
|
||||
this._toBeMoved.every(ele => {
|
||||
return !this._isDraggable(ele);
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._wheeling) {
|
||||
this._wheeling = true;
|
||||
this._selectedBounds = this._toBeMoved.map(element =>
|
||||
Bound.deserialize(element.xywh)
|
||||
);
|
||||
}
|
||||
|
||||
this._alignBound = this.snapOverlay.setMovingElements(
|
||||
this._toBeMoved
|
||||
);
|
||||
|
||||
this._moveContent(delta, this._alignBound);
|
||||
}
|
||||
})
|
||||
);
|
||||
if (this.elementTransformMgr) {
|
||||
this.doc.captureSync();
|
||||
this.elementTransformMgr.initializeDrag({
|
||||
movingElements: this._toBeMoved,
|
||||
event: event.raw,
|
||||
onDragEnd: () => {
|
||||
this.doc.captureSync();
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -742,7 +544,7 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
|
||||
this._isDoubleClickedOnMask = false;
|
||||
this._supportedExts.forEach(ext => ext.click?.(e));
|
||||
this.elementTransformMgr?.dispatch('click', e);
|
||||
}
|
||||
|
||||
override deactivate() {
|
||||
@@ -819,7 +621,7 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
}
|
||||
|
||||
this._supportedExts.forEach(ext => ext.click?.(e));
|
||||
this.elementTransformMgr?.dispatch('dblclick', e);
|
||||
|
||||
if (
|
||||
e.raw.target &&
|
||||
@@ -832,48 +634,9 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
}
|
||||
|
||||
override dragEnd(e: PointerEventState) {
|
||||
this._extHandlers.forEach(handler => handler.dragEnd?.(e));
|
||||
|
||||
this._toBeMoved.forEach(el => {
|
||||
this.doc.transact(() => {
|
||||
el.pop('xywh');
|
||||
});
|
||||
|
||||
if (el instanceof ConnectorElementModel) {
|
||||
el.pop('labelXYWH');
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
const frameManager = this._frameMgr;
|
||||
const toBeMovedTopElements = getTopElements(
|
||||
this._toBeMoved.map(el =>
|
||||
el.group instanceof MindmapElementModel ? el.group : el
|
||||
)
|
||||
);
|
||||
if (this._hoveredFrame) {
|
||||
frameManager.addElementsToFrame(
|
||||
this._hoveredFrame,
|
||||
toBeMovedTopElements
|
||||
);
|
||||
} else {
|
||||
// only apply to root nodes of trees
|
||||
toBeMovedTopElements.forEach(element =>
|
||||
frameManager.removeFromParentFrame(element)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._lock) {
|
||||
this.doc.captureSync();
|
||||
this._lock = false;
|
||||
}
|
||||
|
||||
override dragEnd() {
|
||||
if (this.edgelessSelectionManager.editing) return;
|
||||
|
||||
this._selectedBounds = [];
|
||||
this.snapOverlay.clear();
|
||||
this.frameOverlay.clear();
|
||||
this._toBeMoved = [];
|
||||
this._selectedConnector = null;
|
||||
@@ -897,28 +660,7 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DefaultModeDragType.AltCloning:
|
||||
case DefaultModeDragType.ContentMoving: {
|
||||
if (
|
||||
this._toBeMoved.length &&
|
||||
this._toBeMoved.every(ele => {
|
||||
return !this._isDraggable(ele);
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._wheeling) {
|
||||
this._wheeling = false;
|
||||
}
|
||||
|
||||
const dx = this.dragLastPos[0] - this.dragStartPos[0];
|
||||
const dy = this.dragLastPos[1] - this.dragStartPos[1];
|
||||
const alignBound = this._alignBound.clone();
|
||||
const shifted = e.keys.shift || this.gfx.keyboard.shiftKey$.peek();
|
||||
|
||||
this._moveContent([dx, dy], alignBound, shifted, true);
|
||||
this._extHandlers.forEach(handler => handler.dragMove?.(e));
|
||||
break;
|
||||
}
|
||||
case DefaultModeDragType.ConnectorLabelMoving: {
|
||||
@@ -956,30 +698,12 @@ export class DefaultTool extends BaseTool {
|
||||
this._toBeMoved = Array.from(toBeMoved);
|
||||
|
||||
// If alt key is pressed and content is moving, clone the content
|
||||
if (e.keys.alt && dragType === DefaultModeDragType.ContentMoving) {
|
||||
dragType = DefaultModeDragType.AltCloning;
|
||||
if (dragType === DefaultModeDragType.ContentMoving && e.keys.alt) {
|
||||
await this._cloneContent();
|
||||
}
|
||||
this._filterConnectedConnector();
|
||||
|
||||
// Connector needs to be updated first
|
||||
this._toBeMoved.sort((a, _) =>
|
||||
a instanceof ConnectorElementModel ? -1 : 1
|
||||
);
|
||||
|
||||
// Set up drag state
|
||||
this.initializeDragState(dragType, e);
|
||||
|
||||
// stash the state
|
||||
this._toBeMoved.forEach(ele => {
|
||||
ele.stash('xywh');
|
||||
|
||||
if (ele instanceof ConnectorElementModel) {
|
||||
ele.stash('labelXYWH');
|
||||
}
|
||||
});
|
||||
|
||||
this._extHandlers.forEach(handler => handler.dragStart?.(e));
|
||||
}
|
||||
|
||||
override mounted() {
|
||||
@@ -1003,15 +727,10 @@ export class DefaultTool extends BaseTool {
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this._exts = [MindMapExt, CanvasElementEventExt].map(
|
||||
constructor => new constructor(this)
|
||||
);
|
||||
this._exts.forEach(ext => ext.mounted());
|
||||
}
|
||||
|
||||
override pointerDown(e: PointerEventState): void {
|
||||
this._supportedExts.forEach(ext => ext.pointerDown(e));
|
||||
this.elementTransformMgr?.dispatch('pointerdown', e);
|
||||
}
|
||||
|
||||
override pointerMove(e: PointerEventState) {
|
||||
@@ -1030,20 +749,18 @@ export class DefaultTool extends BaseTool {
|
||||
this.frameOverlay.clear();
|
||||
}
|
||||
|
||||
this._supportedExts.forEach(ext => ext.pointerMove(e));
|
||||
this.elementTransformMgr?.dispatch('pointermove', e);
|
||||
}
|
||||
|
||||
override pointerUp(e: PointerEventState) {
|
||||
this._supportedExts.forEach(ext => ext.pointerUp(e));
|
||||
this.elementTransformMgr?.dispatch('pointerup', e);
|
||||
}
|
||||
|
||||
override tripleClick() {
|
||||
if (this._isDoubleClickedOnMask) return;
|
||||
}
|
||||
|
||||
override unmounted(): void {
|
||||
this._exts.forEach(ext => ext.unmounted());
|
||||
}
|
||||
override unmounted(): void {}
|
||||
}
|
||||
|
||||
declare module '@blocksuite/block-std/gfx' {
|
||||
|
||||
@@ -36,7 +36,7 @@ const ALIGN_THRESHOLD = 8;
|
||||
const DISTRIBUTION_LINE_OFFSET = 1;
|
||||
const STROKE_WIDTH = 2;
|
||||
|
||||
export class SnapManager extends Overlay {
|
||||
export class SnapOverlay extends Overlay {
|
||||
static override overlayName: string = 'snap-manager';
|
||||
|
||||
private _skippedElements: Set<GfxModel> = new Set();
|
||||
@@ -76,8 +76,6 @@ export class SnapManager extends Overlay {
|
||||
};
|
||||
|
||||
override clear() {
|
||||
super.clear();
|
||||
|
||||
this._referenceBounds = {
|
||||
vertical: [],
|
||||
horizontal: [],
|
||||
@@ -89,6 +87,8 @@ export class SnapManager extends Overlay {
|
||||
};
|
||||
this._distributedAlignLines = [];
|
||||
this._skippedElements.clear();
|
||||
|
||||
super.clear();
|
||||
}
|
||||
|
||||
private _alignDistributeHorizontally(
|
||||
|
||||
Reference in New Issue
Block a user