Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Steve/line-points-snap #1464

Merged
merged 3 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bricks/diagram/src/display-canvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { useLineMarkers } from "../shared/canvas/useLineMarkers";
import { updateCells } from "../draw-canvas/processors/updateCells";
import styleText from "../shared/canvas/styles.shadow.css";
import zoomBarStyleText from "../shared/canvas/ZoomBarComponent.shadow.css";
import { useEditableLineMap } from "../shared/canvas/useEditableLineMap";

const { defineElement, property, event } = createDecorators();

Expand Down Expand Up @@ -394,6 +395,7 @@ function EoDisplayCanvasComponent({
defaultEdgeLines,
markerPrefix,
});
const editableLineMap = useEditableLineMap({ cells, lineConfMap });

const ready = useReady({ cells, layout, centered });

Expand Down Expand Up @@ -430,6 +432,7 @@ function EoDisplayCanvasComponent({
degradedNodeLabel={degradedNodeLabel}
defaultNodeBricks={defaultNodeBricks}
lineConfMap={lineConfMap}
editableLineMap={editableLineMap}
transform={transform}
activeTarget={activeTarget}
readOnly
Expand Down
24 changes: 13 additions & 11 deletions bricks/diagram/src/draw-canvas/CellComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
LayoutType,
NodeBrickConf,
NodeCell,
EditableLine,
} from "./interfaces";
import {
isContainerDecoratorCell,
Expand Down Expand Up @@ -43,6 +44,7 @@ export interface CellComponentProps {
defaultNodeBricks?: NodeBrickConf[];
transform: TransformLiteral;
lineConfMap: WeakMap<EdgeCell, ComputedEdgeLineConf>;
editableLineMap: WeakMap<EdgeCell, EditableLine>;
activeTarget: ActiveTarget | null | undefined;
readOnly?: boolean;
unrelatedCells: Cell[];
Expand Down Expand Up @@ -71,6 +73,7 @@ export function CellComponent({
degradedNodeLabel,
defaultNodeBricks,
lineConfMap,
editableLineMap,
activeTarget,
dragNodeToContainerActive,
readOnly,
Expand All @@ -91,6 +94,7 @@ export function CellComponent({
onCellMouseLeave,
}: CellComponentProps): JSX.Element | null {
const {
activeEditableEdge,
lineEditorState,
smartConnectLineState,
setSmartConnectLineState,
Expand Down Expand Up @@ -190,24 +194,21 @@ export function CellComponent({
);
}
setSmartConnectLineState(null);
} else if (lineEditorState) {
const {
type,
source,
target,
edge: { view },
} = lineEditorState;
} else if (activeEditableEdge && lineEditorState) {
const { type } = lineEditorState;
const { source, target } = editableLineMap.get(activeEditableEdge)!;
const { view } = activeEditableEdge;

const isEntry = type === "entry";
if ((isEntry ? target : source) === cell) {
if (isEntry) {
onChangeEdgeView?.(source, target, {
onChangeEdgeView?.(source!, target!, {
...view,
entryPosition: null,
// ...(!view?.exitPosition ? { vertices: null } : {}),
});
} else {
onChangeEdgeView?.(source, target, {
onChangeEdgeView?.(source!, target!, {
...view,
exitPosition: null,
// ...(!view?.entryPosition ? { vertices: null } : {}),
Expand All @@ -222,6 +223,8 @@ export function CellComponent({
g.removeEventListener("mouseup", onMouseUp);
};
}, [
activeEditableEdge,
editableLineMap,
allowEdgeToArea,
cell,
lineEditorState,
Expand Down Expand Up @@ -302,9 +305,8 @@ export function CellComponent({
) : isEdgeCell(cell) ? (
<EdgeComponent
edge={cell}
cells={cells}
lineConfMap={lineConfMap}
active={active}
editableLineMap={editableLineMap}
readOnly={readOnly}
onSwitchActiveTarget={onSwitchActiveTarget}
/>
Expand Down
88 changes: 6 additions & 82 deletions bricks/diagram/src/draw-canvas/EdgeComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@ import { uniqueId } from "lodash";
import ResizeObserver from "resize-observer-polyfill";
import type {
ActiveTarget,
Cell,
ComputedEdgeLineConf,
EdgeCell,
EditableLine,
} from "./interfaces";
import { isEdgeCell, isStraightType } from "./processors/asserts";
import { DEFAULT_LINE_INTERACT_ANIMATE_DURATION } from "./constants";
import { curveLine } from "../diagram/lines/curveLine";
import { getSmartLinePoints } from "../shared/canvas/processors/getSmartLinePoints";
import { findNodeOrAreaDecorator } from "./processors/findNodeOrAreaDecorator";
import { useHoverStateContext } from "./HoverStateContext";
import { getMarkers } from "../shared/canvas/useLineMarkers";
import type {
LineMarkerConf,
Expand All @@ -32,104 +28,32 @@ import { cellToTarget } from "./processors/cellToTarget";

export interface EdgeComponentProps {
edge: EdgeCell;
cells: Cell[];
lineConfMap: WeakMap<EdgeCell, ComputedEdgeLineConf>;
active?: boolean;
editableLineMap: WeakMap<EdgeCell, EditableLine>;
readOnly?: boolean;
onSwitchActiveTarget?(activeTarget: ActiveTarget | null): void;
}

export function EdgeComponent({
edge,
cells,
lineConfMap,
active,
editableLineMap,
readOnly,
onSwitchActiveTarget,
}: EdgeComponentProps): JSX.Element | null {
const { setActiveEditableLine } = useHoverStateContext();
const pathRef = useRef<SVGPathElement | null>(null);
const sourceNode = useMemo(
() => findNodeOrAreaDecorator(cells, edge.source),
[cells, edge.source]
);
const targetNode = useMemo(
() => findNodeOrAreaDecorator(cells, edge.target),
[cells, edge.target]
);
const lineConf = useMemo(() => lineConfMap.get(edge)!, [edge, lineConfMap]);

const parallelGap = useMemo(() => {
const hasOppositeEdge =
isStraightType(edge.view?.type) &&
cells.some(
(cell) =>
isEdgeCell(cell) &&
cell.source === edge.target &&
cell.target === edge.source &&
isStraightType(cell.view?.type)
);
return hasOppositeEdge ? lineConf.parallelGap : 0;
}, [cells, edge.source, edge.target, edge.view, lineConf.parallelGap]);

const linePoints = useMemo(() => {
const points =
sourceNode &&
targetNode &&
sourceNode.view.x != null &&
targetNode.view.x != null
? getSmartLinePoints(
sourceNode.view,
targetNode.view,
edge.view,
parallelGap
)
: null;
return points;
}, [edge.view, parallelGap, sourceNode, targetNode]);
const lineConf = lineConfMap.get(edge)!;
const { points: linePoints } = editableLineMap.get(edge) ?? {};

const line = useMemo(() => {
const fixedLineType = lineConf.type;
return curveLine(
linePoints,
fixedLineType === "curve" ? lineConf.curveType : "curveLinear",
lineConf.type === "curve" ? lineConf.curveType : "curveLinear",
0,
1
);
}, [lineConf, linePoints]);

useEffect(() => {
setActiveEditableLine((prev) =>
active
? linePoints && sourceNode && targetNode
? {
edge,
source: sourceNode,
target: targetNode,
linePoints,
lineType: lineConf.type,
lineCurveType: lineConf.curveType,
parallelGap,
}
: null
: prev?.edge &&
prev.edge.source === edge.source &&
prev.edge.target === edge.target
? null
: prev
);
}, [
active,
edge,
lineConf.curveType,
lineConf.type,
linePoints,
setActiveEditableLine,
sourceNode,
targetNode,
parallelGap,
]);

const lineMaskPrefix = useMemo(() => uniqueId("line-mask-"), []);

const [labelPosition, setLabelPosition] = useState<PositionTuple | null>(
Expand Down
74 changes: 58 additions & 16 deletions bricks/diagram/src/draw-canvas/EditingLineComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// istanbul ignore file: experimental
import React, { useEffect, useMemo, useRef, useState } from "react";
import classNames from "classnames";
import type { ComputedLineConnecterConf } from "./interfaces";
import type {
Cell,
ComputedLineConnecterConf,
EdgeCell,
EditableLine,
} from "./interfaces";
import type {
LineMarkerConf,
NodePosition,
Expand All @@ -17,19 +22,23 @@ import {
import { getMarkers } from "../shared/canvas/useLineMarkers";

export interface EditingLineComponentProps {
cells: Cell[];
editableLineMap: WeakMap<EdgeCell, EditableLine>;
transform: TransformLiteral;
options: ComputedLineConnecterConf;
}

export function EditingLineComponent({
cells,
editableLineMap,
transform,
options,
}: EditingLineComponentProps): JSX.Element {
const [connectLineTo, setConnectLineTo] = useState<PositionTuple | null>(
null
);
const {
activeEditableLine,
activeEditableEdge,
hoverState,
lineEditorState,
setLineEditorState,
Expand All @@ -46,7 +55,7 @@ export function EditingLineComponent({
}, [lineEditorState]);

useEffect(() => {
if (!lineEditorState) {
if (!activeEditableEdge || !lineEditorState) {
return;
}
movedRef.current = false;
Expand All @@ -59,8 +68,11 @@ export function EditingLineComponent({
};
let diff = Infinity;
if (type === "control" && !e.altKey) {
// console.log(cells.filter(isEdgeCell));

// Snap to other points
const { linePoints, control } = lineEditorState;
const { control } = lineEditorState;
const linePoints = editableLineMap.get(activeEditableEdge)!.points;
const axis = control.direction === "ns" ? "y" : "x";
const original = control[axis];
const otherPoints = linePoints.filter(
Expand All @@ -70,6 +82,18 @@ export function EditingLineComponent({
(i !== control.index && i !== control.index + 1)
);
const snapDistance = 5;

// Snap to control points of other lines
for (const cell of cells) {
if (cell.type !== "edge" || cell === activeEditableEdge) {
continue;
}
const editableLine = editableLineMap.get(cell);
if (editableLine) {
otherPoints.push(...editableLine.points.slice(1, -1));
}
}

for (const point of otherPoints) {
const newDiff = Math.abs(point[axis] - position[axis]);
if (newDiff <= snapDistance && newDiff < diff) {
Expand Down Expand Up @@ -100,14 +124,16 @@ export function EditingLineComponent({
if (lineEditorState?.type === "control") {
const newConnectTo = getConnectTo(e);
if (movedRef.current) {
const {
source,
target,
edge: { view },
} = lineEditorState;
const { source, target } = editableLineMap.get(activeEditableEdge!)!;
const { view } = activeEditableEdge!;
onChangeEdgeView?.(source, target, {
...view,
vertices: getNewLineVertices(lineEditorState, newConnectTo),
vertices: getNewLineVertices(
activeEditableEdge!,
lineEditorState,
editableLineMap,
newConnectTo
),
});
}
}
Expand All @@ -122,10 +148,18 @@ export function EditingLineComponent({
document.addEventListener("mouseup", onMouseUp);

return reset;
}, [lineEditorState, transform, setLineEditorState, onChangeEdgeView]);
}, [
activeEditableEdge,
editableLineMap,
lineEditorState,
transform,
setLineEditorState,
onChangeEdgeView,
cells,
]);

useEffect(() => {
if (!activeEditableLine) {
if (!activeEditableEdge) {
return;
}
const handleBodyClick = (e: MouseEvent) => {
Expand All @@ -138,23 +172,31 @@ export function EditingLineComponent({
return () => {
document.body.removeEventListener("click", handleBodyClick);
};
}, [activeEditableLine]);
}, [activeEditableEdge]);

const line = useMemo(() => {
const points = getEditingLinePoints(
activeEditableEdge,
lineEditorState,
editableLineMap,
connectLineTo,
hoverState
);
return curveLine(
points,
lineEditorState?.edge.view?.type === "curve"
? lineEditorState.edge.view.curveType
activeEditableEdge?.view?.type === "curve"
? activeEditableEdge.view.curveType
: "curveLinear",
0,
1
);
}, [connectLineTo, hoverState, lineEditorState]);
}, [
connectLineTo,
hoverState,
activeEditableEdge,
lineEditorState,
editableLineMap,
]);
let markerStart: string | undefined;
let markerEnd: string | undefined;
const lineMarkers: LineMarkerConf[] = getMarkers(options);
Expand Down
Loading
Loading