mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
refactor(editor): move mindmap view to mindmap package (#11102)
This commit is contained in:
@@ -30,7 +30,6 @@ export { fitContent } from './renderer/elements/shape/utils.js';
|
||||
export * from './renderer/elements/type.js';
|
||||
export { Overlay, OverlayIdentifier } from './renderer/overlay.js';
|
||||
export { ToolOverlay } from './renderer/tool-overlay.js';
|
||||
export { MindMapView } from './view/mindmap.js';
|
||||
import {
|
||||
getCursorByCoord,
|
||||
getLineHeight,
|
||||
@@ -49,6 +48,7 @@ export {
|
||||
EdgelessSurfaceBlockAdapterExtensions,
|
||||
SurfaceBlockAdapterExtensions,
|
||||
} from './adapters/index.js';
|
||||
export * from './extensions';
|
||||
export type { SurfaceContext } from './surface-block.js';
|
||||
export { SurfaceBlockComponent } from './surface-block.js';
|
||||
export {
|
||||
@@ -62,25 +62,6 @@ export {
|
||||
PageSurfaceBlockSpec,
|
||||
} from './surface-spec.js';
|
||||
export { SurfaceBlockTransformer } from './surface-transformer.js';
|
||||
export { AStarRunner } from './utils/a-star.js';
|
||||
export {
|
||||
NODE_FIRST_LEVEL_HORIZONTAL_SPACING,
|
||||
NODE_HORIZONTAL_SPACING,
|
||||
NODE_VERTICAL_SPACING,
|
||||
} from './utils/mindmap/layout.js';
|
||||
export { RoughCanvas } from './utils/rough/canvas.js';
|
||||
|
||||
import {
|
||||
addTree,
|
||||
containsNode,
|
||||
createFromTree,
|
||||
detachMindmap,
|
||||
findTargetNode,
|
||||
hideNodeConnector,
|
||||
moveNode,
|
||||
tryMoveNode,
|
||||
} from './utils/mindmap/utils';
|
||||
export * from './extensions';
|
||||
export {
|
||||
addNote,
|
||||
addNoteAtPoint,
|
||||
@@ -91,7 +72,8 @@ export {
|
||||
getSurfaceComponent,
|
||||
normalizeWheelDeltaY,
|
||||
} from './utils';
|
||||
export * from './utils/mindmap/style-svg';
|
||||
export { AStarRunner } from './utils/a-star.js';
|
||||
export { RoughCanvas } from './utils/rough/canvas.js';
|
||||
export type { Options } from './utils/rough/core';
|
||||
export { sortIndex } from './utils/sort';
|
||||
export { updateXYWH } from './utils/update-xywh.js';
|
||||
@@ -114,15 +96,4 @@ export const TextUtils = {
|
||||
isSameFontFamily,
|
||||
};
|
||||
|
||||
export const MindmapUtils = {
|
||||
addTree,
|
||||
createFromTree,
|
||||
detachMindmap,
|
||||
moveNode,
|
||||
findTargetNode,
|
||||
tryMoveNode,
|
||||
hideNodeConnector,
|
||||
containsNode,
|
||||
};
|
||||
|
||||
export * from './commands';
|
||||
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
} from './extensions';
|
||||
import { ExportManagerExtension } from './extensions/export-manager/export-manager';
|
||||
import { SurfaceBlockService } from './surface-service';
|
||||
import { ConnectorElementView } from './view/connector';
|
||||
import { MindMapView } from './view/mindmap';
|
||||
|
||||
const CommonSurfaceBlockSpec: ExtensionType[] = [
|
||||
FlavourExtension('affine:surface'),
|
||||
@@ -23,8 +21,6 @@ const CommonSurfaceBlockSpec: ExtensionType[] = [
|
||||
ExportManagerExtension,
|
||||
];
|
||||
|
||||
const ElementModelViews = [MindMapView, ConnectorElementView];
|
||||
|
||||
export const PageSurfaceBlockSpec: ExtensionType[] = [
|
||||
...CommonSurfaceBlockSpec,
|
||||
...SurfaceBlockAdapterExtensions,
|
||||
@@ -34,6 +30,5 @@ export const PageSurfaceBlockSpec: ExtensionType[] = [
|
||||
export const EdgelessSurfaceBlockSpec: ExtensionType[] = [
|
||||
...CommonSurfaceBlockSpec,
|
||||
...EdgelessSurfaceBlockAdapterExtensions,
|
||||
...ElementModelViews,
|
||||
BlockViewExtension('affine:surface', literal`affine-surface`),
|
||||
];
|
||||
|
||||
@@ -37,4 +37,3 @@ export { addNote, addNoteAtPoint } from './add-note';
|
||||
export { getBgGridGap } from './get-bg-grip-gap';
|
||||
export { getLastPropsKey } from './get-last-props-key';
|
||||
export * from './get-surface-block';
|
||||
export * from './mindmap/style-svg.js';
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
import {
|
||||
LayoutType,
|
||||
type MindmapElementModel,
|
||||
type MindmapNode,
|
||||
type MindmapRoot,
|
||||
} from '@blocksuite/affine-model';
|
||||
import type { SerializedXYWH } from '@blocksuite/global/gfx';
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
|
||||
export const NODE_VERTICAL_SPACING = 45;
|
||||
export const NODE_HORIZONTAL_SPACING = 110;
|
||||
export const NODE_FIRST_LEVEL_HORIZONTAL_SPACING = 200;
|
||||
|
||||
type TreeSize = {
|
||||
/**
|
||||
* The parent of the tree
|
||||
*/
|
||||
parent: TreeSize | null;
|
||||
|
||||
/**
|
||||
* The root node of the tree
|
||||
*/
|
||||
root: MindmapNode;
|
||||
|
||||
/**
|
||||
* The size of the tree, including its descendants
|
||||
*/
|
||||
bound: Bound;
|
||||
|
||||
/**
|
||||
* The size of the children of the root
|
||||
*/
|
||||
children: TreeSize[];
|
||||
};
|
||||
|
||||
const calculateNodeSize = (
|
||||
root: MindmapNode,
|
||||
parent: TreeSize | null = null,
|
||||
rootChildren?: MindmapNode[]
|
||||
): TreeSize => {
|
||||
const bound = root.element.elementBound;
|
||||
const children: TreeSize[] = [];
|
||||
const firstLevel = parent === null;
|
||||
|
||||
rootChildren = rootChildren ?? root.children;
|
||||
|
||||
const treeSize: TreeSize = {
|
||||
parent,
|
||||
root,
|
||||
bound,
|
||||
children,
|
||||
};
|
||||
|
||||
if (rootChildren?.length && !root.detail.collapsed) {
|
||||
const childrenBound = rootChildren.reduce(
|
||||
(pre, node) => {
|
||||
const childSize = calculateNodeSize(node, treeSize);
|
||||
|
||||
children.push(childSize);
|
||||
|
||||
pre.w = Math.max(pre.w, childSize.bound.w);
|
||||
pre.h +=
|
||||
pre.h > 0
|
||||
? NODE_VERTICAL_SPACING + childSize.bound.h
|
||||
: childSize.bound.h;
|
||||
|
||||
return pre;
|
||||
},
|
||||
new Bound(0, 0, 0, 0)
|
||||
);
|
||||
|
||||
bound.w +=
|
||||
childrenBound.w +
|
||||
(firstLevel
|
||||
? NODE_FIRST_LEVEL_HORIZONTAL_SPACING
|
||||
: NODE_HORIZONTAL_SPACING);
|
||||
bound.h = Math.max(bound.h, childrenBound.h);
|
||||
}
|
||||
|
||||
return treeSize;
|
||||
};
|
||||
|
||||
const layoutTree = (
|
||||
tree: TreeSize,
|
||||
layoutType: LayoutType.LEFT | LayoutType.RIGHT,
|
||||
mindmap: MindmapElementModel,
|
||||
path: number[] = [0]
|
||||
) => {
|
||||
const firstLevel = path.length === 1;
|
||||
const treeHeight = tree.bound.h;
|
||||
const currentX =
|
||||
layoutType === LayoutType.RIGHT
|
||||
? tree.root.element.x +
|
||||
tree.root.element.w +
|
||||
(firstLevel
|
||||
? NODE_FIRST_LEVEL_HORIZONTAL_SPACING
|
||||
: NODE_HORIZONTAL_SPACING)
|
||||
: tree.root.element.x -
|
||||
(firstLevel
|
||||
? NODE_FIRST_LEVEL_HORIZONTAL_SPACING
|
||||
: NODE_HORIZONTAL_SPACING);
|
||||
let currentY = tree.root.element.y + (tree.root.element.h - treeHeight) / 2;
|
||||
|
||||
if (tree.root.element.h >= treeHeight && tree.children.length) {
|
||||
const onlyChild = tree.children[0];
|
||||
|
||||
currentY += (tree.root.element.h - onlyChild.root.element.h) / 2;
|
||||
}
|
||||
|
||||
if (!tree.root.detail.collapsed) {
|
||||
tree.children.forEach((subtree, idx) => {
|
||||
const subtreeRootEl = subtree.root.element;
|
||||
const subtreeHeight = subtree.bound.h;
|
||||
const xywh = `[${
|
||||
layoutType === LayoutType.RIGHT ? currentX : currentX - subtreeRootEl.w
|
||||
},${currentY + (subtreeHeight - subtreeRootEl.h) / 2},${subtreeRootEl.w},${subtreeRootEl.h}]` as SerializedXYWH;
|
||||
|
||||
const currentNodePath = [...path, idx];
|
||||
|
||||
if (subtreeRootEl.xywh !== xywh) {
|
||||
subtreeRootEl.xywh = xywh;
|
||||
}
|
||||
|
||||
layoutTree(subtree, layoutType, mindmap, currentNodePath);
|
||||
|
||||
currentY += subtreeHeight + NODE_VERTICAL_SPACING;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const layoutRight = (
|
||||
root: MindmapNode,
|
||||
mindmap: MindmapElementModel,
|
||||
path = [0]
|
||||
) => {
|
||||
const rootTree = calculateNodeSize(root, null);
|
||||
|
||||
layoutTree(rootTree, LayoutType.RIGHT, mindmap, path);
|
||||
};
|
||||
|
||||
const layoutLeft = (
|
||||
root: MindmapNode,
|
||||
mindmap: MindmapElementModel,
|
||||
path = [0]
|
||||
) => {
|
||||
const rootTree = calculateNodeSize(root, null);
|
||||
|
||||
layoutTree(rootTree, LayoutType.LEFT, mindmap, path);
|
||||
};
|
||||
|
||||
const layoutBalance = (
|
||||
root: MindmapNode,
|
||||
mindmap: MindmapElementModel,
|
||||
path = [0]
|
||||
) => {
|
||||
const rootTree = calculateNodeSize(root, null);
|
||||
const leftTree: MindmapNode[] = (root as MindmapRoot).left;
|
||||
const rightTree: MindmapNode[] = (root as MindmapRoot).right;
|
||||
|
||||
{
|
||||
const leftTreeSize = calculateNodeSize(root, null, leftTree);
|
||||
const mockRoot = {
|
||||
parent: null,
|
||||
root: rootTree.root,
|
||||
bound: leftTreeSize.bound,
|
||||
children: leftTreeSize.children,
|
||||
};
|
||||
|
||||
layoutTree(mockRoot, LayoutType.LEFT, mindmap, path);
|
||||
}
|
||||
|
||||
{
|
||||
const rightTreeSize = calculateNodeSize(root, null, rightTree);
|
||||
const mockRoot = {
|
||||
parent: null,
|
||||
root: rootTree.root,
|
||||
bound: rightTreeSize.bound,
|
||||
children: rightTreeSize.children,
|
||||
};
|
||||
|
||||
layoutTree(mockRoot, LayoutType.RIGHT, mindmap, [0]);
|
||||
}
|
||||
};
|
||||
|
||||
export const layout = (
|
||||
root: MindmapNode,
|
||||
mindmap: MindmapElementModel,
|
||||
layoutDir: LayoutType | null,
|
||||
path: number[]
|
||||
) => {
|
||||
layoutDir = layoutDir ?? mindmap.layoutType;
|
||||
|
||||
switch (layoutDir) {
|
||||
case LayoutType.RIGHT:
|
||||
return layoutRight(root, mindmap, path);
|
||||
case LayoutType.LEFT:
|
||||
return layoutLeft(root, mindmap, path);
|
||||
case LayoutType.BALANCE:
|
||||
return layoutBalance(root, mindmap, path);
|
||||
}
|
||||
};
|
||||
@@ -1,680 +0,0 @@
|
||||
import { html } from 'lit';
|
||||
|
||||
export const MindmapStyleOne = html`<svg
|
||||
width="64"
|
||||
height="48"
|
||||
viewBox="0 0 64 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="64" height="48" rx="4" />
|
||||
<path d="M23.976 24L43.4022 24" stroke="#E660A4" stroke-width="1.4" />
|
||||
<path
|
||||
d="M23.976 24.0001L26.5099 24.0001C30.4749 24.0001 33.6891 20.7858 33.6891 16.8208V16.8208C33.6891 12.8559 36.9034 9.6416 40.8684 9.6416L43.4022 9.6416"
|
||||
stroke="#6E52DF"
|
||||
stroke-width="1.4"
|
||||
/>
|
||||
<path
|
||||
d="M23.976 24L26.5099 24C30.4749 24 33.6891 27.2143 33.6891 31.1792V31.1792C33.6891 35.1442 36.9034 38.3585 40.8684 38.3585L43.4022 38.3585"
|
||||
stroke="#FF8C38"
|
||||
stroke-width="1.4"
|
||||
/>
|
||||
<g>
|
||||
<rect
|
||||
x="42.7023"
|
||||
y="33.4353"
|
||||
width="15.7585"
|
||||
height="9.84617"
|
||||
rx="2.38923"
|
||||
stroke="#FF8C38"
|
||||
stroke-width="1.4"
|
||||
shape-rendering="crispEdges"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<rect
|
||||
x="42.7023"
|
||||
y="19.0769"
|
||||
width="15.7585"
|
||||
height="9.84617"
|
||||
rx="2.38923"
|
||||
stroke="#E660A4"
|
||||
stroke-width="1.4"
|
||||
shape-rendering="crispEdges"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<rect
|
||||
x="42.7023"
|
||||
y="4.71846"
|
||||
width="15.7585"
|
||||
height="9.84617"
|
||||
rx="2.38923"
|
||||
stroke="#6E52DF"
|
||||
stroke-width="1.4"
|
||||
shape-rendering="crispEdges"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<rect
|
||||
x="5.53921"
|
||||
y="17.3879"
|
||||
width="18.2923"
|
||||
height="13.2246"
|
||||
rx="2.77776"
|
||||
stroke="#84CFFF"
|
||||
stroke-width="1.4"
|
||||
shape-rendering="crispEdges"
|
||||
/>
|
||||
</g>
|
||||
</svg> `;
|
||||
|
||||
export const MindmapStyleTwo = html`<svg
|
||||
width="64"
|
||||
height="48"
|
||||
viewBox="0 0 64 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M23.9761 24L43.4023 24"
|
||||
stroke="black"
|
||||
stroke-width="1.4"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M23.9761 24.0001L33.5892 24.0001C33.6444 24.0001 33.6892 23.9553 33.6892 23.9001L33.6892 9.7416C33.6892 9.68637 33.7339 9.6416 33.7892 9.6416L43.4023 9.6416"
|
||||
stroke="black"
|
||||
stroke-width="1.4"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M23.9761 24L33.5892 24C33.6444 24 33.6892 24.0448 33.6892 24.1L33.6892 38.2585C33.6892 38.3137 33.7339 38.3585 33.7892 38.3585L43.4023 38.3585"
|
||||
stroke="black"
|
||||
stroke-width="1.4"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<g filter="url(#filter0_dd_7851_11431)">
|
||||
<rect
|
||||
x="43.4023"
|
||||
y="34.1353"
|
||||
width="14.3585"
|
||||
height="8.44617"
|
||||
rx="0.1"
|
||||
fill="#84CFFF"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter1_dd_7851_11431)">
|
||||
<rect
|
||||
x="43.4023"
|
||||
y="19.7769"
|
||||
width="14.3585"
|
||||
height="8.44617"
|
||||
rx="0.1"
|
||||
fill="#84CFFF"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter2_dd_7851_11431)">
|
||||
<rect
|
||||
x="43.4023"
|
||||
y="5.41846"
|
||||
width="14.3585"
|
||||
height="8.44617"
|
||||
rx="0.1"
|
||||
fill="#84CFFF"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter3_dd_7851_11431)">
|
||||
<rect
|
||||
x="6.23926"
|
||||
y="18.0879"
|
||||
width="16.8923"
|
||||
height="11.8246"
|
||||
rx="0.1"
|
||||
fill="#FFC46B"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_dd_7851_11431"
|
||||
x="42.4023"
|
||||
y="33.1353"
|
||||
width="17.3585"
|
||||
height="11.4463"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="1"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow_7851_11431"
|
||||
/>
|
||||
<feOffset dx="1" dy="1" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7851_11431"
|
||||
/>
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="1"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect2_dropShadow_7851_11431"
|
||||
/>
|
||||
<feOffset />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="effect1_dropShadow_7851_11431"
|
||||
result="effect2_dropShadow_7851_11431"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect2_dropShadow_7851_11431"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_dd_7851_11431"
|
||||
x="42.4023"
|
||||
y="18.7769"
|
||||
width="17.3585"
|
||||
height="11.4463"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="1"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow_7851_11431"
|
||||
/>
|
||||
<feOffset dx="1" dy="1" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7851_11431"
|
||||
/>
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="1"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect2_dropShadow_7851_11431"
|
||||
/>
|
||||
<feOffset />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="effect1_dropShadow_7851_11431"
|
||||
result="effect2_dropShadow_7851_11431"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect2_dropShadow_7851_11431"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_dd_7851_11431"
|
||||
x="42.4023"
|
||||
y="4.41846"
|
||||
width="17.3585"
|
||||
height="11.4463"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="1"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow_7851_11431"
|
||||
/>
|
||||
<feOffset dx="1" dy="1" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7851_11431"
|
||||
/>
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="1"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect2_dropShadow_7851_11431"
|
||||
/>
|
||||
<feOffset />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="effect1_dropShadow_7851_11431"
|
||||
result="effect2_dropShadow_7851_11431"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect2_dropShadow_7851_11431"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter3_dd_7851_11431"
|
||||
x="5.23926"
|
||||
y="17.0879"
|
||||
width="19.8923"
|
||||
height="14.8247"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="1"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow_7851_11431"
|
||||
/>
|
||||
<feOffset dx="1" dy="1" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7851_11431"
|
||||
/>
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="1"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect2_dropShadow_7851_11431"
|
||||
/>
|
||||
<feOffset />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="effect1_dropShadow_7851_11431"
|
||||
result="effect2_dropShadow_7851_11431"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect2_dropShadow_7851_11431"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg> `;
|
||||
|
||||
export const MindmapStyleThree = html`<svg
|
||||
width="64"
|
||||
height="48"
|
||||
viewBox="0 0 64 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M23.976 24L43.4022 24" stroke="#FFD338" stroke-width="1.4" />
|
||||
<path
|
||||
d="M23.976 24.0001L26.5099 24.0001C30.4749 24.0001 33.6891 20.7858 33.6891 16.8208V16.8208C33.6891 12.8559 36.9034 9.6416 40.8684 9.6416L43.4022 9.6416"
|
||||
stroke="#FFD338"
|
||||
stroke-width="1.4"
|
||||
/>
|
||||
<path
|
||||
d="M23.976 24L26.5099 24C30.4749 24 33.6891 27.2143 33.6891 31.1792V31.1792C33.6891 35.1442 36.9034 38.3585 40.8684 38.3585L43.4022 38.3585"
|
||||
stroke="#FFD338"
|
||||
stroke-width="1.4"
|
||||
/>
|
||||
<g filter="url(#filter0_d_7851_6843)">
|
||||
<rect
|
||||
x="43.4023"
|
||||
y="34.1353"
|
||||
width="14.3585"
|
||||
height="8.44617"
|
||||
rx="1.68923"
|
||||
fill="white"
|
||||
/>
|
||||
<rect
|
||||
x="42.7023"
|
||||
y="33.4353"
|
||||
width="15.7585"
|
||||
height="9.84617"
|
||||
rx="2.38923"
|
||||
stroke="#FFD338"
|
||||
stroke-width="1.4"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d_7851_6843)">
|
||||
<rect
|
||||
x="43.4023"
|
||||
y="19.7769"
|
||||
width="14.3585"
|
||||
height="8.44617"
|
||||
rx="1.68923"
|
||||
fill="white"
|
||||
/>
|
||||
<rect
|
||||
x="42.7023"
|
||||
y="19.0769"
|
||||
width="15.7585"
|
||||
height="9.84617"
|
||||
rx="2.38923"
|
||||
stroke="#FFD338"
|
||||
stroke-width="1.4"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter2_d_7851_6843)">
|
||||
<rect
|
||||
x="43.4023"
|
||||
y="5.41846"
|
||||
width="14.3585"
|
||||
height="8.44617"
|
||||
rx="1.68923"
|
||||
fill="white"
|
||||
/>
|
||||
<rect
|
||||
x="42.7023"
|
||||
y="4.71846"
|
||||
width="15.7585"
|
||||
height="9.84617"
|
||||
rx="2.38923"
|
||||
stroke="#FFD338"
|
||||
stroke-width="1.4"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter3_d_7851_6843)">
|
||||
<rect
|
||||
x="6.23921"
|
||||
y="18.0879"
|
||||
width="16.8923"
|
||||
height="11.8246"
|
||||
rx="2.07776"
|
||||
fill="#FFD338"
|
||||
/>
|
||||
<rect
|
||||
x="5.53921"
|
||||
y="17.3879"
|
||||
width="18.2923"
|
||||
height="13.2246"
|
||||
rx="2.77776"
|
||||
stroke="white"
|
||||
stroke-width="1.4"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_7851_6843"
|
||||
x="39.4684"
|
||||
y="30.2015"
|
||||
width="22.2262"
|
||||
height="16.3138"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="1.26693" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7851_6843"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_7851_6843"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_7851_6843"
|
||||
x="39.4684"
|
||||
y="15.8431"
|
||||
width="22.2262"
|
||||
height="16.3138"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="1.26693" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7851_6843"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_7851_6843"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_d_7851_6843"
|
||||
x="39.4684"
|
||||
y="1.4847"
|
||||
width="22.2262"
|
||||
height="16.3138"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="1.26693" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7851_6843"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_7851_6843"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter3_d_7851_6843"
|
||||
x="2.30537"
|
||||
y="14.1541"
|
||||
width="24.76"
|
||||
height="19.6922"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="1.26693" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7851_6843"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_7851_6843"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg> `;
|
||||
|
||||
export const MindmapStyleFour = html`<svg
|
||||
width="64"
|
||||
height="48"
|
||||
viewBox="0 0 64 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M23.9761 24L43.4023 24"
|
||||
stroke="#E660A4"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M23.9761 24.0001L26.5099 24.0001C30.4749 24.0001 33.6892 20.7858 33.6892 16.8208V16.8208C33.6892 12.8559 36.9034 9.6416 40.8684 9.6416L43.4023 9.6416"
|
||||
stroke="#6E52DF"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M23.9761 24L26.5099 24C30.4749 24 33.6892 27.2143 33.6892 31.1792V31.1792C33.6892 35.1442 36.9034 38.3585 40.8684 38.3585L43.4023 38.3585"
|
||||
stroke="#FF8C38"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M7 26C9.85124 21.8236 11.8347 18.5607 11.8347 20.649C11.8347 22.7372 10.9669 24.695 11.2149 25.3475C11.4628 26 12.9504 24.0423 13.9421 22.8677C14.9339 21.6931 15.9256 21.0405 15.8017 22.8677C15.6777 24.6949 16.2975 25.739 17.2893 24.8254C18.281 23.9118 19.2727 23.5203 20.0165 23.7813C20.6116 23.9901 21.5868 24.5644 22 24.8254"
|
||||
stroke="black"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M44 11C46.4711 7.51964 48.1901 4.80062 48.1901 6.54079C48.1901 8.28097 47.438 9.91246 47.6529 10.4562C47.8678 11 49.157 9.36862 50.0165 8.38977C50.876 7.41092 51.7355 6.86712 51.6281 8.38977C51.5207 9.91243 52.0579 10.7825 52.9174 10.0212C53.7769 9.25986 54.6364 8.93358 55.281 9.1511C55.7967 9.32512 56.6419 9.80367 57 10.0212"
|
||||
stroke="black"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M44 25.5C46.4711 22.0196 48.1901 19.3006 48.1901 21.0408C48.1901 22.781 47.438 24.4125 47.6529 24.9562C47.8678 25.5 49.157 23.8686 50.0165 22.8898C50.876 21.9109 51.7355 21.3671 51.6281 22.8898C51.5207 24.4124 52.0579 25.2825 52.9174 24.5212C53.7769 23.7599 54.6364 23.4336 55.281 23.6511C55.7967 23.8251 56.6419 24.3037 57 24.5212"
|
||||
stroke="black"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M44 40C46.4711 36.5196 48.1901 33.8006 48.1901 35.5408C48.1901 37.281 47.438 38.9125 47.6529 39.4562C47.8678 40 49.157 38.3686 50.0165 37.3898C50.876 36.4109 51.7355 35.8671 51.6281 37.3898C51.5207 38.9124 52.0579 39.7825 52.9174 39.0212C53.7769 38.2599 54.6364 37.9336 55.281 38.1511C55.7967 38.3251 56.6419 38.8037 57 39.0212"
|
||||
stroke="black"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg> `;
|
||||
@@ -1,692 +0,0 @@
|
||||
import {
|
||||
applyNodeStyle,
|
||||
LayoutType,
|
||||
type MindmapElementModel,
|
||||
type MindmapNode,
|
||||
type MindmapRoot,
|
||||
type MindmapStyle,
|
||||
type NodeDetail,
|
||||
type NodeType,
|
||||
type ShapeElementModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
generateKeyBetween,
|
||||
type SurfaceBlockModel,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
import type { IVec } from '@blocksuite/global/gfx';
|
||||
import { assertType } from '@blocksuite/global/utils';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import last from 'lodash-es/last';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { fitContent } from '../../renderer/elements/shape/utils.js';
|
||||
import { layout } from './layout.js';
|
||||
|
||||
export function getHoveredArea(
|
||||
target: ShapeElementModel,
|
||||
position: [number, number],
|
||||
layoutDir: LayoutType
|
||||
): 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' {
|
||||
const { x, y, w, h } = target;
|
||||
const center =
|
||||
layoutDir === LayoutType.BALANCE
|
||||
? [x + w / 2, y + h / 2]
|
||||
: layoutDir === LayoutType.LEFT
|
||||
? [x + (w / 3) * 1, y + h / 2]
|
||||
: [x + (w / 3) * 2, y + h / 2];
|
||||
|
||||
return `${position[1] - center[1] > 0 ? 'bottom' : 'top'}-${position[0] - center[0] > 0 ? 'right' : 'left'}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the connector between the target node and its parent
|
||||
*/
|
||||
export function hideNodeConnector(
|
||||
mindmap: MindmapElementModel,
|
||||
/**
|
||||
* The mind map node which's connector will be hide
|
||||
*/
|
||||
target: MindmapNode
|
||||
) {
|
||||
const parent = mindmap.getParentNode(target.id);
|
||||
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectorId = `#${parent.id}-${target.id}`;
|
||||
const connector = mindmap.connectors.get(connectorId);
|
||||
|
||||
if (!connector) {
|
||||
return;
|
||||
}
|
||||
|
||||
connector.opacity = 0;
|
||||
|
||||
return () => {
|
||||
connector.opacity = 1;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the node to the new parent within the same mind map
|
||||
* @param mindmap
|
||||
* @param node the node should already exist in the mind map
|
||||
* @param parent
|
||||
* @param targetIndex
|
||||
* @param layout
|
||||
* @returns
|
||||
*/
|
||||
function moveNodePosition(
|
||||
mindmap: MindmapElementModel,
|
||||
node: MindmapNode,
|
||||
parent: string | MindmapNode,
|
||||
targetIndex: number,
|
||||
layout?: LayoutType
|
||||
) {
|
||||
parent = mindmap.nodeMap.get(
|
||||
typeof parent === 'string' ? parent : parent.id
|
||||
)!;
|
||||
|
||||
if (!parent || !mindmap.nodeMap.has(node.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertType<MindmapNode>(parent);
|
||||
|
||||
if (layout === LayoutType.BALANCE || parent !== mindmap.tree) {
|
||||
layout = undefined;
|
||||
}
|
||||
|
||||
const siblings = parent.children.filter(child => child !== node);
|
||||
|
||||
targetIndex = Math.min(targetIndex, parent.children.length);
|
||||
|
||||
siblings.splice(targetIndex, 0, node);
|
||||
|
||||
// calculate the index
|
||||
// the sibling node may be the same node, so we need to filter it out
|
||||
const preSibling = siblings[targetIndex - 1];
|
||||
const afterSibling = siblings[targetIndex + 1];
|
||||
const index =
|
||||
preSibling || afterSibling
|
||||
? generateKeyBetween(
|
||||
preSibling?.detail.index ?? null,
|
||||
afterSibling?.detail.index ?? null
|
||||
)
|
||||
: (node.detail.index ?? undefined);
|
||||
|
||||
mindmap.surface.doc.transact(() => {
|
||||
const val: NodeDetail = {
|
||||
...node.detail,
|
||||
index,
|
||||
parent: parent.id,
|
||||
};
|
||||
|
||||
mindmap.children.set(node.id, val);
|
||||
});
|
||||
|
||||
if (parent.detail.collapsed) {
|
||||
mindmap.toggleCollapse(parent);
|
||||
}
|
||||
|
||||
mindmap.layout();
|
||||
|
||||
return mindmap.nodeMap.get(node.id);
|
||||
}
|
||||
|
||||
export function applyStyle(
|
||||
mindmap: MindmapElementModel,
|
||||
shouldFitContent: boolean = false
|
||||
) {
|
||||
mindmap.surface.doc.transact(() => {
|
||||
const style = mindmap.styleGetter;
|
||||
|
||||
if (!style) return;
|
||||
|
||||
applyNodeStyle(mindmap.tree, style.root);
|
||||
if (shouldFitContent) {
|
||||
fitContent(mindmap.tree.element as ShapeElementModel);
|
||||
}
|
||||
|
||||
const walk = (node: MindmapNode, path: number[]) => {
|
||||
node.children.forEach((child, idx) => {
|
||||
const currentPath = [...path, idx];
|
||||
const nodeStyle = style.getNodeStyle(child, currentPath);
|
||||
|
||||
applyNodeStyle(child, nodeStyle.node);
|
||||
if (shouldFitContent) {
|
||||
fitContent(child.element as ShapeElementModel);
|
||||
}
|
||||
|
||||
walk(child, currentPath);
|
||||
});
|
||||
};
|
||||
|
||||
walk(mindmap.tree, [0]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mindmap the mind map to add the node to
|
||||
* @param parent the parent node or the parent node id
|
||||
* @param node the node must be an detached node
|
||||
* @param targetIndex the index to insert the node at
|
||||
* @returns
|
||||
*/
|
||||
export function addNode(
|
||||
mindmap: MindmapElementModel,
|
||||
parent: string | MindmapNode,
|
||||
node: MindmapNode,
|
||||
targetIndex?: number
|
||||
) {
|
||||
const parentNode = mindmap.getNode(
|
||||
typeof parent === 'string' ? parent : parent.id
|
||||
);
|
||||
|
||||
if (!parentNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const children = parentNode.children.slice();
|
||||
|
||||
targetIndex =
|
||||
targetIndex !== undefined
|
||||
? Math.min(targetIndex, children.length)
|
||||
: children.length;
|
||||
|
||||
children.splice(targetIndex, 0, node);
|
||||
|
||||
const before = children[targetIndex - 1] ?? null;
|
||||
const after = children[targetIndex + 1] ?? null;
|
||||
const index =
|
||||
before || after
|
||||
? generateKeyBetween(
|
||||
before?.detail.index ?? null,
|
||||
after?.detail.index ?? null
|
||||
)
|
||||
: node.detail.index;
|
||||
|
||||
mindmap.surface.doc.transact(() => {
|
||||
mindmap.children.set(node.id, {
|
||||
...node.detail,
|
||||
index,
|
||||
parent: parentNode.id,
|
||||
});
|
||||
|
||||
const recursiveAddChild = (node: MindmapNode) => {
|
||||
node.children?.forEach(child => {
|
||||
mindmap.children.set(child.id, {
|
||||
...child.detail,
|
||||
parent: node.id,
|
||||
});
|
||||
|
||||
recursiveAddChild(child);
|
||||
});
|
||||
};
|
||||
|
||||
recursiveAddChild(node);
|
||||
});
|
||||
|
||||
if (parentNode.detail.collapsed) {
|
||||
mindmap.toggleCollapse(parentNode);
|
||||
}
|
||||
|
||||
mindmap.layout();
|
||||
}
|
||||
|
||||
export function addTree(
|
||||
mindmap: MindmapElementModel,
|
||||
parent: string | MindmapNode,
|
||||
tree: NodeType | MindmapNode,
|
||||
/**
|
||||
* `sibling` indicates where to insert a subtree among peer elements.
|
||||
* If it's a string, it represents a peer element's ID;
|
||||
* if it's a number, it represents its index.
|
||||
* The subtree will be inserted before the sibling element.
|
||||
*/
|
||||
sibling?: string | number
|
||||
) {
|
||||
parent = typeof parent === 'string' ? parent : parent.id;
|
||||
|
||||
if (!mindmap.nodeMap.has(parent) || !parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
assertType<string>(parent);
|
||||
|
||||
const traverse = (
|
||||
node: NodeType | MindmapNode,
|
||||
parent: string,
|
||||
sibling?: string | number
|
||||
) => {
|
||||
let nodeId: string;
|
||||
if ('text' in node) {
|
||||
nodeId = mindmap.addNode(parent, sibling, 'before', {
|
||||
text: node.text,
|
||||
});
|
||||
} else {
|
||||
mindmap.children.set(node.id, {
|
||||
...node.detail,
|
||||
parent,
|
||||
});
|
||||
nodeId = node.id;
|
||||
}
|
||||
|
||||
node.children?.forEach(child => traverse(child, nodeId));
|
||||
|
||||
return nodeId;
|
||||
};
|
||||
|
||||
if (!('text' in tree)) {
|
||||
// Modify the children ymap directly hence need transaction
|
||||
mindmap.surface.doc.transact(() => {
|
||||
traverse(tree, parent, sibling);
|
||||
});
|
||||
|
||||
applyStyle(mindmap);
|
||||
mindmap.layout();
|
||||
|
||||
return mindmap.nodeMap.get(tree.id);
|
||||
} else {
|
||||
const nodeId = traverse(tree, parent, sibling);
|
||||
|
||||
mindmap.layout();
|
||||
|
||||
return mindmap.nodeMap.get(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach a mindmap node or subtree. It is similar to `removeChild` but
|
||||
* it does not delete the node.
|
||||
*
|
||||
* So the node can be used to create a new mind map or merge into other mind map
|
||||
* @param mindmap the mind map that the subtree belongs to
|
||||
* @param subtree the subtree to detach
|
||||
*/
|
||||
export function detachMindmap(
|
||||
mindmap: MindmapElementModel,
|
||||
subtree: string | MindmapNode
|
||||
) {
|
||||
subtree =
|
||||
typeof subtree === 'string' ? mindmap.nodeMap.get(subtree)! : subtree;
|
||||
|
||||
assertType<MindmapNode>(subtree);
|
||||
|
||||
if (!subtree) return;
|
||||
|
||||
const traverse = (subtree: MindmapNode) => {
|
||||
mindmap.children.delete(subtree.id);
|
||||
|
||||
// cut the reference inside the ymap
|
||||
subtree.detail = {
|
||||
...subtree.detail,
|
||||
};
|
||||
|
||||
subtree.children.forEach(child => traverse(child));
|
||||
};
|
||||
|
||||
mindmap.surface.doc.transact(() => {
|
||||
traverse(subtree);
|
||||
});
|
||||
|
||||
mindmap.layout();
|
||||
|
||||
delete subtree.detail.parent;
|
||||
|
||||
return subtree;
|
||||
}
|
||||
|
||||
export function handleLayout(
|
||||
mindmap: MindmapElementModel,
|
||||
tree?: MindmapNode | MindmapRoot,
|
||||
shouldApplyStyle = true,
|
||||
layoutType?: LayoutType
|
||||
) {
|
||||
if (!tree || !tree.element) return;
|
||||
|
||||
if (shouldApplyStyle) {
|
||||
applyStyle(mindmap, true);
|
||||
}
|
||||
|
||||
mindmap.surface.doc.transact(() => {
|
||||
const path = mindmap.getPath(tree.id);
|
||||
layout(tree, mindmap, layoutType ?? mindmap.getLayoutDir(tree.id), path);
|
||||
});
|
||||
}
|
||||
|
||||
export function createFromTree(
|
||||
tree: MindmapNode,
|
||||
style: MindmapStyle,
|
||||
layoutType: LayoutType,
|
||||
surface: SurfaceBlockModel
|
||||
) {
|
||||
const children = new Y.Map();
|
||||
const traverse = (subtree: MindmapNode, parent?: string) => {
|
||||
const value: NodeDetail = {
|
||||
...subtree.detail,
|
||||
parent,
|
||||
};
|
||||
|
||||
if (!parent) {
|
||||
delete value.parent;
|
||||
}
|
||||
|
||||
children.set(subtree.id, value);
|
||||
|
||||
subtree.children.forEach(child => traverse(child, subtree.id));
|
||||
};
|
||||
|
||||
traverse(tree);
|
||||
|
||||
const mindmapId = surface.addElement({
|
||||
type: 'mindmap',
|
||||
children,
|
||||
layoutType,
|
||||
style,
|
||||
});
|
||||
const mindmap = surface.getElementById(mindmapId) as MindmapElementModel;
|
||||
handleLayout(mindmap, mindmap.tree, true, mindmap.layoutType);
|
||||
|
||||
return mindmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a subtree from one mind map to another
|
||||
* @param from the mind map that the `subtree` belongs to
|
||||
* @param subtree the subtree to move
|
||||
* @param to the mind map to move the `subtree` to
|
||||
* @param parent the new parent node to attach the `subtree` to
|
||||
* @param index the index to insert the `subtree` at
|
||||
*/
|
||||
export function moveNode(
|
||||
from: MindmapElementModel,
|
||||
subtree: MindmapNode,
|
||||
to: MindmapElementModel,
|
||||
parent: MindmapNode | string,
|
||||
index: number
|
||||
) {
|
||||
if (from === to) {
|
||||
return moveNodePosition(from, subtree, parent, index);
|
||||
}
|
||||
|
||||
if (!detachMindmap(from, subtree)) return;
|
||||
|
||||
return addNode(to, parent, subtree, index);
|
||||
}
|
||||
|
||||
export function findTargetNode(
|
||||
mindmap: MindmapElementModel,
|
||||
position: IVec
|
||||
): MindmapNode | null {
|
||||
const find = (node: MindmapNode): MindmapNode | null => {
|
||||
if (!node.responseArea) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const layoutDir = mindmap.getLayoutDir(node);
|
||||
|
||||
if (
|
||||
layoutDir === LayoutType.BALANCE ||
|
||||
(layoutDir === LayoutType.RIGHT &&
|
||||
position[0] > node.element.x + node.element.w) ||
|
||||
(layoutDir === LayoutType.LEFT && position[0] < node.element.x)
|
||||
) {
|
||||
for (const child of node.children) {
|
||||
const result = find(child);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node.responseArea.containsPoint(position) ? node : null;
|
||||
};
|
||||
|
||||
return find(mindmap.tree);
|
||||
}
|
||||
|
||||
function determineInsertPosition(
|
||||
mindmap: MindmapElementModel,
|
||||
mindmapNode: MindmapNode,
|
||||
position: IVec
|
||||
):
|
||||
| {
|
||||
type: 'child';
|
||||
layoutDir: LayoutType.LEFT | LayoutType.RIGHT;
|
||||
}
|
||||
| {
|
||||
type: 'sibling';
|
||||
layoutDir: LayoutType.LEFT | LayoutType.RIGHT;
|
||||
position: 'prev' | 'next';
|
||||
}
|
||||
| null {
|
||||
if (
|
||||
!mindmapNode.responseArea ||
|
||||
!mindmapNode.responseArea.containsPoint(position)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const layoutDir = mindmap.getLayoutDir(mindmapNode);
|
||||
const elementBound = mindmapNode.element.elementBound;
|
||||
const targetLayout: LayoutType.LEFT | LayoutType.RIGHT =
|
||||
layoutDir === LayoutType.BALANCE
|
||||
? position[0] > elementBound.x + elementBound.w / 2
|
||||
? LayoutType.RIGHT
|
||||
: LayoutType.LEFT
|
||||
: layoutDir;
|
||||
|
||||
if (
|
||||
elementBound.containsPoint(position) ||
|
||||
(layoutDir === LayoutType.RIGHT
|
||||
? position[0] > elementBound.x + elementBound.w
|
||||
: position[0] < elementBound.x)
|
||||
) {
|
||||
return {
|
||||
type: 'child',
|
||||
layoutDir: targetLayout,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
mindmap.layoutType === LayoutType.BALANCE &&
|
||||
mindmap.getPath(mindmapNode.id).length === 2
|
||||
) {
|
||||
return {
|
||||
type: 'sibling',
|
||||
layoutDir: targetLayout,
|
||||
position:
|
||||
targetLayout === LayoutType.LEFT
|
||||
? position[1] > elementBound.y + elementBound.h / 2
|
||||
? 'prev'
|
||||
: 'next'
|
||||
: position[1] > elementBound.y + elementBound.h / 2
|
||||
? 'next'
|
||||
: 'prev',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'sibling',
|
||||
layoutDir: targetLayout,
|
||||
position:
|
||||
position[1] > elementBound.y + elementBound.h / 2 ? 'next' : 'prev',
|
||||
};
|
||||
}
|
||||
|
||||
function showMergeIndicator(
|
||||
targetMindMap: MindmapElementModel,
|
||||
target: MindmapNode,
|
||||
sourceMindMap: MindmapElementModel,
|
||||
source: MindmapNode,
|
||||
insertPosition:
|
||||
| {
|
||||
type: 'sibling';
|
||||
layoutDir: Exclude<LayoutType, LayoutType.BALANCE>;
|
||||
position: 'prev' | 'next';
|
||||
}
|
||||
| { type: 'child'; layoutDir: Exclude<LayoutType, LayoutType.BALANCE> },
|
||||
callback: (option: {
|
||||
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[];
|
||||
}) => () => void
|
||||
) {
|
||||
const newParent =
|
||||
insertPosition.type === 'child'
|
||||
? target
|
||||
: targetMindMap.getParentNode(target.id)!;
|
||||
|
||||
if (!newParent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const path = targetMindMap.getPath(newParent);
|
||||
const curPath = sourceMindMap.getPath(source.id);
|
||||
|
||||
if (insertPosition.type === 'sibling') {
|
||||
const curPath = targetMindMap.getPath(target.id);
|
||||
const parent = targetMindMap.getParentNode(target.id);
|
||||
|
||||
if (!parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const idx = parent.children
|
||||
.filter(child => child.id !== source.id)
|
||||
.indexOf(target);
|
||||
|
||||
path.push(
|
||||
idx === -1
|
||||
? Math.max(
|
||||
0,
|
||||
last(curPath)! + (insertPosition.position === 'next' ? 1 : 0)
|
||||
)
|
||||
: Math.max(0, idx + (insertPosition.position === 'next' ? 1 : 0))
|
||||
);
|
||||
} else {
|
||||
path.push(target.children.length);
|
||||
}
|
||||
|
||||
// hide original connector
|
||||
const abortPreview = callback({
|
||||
targetMindMap,
|
||||
target: target,
|
||||
sourceMindMap,
|
||||
source,
|
||||
newParent,
|
||||
insertPosition,
|
||||
path,
|
||||
});
|
||||
|
||||
const abort = () => {
|
||||
abortPreview?.();
|
||||
};
|
||||
|
||||
const merge = () => {
|
||||
abort();
|
||||
|
||||
if (targetMindMap === sourceMindMap && isEqual(path, curPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
moveNode(sourceMindMap, source, targetMindMap, newParent, last(path)!);
|
||||
};
|
||||
|
||||
return {
|
||||
abort,
|
||||
merge,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to move a node to another mind map.
|
||||
* It will show a merge indicator if the node can be merged to the target mind map.
|
||||
* @param targetMindMap
|
||||
* @param target
|
||||
* @param sourceMindMap
|
||||
* @param source
|
||||
* @param position
|
||||
* @return return two functions, `abort` and `merge`. `abort` will cancel the operation and `merge` will merge the node to the target mind map.
|
||||
*/
|
||||
export function tryMoveNode(
|
||||
targetMindMap: MindmapElementModel,
|
||||
target: MindmapNode,
|
||||
sourceMindMap: MindmapElementModel,
|
||||
source: MindmapNode,
|
||||
position: IVec,
|
||||
callback: (option: {
|
||||
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[];
|
||||
}) => () => void
|
||||
) {
|
||||
const insertInfo = determineInsertPosition(targetMindMap, target, position);
|
||||
|
||||
if (!insertInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return showMergeIndicator(
|
||||
targetMindMap,
|
||||
target,
|
||||
sourceMindMap,
|
||||
source,
|
||||
insertInfo,
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the mind map contains the target node.
|
||||
* @param mindmap Mind map to check
|
||||
* @param targetNode Node to check
|
||||
* @param searchParent If provided, check if the node is a descendant of the parent node. Otherwise, check the whole mind map.
|
||||
* @returns
|
||||
*/
|
||||
export function containsNode(
|
||||
mindmap: MindmapElementModel,
|
||||
targetNode: MindmapNode,
|
||||
searchParent?: MindmapNode
|
||||
) {
|
||||
searchParent = searchParent ?? mindmap.tree;
|
||||
|
||||
const find = (checkAgainstNode: MindmapNode) => {
|
||||
if (checkAgainstNode.id === targetNode.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const child of checkAgainstNode.children) {
|
||||
if (find(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
return find(searchParent);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { ConnectorElementModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
type DragEndContext,
|
||||
type DragMoveContext,
|
||||
type DragStartContext,
|
||||
GfxElementModelView,
|
||||
} from '@blocksuite/block-std/gfx';
|
||||
|
||||
export class ConnectorElementView extends GfxElementModelView<ConnectorElementModel> {
|
||||
static override type = 'connector';
|
||||
|
||||
override onDragStart = (context: DragStartContext) => {
|
||||
super.onDragStart(context);
|
||||
this.model.stash('labelXYWH');
|
||||
};
|
||||
|
||||
override onDragEnd = (context: DragEndContext) => {
|
||||
super.onDragEnd(context);
|
||||
this.model.stash('labelXYWH');
|
||||
};
|
||||
|
||||
override onDragMove = (context: DragMoveContext) => {
|
||||
const { dx, dy, currentBound } = context;
|
||||
|
||||
this.model.moveTo(currentBound.moveDelta(dx, dy));
|
||||
};
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
import {
|
||||
LayoutType,
|
||||
LocalShapeElementModel,
|
||||
type MindmapElementModel,
|
||||
type MindmapNode,
|
||||
type MindmapRoot,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
|
||||
import { requestThrottledConnectedFrame } from '@blocksuite/affine-shared/utils';
|
||||
import type { PointerEventState } from '@blocksuite/block-std';
|
||||
import { GfxElementModelView } from '@blocksuite/block-std/gfx';
|
||||
|
||||
import { handleLayout } from '../utils/mindmap/utils.js';
|
||||
|
||||
export class MindMapView extends GfxElementModelView<MindmapElementModel> {
|
||||
static override type = 'mindmap';
|
||||
|
||||
private readonly _collapseButtons = new Map<string, LocalShapeElementModel>();
|
||||
|
||||
private _hoveredState = new Map<
|
||||
string,
|
||||
{
|
||||
button: boolean;
|
||||
node: boolean;
|
||||
}
|
||||
>();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param node The mindmap node or its id to get the collapse button
|
||||
* @returns
|
||||
*/
|
||||
getCollapseButton(node: MindmapNode | string) {
|
||||
const id = typeof node === 'string' ? node : node.id;
|
||||
return this._collapseButtons.get(`collapse-btn-${id}`);
|
||||
}
|
||||
|
||||
private _initCollapseButtons() {
|
||||
const updateButtons = requestThrottledConnectedFrame(() => {
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const visited = new Set<LocalShapeElementModel>();
|
||||
|
||||
this.model.traverse(node => {
|
||||
const btn = this._updateCollapseButton(node);
|
||||
|
||||
btn && visited.add(btn);
|
||||
});
|
||||
|
||||
this._collapseButtons.forEach(btn => {
|
||||
if (!visited.has(btn)) {
|
||||
this.surface.deleteLocalElement(btn);
|
||||
this._collapseButtons.delete(btn.id);
|
||||
const hoveredId = btn.id.replace('collapse-btn-', '');
|
||||
|
||||
this._hoveredState.delete(hoveredId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.disposable.add(
|
||||
this.model.propsUpdated.subscribe(({ key }) => {
|
||||
if (key === 'layoutType' || key === 'style') {
|
||||
updateButtons();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.disposable.add(
|
||||
this.surface.elementUpdated.subscribe(payload => {
|
||||
if (this.model.children.has(payload.id)) {
|
||||
if (payload.props['xywh']) {
|
||||
updateButtons();
|
||||
}
|
||||
if (
|
||||
payload.props['hidden'] !== undefined ||
|
||||
payload.props['opacity'] !== undefined
|
||||
) {
|
||||
this._updateButtonVisibility(payload.id);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.model.children.observe(updateButtons);
|
||||
|
||||
this.disposable.add(() => {
|
||||
this.model.children.unobserve(updateButtons);
|
||||
});
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private _needToUpdateButtonStyle(options: {
|
||||
button: LocalShapeElementModel;
|
||||
node: MindmapNode;
|
||||
updateKey?: boolean;
|
||||
}) {
|
||||
const { button, node } = options;
|
||||
const layout = this.model.getLayoutDir(node);
|
||||
const cacheKey = `${node.detail.collapsed ?? false}-${layout}-${node.element.xywh}-${this.model.style}`;
|
||||
|
||||
if (button.cache.get('MINDMAP_COLLAPSE_BUTTON') === cacheKey) {
|
||||
return false;
|
||||
} else if (options.updateKey) {
|
||||
button.cache.set('MINDMAP_COLLAPSE_BUTTON', cacheKey);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _setLayoutMethod() {
|
||||
this.model.setLayoutMethod(function (
|
||||
this: MindmapElementModel,
|
||||
tree: MindmapNode | MindmapRoot = this.tree,
|
||||
options: {
|
||||
applyStyle?: boolean;
|
||||
layoutType?: LayoutType;
|
||||
stashed?: boolean;
|
||||
} = {
|
||||
applyStyle: true,
|
||||
stashed: true,
|
||||
}
|
||||
) {
|
||||
const { stashed, applyStyle, layoutType } = Object.assign(
|
||||
{
|
||||
applyStyle: true,
|
||||
calculateTreeBound: true,
|
||||
stashed: true,
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
const pop = stashed ? this.stashTree(tree) : null;
|
||||
handleLayout(this, tree, applyStyle, layoutType);
|
||||
pop?.();
|
||||
});
|
||||
}
|
||||
|
||||
private _setVisibleOnSelection() {
|
||||
let lastNode: null | string = null;
|
||||
this.disposable.add(
|
||||
this.gfx.selection.slots.updated.subscribe(() => {
|
||||
const elm = this.gfx.selection.firstElement;
|
||||
|
||||
if (lastNode) {
|
||||
this._updateButtonVisibility(lastNode);
|
||||
}
|
||||
|
||||
if (
|
||||
this.gfx.selection.selectedElements.length === 1 &&
|
||||
elm?.id &&
|
||||
this.model.children.has(elm.id)
|
||||
) {
|
||||
const button = this.getCollapseButton(elm.id);
|
||||
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateButtonVisibility(elm.id);
|
||||
lastNode = elm.id;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _updateButtonVisibility(node: string) {
|
||||
const latestNode = this.model.getNode(node);
|
||||
const buttonModel = this.getCollapseButton(node);
|
||||
|
||||
if (!buttonModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!latestNode) {
|
||||
buttonModel.opacity = 0;
|
||||
buttonModel.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const hoveredState = this._hoveredState.get(node) ?? {
|
||||
button: false,
|
||||
node: false,
|
||||
};
|
||||
|
||||
const hovered = hoveredState.button || hoveredState.node;
|
||||
const hasChildren = (latestNode.children.length ?? 0) > 0;
|
||||
const notHidden = !latestNode.element.hidden;
|
||||
const isNodeSelected =
|
||||
this.gfx.selection.firstElement === latestNode.element;
|
||||
const collapsed = latestNode.detail.collapsed ?? false;
|
||||
|
||||
buttonModel.hidden = latestNode.element.hidden;
|
||||
buttonModel.opacity =
|
||||
(hasChildren && notHidden && (collapsed || isNodeSelected || hovered)
|
||||
? 1
|
||||
: 0) * (latestNode.element.opacity ?? 1);
|
||||
}
|
||||
|
||||
private _updateCollapseButton(node: MindmapNode) {
|
||||
if (!node?.element || node.children.length === 0) return null;
|
||||
|
||||
const id = `collapse-btn-${node.id}`;
|
||||
const alreadyCreated = this._collapseButtons.has(id);
|
||||
const collapseButton =
|
||||
this._collapseButtons.get(id) ||
|
||||
new LocalShapeElementModel(this.model.surface);
|
||||
const collapsed = node.detail.collapsed ?? false;
|
||||
|
||||
if (
|
||||
this._needToUpdateButtonStyle({
|
||||
button: collapseButton,
|
||||
node,
|
||||
updateKey: true,
|
||||
})
|
||||
) {
|
||||
const style = this.model.styleGetter.getNodeStyle(
|
||||
node,
|
||||
this.model.getPath(node)
|
||||
);
|
||||
const layout = this.model.getLayoutDir(node);
|
||||
const buttonStyle = collapsed ? style.expandButton : style.collapseButton;
|
||||
|
||||
Object.entries(buttonStyle).forEach(([key, value]) => {
|
||||
// @ts-expect-error key is string
|
||||
collapseButton[key as unknown] = value;
|
||||
});
|
||||
|
||||
const nodeElementBound = node.element.elementBound;
|
||||
const buttonBound = nodeElementBound.moveDelta(
|
||||
layout === LayoutType.LEFT
|
||||
? -6 - buttonStyle.width
|
||||
: 6 + nodeElementBound.w,
|
||||
(nodeElementBound.h - buttonStyle.height) / 2
|
||||
);
|
||||
|
||||
buttonBound.w = buttonStyle.width;
|
||||
buttonBound.h = buttonStyle.height;
|
||||
|
||||
collapseButton.responseExtension = [16, 16];
|
||||
collapseButton.xywh = buttonBound.serialize();
|
||||
collapseButton.groupId = this.model.id;
|
||||
collapseButton.text = collapsed ? node.children.length.toString() : '';
|
||||
}
|
||||
|
||||
if (!alreadyCreated) {
|
||||
collapseButton.id = id;
|
||||
collapseButton.opacity = !node.element.hidden && collapsed ? 1 : 0;
|
||||
|
||||
this._collapseButtons.set(id, collapseButton);
|
||||
this.surface.addLocalElement(collapseButton);
|
||||
|
||||
const hoveredState = {
|
||||
button: false,
|
||||
node: false,
|
||||
};
|
||||
const buttonView = this.gfx.view.get(id) as GfxElementModelView;
|
||||
const isOnElementBound = (evt: PointerEventState) => {
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(evt.x, evt.y);
|
||||
|
||||
return buttonView.model.includesPoint(
|
||||
x,
|
||||
y,
|
||||
{ useElementBound: true },
|
||||
this.gfx.std.host
|
||||
);
|
||||
};
|
||||
|
||||
this._hoveredState = this._hoveredState.set(node.id, hoveredState);
|
||||
|
||||
buttonView.on('pointerenter', () => {
|
||||
hoveredState.button = true;
|
||||
this._updateButtonVisibility(node.id);
|
||||
});
|
||||
buttonView.on('pointermove', evt => {
|
||||
const latestNode = this.model.getNode(node.id);
|
||||
if (
|
||||
latestNode &&
|
||||
!latestNode.element.hidden &&
|
||||
latestNode.children.length > 0
|
||||
) {
|
||||
if (isOnElementBound(evt)) {
|
||||
this.gfx.cursor$.value = 'pointer';
|
||||
} else {
|
||||
this.gfx.cursor$.value = 'default';
|
||||
}
|
||||
}
|
||||
});
|
||||
buttonView.on('pointerleave', () => {
|
||||
this.gfx.cursor$.value = 'default';
|
||||
|
||||
hoveredState.button = false;
|
||||
this._updateButtonVisibility(node.id);
|
||||
});
|
||||
buttonView.on('click', evt => {
|
||||
const latestNode = this.model.getNode(node.id);
|
||||
const telemetry = this.gfx.std.getOptional(TelemetryProvider);
|
||||
|
||||
if (latestNode && isOnElementBound(evt)) {
|
||||
if (telemetry) {
|
||||
telemetry.track('ExpandedAndCollapsed', {
|
||||
page: 'whiteboard editor',
|
||||
segment: 'mind map',
|
||||
type: latestNode.detail.collapsed ? 'expand' : 'collapse',
|
||||
});
|
||||
}
|
||||
|
||||
this.model.toggleCollapse(latestNode!, { layout: true });
|
||||
}
|
||||
});
|
||||
|
||||
const nodeView = this.gfx.view.get(node.id) as GfxElementModelView;
|
||||
|
||||
nodeView.on('pointerenter', () => {
|
||||
hoveredState.node = true;
|
||||
this._updateButtonVisibility(node.id);
|
||||
});
|
||||
nodeView.on('pointerleave', () => {
|
||||
hoveredState.node = false;
|
||||
this._updateButtonVisibility(node.id);
|
||||
});
|
||||
} else {
|
||||
this._updateButtonVisibility(node.id);
|
||||
}
|
||||
|
||||
return collapseButton;
|
||||
}
|
||||
|
||||
override onCreated(): void {
|
||||
this._setLayoutMethod();
|
||||
this._initCollapseButtons();
|
||||
this._setVisibleOnSelection();
|
||||
}
|
||||
|
||||
override onDestroyed() {
|
||||
super.onDestroyed();
|
||||
this._collapseButtons.forEach(btn => {
|
||||
this.surface.deleteLocalElement(btn);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user