From 8c5dd6b875d1f41c46023c5f3f4ce1a4fe2e9346 Mon Sep 17 00:00:00 2001 From: Ivo Bek Date: Sun, 13 Oct 2024 18:28:42 +0200 Subject: [PATCH] feat #1540: Add quick icon edge end --- .../Canvas/controller.service.ts | 4 +- .../Visualization/Canvas/flow.service.ts | 13 ++++++ .../Custom/ContextMenu/ItemAddStep.tsx | 45 ++++++++++++------- .../Visualization/Custom/Edge/CustomEdge.scss | 20 +++++++++ .../Visualization/Custom/Edge/EdgeEnd.tsx | 44 ++++++++++++++++++ .../Visualization/Custom/NoBendingEdge.tsx | 38 +++++++++++++++- .../components/Visualization/Custom/index.ts | 1 + 7 files changed, 148 insertions(+), 17 deletions(-) create mode 100644 packages/ui/src/components/Visualization/Custom/Edge/CustomEdge.scss create mode 100644 packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx diff --git a/packages/ui/src/components/Visualization/Canvas/controller.service.ts b/packages/ui/src/components/Visualization/Canvas/controller.service.ts index 2f5089a1a..e636903f0 100644 --- a/packages/ui/src/components/Visualization/Canvas/controller.service.ts +++ b/packages/ui/src/components/Visualization/Canvas/controller.service.ts @@ -18,7 +18,7 @@ import { Visualization, withPanZoom, } from '@patternfly/react-topology'; -import { CustomGroupWithSelection, CustomNodeWithSelection, NoBendpointsEdge } from '../Custom'; +import { CustomGroupWithSelection, CustomNodeWithSelection, NoBendpointsEdge, EdgeEndWithButton } from '../Custom'; import { LayoutType } from './canvas.models'; export class ControllerService { @@ -109,6 +109,8 @@ export class ControllerService { switch (type) { case 'group': return CustomGroupWithSelection; + case 'edge-end': + return EdgeEndWithButton; default: switch (kind) { case ModelKind.graph: diff --git a/packages/ui/src/components/Visualization/Canvas/flow.service.ts b/packages/ui/src/components/Visualization/Canvas/flow.service.ts index 2b9d5b5f2..c93e06262 100644 --- a/packages/ui/src/components/Visualization/Canvas/flow.service.ts +++ b/packages/ui/src/components/Visualization/Canvas/flow.service.ts @@ -64,9 +64,12 @@ export class FlowService { private static getEdgesFromVizNode(vizNodeParam: IVisualizationNode): CanvasEdge[] { const edges: CanvasEdge[] = []; + const nodeInteractions = vizNodeParam.getNodeInteraction(); if (vizNodeParam.getNextNode() !== undefined) { edges.push(this.getEdge(vizNodeParam.id, vizNodeParam.getNextNode()!.id)); + } else if (nodeInteractions.canHaveNextStep) { + edges.push(this.getEdgeEnd(vizNodeParam.id)); } return edges; @@ -111,4 +114,14 @@ export class FlowService { edgeStyle: EdgeStyle.solid, }; } + + private static getEdgeEnd(source: string): CanvasEdge { + return { + id: `${source}-end`, + type: 'edge-end', + source, + target: source, + edgeStyle: EdgeStyle.dashed, + }; + } } diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx index 514493401..16b84d67c 100644 --- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemAddStep.tsx @@ -1,34 +1,49 @@ import { ContextMenuItem } from '@patternfly/react-topology'; import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react'; import { IDataTestID } from '../../../../models'; -import { AddStepMode, IVisualizationNode } from '../../../../models/visualization/base-visual-entity'; +import { + AddStepMode, + IVisualizationNode, + IVisualizationNodeData, +} from '../../../../models/visualization/base-visual-entity'; import { CatalogModalContext } from '../../../../providers/catalog-modal.provider'; import { EntitiesContext } from '../../../../providers/entities.provider'; +import { EntitiesContextResult } from '../../../../hooks'; interface ItemAddStepProps extends PropsWithChildren { mode: AddStepMode.PrependStep | AddStepMode.AppendStep; vizNode: IVisualizationNode; } -export const ItemAddStep: FunctionComponent = (props) => { - const entitiesContext = useContext(EntitiesContext); - const catalogModalContext = useContext(CatalogModalContext); +export const addNode = async ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + catalogModalContext: any, + entitiesContext: EntitiesContextResult | null, + vizNode: IVisualizationNode, + mode: AddStepMode = AddStepMode.AppendStep, +) => { + if (!vizNode || !entitiesContext) return; - const onAddNode = useCallback(async () => { - if (!props.vizNode || !entitiesContext) return; + /** Get compatible nodes and the location where can be introduced */ + const compatibleNodes = entitiesContext.camelResource.getCompatibleComponents(mode, vizNode.data); + + /** Open Catalog modal, filtering the compatible nodes */ + const definedComponent = await catalogModalContext?.getNewComponent(compatibleNodes); + if (!definedComponent) return; - /** Get compatible nodes and the location where can be introduced */ - const compatibleNodes = entitiesContext.camelResource.getCompatibleComponents(props.mode, props.vizNode.data); + /** Add new node to the entities */ + vizNode.addBaseEntityStep(definedComponent, mode); - /** Open Catalog modal, filtering the compatible nodes */ - const definedComponent = await catalogModalContext?.getNewComponent(compatibleNodes); - if (!definedComponent) return; + /** Update entity */ + entitiesContext.updateEntitiesFromCamelResource(); +}; - /** Add new node to the entities */ - props.vizNode.addBaseEntityStep(definedComponent, props.mode); +export const ItemAddStep: FunctionComponent = (props) => { + const entitiesContext = useContext(EntitiesContext); + const catalogModalContext = useContext(CatalogModalContext); - /** Update entity */ - entitiesContext.updateEntitiesFromCamelResource(); + const onAddNode = useCallback(async () => { + addNode(catalogModalContext, entitiesContext, props.vizNode, props.mode); }, [catalogModalContext, entitiesContext, props.mode, props.vizNode]); return ( diff --git a/packages/ui/src/components/Visualization/Custom/Edge/CustomEdge.scss b/packages/ui/src/components/Visualization/Custom/Edge/CustomEdge.scss new file mode 100644 index 000000000..606307a07 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/Edge/CustomEdge.scss @@ -0,0 +1,20 @@ +/* stylelint-disable */ + +.custom-edge { + --pf-topology__edge--m-hover--background--Stroke: none; + &__end { + background: var(--pf-global--BackgroundColor--light-100); + border-radius: 3px; + border: 1px solid var(--pf-v5-global--palette--black-400); + --pf-topology__node--Color: var(--pf-v5-global--palette--black-500); + width: 24px; + height: 24px; + padding: 3px; + display: flex; + position: relative; + + &:hover { + border-color: var(--pf-v5-global--palette--blue-500); + } + } +} diff --git a/packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx b/packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx new file mode 100644 index 000000000..1ab80c237 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/Edge/EdgeEnd.tsx @@ -0,0 +1,44 @@ +import { DefaultEdge, Edge, EdgeModel, EdgeTerminalType, observer } from '@patternfly/react-topology'; +import { Button, Tooltip } from '@patternfly/react-core'; +import { PlusIcon } from '@patternfly/react-icons'; +import './CustomEdge.scss'; +import { addNode } from '../ContextMenu/ItemAddStep'; +import { CatalogModalContext, EntitiesContext } from '../../../../providers'; +import { useContext } from 'react'; +import { IVisualizationNode } from '../../../../models'; + +interface EdgeEndProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + element: Edge; +} + +const EdgeEnd: React.FC = observer(({ element, ...rest }) => { + const entitiesContext = useContext(EntitiesContext); + const catalogModalContext = useContext(CatalogModalContext); + const vizNode: IVisualizationNode = element.getSource().getData()?.vizNode; + const endPoint = element.getEndPoint(); + const onAdd = () => { + addNode(catalogModalContext, entitiesContext, vizNode); + }; + return ( + + + + + + + + + + ); +}); + +export const EdgeEndWithButton: typeof DefaultEdge = EdgeEnd as typeof DefaultEdge; diff --git a/packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx b/packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx index 190fe2d2c..9460fe29b 100644 --- a/packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx +++ b/packages/ui/src/components/Visualization/Custom/NoBendingEdge.tsx @@ -1,7 +1,43 @@ -import { BaseEdge, Point } from '@patternfly/react-topology'; +import { BaseEdge, getTopCollapsedParent, Point } from '@patternfly/react-topology'; export class NoBendpointsEdge extends BaseEdge { getBendpoints(): Point[] { return []; } + getStartPoint(): Point { + if (this.getTarget() === this.getSource()) { + const parent = getTopCollapsedParent(this.getSource()); + const parentPos = parent.getPosition(); + const parentSize = parent.getDimensions(); + let x, y; + if (parent.getType() === 'group') { + x = parentPos.x + parentSize.width / 2.0; + y = parentPos.y; + } else { + x = parentPos.x + parentSize.width; + y = parentPos.y + parentSize.height / 2.0; + } + return new Point(x, y); + } else { + return super.getStartPoint(); + } + } + getEndPoint(): Point { + if (this.getTarget() === this.getSource()) { + const parent = getTopCollapsedParent(this.getSource()); + const parentPos = parent.getPosition(); + const parentSize = parent.getDimensions(); + let x, y; + if (parent.getType() === 'group') { + x = parentPos.x + parentSize.width / 2.0 + 15; + y = parentPos.y; + } else { + x = parentPos.x + parentSize.width / 2.0 + 55; + y = parentPos.y + parentSize.height / 2.0; + } + return new Point(x, y); + } else { + return super.getEndPoint(); + } + } } diff --git a/packages/ui/src/components/Visualization/Custom/index.ts b/packages/ui/src/components/Visualization/Custom/index.ts index c64861e62..3b8d52f3a 100644 --- a/packages/ui/src/components/Visualization/Custom/index.ts +++ b/packages/ui/src/components/Visualization/Custom/index.ts @@ -1,3 +1,4 @@ export * from './Group/CustomGroup'; export * from './NoBendingEdge'; export * from './Node/CustomNode'; +export * from './Edge/EdgeEnd';