Skip to content

Commit

Permalink
fix: mind map circular reference issue & can't not drag existing mind…
Browse files Browse the repository at this point in the history
… map (#8837)
  • Loading branch information
doouding committed Dec 5, 2024
1 parent 45342bc commit 8cf2b40
Show file tree
Hide file tree
Showing 13 changed files with 1,165 additions and 846 deletions.
6 changes: 6 additions & 0 deletions packages/affine/block-surface/src/renderer/overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ export abstract class Overlay extends Extension {

clear() {}

refresh() {
if (this._renderer) {
this._renderer.refresh();
}
}

abstract render(ctx: CanvasRenderingContext2D, rc: RoughCanvas): void;

setRenderer(renderer: CanvasRenderer | null) {
Expand Down
234 changes: 14 additions & 220 deletions packages/affine/block-surface/src/utils/mindmap/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type MindmapNode,
type MindmapRoot,
} from '@blocksuite/affine-model';
import { Bound, last } from '@blocksuite/global/utils';
import { Bound } from '@blocksuite/global/utils';

export const NODE_VERTICAL_SPACING = 45;
export const NODE_HORIZONTAL_SPACING = 110;
Expand Down Expand Up @@ -81,93 +81,11 @@ const calculateNodeSize = (
return treeSize;
};

const isOnEdge = (tree: TreeSize, direction: 'tail' | 'front') => {
let current = tree;

while (current) {
if (!current.parent) return true;

if (direction === 'tail' && last(current.parent.children) === current) {
current = current.parent;
} else if (
direction === 'front' &&
current.parent.children[0] === current
) {
current = current.parent;
} else {
return false;
}
}

return true;
};

const calculateResponseArea = (
tree: TreeSize,
layoutType: LayoutType,
parent: TreeSize | null
) => {
const TAIL_RESPONSE_AREA = NODE_HORIZONTAL_SPACING;
const idx = parent?.children.indexOf(tree) ?? -1;
const isLast =
idx === (parent?.children.length || -1) - 1 && isOnEdge(tree, 'tail');
const isFirst = idx === 0 && isOnEdge(tree, 'front');

// root node
if (!parent) {
const rootElmBound = tree.root.element.elementBound;
const width =
layoutType === LayoutType.BALANCE
? rootElmBound.w + TAIL_RESPONSE_AREA * 2
: rootElmBound.w + TAIL_RESPONSE_AREA;

tree.root.responseArea = new Bound(
layoutType === LayoutType.BALANCE || layoutType === LayoutType.LEFT
? rootElmBound.x - TAIL_RESPONSE_AREA
: rootElmBound.x,
rootElmBound.y,
width,
rootElmBound.h
);
tree.root.treeBound = tree.root.responseArea;
return;
}

const upperSpacing = isFirst
? NODE_VERTICAL_SPACING * 2
: NODE_VERTICAL_SPACING / 2;
const lowerSpacing = isLast
? NODE_VERTICAL_SPACING * 2
: NODE_VERTICAL_SPACING / 2;

const h = tree.bound.h + upperSpacing + lowerSpacing;
const w =
(layoutType === LayoutType.RIGHT
? tree.root.element.x +
tree.root.element.w -
(parent.root.element.x + parent.root.element.w)
: parent.root.element.x - tree.root.element.x) + TAIL_RESPONSE_AREA;

tree.root.responseArea = new Bound(
layoutType === LayoutType.RIGHT
? parent.root.element.x + parent.root.element.w
: parent.root.element.x - w,
tree.root.element.y -
(tree.bound.h - tree.root.element.h) / 2 -
upperSpacing,
w,
h
);
tree.root.treeBound = tree.root.responseArea;
};

const layoutTree = (
tree: TreeSize,
layoutType: LayoutType.LEFT | LayoutType.RIGHT,
mindmap: MindmapElementModel,
path: number[] = [0],
parent: TreeSize | null = null,
calculateTreeBound = true
path: number[] = [0]
) => {
const firstLevel = path.length === 1;
const treeHeight = tree.bound.h;
Expand All @@ -190,10 +108,6 @@ const layoutTree = (
currentY += (tree.root.element.h - onlyChild.root.element.h) / 2;
}

if (calculateTreeBound) {
calculateResponseArea(tree, layoutType, parent);
}

tree.children.forEach((subtree, idx) => {
const subtreeRootEl = subtree.root.element;
const subtreeHeight = subtree.bound.h;
Expand All @@ -207,64 +121,36 @@ const layoutTree = (
subtreeRootEl.xywh = xywh;
}

layoutTree(
subtree,
layoutType,
mindmap,
currentNodePath,
tree,
calculateTreeBound
);
layoutTree(subtree, layoutType, mindmap, currentNodePath);

currentY += subtreeHeight + NODE_VERTICAL_SPACING;

if (calculateTreeBound && subtree.root.treeBound && tree.root.treeBound) {
tree.root.treeBound = tree.root.treeBound.unite(subtree.root.treeBound);
}
});
};

const layoutRight = (
root: MindmapNode,
mindmap: MindmapElementModel,
path = [0],
calculateTreeBound = true
path = [0]
) => {
const rootTree = calculateNodeSize(root, null);

layoutTree(
rootTree,
LayoutType.RIGHT,
mindmap,
path,
null,
calculateTreeBound
);
layoutTree(rootTree, LayoutType.RIGHT, mindmap, path);
};

const layoutLeft = (
root: MindmapNode,
mindmap: MindmapElementModel,
path = [0],
calculateTreeBound = true
path = [0]
) => {
const rootTree = calculateNodeSize(root, null);

layoutTree(
rootTree,
LayoutType.LEFT,
mindmap,
path,
null,
calculateTreeBound
);
layoutTree(rootTree, LayoutType.LEFT, mindmap, path);
};

const layoutBalance = (
root: MindmapNode,
mindmap: MindmapElementModel,
path = [0],
calculateTreeBound = true
path = [0]
) => {
const rootTree = calculateNodeSize(root, null);
const leftTree: MindmapNode[] = (root as MindmapRoot).left;
Expand All @@ -279,14 +165,7 @@ const layoutBalance = (
children: leftTreeSize.children,
};

layoutTree(
mockRoot,
LayoutType.LEFT,
mindmap,
path,
null,
calculateTreeBound
);
layoutTree(mockRoot, LayoutType.LEFT, mindmap, path);
}

{
Expand All @@ -298,109 +177,24 @@ const layoutBalance = (
children: rightTreeSize.children,
};

layoutTree(
mockRoot,
LayoutType.RIGHT,
mindmap,
[0],
null,
calculateTreeBound
);
}

if (calculateTreeBound) {
calculateResponseArea(rootTree, LayoutType.BALANCE, null);
const rightTreeBound = rightTree.reduce((pre: null | Bound, cur) => {
return cur.treeBound
? pre === null
? cur.treeBound
: pre.unite(cur.treeBound)
: pre;
}, null);
const leftTreeBound = leftTree.reduce((pre: null | Bound, cur) => {
return cur.treeBound
? pre === null
? cur.treeBound
: pre.unite(cur.treeBound)
: pre;
}, null);

if (rightTreeBound) {
rootTree.root.treeBound = rootTree.root.treeBound!.unite(rightTreeBound);
}

if (leftTreeBound) {
rootTree.root.treeBound = rootTree.root.treeBound!.unite(leftTreeBound);
}

// 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 (
leftTreeBound &&
rightTreeBound &&
leftTreeBound.h !== rightTreeBound.h
) {
const isLeftHigher = leftTreeBound.h > rightTreeBound.h;
const upperBound = isLeftHigher ? leftTreeBound.y : rightTreeBound.y;
const bottomBound = isLeftHigher
? leftTreeBound.y + leftTreeBound.h
: rightTreeBound.y + rightTreeBound.h;
const targetChildren = isLeftHigher ? rightTree : leftTree;

const expand = (children: MindmapNode[], direction: 'up' | 'down') => {
const node = direction === 'up' ? children[0] : last(children)!;

if (!node) return;

if (direction === 'up') {
if (node.responseArea) {
node.responseArea.h =
node.responseArea.h + (node.responseArea.y - upperBound);
node.responseArea.y = upperBound;
}

if (node.treeBound) {
node.treeBound.h =
node.treeBound.h + (node.treeBound.y - upperBound);
node.treeBound.y = upperBound;
}
expand(node.children, direction);
} else {
if (node.responseArea) {
node.responseArea.h =
node.responseArea.h +
(bottomBound - node.responseArea.y - node.responseArea.h);
}
if (node.treeBound) {
node.treeBound.h =
node.treeBound.h +
(bottomBound - node.treeBound.y - node.treeBound.h);
}
expand(node.children, direction);
}
};

expand(targetChildren, 'up');
expand(targetChildren, 'down');
}
layoutTree(mockRoot, LayoutType.RIGHT, mindmap, [0]);
}
};

export const layout = (
root: MindmapNode,
mindmap: MindmapElementModel,
layoutDir: LayoutType | null,
path: number[],
calculateTreeBound = true
path: number[]
) => {
layoutDir = layoutDir ?? mindmap.layoutType;

switch (layoutDir) {
case LayoutType.RIGHT:
return layoutRight(root, mindmap, path, calculateTreeBound);
return layoutRight(root, mindmap, path);
case LayoutType.LEFT:
return layoutLeft(root, mindmap, path, calculateTreeBound);
return layoutLeft(root, mindmap, path);
case LayoutType.BALANCE:
return layoutBalance(root, mindmap, path, calculateTreeBound);
return layoutBalance(root, mindmap, path);
}
};
Loading

0 comments on commit 8cf2b40

Please sign in to comment.