From 08512a1a23c49d9a161dce0d2b46c4bd7125a739 Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Date: Wed, 8 May 2024 12:33:54 -0400 Subject: [PATCH] feat(pipelines): Add ability and styling for selected task edges --- .../demos/pipelineGroupsDemo/DemoTaskEdge.tsx | 5 +-- .../pipelineGroupsDemo/PipelineGroupsDemo.tsx | 21 ++++++++++++ .../pipelineGroupsComponentFactory.tsx | 4 +-- .../module/src/css/topology-components.css | 34 ++++++++++++++----- .../pipelines/components/edges/TaskEdge.tsx | 30 ++++++++++++++-- 5 files changed, 78 insertions(+), 16 deletions(-) diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskEdge.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskEdge.tsx index 7a6928c8..f42b5a82 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskEdge.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskEdge.tsx @@ -5,10 +5,11 @@ import { Edge, EdgeTerminalType, GraphElement, - TaskEdge + TaskEdge, + WithSelectionProps } from '@patternfly/react-topology'; -interface DemoTaskEdgeProps { +interface DemoTaskEdgeProps extends WithSelectionProps { element: GraphElement; } diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemo.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemo.tsx index 4b6e392f..a6fa611c 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemo.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemo.tsx @@ -21,6 +21,8 @@ import { useVisualizationController, addSpacerNodes, pipelineElementFactory, + isEdge, + Edge, } from '@patternfly/react-topology'; import pipelineGroupsComponentFactory from './pipelineGroupsComponentFactory'; import { @@ -37,6 +39,25 @@ const TopologyPipelineGroups: React.FC<{ nodes: PipelineNodeModel[] }> = observe const [selectedIds, setSelectedIds] = React.useState(); useEventListener(SELECTION_EVENT, ids => { + if (ids?.[0]) { + const element = controller?.getElementById(ids[0]); + if (element && isEdge(element)) { + const edge = element as Edge; + const selectedEdges = [edge.getId()]; + const source = edge.getSource(); + const target = edge.getTarget(); + if (source.getType() === DEFAULT_SPACER_NODE_TYPE) { + const sourceEdges = source.getTargetEdges(); + selectedEdges.push(...sourceEdges.map((e) => e.getId())); + } + if (target.getType() === DEFAULT_SPACER_NODE_TYPE) { + const targetEdges = target.getSourceEdges(); + selectedEdges.push(...targetEdges.map((e) => e.getId())); + } + setSelectedIds(selectedEdges); + return; + } + } setSelectedIds(ids); }); diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx index c368731d..aba7e1de 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx @@ -18,7 +18,7 @@ const pipelineGroupsComponentFactory: ComponentFactory = ( type: string ): React.ComponentType<{ element: GraphElement }> | undefined => { if (kind === ModelKind.graph) { - return withPanZoom()(GraphComponent); + return withPanZoom()(withSelection()(GraphComponent)); } switch (type) { case 'Execution': @@ -32,7 +32,7 @@ const pipelineGroupsComponentFactory: ComponentFactory = ( return SpacerNode; case 'edge': // draw arrow terminal when isDependency is set on data - return DemoTaskEdge; + return withSelection()(DemoTaskEdge); default: return undefined; } diff --git a/packages/module/src/css/topology-components.css b/packages/module/src/css/topology-components.css index bdda4f56..81d8cdf4 100644 --- a/packages/module/src/css/topology-components.css +++ b/packages/module/src/css/topology-components.css @@ -622,7 +622,7 @@ --edge--stroke: var(--pf-topology__edge--Stroke); --edge--fill: var(--edge--stroke); --edge--opacity: 1; - --edge--cursor: pointer; + --edge--cursor: default; --edge--hover--stroke: var(--pf-topology__edge--HoverStroke); --edge--hover--fill: var(--edge--hover--stroke); --edge--active--stroke: var(--pf-topology__edge--ActiveStroke); @@ -635,13 +635,15 @@ cursor: var(--edge--cursor); stroke: var(--edge--stroke); - fill: var(--edge--fill); + fill: none; opacity: var(--edge--opacity); } +.pf-topology__edge.pf-m-selectable { + --edge--cursor: pointer; +} .pf-topology__edge.pf-m-info { --edge--stroke: var(--pf-topology__edge--m-info--EdgeStroke); - --edge--fill: var(--pf-v5-global--primary-color--light-100); } .pf-topology__edge.pf-m-success { @@ -662,6 +664,10 @@ stroke-width: 10px; stroke: transparent; fill: none; + cursor: var(--edge--cursor); +} +.pf-topology__edge__background.pf-m-selectable { + cursor: pointer; } .pf-topology__edge.pf-m-selected .pf-topology__edge__background { stroke: var(--pf-topology__edge--m-selected--background--Stroke); @@ -673,9 +679,10 @@ .pf-topology__edge__link { stroke-width: var(--edge--stroke-width); stroke-dasharray: var(--edge--stroke-dasharray); - fill-opacity: 0; animation: pf-topology__edge__dash 0s linear infinite forwards; - } + --edge--cursor: pointer; + fill: none; +} .pf-topology__edge__link.pf-m-dotted { stroke-dasharray: 2; @@ -706,31 +713,39 @@ .pf-topology__edge.pf-m-selected .pf-topology__edge__link, .pf-topology__edge.pf-m-selected .pf-topology-connector-arrow { - fill: var(--edge--active--fill); stroke: var(--edge--active--stroke); stroke-width: var(--edge--active--stroke-width); } +.pf-topology__edge.pf-m-selected .pf-topology-connector-arrow { + fill: var(--edge--active--fill); +} .pf-topology__edge.pf-m-selected.pf-m-hover .pf-topology__edge__link, .pf-topology__edge.pf-m-selected.pf-m-hover .pf-topology-connector-arrow { - fill: var(--edge--active--fill); stroke: var(--edge--active--stroke); } +.pf-topology__edge.pf-m-selected.pf-m-hover .pf-topology-connector-arrow { + fill: var(--edge--active--fill); +} .pf-topology__edge.pf-m-dragging { pointer-events: none; } .pf-topology__edge.pf-m-hover .pf-topology__edge__link, .pf-topology__edge.pf-m-hover .pf-topology-connector-arrow { - fill: var(--edge--hover--fill); stroke: var(--edge--hover--stroke); } +.pf-topology__edge.pf-m-hover .pf-topology-connector-arrow { + fill: var(--edge--hover--fill); +} .pf-topology__edge.pf-m-dragging .pf-topology__edge__link, .pf-topology__edge.pf-m-dragging .pf-topology-connector-arrow { - fill: var(--edge--interactive--fill); stroke: var(--edge--interactive--stroke); } +.pf-topology__edge.pf-m-dragging .pf-topology-connector-arrow { + fill: var(--edge--interactive--fill); +} .pf-topology__edge .pf-topology-connector-arrow { cursor: var(--edge__arrow--cursor); @@ -779,6 +794,7 @@ .pf-topology-connector-arrow { stroke-width: 1; stroke: var(--edge--stroke); + fill: var(--edge--fill) } .pf-topology-connector-arrow.pf-m-alt-connector-arrow { diff --git a/packages/module/src/pipelines/components/edges/TaskEdge.tsx b/packages/module/src/pipelines/components/edges/TaskEdge.tsx index c6d1726b..f14ef6c8 100644 --- a/packages/module/src/pipelines/components/edges/TaskEdge.tsx +++ b/packages/module/src/pipelines/components/edges/TaskEdge.tsx @@ -6,6 +6,9 @@ import { Edge, EdgeTerminalType, GraphElement, NodeStatus, isEdge } from '../../ import { integralShapePath } from '../../utils'; import { DagreLayoutOptions, TOP_TO_BOTTOM } from '../../../layouts'; import { DefaultConnectorTerminal } from '../../../components'; +import { OnSelect } from '../../../behavior'; +import Layer from '../../../components/layers/Layer'; +import { TOP_LAYER } from '../../../const'; interface TaskEdgeProps { /** The graph edge element to represent */ @@ -30,6 +33,10 @@ interface TaskEdgeProps { endTerminalStatus?: NodeStatus; /** The size of the end terminal */ endTerminalSize?: number; + /** Flag if the element is selected. Part of WithSelectionProps */ + selected?: boolean; + /** Function to call when the element should become selected (or deselected). Part of WithSelectionProps */ + onSelect?: OnSelect; } type TaskEdgeInnerProps = Omit & { element: Edge }; @@ -46,18 +53,35 @@ const TaskEdgeInner: React.FunctionComponent = observer( endTerminalStatus, endTerminalSize = 14, className, - nodeSeparation + nodeSeparation, + selected, + onSelect, }) => { const startPoint = element.getStartPoint(); const endPoint = element.getEndPoint(); - const groupClassName = css(styles.topologyEdge, className); + const groupClassName = css( + styles.topologyEdge, + className, + selected && 'pf-m-selected', + onSelect && 'pf-m-selectable', + ); const startIndent: number = element.getData()?.indent || 0; const verticalLayout = (element.getGraph().getLayoutOptions?.() as DagreLayoutOptions)?.rankdir === TOP_TO_BOTTOM; + const edgePath = integralShapePath(startPoint, endPoint, startIndent, nodeSeparation, verticalLayout); + const edgeBackground = ( + + ); + return ( + {selected ? edgeBackground : {edgeBackground}}