mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-22 12:36:24 +08:00
refactor(editor): move mindmap view to mindmap package (#11102)
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
import { Overlay, PathGenerator } from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
ConnectorMode,
|
||||
LayoutType,
|
||||
type MindmapElementModel,
|
||||
type MindmapNode,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
type Bound,
|
||||
isVecZero,
|
||||
type IVec,
|
||||
PointLocation,
|
||||
toRadian,
|
||||
Vec,
|
||||
} from '@blocksuite/global/gfx';
|
||||
import last from 'lodash-es/last';
|
||||
|
||||
import { NODE_HORIZONTAL_SPACING, NODE_VERTICAL_SPACING } from '../view/layout';
|
||||
|
||||
export class MindMapIndicatorOverlay extends Overlay {
|
||||
static INDICATOR_SIZE = [48, 22];
|
||||
|
||||
static override overlayName: string = 'mindmap-indicator';
|
||||
|
||||
currentDragPos: IVec | null = null;
|
||||
|
||||
direction: LayoutType.LEFT | LayoutType.RIGHT = LayoutType.RIGHT;
|
||||
|
||||
dragNodeImage: HTMLCanvasElement | null = null;
|
||||
|
||||
dragNodePos: IVec = [0, 0];
|
||||
|
||||
mode: ConnectorMode = ConnectorMode.Straight;
|
||||
|
||||
parentBound: Bound | null = null;
|
||||
|
||||
pathGen = new PathGenerator();
|
||||
|
||||
targetBound: Bound | null = null;
|
||||
|
||||
get themeService() {
|
||||
return this.gfx.std.get(ThemeProvider);
|
||||
}
|
||||
|
||||
private _generatePath() {
|
||||
const startRelativePos =
|
||||
this.direction === LayoutType.RIGHT
|
||||
? PointLocation.fromVec([1, 0.5])
|
||||
: PointLocation.fromVec([0, 0.5]);
|
||||
const endRelativePos =
|
||||
this.direction === LayoutType.RIGHT
|
||||
? PointLocation.fromVec([0, 0.5])
|
||||
: PointLocation.fromVec([1, 0.5]);
|
||||
const { parentBound, targetBound: newPosBound } = this;
|
||||
|
||||
if (this.mode === ConnectorMode.Orthogonal) {
|
||||
return this.pathGen
|
||||
.generateOrthogonalConnectorPath({
|
||||
startPoint: this._getRelativePoint(parentBound!, startRelativePos),
|
||||
endPoint: this._getRelativePoint(newPosBound!, endRelativePos),
|
||||
startBound: parentBound,
|
||||
endBound: newPosBound,
|
||||
})
|
||||
.map(p => new PointLocation(p));
|
||||
} else if (this.mode === ConnectorMode.Curve) {
|
||||
const startPoint = this._getRelativePoint(
|
||||
this.parentBound!,
|
||||
startRelativePos
|
||||
);
|
||||
const endPoint = this._getRelativePoint(
|
||||
this.targetBound!,
|
||||
endRelativePos
|
||||
);
|
||||
|
||||
const startTangentVertical = Vec.rot(startPoint.tangent, -Math.PI / 2);
|
||||
startPoint.out = Vec.mul(
|
||||
startTangentVertical,
|
||||
Math.max(
|
||||
100,
|
||||
Math.abs(
|
||||
Vec.pry(Vec.sub(endPoint, startPoint), startTangentVertical)
|
||||
) / 3
|
||||
)
|
||||
);
|
||||
|
||||
const endTangentVertical = Vec.rot(endPoint.tangent, -Math.PI / 2);
|
||||
endPoint.in = Vec.mul(
|
||||
endTangentVertical,
|
||||
Math.max(
|
||||
100,
|
||||
Math.abs(Vec.pry(Vec.sub(startPoint, endPoint), endTangentVertical)) /
|
||||
3
|
||||
)
|
||||
);
|
||||
|
||||
return [startPoint, endPoint];
|
||||
} else {
|
||||
const startPoint = new PointLocation(
|
||||
this.parentBound!.getRelativePoint(startRelativePos)
|
||||
);
|
||||
const endPoint = new PointLocation(
|
||||
this.targetBound!.getRelativePoint(endRelativePos)
|
||||
);
|
||||
|
||||
return [startPoint, endPoint];
|
||||
}
|
||||
}
|
||||
|
||||
private _getRelativePoint(bound: Bound, position: IVec) {
|
||||
const location = new PointLocation(
|
||||
bound.getRelativePoint(position as IVec)
|
||||
);
|
||||
|
||||
if (isVecZero(Vec.sub(position, [0, 0.5])))
|
||||
location.tangent = Vec.rot([0, -1], toRadian(0));
|
||||
else if (isVecZero(Vec.sub(position, [1, 0.5])))
|
||||
location.tangent = Vec.rot([0, 1], toRadian(0));
|
||||
else if (isVecZero(Vec.sub(position, [0.5, 0])))
|
||||
location.tangent = Vec.rot([1, 0], toRadian(0));
|
||||
else if (isVecZero(Vec.sub(position, [0.5, 1])))
|
||||
location.tangent = Vec.rot([-1, 0], toRadian(0));
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use to calculate the position of the indicator given its sibling's bound
|
||||
* @param siblingBound
|
||||
* @param direction
|
||||
*/
|
||||
private _moveRelativeToBound(
|
||||
siblingBound: Bound,
|
||||
direction: 'up' | 'down',
|
||||
layoutDir: Exclude<LayoutType, LayoutType.BALANCE>
|
||||
) {
|
||||
const isLeftLayout = layoutDir === LayoutType.LEFT;
|
||||
const isUpDirection = direction === 'up';
|
||||
|
||||
return siblingBound.moveDelta(
|
||||
isLeftLayout
|
||||
? siblingBound.w - MindMapIndicatorOverlay.INDICATOR_SIZE[0]
|
||||
: 0,
|
||||
isUpDirection
|
||||
? -(
|
||||
NODE_VERTICAL_SPACING / 2 +
|
||||
MindMapIndicatorOverlay.INDICATOR_SIZE[1] / 2
|
||||
)
|
||||
: siblingBound.h +
|
||||
NODE_VERTICAL_SPACING / 2 -
|
||||
MindMapIndicatorOverlay.INDICATOR_SIZE[1] / 2
|
||||
);
|
||||
}
|
||||
|
||||
override clear() {
|
||||
this.targetBound = null;
|
||||
this.parentBound = null;
|
||||
}
|
||||
|
||||
override render(ctx: CanvasRenderingContext2D): void {
|
||||
if (this.currentDragPos && this.dragNodeImage) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.3;
|
||||
ctx.drawImage(
|
||||
this.dragNodeImage,
|
||||
this.currentDragPos[0] + this.dragNodePos[0],
|
||||
this.currentDragPos[1] + this.dragNodePos[1],
|
||||
this.dragNodeImage.width / 2,
|
||||
this.dragNodeImage.height / 2
|
||||
);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
if (!this.parentBound || !this.targetBound) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetPos = this.targetBound;
|
||||
const points = this._generatePath();
|
||||
const color = this.themeService.getColorValue(
|
||||
'--affine-primary-color',
|
||||
'#1E96EB',
|
||||
true
|
||||
);
|
||||
|
||||
ctx.strokeStyle = color;
|
||||
ctx.fillStyle = color;
|
||||
ctx.lineWidth = 3;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(targetPos.x, targetPos.y, targetPos.w, targetPos.h, 4);
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(points[0][0], points[0][1]);
|
||||
|
||||
if (this.mode === ConnectorMode.Curve) {
|
||||
points.forEach((point, idx) => {
|
||||
if (idx === 0) return;
|
||||
const last = points[idx - 1];
|
||||
ctx.bezierCurveTo(
|
||||
last.absOut[0],
|
||||
last.absOut[1],
|
||||
point.absIn[0],
|
||||
point.absIn[1],
|
||||
point[0],
|
||||
point[1]
|
||||
);
|
||||
});
|
||||
} else {
|
||||
points.forEach((point, idx) => {
|
||||
if (idx === 0) return;
|
||||
ctx.lineTo(point[0], point[1]);
|
||||
});
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
setIndicatorInfo(options: {
|
||||
targetMindMap: MindmapElementModel;
|
||||
target: MindmapNode;
|
||||
parent: MindmapNode;
|
||||
parentChildren: MindmapNode[];
|
||||
insertPosition:
|
||||
| {
|
||||
type: 'sibling';
|
||||
layoutDir: Exclude<LayoutType, LayoutType.BALANCE>;
|
||||
position: 'prev' | 'next';
|
||||
}
|
||||
| { type: 'child'; layoutDir: Exclude<LayoutType, LayoutType.BALANCE> };
|
||||
path: number[];
|
||||
}) {
|
||||
const {
|
||||
insertPosition,
|
||||
parent,
|
||||
parentChildren,
|
||||
targetMindMap,
|
||||
target,
|
||||
path,
|
||||
} = options;
|
||||
|
||||
const parentBound = parent.element.elementBound;
|
||||
const isBalancedMindMap = targetMindMap.layoutType === LayoutType.BALANCE;
|
||||
const isLeftLayout = insertPosition.layoutDir === LayoutType.LEFT;
|
||||
const isFirstLevel = path.length === 2;
|
||||
|
||||
this.direction = insertPosition.layoutDir;
|
||||
this.parentBound = parentBound;
|
||||
|
||||
if (insertPosition.type === 'sibling') {
|
||||
const targetBound = target.element.elementBound;
|
||||
|
||||
this.targetBound =
|
||||
isBalancedMindMap && isFirstLevel && isLeftLayout
|
||||
? this._moveRelativeToBound(
|
||||
targetBound,
|
||||
insertPosition.position === 'next' ? 'up' : 'down',
|
||||
insertPosition.layoutDir
|
||||
)
|
||||
: this._moveRelativeToBound(
|
||||
targetBound,
|
||||
insertPosition.position === 'next' ? 'down' : 'up',
|
||||
insertPosition.layoutDir
|
||||
);
|
||||
} else {
|
||||
if (parentChildren.length === 0 || parent.detail.collapsed) {
|
||||
this.targetBound = parentBound.moveDelta(
|
||||
(isLeftLayout ? -1 : 1) *
|
||||
(NODE_HORIZONTAL_SPACING / 2 + parentBound.w),
|
||||
parentBound.h / 2 - MindMapIndicatorOverlay.INDICATOR_SIZE[1] / 2
|
||||
);
|
||||
} else {
|
||||
const lastChildBound = last(parentChildren)!.element.elementBound;
|
||||
|
||||
this.targetBound =
|
||||
isBalancedMindMap && isFirstLevel && isLeftLayout
|
||||
? this._moveRelativeToBound(
|
||||
lastChildBound,
|
||||
'up',
|
||||
insertPosition.layoutDir
|
||||
)
|
||||
: this._moveRelativeToBound(
|
||||
lastChildBound,
|
||||
'down',
|
||||
insertPosition.layoutDir
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.targetBound.w = MindMapIndicatorOverlay.INDICATOR_SIZE[0];
|
||||
this.targetBound.h = MindMapIndicatorOverlay.INDICATOR_SIZE[1];
|
||||
|
||||
this.mode = targetMindMap.styleGetter.getNodeStyle(
|
||||
target,
|
||||
options.path
|
||||
).connector.mode;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user