refactor(editor): move mindmap transform to its package (#11115)

This commit is contained in:
Saul-Mirone
2025-03-24 05:19:30 +00:00
parent 83a2e3bcba
commit b8df65a2b0
13 changed files with 43 additions and 36 deletions

View File

@@ -18,6 +18,7 @@ import {
OverlayIdentifier,
TextUtils,
} from '@blocksuite/affine-block-surface';
import { isMindmapNode } from '@blocksuite/affine-gfx-mindmap';
import {
type BookmarkBlockModel,
ConnectorElementModel,
@@ -85,7 +86,6 @@ import {
isEmbedSyncedDocBlock,
isEmbedYoutubeBlock,
isImageBlock,
isMindmapNode,
} from '../../utils/query.js';
import {
HandleDirection,

View File

@@ -4,8 +4,14 @@ import {
PresentTool,
} from '@blocksuite/affine-block-frame';
import { ConnectionOverlay } from '@blocksuite/affine-block-surface';
import { ConnectorTool } from '@blocksuite/affine-gfx-connector';
import { MindMapIndicatorOverlay } from '@blocksuite/affine-gfx-mindmap';
import {
ConnectorFilter,
ConnectorTool,
} from '@blocksuite/affine-gfx-connector';
import {
MindMapDragExtension,
MindMapIndicatorOverlay,
} from '@blocksuite/affine-gfx-mindmap';
import { NoteTool } from '@blocksuite/affine-gfx-note';
import { ShapeTool } from '@blocksuite/affine-gfx-shape';
import { TextTool } from '@blocksuite/affine-gfx-text';
@@ -17,8 +23,6 @@ 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 { MindMapDragExtension } from './element-transform/mind-map-drag.js';
import { SnapExtension } from './element-transform/snap-manager.js';
import { BrushTool } from './gfx-tool/brush-tool.js';
import { DefaultTool } from './gfx-tool/default-tool.js';

View File

@@ -3,6 +3,11 @@ import { EdgelessTextBlockComponent } from '@blocksuite/affine-block-edgeless-te
import { isNoteBlock } from '@blocksuite/affine-block-surface';
import { toast } from '@blocksuite/affine-components/toast';
import { mountConnectorLabelEditor } from '@blocksuite/affine-gfx-connector';
import {
getNearestTranslation,
isElementOutsideViewport,
isSingleMindMapNode,
} from '@blocksuite/affine-gfx-mindmap';
import { mountShapeTextEditor, ShapeTool } from '@blocksuite/affine-gfx-shape';
import {
ConnectorElementModel,
@@ -43,11 +48,6 @@ import {
} from './utils/consts.js';
import { deleteElements } from './utils/crud.js';
import { getNextShapeType } from './utils/hotkey-utils.js';
import {
getNearestTranslation,
isElementOutsideViewport,
isSingleMindMapNode,
} from './utils/mindmap.js';
import { isCanvasElement } from './utils/query.js';
export class EdgelessPageKeyboardManager extends PageKeyboardManager {

View File

@@ -7,6 +7,7 @@ import {
getBgGridGap,
normalizeWheelDeltaY,
} from '@blocksuite/affine-block-surface';
import { isSingleMindMapNode } from '@blocksuite/affine-gfx-mindmap';
import { mountShapeTextEditor } from '@blocksuite/affine-gfx-shape';
import {
NoteBlockModel,
@@ -51,7 +52,6 @@ import { EdgelessClipboardController } from './clipboard/clipboard.js';
import type { EdgelessSelectedRectWidget } from './components/rects/edgeless-selected-rect.js';
import { EdgelessPageKeyboardManager } from './edgeless-keyboard.js';
import type { EdgelessRootService } from './edgeless-root-service.js';
import { isSingleMindMapNode } from './utils/mindmap.js';
import { isCanvasElement } from './utils/query.js';
export class EdgelessRootBlockComponent extends BlockComponent<

View File

@@ -1,39 +0,0 @@
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 {};
}
}

View File

@@ -1,455 +0,0 @@
import {
OverlayIdentifier,
type SurfaceBlockComponent,
} from '@blocksuite/affine-block-surface';
import {
containsNode,
createFromTree,
detachMindmap,
findTargetNode,
hideNodeConnector,
type MindMapIndicatorOverlay,
NODE_HORIZONTAL_SPACING,
NODE_VERTICAL_SPACING,
tryMoveNode,
} from '@blocksuite/affine-gfx-mindmap';
import {
type LayoutType,
type LocalConnectorElementModel,
MindmapElementModel,
type MindmapNode,
} from '@blocksuite/affine-model';
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';
import { isMindmapNode } from '../utils/query';
import { calculateResponseArea } from './utils/drag-utils';
type DragMindMapCtx = {
mindmap: MindmapElementModel;
node: MindmapNode;
/**
* Whether the dragged node is the root node of the mind map
*/
isRoot: boolean;
originalMindMapBound: Bound;
};
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>();
private get _indicatorOverlay() {
return this.std.getOptional(
OverlayIdentifier('mindmap-indicator')
) as MindMapIndicatorOverlay | null;
}
private _calcDragResponseArea(mindmap: MindmapElementModel) {
calculateResponseArea(mindmap);
this._responseAreaUpdated.add(mindmap);
}
/**
* Create handlers that can drag and drop mind map nodes
* @param dragMindMapCtx
* @param dragState
* @returns
*/
private _createManipulationHandlers(dragMindMapCtx: DragMindMapCtx): {
onDragMove?: (context: ExtensionDragMoveContext) => void;
onDragEnd?: (context: ExtensionDragEndContext) => void;
} {
let hoveredCtx: {
mindmap: MindmapElementModel | null;
node: MindmapNode | null;
detach?: boolean;
abort?: () => void;
merge?: () => void;
} | null = null;
return {
onDragMove: (context: ExtensionDragMoveContext) => {
const { x, y } = context.dragLastPos;
const hoveredMindMap = this._getHoveredMindMap([x, y], dragMindMapCtx);
const indicator = this._indicatorOverlay;
if (indicator) {
indicator.currentDragPos = [x, y];
indicator.refresh();
}
hoveredCtx?.abort?.();
const hoveredNode = hoveredMindMap
? findTargetNode(hoveredMindMap, [x, y])
: null;
hoveredCtx = {
mindmap: hoveredMindMap,
node: hoveredNode,
};
// hovered on the currently dragged mind map but
// 1. not hovered on any node or
// 2. hovered on the node that is itself or its children (which is not allowed)
// then consider user is trying to drop the node to its original position
if (
hoveredNode &&
hoveredMindMap &&
!containsNode(hoveredMindMap, hoveredNode, dragMindMapCtx.node)
) {
const operation = tryMoveNode(
hoveredMindMap,
hoveredNode,
dragMindMapCtx.mindmap,
dragMindMapCtx.node,
[x, y],
options => this._drawIndicator(options)
);
if (operation) {
hoveredCtx.abort = operation.abort;
hoveredCtx.merge = operation.merge;
}
} else if (dragMindMapCtx.isRoot) {
dragMindMapCtx.mindmap.layout();
hoveredCtx.merge = () => {
dragMindMapCtx.mindmap.layout();
};
} else {
// if `hoveredMindMap` is not null
// either the node is hovered on the dragged node's children
// or the there is no hovered node at all
// then consider user is trying to place the node to its original position
if (hoveredMindMap) {
const { node: draggedNode, mindmap } = dragMindMapCtx;
const nodeBound = draggedNode.element.elementBound;
hoveredCtx.abort = this._drawIndicator({
targetMindMap: mindmap,
target: draggedNode,
sourceMindMap: mindmap,
source: draggedNode,
newParent: draggedNode.parent!,
insertPosition: {
type: 'sibling',
layoutDir: mindmap.getLayoutDir(draggedNode) as Exclude<
LayoutType,
LayoutType.BALANCE
>,
position: y > nodeBound.y + nodeBound.h / 2 ? 'next' : 'prev',
},
path: mindmap.getPath(draggedNode),
});
} else {
hoveredCtx.detach = true;
const reset = (hoveredCtx.abort = hideNodeConnector(
dragMindMapCtx.mindmap,
dragMindMapCtx.node
));
hoveredCtx.abort = () => {
reset?.();
};
}
}
},
onDragEnd: (dragEndContext: ExtensionDragEndContext) => {
if (hoveredCtx?.merge) {
hoveredCtx.merge();
} else {
hoveredCtx?.abort?.();
if (hoveredCtx?.detach) {
const { x: startX, y: startY } = dragEndContext.dragStartPos;
const { x: endX, y: endY } = dragEndContext.dragLastPos;
dragMindMapCtx.node.element.xywh =
dragMindMapCtx.node.element.elementBound
.moveDelta(endX - startX, endY - startY)
.serialize();
if (dragMindMapCtx.node !== dragMindMapCtx.mindmap.tree) {
detachMindmap(dragMindMapCtx.mindmap, dragMindMapCtx.node);
const mindmap = createFromTree(
dragMindMapCtx.node,
dragMindMapCtx.mindmap.style,
dragMindMapCtx.mindmap.layoutType,
this.gfx.surface!
);
mindmap.layout();
} else {
dragMindMapCtx.mindmap.layout();
}
}
}
hoveredCtx = null;
this._responseAreaUpdated.clear();
},
};
}
/**
* Create handlers that can translate entire mind map
*/
private _createTranslationHandlers(ctx: {
mindmaps: Set<MindmapElementModel>;
nodes: Set<GfxModel>;
}): {
onDragStart?: (context: ExtensionDragStartContext) => void;
onDragMove?: (context: ExtensionDragMoveContext) => void;
onDragEnd?: (context: ExtensionDragEndContext) => void;
} {
return {
onDragStart: () => {
ctx.nodes.forEach(node => {
node.stash('xywh');
});
},
onDragEnd: () => {
ctx.mindmaps.forEach(mindmap => {
mindmap.layout();
});
},
};
}
private _drawIndicator(options: {
targetMindMap: MindmapElementModel;
target: MindmapNode;
sourceMindMap: MindmapElementModel;
source: MindmapNode;
newParent: MindmapNode;
insertPosition:
| {
type: 'sibling';
layoutDir: Exclude<LayoutType, LayoutType.BALANCE>;
position: 'prev' | 'next';
}
| { type: 'child'; layoutDir: Exclude<LayoutType, LayoutType.BALANCE> };
path: number[];
}) {
const indicatorOverlay = this._indicatorOverlay;
if (!indicatorOverlay) {
return () => {};
}
// draw the indicator at given position
const { newParent, insertPosition, targetMindMap, target, source, path } =
options;
const children = newParent.children.filter(
node => node.element.id !== source.id
);
indicatorOverlay.setIndicatorInfo({
targetMindMap,
target,
parent: newParent,
insertPosition,
parentChildren: children,
path,
});
return () => {
indicatorOverlay.clear();
};
}
private _getHoveredMindMap(
position: IVec,
dragMindMapCtx: DragMindMapCtx
): MindmapElementModel | null {
const mindmap =
(this.gfx
.getElementByPoint(position[0], position[1], {
all: true,
responsePadding: [NODE_HORIZONTAL_SPACING, NODE_VERTICAL_SPACING * 2],
})
.find(el => {
if (!(el instanceof MindmapElementModel)) {
return false;
}
if (
el === dragMindMapCtx.mindmap &&
!dragMindMapCtx.originalMindMapBound.containsPoint(position)
) {
return false;
}
return true;
}) as MindmapElementModel) ?? null;
if (
mindmap &&
(!this._responseAreaUpdated.has(mindmap) || !mindmap.tree.responseArea)
) {
this._calcDragResponseArea(mindmap);
}
return mindmap;
}
private _setupDragNodeImage(
mindmapNode: MindmapNode,
pos: { x: number; y: number }
) {
const surfaceBlock = this.gfx.surfaceComponent as SurfaceBlockComponent;
const renderer = surfaceBlock?.renderer;
const indicatorOverlay = this._indicatorOverlay;
if (!renderer || !indicatorOverlay) {
return;
}
const nodeBound = mindmapNode.element.elementBound;
const canvas = renderer.getCanvasByBound(
mindmapNode.element.elementBound,
[mindmapNode.element],
undefined,
undefined,
false
);
indicatorOverlay.dragNodePos = [nodeBound.x - pos.x, nodeBound.y - pos.y];
indicatorOverlay.dragNodeImage = canvas;
return () => {
indicatorOverlay.dragNodeImage = null;
indicatorOverlay.currentDragPos = null;
};
}
private _updateNodeOpacity(
mindmap: MindmapElementModel,
mindNode: MindmapNode
) {
const OPACITY = 0.3;
const updatedNodes = new Set<
GfxPrimitiveElementModel | LocalConnectorElementModel
>();
const traverse = (node: MindmapNode, parent: MindmapNode | null) => {
node.element.opacity = OPACITY;
updatedNodes.add(node.element);
if (parent) {
const connectorId = `#${parent.element.id}-${node.element.id}`;
const connector = mindmap.connectors.get(connectorId);
if (connector) {
connector.opacity = OPACITY;
updatedNodes.add(connector);
}
}
if (node.children.length) {
node.children.forEach(child => traverse(child, node));
}
};
const parentNode = mindmap.getParentNode(mindNode.element.id) ?? null;
traverse(mindNode, parentNode);
return () => {
updatedNodes.forEach(el => {
el.opacity = 1;
});
};
}
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;
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);
const clearDragStatus = isRoot
? mindmap.stashTree(mindmapNode)
: this._setupDragNodeImage(mindmapNode, context.dragStartPos);
const clearOpacity = this._updateNodeOpacity(mindmap, mindmapNode);
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 mindmapNodes = new Set<GfxModel>();
const mindmaps = new Set<MindmapElementModel>();
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 {};
}
}

View File

@@ -1,192 +0,0 @@
import {
NODE_HORIZONTAL_SPACING,
NODE_VERTICAL_SPACING,
} from '@blocksuite/affine-gfx-mindmap';
import {
LayoutType,
type MindmapElementModel,
type MindmapNode,
type MindmapRoot,
} from '@blocksuite/affine-model';
import { Bound } from '@blocksuite/global/gfx';
import last from 'lodash-es/last';
const isOnEdge = (node: MindmapNode, direction: 'tail' | 'head') => {
let current = node;
while (current) {
if (!current.parent) return true;
if (direction === 'tail' && last(current.parent.children) === current) {
current = current.parent;
} else if (direction === 'head' && current.parent.children[0] === current) {
current = current.parent;
} else {
return false;
}
}
return true;
};
const TAIL_RESPONSE_AREA = NODE_HORIZONTAL_SPACING;
const fillResponseArea = (
node: MindmapNode,
layoutType: LayoutType,
parent: MindmapNode | null
) => {
// root node
if (!parent) {
const rootElmBound = node.element.elementBound;
const width =
layoutType === LayoutType.BALANCE
? rootElmBound.w + TAIL_RESPONSE_AREA * 2
: rootElmBound.w + TAIL_RESPONSE_AREA;
node.responseArea = new Bound(
layoutType === LayoutType.BALANCE || layoutType === LayoutType.LEFT
? rootElmBound.x - TAIL_RESPONSE_AREA
: rootElmBound.x,
rootElmBound.y,
width,
rootElmBound.h
);
if (node.detail.collapsed) {
return;
}
if (layoutType === LayoutType.BALANCE) {
(node as MindmapRoot).right.forEach(child => {
fillResponseArea(child, LayoutType.RIGHT, node);
});
(node as MindmapRoot).left.forEach(child => {
fillResponseArea(child, LayoutType.LEFT, node);
});
} else {
node.children.forEach(child => {
fillResponseArea(child, layoutType, node);
});
}
return;
} else {
const nodeBound = node.element.elementBound;
const idx = parent.children.indexOf(node) ?? -1;
const isLast =
idx === (parent.children.length || -1) - 1 && isOnEdge(node, 'tail');
const isFirst = idx === 0 && isOnEdge(node, 'head');
const upperSpacing = isFirst
? NODE_VERTICAL_SPACING * 2
: NODE_VERTICAL_SPACING / 2;
const lowerSpacing = isLast
? NODE_VERTICAL_SPACING * 2
: NODE_VERTICAL_SPACING / 2;
const h = nodeBound.h + upperSpacing + lowerSpacing;
const w =
(layoutType === LayoutType.RIGHT
? node.element.x +
node.element.w -
(parent.element.x + parent.element.w)
: parent.element.x - node.element.x) + TAIL_RESPONSE_AREA;
node.responseArea = new Bound(
layoutType === LayoutType.RIGHT
? parent.element.x + parent.element.w
: parent.element.x - w,
node.element.y - upperSpacing,
w,
h
);
if (node.children.length > 0 && !node.detail.collapsed) {
let responseArea: Bound;
node.children.forEach(child => {
fillResponseArea(child, layoutType, node);
if (responseArea) {
responseArea = responseArea.unite(child.responseArea!);
} else {
responseArea = child.responseArea!;
}
});
node.responseArea.h = responseArea!.h;
node.responseArea.y = responseArea!.y;
}
}
};
export const balanceLeftRightResponseArea = (tree: MindmapRoot) => {
const leftTreeArea = tree.left.reduce((pre: Bound | null, node) => {
if (pre) {
return pre.unite(node.responseArea!);
}
return node.responseArea!;
}, null);
const rightTreeArea = tree.right.reduce((pre: Bound | null, node) => {
if (pre) {
return pre.unite(node.responseArea!);
}
return node.responseArea!;
}, null);
if (!leftTreeArea || !rightTreeArea) {
return;
}
// if the height of the left tree and right tree are not equal
// expand the response area of lower tree to match the height of the higher tree
if (leftTreeArea.h !== rightTreeArea.h) {
const isLeftHigher = leftTreeArea.h > rightTreeArea.h;
const upperBoundary = isLeftHigher ? leftTreeArea.y : rightTreeArea.y;
const bottomBoundary = isLeftHigher
? leftTreeArea.y + leftTreeArea.h
: rightTreeArea.y + rightTreeArea.h;
const targetChildren = isLeftHigher ? tree.right : tree.left;
const expandEdge = (children: MindmapNode[]) => {
const expand = (direction: 'up' | 'down') => {
const expandUpperEdge = direction === 'up';
const node = direction === 'up' ? children[0] : last(children)!;
if (!node) return;
if (node.responseArea) {
node.responseArea.h = expandUpperEdge
? node.responseArea.h + (node.responseArea.y - upperBoundary)
: node.responseArea.h +
(bottomBoundary - node.responseArea.y - node.responseArea.h);
expandUpperEdge && (node.responseArea.y = upperBoundary);
}
};
expand('up');
expand('down');
};
expandEdge(targetChildren);
}
};
export const calculateResponseArea = (mindmap: MindmapElementModel) => {
const layoutDir = mindmap.layoutType;
const tree = mindmap.tree;
switch (layoutDir) {
case LayoutType.RIGHT:
case LayoutType.LEFT:
{
fillResponseArea(tree, layoutDir, null);
}
break;
case LayoutType.BALANCE:
{
fillResponseArea(tree, LayoutType.BALANCE, null);
balanceLeftRightResponseArea(tree);
}
break;
}
};

View File

@@ -1,62 +0,0 @@
import { MindmapElementModel } from '@blocksuite/affine-model';
import type { GfxModel, Viewport } from '@blocksuite/block-std/gfx';
export function isSingleMindMapNode(els: GfxModel[]) {
return els.length === 1 && els[0].group instanceof MindmapElementModel;
}
export function isElementOutsideViewport(
viewport: Viewport,
element: GfxModel,
padding: [number, number] = [0, 0]
) {
const elementBound = element.elementBound;
padding[0] /= viewport.zoom;
padding[1] /= viewport.zoom;
elementBound.x -= padding[1];
elementBound.w += padding[1];
elementBound.y -= padding[0];
elementBound.h += padding[0];
return !viewport.viewportBounds.contains(elementBound);
}
export function getNearestTranslation(
viewport: Viewport,
element: GfxModel,
padding: [number, number] = [0, 0]
) {
const viewportBound = viewport.viewportBounds;
const elementBound = element.elementBound;
let dx = 0;
let dy = 0;
if (elementBound.x - padding[1] < viewportBound.x) {
dx = viewportBound.x - (elementBound.x - padding[1]);
} else if (
elementBound.x + elementBound.w + padding[1] >
viewportBound.x + viewportBound.w
) {
dx =
viewportBound.x +
viewportBound.w -
(elementBound.x + elementBound.w + padding[1]);
}
if (elementBound.y - padding[0] < viewportBound.y) {
dy = elementBound.y - padding[0] - viewportBound.y;
} else if (
elementBound.y + elementBound.h + padding[0] >
viewportBound.y + viewportBound.h
) {
dy =
elementBound.y +
elementBound.h +
padding[0] -
(viewportBound.y + viewportBound.h);
}
return [dx, dy];
}

View File

@@ -14,7 +14,6 @@ import {
type EmbedSyncedDocModel,
type EmbedYoutubeModel,
type ImageBlockModel,
MindmapElementModel,
ShapeElementModel,
TextElementModel,
} from '@blocksuite/affine-model';
@@ -23,7 +22,6 @@ import {
isTopLevelBlock,
} from '@blocksuite/affine-shared/utils';
import type {
GfxBlockElementModel,
GfxModel,
GfxPrimitiveElementModel,
GfxToolsFullOptionValue,
@@ -33,10 +31,6 @@ import type { PointLocation } from '@blocksuite/global/gfx';
import { Bound } from '@blocksuite/global/gfx';
import type { BlockModel } from '@blocksuite/store';
export function isMindmapNode(element: GfxBlockElementModel | GfxModel | null) {
return element?.group instanceof MindmapElementModel;
}
export function isEdgelessTextBlock(
element: BlockModel | GfxModel | null
): element is EdgelessTextBlockModel {