From f9cd6b24db603789077ff7c092e19fe056ec5167 Mon Sep 17 00:00:00 2001 From: Yassine Sallemi Date: Fri, 27 Sep 2024 17:05:41 +0100 Subject: [PATCH 1/2] feat: self attached node --- .../visual-editor/hooks/useVisualEditor.tsx | 17 +-- .../v2/AdvancedLink/AdvancedLink.tsx | 103 ++++++++++++++++++ .../components/visual-editor/v2/Diagrams.tsx | 9 +- 3 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLink.tsx diff --git a/frontend/src/components/visual-editor/hooks/useVisualEditor.tsx b/frontend/src/components/visual-editor/hooks/useVisualEditor.tsx index 6fd146d..18dd47a 100644 --- a/frontend/src/components/visual-editor/hooks/useVisualEditor.tsx +++ b/frontend/src/components/visual-editor/hooks/useVisualEditor.tsx @@ -8,10 +8,7 @@ */ import { debounce } from "@mui/material"; -import createEngine, { - DefaultLinkModel, - DiagramModel, -} from "@projectstorm/react-diagrams"; +import createEngine, { DiagramModel } from "@projectstorm/react-diagrams"; import * as React from "react"; import { createContext, useContext } from "react"; @@ -26,6 +23,10 @@ import { } from "@/types/visual-editor.types"; import { ZOOM_LEVEL } from "../constants"; +import { + AdvancedLinkFactory, + AdvancedLinkModel, +} from "../v2/AdvancedLink/AdvancedLink"; import { CustomCanvasWidget } from "../v2/CustomCanvasWidget"; import { CustomDeleteItemsAction } from "../v2/CustomDiagramNodes/CustomDeleteAction"; import { NodeFactory } from "../v2/CustomDiagramNodes/NodeFactory"; @@ -87,6 +88,8 @@ const buildDiagram = ({ model = new DiagramModel(); engine.getNodeFactories().registerFactory(new NodeFactory()); + engine.getLinkFactories().registerFactory(new AdvancedLinkFactory()); + engine .getActionEventBus() .registerAction(new CustomDeleteItemsAction({ callback: onRemoveNode })); @@ -139,12 +142,12 @@ const buildDiagram = ({ } } }; - const links: DefaultLinkModel[] = []; + const links: AdvancedLinkModel[] = []; data.forEach((datum, index) => { if ("nextBlocks" in datum && Array.isArray(datum.nextBlocks)) { datum.nextBlocks?.forEach((nextBlock) => { - const link = new DefaultLinkModel(); + const link = new AdvancedLinkModel(); const sourceNode = nodes[index]; const targetNode = nodes.find( // @ts-ignore @@ -164,7 +167,7 @@ const buildDiagram = ({ //recursive link if ("attachedBlock" in datum && datum.attachedBlock) { - const link = new DefaultLinkModel({ + const link = new AdvancedLinkModel({ color: "#019185", selectedColor: "#019185", type: "default", diff --git a/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLink.tsx b/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLink.tsx new file mode 100644 index 0000000..fbf933f --- /dev/null +++ b/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLink.tsx @@ -0,0 +1,103 @@ +import { css, keyframes } from "@emotion/react"; +import styled from "@emotion/styled"; +import { + DefaultLinkFactory, + DefaultLinkModel, + DefaultLinkModelOptions, + DefaultLinkWidget, +} from "@projectstorm/react-diagrams"; +import React from "react"; + +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 AdvancedLinkModel extends DefaultLinkModel { + constructor(options?: DefaultLinkModelOptions) { + super({ + ...options, + type: "advanced", + }); + } +} + +export class AdvancedLinkFactory extends DefaultLinkFactory { + constructor() { + super("advanced"); + } + + generateModel(): AdvancedLinkModel { + return new AdvancedLinkModel(); + } + + generateReactWidget(event): JSX.Element { + return ; + } + + 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 ( + + ); + } +} diff --git a/frontend/src/components/visual-editor/v2/Diagrams.tsx b/frontend/src/components/visual-editor/v2/Diagrams.tsx index 431a408..3b5aee3 100644 --- a/frontend/src/components/visual-editor/v2/Diagrams.tsx +++ b/frontend/src/components/visual-editor/v2/Diagrams.tsx @@ -25,7 +25,6 @@ import { tabsClasses, } from "@mui/material"; import { - DefaultLinkModel, DefaultPortModel, DiagramEngine, DiagramModel, @@ -54,6 +53,7 @@ import { IBlock } from "@/types/block.types"; import { ICategory } from "@/types/category.types"; import { BlockPorts } from "@/types/visual-editor.types"; +import { AdvancedLinkModel } from "./AdvancedLink/AdvancedLink"; import BlockDialog from "../BlockDialog"; import { ZOOM_LEVEL } from "../constants"; import { useVisualEditor } from "../hooks/useVisualEditor"; @@ -195,7 +195,7 @@ const Diagrams = () => { entity, port, }: { - entity: DefaultLinkModel; + entity: AdvancedLinkModel; port: DefaultPortModel; }) => { const link = model.getLink(entity.getOptions().id as string); @@ -205,7 +205,10 @@ const Diagrams = () => { [BlockPorts.nextBlocksOutPort, BlockPorts.attachmentOutPort].includes( // @ts-expect-error protected attr entity.targetPort.getOptions().label, - ) + ) || + (link.getSourcePort().getType() === "attached" && + link.getSourcePort().getParent().getOptions().id === + link.getTargetPort().getParent().getOptions().id) ) { model.removeLink(link); From cfd1f0850dd1e45e22e0b8493f42fd7101d17683 Mon Sep 17 00:00:00 2001 From: Yassine Sallemi Date: Fri, 27 Sep 2024 17:09:52 +0100 Subject: [PATCH 2/2] fix: seperated files --- .../visual-editor/hooks/useVisualEditor.tsx | 6 ++---- .../{AdvancedLink.tsx => AdvancedLinkFactory.tsx} | 14 +++----------- .../v2/AdvancedLink/AdvancedLinkModel.tsx | 13 +++++++++++++ .../src/components/visual-editor/v2/Diagrams.tsx | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) rename frontend/src/components/visual-editor/v2/AdvancedLink/{AdvancedLink.tsx => AdvancedLinkFactory.tsx} (90%) create mode 100644 frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkModel.tsx diff --git a/frontend/src/components/visual-editor/hooks/useVisualEditor.tsx b/frontend/src/components/visual-editor/hooks/useVisualEditor.tsx index 18dd47a..eb1e9e1 100644 --- a/frontend/src/components/visual-editor/hooks/useVisualEditor.tsx +++ b/frontend/src/components/visual-editor/hooks/useVisualEditor.tsx @@ -23,10 +23,8 @@ import { } from "@/types/visual-editor.types"; import { ZOOM_LEVEL } from "../constants"; -import { - AdvancedLinkFactory, - AdvancedLinkModel, -} from "../v2/AdvancedLink/AdvancedLink"; +import { AdvancedLinkFactory } from "../v2/AdvancedLink/AdvancedLinkFactory"; +import { AdvancedLinkModel } from "../v2/AdvancedLink/AdvancedLinkModel"; import { CustomCanvasWidget } from "../v2/CustomCanvasWidget"; import { CustomDeleteItemsAction } from "../v2/CustomDiagramNodes/CustomDeleteAction"; import { NodeFactory } from "../v2/CustomDiagramNodes/NodeFactory"; diff --git a/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLink.tsx b/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkFactory.tsx similarity index 90% rename from frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLink.tsx rename to frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkFactory.tsx index fbf933f..7dcb511 100644 --- a/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLink.tsx +++ b/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkFactory.tsx @@ -2,16 +2,17 @@ import { css, keyframes } from "@emotion/react"; import styled from "@emotion/styled"; import { DefaultLinkFactory, - DefaultLinkModel, - DefaultLinkModelOptions, 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; @@ -43,15 +44,6 @@ namespace S { `; } -export class AdvancedLinkModel extends DefaultLinkModel { - constructor(options?: DefaultLinkModelOptions) { - super({ - ...options, - type: "advanced", - }); - } -} - export class AdvancedLinkFactory extends DefaultLinkFactory { constructor() { super("advanced"); diff --git a/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkModel.tsx b/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkModel.tsx new file mode 100644 index 0000000..a576f5c --- /dev/null +++ b/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkModel.tsx @@ -0,0 +1,13 @@ +import { + DefaultLinkModel, + DefaultLinkModelOptions, +} from "@projectstorm/react-diagrams"; + +export class AdvancedLinkModel extends DefaultLinkModel { + constructor(options?: DefaultLinkModelOptions) { + super({ + ...options, + type: "advanced", + }); + } +} diff --git a/frontend/src/components/visual-editor/v2/Diagrams.tsx b/frontend/src/components/visual-editor/v2/Diagrams.tsx index 3b5aee3..3e223ba 100644 --- a/frontend/src/components/visual-editor/v2/Diagrams.tsx +++ b/frontend/src/components/visual-editor/v2/Diagrams.tsx @@ -53,7 +53,7 @@ import { IBlock } from "@/types/block.types"; import { ICategory } from "@/types/category.types"; import { BlockPorts } from "@/types/visual-editor.types"; -import { AdvancedLinkModel } from "./AdvancedLink/AdvancedLink"; +import { AdvancedLinkModel } from "./AdvancedLink/AdvancedLinkModel"; import BlockDialog from "../BlockDialog"; import { ZOOM_LEVEL } from "../constants"; import { useVisualEditor } from "../hooks/useVisualEditor";