Merge pull request #96 from Hexastack/47-request-visual-editor-allow-to-link-a-block-to-itself

Feat: Self Attached Node
This commit is contained in:
Mohamed Marrouchi 2024-09-29 12:29:06 +01:00 committed by GitHub
commit 811d5219f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 122 additions and 10 deletions

View File

@ -8,10 +8,7 @@
*/ */
import { debounce } from "@mui/material"; import { debounce } from "@mui/material";
import createEngine, { import createEngine, { DiagramModel } from "@projectstorm/react-diagrams";
DefaultLinkModel,
DiagramModel,
} from "@projectstorm/react-diagrams";
import * as React from "react"; import * as React from "react";
import { createContext, useContext } from "react"; import { createContext, useContext } from "react";
@ -26,6 +23,8 @@ import {
} from "@/types/visual-editor.types"; } from "@/types/visual-editor.types";
import { ZOOM_LEVEL } from "../constants"; import { ZOOM_LEVEL } from "../constants";
import { AdvancedLinkFactory } from "../v2/AdvancedLink/AdvancedLinkFactory";
import { AdvancedLinkModel } from "../v2/AdvancedLink/AdvancedLinkModel";
import { CustomCanvasWidget } from "../v2/CustomCanvasWidget"; import { CustomCanvasWidget } from "../v2/CustomCanvasWidget";
import { CustomDeleteItemsAction } from "../v2/CustomDiagramNodes/CustomDeleteAction"; import { CustomDeleteItemsAction } from "../v2/CustomDiagramNodes/CustomDeleteAction";
import { NodeFactory } from "../v2/CustomDiagramNodes/NodeFactory"; import { NodeFactory } from "../v2/CustomDiagramNodes/NodeFactory";
@ -87,6 +86,8 @@ const buildDiagram = ({
model = new DiagramModel(); model = new DiagramModel();
engine.getNodeFactories().registerFactory(new NodeFactory()); engine.getNodeFactories().registerFactory(new NodeFactory());
engine.getLinkFactories().registerFactory(new AdvancedLinkFactory());
engine engine
.getActionEventBus() .getActionEventBus()
.registerAction(new CustomDeleteItemsAction({ callback: onRemoveNode })); .registerAction(new CustomDeleteItemsAction({ callback: onRemoveNode }));
@ -139,12 +140,12 @@ const buildDiagram = ({
} }
} }
}; };
const links: DefaultLinkModel[] = []; const links: AdvancedLinkModel[] = [];
data.forEach((datum, index) => { data.forEach((datum, index) => {
if ("nextBlocks" in datum && Array.isArray(datum.nextBlocks)) { if ("nextBlocks" in datum && Array.isArray(datum.nextBlocks)) {
datum.nextBlocks?.forEach((nextBlock) => { datum.nextBlocks?.forEach((nextBlock) => {
const link = new DefaultLinkModel(); const link = new AdvancedLinkModel();
const sourceNode = nodes[index]; const sourceNode = nodes[index];
const targetNode = nodes.find( const targetNode = nodes.find(
// @ts-ignore // @ts-ignore
@ -164,7 +165,7 @@ const buildDiagram = ({
//recursive link //recursive link
if ("attachedBlock" in datum && datum.attachedBlock) { if ("attachedBlock" in datum && datum.attachedBlock) {
const link = new DefaultLinkModel({ const link = new AdvancedLinkModel({
color: "#019185", color: "#019185",
selectedColor: "#019185", selectedColor: "#019185",
type: "default", type: "default",

View File

@ -0,0 +1,95 @@
import { css, keyframes } from "@emotion/react";
import styled from "@emotion/styled";
import {
DefaultLinkFactory,
DefaultLinkWidget,
} from "@projectstorm/react-diagrams";
import React from "react";
import { AdvancedLinkModel } from "./AdvancedLinkModel";
interface Point {
x: number;
y: number;
}
const createCurvedPath = (start: Point, end: Point) => {
const controlPoint1X = start.x + 220;
const controlPoint1Y = start.y - 250;
const controlPoint2X = end.x - 250;
const controlPoint2Y = end.y - 250;
return `M ${start.x},${start.y} C ${controlPoint1X},${controlPoint1Y} ${controlPoint2X},${controlPoint2Y} ${end.x},${end.y}`;
};
namespace S {
export const Keyframes = keyframes`
from {
stroke-dashoffset: 24;
}
to {
stroke-dashoffset: 0;
}
`;
const selected = css`
stroke-dasharray: 10, 2;
animation: ${Keyframes} 1s linear infinite;
`;
export const Path = styled.path<{ selected: boolean }>`
${(p) => p.selected && selected};
fill: none;
pointer-events: auto;
`;
}
export class AdvancedLinkFactory extends DefaultLinkFactory {
constructor() {
super("advanced");
}
generateModel(): AdvancedLinkModel {
return new AdvancedLinkModel();
}
generateReactWidget(event): JSX.Element {
return <DefaultLinkWidget link={event.model} diagramEngine={this.engine} />;
}
generateLinkSegment(
model: AdvancedLinkModel,
selected: boolean,
path: string,
) {
const isSelfLoop =
model.getSourcePort().getNode() === model.getTargetPort().getNode();
if (isSelfLoop) {
// Adjust the path to create a curve
const sourcePortPosition = model.getSourcePort().getPosition();
const targetPortPosition = model.getTargetPort().getPosition();
const startPoint: Point = {
x: sourcePortPosition.x + 20,
y: sourcePortPosition.y + 20,
};
const endPoint: Point = {
x: targetPortPosition.x + 20,
y: targetPortPosition.y + 20,
};
path = createCurvedPath(startPoint, endPoint);
}
return (
<S.Path
selected={selected}
stroke={
selected ? model.getOptions().selectedColor : model.getOptions().color
}
strokeWidth={model.getOptions().width}
d={path}
/>
);
}
}

View File

@ -0,0 +1,13 @@
import {
DefaultLinkModel,
DefaultLinkModelOptions,
} from "@projectstorm/react-diagrams";
export class AdvancedLinkModel extends DefaultLinkModel {
constructor(options?: DefaultLinkModelOptions) {
super({
...options,
type: "advanced",
});
}
}

View File

@ -25,7 +25,6 @@ import {
tabsClasses, tabsClasses,
} from "@mui/material"; } from "@mui/material";
import { import {
DefaultLinkModel,
DefaultPortModel, DefaultPortModel,
DiagramEngine, DiagramEngine,
DiagramModel, DiagramModel,
@ -54,6 +53,7 @@ import { IBlock } from "@/types/block.types";
import { ICategory } from "@/types/category.types"; import { ICategory } from "@/types/category.types";
import { BlockPorts } from "@/types/visual-editor.types"; import { BlockPorts } from "@/types/visual-editor.types";
import { AdvancedLinkModel } from "./AdvancedLink/AdvancedLinkModel";
import BlockDialog from "../BlockDialog"; import BlockDialog from "../BlockDialog";
import { ZOOM_LEVEL } from "../constants"; import { ZOOM_LEVEL } from "../constants";
import { useVisualEditor } from "../hooks/useVisualEditor"; import { useVisualEditor } from "../hooks/useVisualEditor";
@ -195,7 +195,7 @@ const Diagrams = () => {
entity, entity,
port, port,
}: { }: {
entity: DefaultLinkModel; entity: AdvancedLinkModel;
port: DefaultPortModel; port: DefaultPortModel;
}) => { }) => {
const link = model.getLink(entity.getOptions().id as string); const link = model.getLink(entity.getOptions().id as string);
@ -205,7 +205,10 @@ const Diagrams = () => {
[BlockPorts.nextBlocksOutPort, BlockPorts.attachmentOutPort].includes( [BlockPorts.nextBlocksOutPort, BlockPorts.attachmentOutPort].includes(
// @ts-expect-error protected attr // @ts-expect-error protected attr
entity.targetPort.getOptions().label, entity.targetPort.getOptions().label,
) ) ||
(link.getSourcePort().getType() === "attached" &&
link.getSourcePort().getParent().getOptions().id ===
link.getTargetPort().getParent().getOptions().id)
) { ) {
model.removeLink(link); model.removeLink(link);