diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx index 84bace39..f07457e3 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx @@ -12,11 +12,10 @@ import { EdgeCreationTypes, useHover, ScaleDetailsLevel, - RunStatus - // NodeLabel + RunStatus, + TaskGroupPillLabel } from '@patternfly/react-topology'; import { DEFAULT_TASK_HEIGHT, GROUP_TASK_WIDTH } from './createDemoPipelineGroupsNodes'; -import TaskGroupPillLabel from '@patternfly/react-topology/dist/esm/pipelines/components/groups/TaskGroupPillLabel'; type DemoTaskGroupProps = { element: GraphElement; diff --git a/packages/demo-app-ts/src/demos/pipelinesDemo/DemoPipelinesGroup.tsx b/packages/demo-app-ts/src/demos/pipelinesDemo/DemoPipelinesGroup.tsx index 8a2e48ea..da313b93 100644 --- a/packages/demo-app-ts/src/demos/pipelinesDemo/DemoPipelinesGroup.tsx +++ b/packages/demo-app-ts/src/demos/pipelinesDemo/DemoPipelinesGroup.tsx @@ -4,7 +4,6 @@ import { GraphElement, LabelPosition, observer, - ScaleDetailsLevel, WithContextMenuProps, WithDragNodeProps, WithSelectionProps @@ -18,14 +17,14 @@ type DemoPipelinesGroupProps = { const DemoPipelinesGroup: React.FunctionComponent = ({ element }) => { const data = element.getData(); - const detailsLevel = element.getGraph().getDetailsLevel(); return ( ); diff --git a/packages/module/src/components/nodes/labels/NodeLabel.tsx b/packages/module/src/components/nodes/labels/NodeLabel.tsx index 8491078c..9e8fd7e0 100644 --- a/packages/module/src/components/nodes/labels/NodeLabel.tsx +++ b/packages/module/src/components/nodes/labels/NodeLabel.tsx @@ -22,7 +22,6 @@ export type NodeLabelProps = { y?: number; position?: LabelPosition; centerLabelOnEdge?: boolean; - // isLabelPillShape?: boolean; boxRef?: React.Ref; cornerRadius?: number; status?: NodeStatus; @@ -60,7 +59,6 @@ const NodeLabel: React.FunctionComponent = ({ y = 0, position = LabelPosition.bottom, centerLabelOnEdge, - // isLabelPillShape, secondaryLabel, status, badge, @@ -204,25 +202,6 @@ const NodeLabel: React.FunctionComponent = ({ filterId = NODE_SHADOW_FILTER_ID_HOVER; } - // if (isLabelPillShape) { - // return ( - // - // - // {' '} - // - // ); - // } else { return ( @@ -338,6 +317,5 @@ const NodeLabel: React.FunctionComponent = ({ ); }; -// }; export default NodeLabel; diff --git a/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx b/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx index 3229ccf2..4a5b8cb6 100644 --- a/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx +++ b/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx @@ -67,8 +67,6 @@ export interface DefaultTaskGroupProps { GroupLabelComponent?: React.FC; /** Center the label on the edge, overrides the label offset, only applicable to expanded groups */ centerLabelOnEdge?: boolean; - /** Applies task node styling to the group label */ - isLabelPillShape?: boolean; /** The Icon class to show in the label, ignored when labelIcon is specified */ labelIconClass?: string; /** The label icon component to show in the label, takes precedence over labelIconClass */ @@ -131,7 +129,6 @@ type PipelinesDefaultGroupInnerProps = Omit & const DefaultTaskGroupInner: React.FunctionComponent = observer( ({ - className, element, badge, onCollapseChange, @@ -212,7 +209,6 @@ const DefaultTaskGroupInner: React.FunctionComponent ); } - return ( - // TODO: Support status indicators on expanded state. - - ); + return ; } ); diff --git a/packages/module/src/pipelines/components/groups/DefaultTaskGroupExpanded.tsx b/packages/module/src/pipelines/components/groups/DefaultTaskGroupExpanded.tsx index 1d6bb46e..0caf46bd 100644 --- a/packages/module/src/pipelines/components/groups/DefaultTaskGroupExpanded.tsx +++ b/packages/module/src/pipelines/components/groups/DefaultTaskGroupExpanded.tsx @@ -6,8 +6,8 @@ import CollapseIcon from '@patternfly/react-icons/dist/esm/icons/compress-alt-ic import NodeLabel from '../../../components/nodes/labels/NodeLabel'; import { Layer } from '../../../components/layers'; import { GROUPS_LAYER, TOP_LAYER } from '../../../const'; -import { maxPadding, useCombineRefs, useHover, useSize } from '../../../utils'; -import { AnchorEnd, isGraph, LabelPosition, Node, NodeStyle, ScaleDetailsLevel } from '../../../types'; +import { useCombineRefs, useHover, useSize } from '../../../utils'; +import { AnchorEnd, isGraph, LabelPosition, Node, ScaleDetailsLevel } from '../../../types'; import { useAnchor, useDragNode } from '../../../behavior'; import { DagreLayoutOptions, TOP_TO_BOTTOM } from '../../../layouts'; import TaskGroupSourceAnchor from '../anchors/TaskGroupSourceAnchor'; @@ -28,6 +28,7 @@ const DefaultTaskGroupExpanded: React.FunctionComponent(hoverRef, dragNodeRef); const isHover = hover !== undefined ? hover : hovered || labelHover; + const [labelSize, labelRef] = useSize([centerLabelOnEdge]); const verticalLayout = (element.getGraph().getLayoutOptions?.() as DagreLayoutOptions)?.rankdir === TOP_TO_BOTTOM; const groupLabelPosition = labelPosition ?? element.getLabelPosition() ?? LabelPosition.bottom; let parent = element.getParent(); const detailsLevel = element.getGraph().getDetailsLevel(); - const { width } = element.getBounds(); let altGroup = false; while (!isGraph(parent)) { @@ -110,23 +108,7 @@ const DefaultTaskGroupExpanded: React.FunctionComponent c.isVisible()); - - // cast to number and coerce - const padding = maxPadding(element.getStyle().padding ?? 17); - - const { minX, minY, maxX, maxY } = children.reduce( - (acc, child) => { - const bounds = child.getBounds(); - return { - minX: Math.min(acc.minX, bounds.x - padding), - minY: Math.min(acc.minY, bounds.y - padding), - maxX: Math.max(acc.maxX, bounds.x + bounds.width + padding), - maxY: Math.max(acc.maxY, bounds.y + bounds.height + padding) - }; - }, - { minX: Infinity, minY: Infinity, maxX: 0, maxY: 0 } - ); + const bounds = element.getBounds(); const [labelX, labelY] = React.useMemo(() => { if (!showLabel || !(label || element.getLabel())) { @@ -134,17 +116,29 @@ const DefaultTaskGroupExpanded: React.FunctionComponent c.isVisible()); if (children.length === 0) { return null; } @@ -175,13 +169,15 @@ const DefaultTaskGroupExpanded: React.FunctionComponent - : undefined} onActionIconClick={() => onCollapseChange(element, true)} > {label || element.getLabel()} - + ) : null; @@ -213,53 +209,15 @@ const DefaultTaskGroupExpanded: React.FunctionComponent {groupLabel && isHover ? {groupLabel} : groupLabel} - {showLabel && (label || element.getLabel()) && ( - - : undefined} - onActionIconClick={() => onCollapseChange(element, true)} - width={width} - taskRef={taskRef} - pillRef={pillRef as any} - > - {label || element.getLabel()} - - - )} ); } diff --git a/packages/module/src/pipelines/components/groups/TaskGroupPillLabel.tsx b/packages/module/src/pipelines/components/groups/TaskGroupPillLabel.tsx index 4c1845e4..b4558565 100644 --- a/packages/module/src/pipelines/components/groups/TaskGroupPillLabel.tsx +++ b/packages/module/src/pipelines/components/groups/TaskGroupPillLabel.tsx @@ -1,95 +1,108 @@ import * as React from 'react'; import { observer } from 'mobx-react'; -// import { NodeLabelProps } from '../../../components'; -import { TaskNodeProps } from '../nodes/TaskNode'; +import styles from '../../../css/topology-components'; import TaskPill, { TaskPillProps } from '../nodes/TaskPill'; -import { useSize } from '../../../utils/useSize'; import { NodeLabelProps } from '../../../components'; import { RunStatus } from '../../types'; -// import { useCombineRefs } from '../../../utils'; +import useCombineRefs from '../../../utils/useCombineRefs'; +import { useSize } from '../../../utils'; +import { LabelPosition, ScaleDetailsLevel } from '../../../types'; export type TaskGroupPillLabelProps = { shadowCount?: number; - expandable?: boolean; - status?: RunStatus; -} & Omit & - Omit & - NodeLabelProps & - TaskPillProps; - -// type PointWithSize = [number, number, number]; + runStatus?: RunStatus; + labelOffset?: number; +} & NodeLabelProps & + Omit; const TaskGroupPillLabel: React.FC = ({ element, - // children, - // className, - // paddingX = 0, - // paddingY = 0, - // cornerRadius = 4, - x = 0, - y = 0, - // position = LabelPosition.bottom, - // centerLabelOnEdge, - // secondaryLabel, - // status, + labelOffset = 17, badge, badgeColor, badgeTextColor, badgeBorderColor, badgeClassName, - // badgeLocation = BadgeLocation.inner, - // labelIconClass, - // labelIcon, - // labelIconPadding = 4, + runStatus, truncateLength, - width, - // dragRef, - // hover, - // dragging, - // edgeDragging, - // dropTarget, + boxRef, + position, + centerLabelOnEdge, onContextMenu, contextMenuOpen, actionIcon, - // actionIconClassName, onActionIconClick, - // everything that's in node label pass here? ...rest }) => { - const taskRef = React.useRef(); - const pillRef = useSize(); - // const labelLocation = React.useRef(); + const [labelSize, labelRef] = useSize([]); + const pillRef = useCombineRefs(boxRef, labelRef); + const labelWidth = labelSize?.width || 0; + const labelHeight = labelSize?.height || 0; + + const bounds = element.getBounds(); + + const detailsLevel = element.getGraph().getDetailsLevel(); + const scale = element.getGraph().getScale(); + const medScale = element.getGraph().getDetailsLevelThresholds().medium; + const labelScale = detailsLevel !== ScaleDetailsLevel.high ? Math.min(1 / scale, 1 / medScale) : 1; + const labelPositionScale = detailsLevel !== ScaleDetailsLevel.high ? 1 / labelScale : 1; - // const refs = useCombineRefs(hoverRef, dragNodeRef); + const { startX, startY } = React.useMemo(() => { + let startX: number; + let startY: number; + const scaledWidth = labelWidth / labelPositionScale; + const scaledHeight = labelHeight / labelPositionScale; + + if (position === LabelPosition.top) { + startX = bounds.x + bounds.width / 2 - scaledWidth / 2; + startY = bounds.y - (centerLabelOnEdge ? scaledHeight / 2 : labelOffset); + } else if (position === LabelPosition.right) { + startX = bounds.x + bounds.width + (centerLabelOnEdge ? -scaledWidth / 2 : labelOffset); + startY = bounds.y + bounds.height / 2; + } else if (position === LabelPosition.left) { + startX = bounds.x - (centerLabelOnEdge ? scaledWidth / 2 : scaledWidth + labelOffset); + startY = bounds.y + bounds.height / 2; + } else { + startX = bounds.x + bounds.width / 2 - scaledWidth / 2; + startY = bounds.y + bounds.height + (centerLabelOnEdge ? -scaledHeight / 2 : labelOffset); + } + return { startX, startY }; + }, [ + labelPositionScale, + position, + bounds.width, + bounds.x, + bounds.y, + bounds.height, + centerLabelOnEdge, + labelHeight, + labelOffset, + labelWidth + ]); return ( ); }; diff --git a/packages/module/src/pipelines/components/groups/index.ts b/packages/module/src/pipelines/components/groups/index.ts index 0784ce94..89a22d9e 100644 --- a/packages/module/src/pipelines/components/groups/index.ts +++ b/packages/module/src/pipelines/components/groups/index.ts @@ -2,4 +2,4 @@ export type { EdgeCreationTypes } from './DefaultTaskGroup'; export { default as DefaultTaskGroup } from './DefaultTaskGroup'; export { default as DefaultTaskGroupExpanded } from './DefaultTaskGroupExpanded'; export { default as DefaultTaskGroupCollapsed } from './DefaultTaskGroupCollapsed'; -export { default as GroupPillLabel } from './TaskGroupPillLabel'; +export { default as TaskGroupPillLabel } from './TaskGroupPillLabel'; diff --git a/packages/module/src/pipelines/components/nodes/TaskPill.tsx b/packages/module/src/pipelines/components/nodes/TaskPill.tsx index 3b1c4aea..49d9f108 100644 --- a/packages/module/src/pipelines/components/nodes/TaskPill.tsx +++ b/packages/module/src/pipelines/components/nodes/TaskPill.tsx @@ -1,11 +1,10 @@ import * as React from 'react'; -import { action } from 'mobx'; import { css } from '@patternfly/react-styles'; import styles from '../../../css/topology-pipelines'; import topologyStyles from '../../../css/topology-components'; import { Popover, Tooltip } from '@patternfly/react-core'; import { observer } from '../../../mobx-exports'; -import { Edge, Node, ScaleDetailsLevel } from '../../../types'; +import { Node, ScaleDetailsLevel } from '../../../types'; import { RunStatus } from '../../types'; import { truncateMiddle } from '../../../utils/truncate-middle'; import { createSvgIdUrl, useCombineRefs, useHover, useSize } from '../../../utils'; @@ -26,11 +25,11 @@ const STATUS_ICON_SIZE = 16; export interface TaskPillProps extends Omit { verticalLayout?: boolean; - width: number; + width?: number; x: number; y: number; - taskRef: React.Ref; - pillRef: React.Ref; + taskRef?: React.Ref; + pillRef: (node: SVGGraphicsElement) => void; element: Node; } @@ -40,6 +39,7 @@ const TaskPill: React.FC = observer( taskRef, pillRef, className, + width = 0, paddingX = 8, paddingY = 8, status, @@ -82,7 +82,6 @@ const TaskPill: React.FC = observer( const [hovered] = useHover(); const taskIconComponentRef = React.useRef(); const isHover = hover !== undefined ? hover : hovered; - const { width, height: boundsHeight } = element.getBounds(); const label = truncateMiddle(element.getLabel(), { length: truncateLength, omission: '...' }); const [textSize, textRef] = useSize([label, className]); const nameLabelTriggerRef = React.useRef(); @@ -204,28 +203,6 @@ const TaskPill: React.FC = observer( width ]); - React.useEffect(() => { - const sourceEdges = element.getSourceEdges(); - action(() => { - const indent = detailsLevel === ScaleDetailsLevel.high && !verticalLayout ? width - pillWidth : 0; - sourceEdges.forEach((edge: Edge) => { - const data = edge.getData(); - if ((data?.indent ?? 0) !== indent) { - edge.setData({ ...(data || {}), indent }); - } - }); - })(); - - return action(() => { - sourceEdges.forEach((edge: Edge) => { - const data = edge.getData(); - if (data?.indent) { - edge.setData({ ...(data || {}), indent: 0 }); - } - }); - }); - }, [detailsLevel, element, pillWidth, verticalLayout, width]); - const scale = element.getGraph().getScale(); const nameLabel = ( @@ -249,6 +226,16 @@ const TaskPill: React.FC = observer( onSelect && styles.modifiers.selectable ); + // Force an update of the given pillRef when dependencies change + const pillUpdatedRef = React.useCallback( + (node: SVGGraphicsElement): void => { + pillRef(node); + }, + // dependencies causing the pill rect to resize + // eslint-disable-next-line react-hooks/exhaustive-deps + [pillClasses, width, height] + ); + let filter: string; if (runStatusModifier === styles.modifiers.danger) { filter = createSvgIdUrl(NODE_SHADOW_FILTER_ID_DANGER); @@ -305,6 +292,7 @@ const TaskPill: React.FC = observer( if (showStatusState && !scaleNode && hideDetailsAtMedium && detailsLevel !== ScaleDetailsLevel.high) { const statusBackgroundRadius = statusIconSize / 2 + 4; const upScale = 1 / scale; + const { height: boundsHeight } = element.getBounds(); const translateX = verticalLayout ? width / 2 - statusBackgroundRadius * upScale : 0; const translateY = verticalLayout ? 0 : (boundsHeight - statusBackgroundRadius * 2 * upScale) / 2; @@ -368,7 +356,7 @@ const TaskPill: React.FC = observer( x={offsetX} y={0} width={pillWidth} - ref={pillRef} + ref={pillUpdatedRef} height={height} rx={height / 2} className={css(styles.topologyPipelinesPillBackground)} diff --git a/packages/module/src/pipelines/components/nodes/index.ts b/packages/module/src/pipelines/components/nodes/index.ts index 3fdcc26c..4d2496bf 100644 --- a/packages/module/src/pipelines/components/nodes/index.ts +++ b/packages/module/src/pipelines/components/nodes/index.ts @@ -2,4 +2,5 @@ export { default as FinallyNode } from './FinallyNode'; export { default as SpacerNode } from './SpacerNode'; export { default as StatusIcon } from '../../utils/StatusIcon'; export { default as TaskNode } from './TaskNode'; +export { default as TaskPill } from './TaskPill'; export { default as WhenNode } from '../../decorators/WhenDecorator';