From e5fb53cfcc2ea5c4923520ad5ddcfc75405a7e98 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:35:50 -0500 Subject: [PATCH 01/13] add collapsible and expandable logic, add new classnames --- .../pipelineComponentFactory.tsx | 3 +- .../pipelinesDemo/useDemoPipelineNodes.tsx | 16 +- .../src/demos/stylesDemo/StyleGroup.tsx | 18 +- .../nodes/labels/LabelActionIcon.tsx | 5 +- .../nodes/labels/PipelinesNodeLabel.tsx | 303 ++++++++++++++++++ .../src/components/nodes/labels/index.ts | 2 + .../module/src/css/topology-components.css | 92 ++++++ .../groups/PipelinesDefaultGroup.tsx | 143 +++++++++ .../groups/PipelinesDefaultGroupCollapsed.tsx | 172 ++++++++++ .../groups/PipelinesDefaultGroupExpanded.tsx | 277 ++++++++++++++++ .../src/pipelines/components/groups/index.ts | 3 + 11 files changed, 1018 insertions(+), 16 deletions(-) create mode 100644 packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx create mode 100644 packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx create mode 100644 packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx create mode 100644 packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx diff --git a/packages/demo-app-ts/src/demos/pipelinesDemo/pipelineComponentFactory.tsx b/packages/demo-app-ts/src/demos/pipelinesDemo/pipelineComponentFactory.tsx index a33ef892..13abf598 100644 --- a/packages/demo-app-ts/src/demos/pipelinesDemo/pipelineComponentFactory.tsx +++ b/packages/demo-app-ts/src/demos/pipelinesDemo/pipelineComponentFactory.tsx @@ -20,6 +20,7 @@ import { import DemoTaskNode from './DemoTaskNode'; import DemoFinallyNode from './DemoFinallyNode'; import DemoTaskGroupEdge from './DemoTaskGroupEdge'; +import StyleGroup from '../stylesDemo/StyleGroup'; export const GROUPED_EDGE_TYPE = 'GROUPED_EDGE'; @@ -52,7 +53,7 @@ const pipelineComponentFactory: ComponentFactory = ( case DEFAULT_FINALLY_NODE_TYPE: return withContextMenu(() => defaultMenu)(withSelection()(DemoFinallyNode)); case 'task-group': - return DefaultTaskGroup; + return withSelection()(StyleGroup); case 'finally-group': return DefaultTaskGroup; case DEFAULT_SPACER_NODE_TYPE: diff --git a/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx b/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx index ef18f00a..7cabf524 100644 --- a/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx +++ b/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx @@ -149,7 +149,14 @@ export const useDemoPipelineNodes = ( type: 'task-group', children: parallelTasks.map(t => t.id), group: true, - label: 'Parallel tasks' + label: 'Parallel tasks', + data: { + selected: true, + badge: 'Label', + collapsedWidth: 75, + collapsedHeight: 42, + collapsible: true, + }, }); } } @@ -190,7 +197,12 @@ export const useDemoPipelineNodes = ( type: 'task-group', children: [], group: true, - label: `Group ${task.data.columnGroup}` + label: `Group ${task.data.columnGroup}`, + data: { + collapsedWidth: 75, + collapsedHeight: 42, + collapsible: true, + }, }; acc.push(taskGroup); } diff --git a/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx b/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx index 44bc9be0..d1b7ee51 100644 --- a/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx +++ b/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { - DefaultGroup, GraphElement, Node, observer, @@ -8,7 +7,8 @@ import { ShapeProps, WithContextMenuProps, WithDragNodeProps, - WithSelectionProps + WithSelectionProps, + PipelinesDefaultGroup, } from '@patternfly/react-topology'; import AlternateIcon from '@patternfly/react-icons/dist/esm/icons/regions-icon'; import DefaultIcon from '@patternfly/react-icons/dist/esm/icons/builder-image-icon'; @@ -34,10 +34,8 @@ type StyleGroupProps = { const StyleGroup: React.FunctionComponent = ({ element, - onContextMenu, - contextMenuOpen, collapsedWidth = 75, - collapsedHeight = 75, + collapsedHeight = 60, ...rest }) => { const groupElement = element as Node; @@ -55,11 +53,11 @@ const StyleGroup: React.FunctionComponent = ({ const renderIcon = (): React.ReactNode => { const iconSize = Math.min(collapsedWidth, collapsedHeight) - ICON_PADDING * 2; - const Component = getTypeIcon(data.dataType); +const label = element.getLabel(); return ( - + {label} ); }; @@ -75,9 +73,7 @@ const StyleGroup: React.FunctionComponent = ({ }, [data]); return ( - = ({ {...passedData} > {groupElement.isCollapsed() ? renderIcon() : null} - + ); }; diff --git a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx index 44610754..1e0c671c 100644 --- a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx +++ b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx @@ -6,6 +6,7 @@ import styles from '../../../css/topology-components'; interface LabelActionIconProps { className?: string; icon: React.ReactElement; + isIconExternal?: boolean; onClick: (e: React.MouseEvent) => void; iconOffsetX?: number; iconOffsetY?: number; @@ -17,7 +18,7 @@ interface LabelActionIconProps { } const LabelActionIcon = React.forwardRef( - ({ icon, onClick, className, x, y, paddingX, height, iconOffsetX = 0, iconOffsetY = 0 }, actionRef) => { + ({ icon, isIconExternal, onClick, className, x, y, paddingX, height, iconOffsetX = 0, iconOffsetY = 0 }, actionRef) => { const [iconSize, iconRef] = useSize([icon, paddingX]); const iconWidth = iconSize?.width ?? 0; const iconHeight = iconSize?.height ?? 0; @@ -37,7 +38,7 @@ const LabelActionIcon = React.forwardRef( {iconSize && ( void; + badge?: string; + badgeColor?: string; + badgeTextColor?: string; + badgeBorderColor?: string; + badgeClassName?: string; + badgeLocation?: BadgeLocation; +} & Partial; + +/** + * Renders a `` component with a `` box behind. + */ +const PipelinesNodeLabel: React.FunctionComponent = ({ + children, + className, + paddingX = 0, + paddingY = 0, + cornerRadius = 22, + x = 0, + y = 0, + position = LabelPosition.bottom, + secondaryLabel, + status, + badge, + badgeColor, + badgeTextColor, + badgeBorderColor, + badgeClassName, + badgeLocation = BadgeLocation.inner, + isExpanded = false, + labelIconClass, + labelIcon, + labelIconPadding = 4, + truncateLength, + dragRef, + hover, + dragging, + edgeDragging, + dropTarget, + onContextMenu, + contextMenuOpen, + actionIcon, + actionIconClassName, + onActionIconClick, + ...other +}) => { + const [labelHover, labelHoverRef] = useHover(); + const [expandIconHovered, setExpandIconHovered] = React.useState(false); + + const refs = useCombineRefs( + dragRef as React.Ref, + (typeof truncateLength === 'number' ? labelHoverRef : undefined) as React.Ref, + ); + + const [textSize, textRef] = useSize([children, truncateLength, className, labelHover]); + const [secondaryTextSize, secondaryTextRef] = useSize([ + secondaryLabel, + truncateLength, + className, + labelHover, + ]); + const [badgeSize, badgeRef] = useSize([badge]); + const [actionSize, actionRef] = useSize([actionIcon, paddingX]); + const [contextSize, contextRef] = useSize([onContextMenu, paddingX]); + + const onMouseEnter = (e) => { + console.log('mouse enetered'); + setExpandIconHovered(true); + }; + + const onMouseLeave = (e) => { + console.log('mouse letft'); + setExpandIconHovered(false); + }; + + const { + width, + height, + backgroundHeight, + startX, + startY, + badgeStartX, + badgeStartY, + actionStartX, + contextStartX, + iconSpace, + badgeSpace, + } = React.useMemo(() => { + if (!textSize) { + return { + width: 0, + height: 0, + backgroundHeight: 0, + startX: 0, + startY: 0, + badgeStartX: 0, + badgeStartY: 0, + actionStartX: 0, + contextStartX: 0, + iconSpace: 0, + badgeSpace: 0, + }; + } + const badgeSpace = badge && badgeSize && badgeLocation === BadgeLocation.inner ? badgeSize.width + paddingX : 0; + const height = Math.max(textSize.height, badgeSize?.height ?? 0) + paddingY * 2; + const iconSpace = labelIconClass || labelIcon ? (height + paddingY * 0.5) / 2 : 0; + const actionSpace = actionIcon && actionSize ? actionSize.width : 0; + const contextSpace = onContextMenu && contextSize ? contextSize.width : 0; + const primaryWidth = iconSpace + badgeSpace + paddingX + textSize.width + actionSpace + contextSpace + paddingX; + const secondaryWidth = secondaryLabel && secondaryTextSize ? secondaryTextSize.width + 2 * paddingX : 0; + const width = Math.max(primaryWidth, secondaryWidth); + + let startX: number; + let startY: number; + if (position === LabelPosition.top) { + startX = x - width / 2; + startY = -y - height - paddingY; + } else if (position === LabelPosition.right) { + startX = x + iconSpace; + startY = y - height / 2; + } else if (position === LabelPosition.left) { + startX = - width - paddingX; + startY = y - height / 2 + paddingY; + } else { + startX = x - width / 2 + iconSpace / 2; + startY = y; + } + const actionStartX = iconSpace + badgeSpace + paddingX + textSize.width + paddingX; + const contextStartX = actionStartX + actionSpace; + const backgroundHeight = + height + (secondaryLabel && secondaryTextSize ? secondaryTextSize.height + paddingY * 2 : 0); + let badgeStartX = 0; + let badgeStartY = 0; + if (badgeSize) { + if (badgeLocation === BadgeLocation.below) { + badgeStartX = (width - badgeSize.width) / 2; + badgeStartY = height + paddingY; + } else { + badgeStartX = iconSpace + paddingX; + badgeStartY = (height - badgeSize.height) / 2; + } + } + + return { + width, + height, + backgroundHeight, + startX, + startY, + actionStartX, + contextStartX, + badgeStartX, + badgeStartY, + iconSpace, + badgeSpace: badgeSize && badgeLocation === BadgeLocation.inner ? badgeSpace : 0, + }; + }, [ + textSize, + badge, + badgeSize, + badgeLocation, + paddingX, + paddingY, + labelIconClass, + labelIcon, + actionIcon, + actionSize, + onContextMenu, + contextSize, + secondaryLabel, + secondaryTextSize, + position, + x, + y, + ]); + + let filterId; + if (status === 'danger') { + filterId = NODE_SHADOW_FILTER_ID_DANGER; + } else if (hover || dragging || edgeDragging || dropTarget) { + filterId = NODE_SHADOW_FILTER_ID_HOVER; + } + + return ( + <> + + + {textSize && ( + <> + + + )} + {textSize && (labelIconClass || labelIcon) && ( + + )} + + {truncateLength > 0 && !labelHover + ? truncateMiddle(children, { length: truncateLength }) + : children} + + {textSize && badge && ( + + )} + + setExpandIconHovered(true)} + onMouseLeave={() => setExpandIconHovered(false)} + > + + + + ); +}; + +export default PipelinesNodeLabel; diff --git a/packages/module/src/components/nodes/labels/index.ts b/packages/module/src/components/nodes/labels/index.ts index 11dcc59c..e16cc915 100644 --- a/packages/module/src/components/nodes/labels/index.ts +++ b/packages/module/src/components/nodes/labels/index.ts @@ -3,3 +3,5 @@ export { default as LabelBadge } from './LabelBadge'; export { default as LabelContextMenu } from './LabelContextMenu'; export { default as LabelIcon } from './LabelIcon'; export { default as NodeLabel } from './NodeLabel'; +export { default as PipelinesNodeLabel } from './PipelinesNodeLabel'; + diff --git a/packages/module/src/css/topology-components.css b/packages/module/src/css/topology-components.css index bdda4f56..1421b824 100644 --- a/packages/module/src/css/topology-components.css +++ b/packages/module/src/css/topology-components.css @@ -845,3 +845,95 @@ fill: var(--pf-topology__create-connector-color--Fill); } +.pf-topology__node__label__background.pf-m-disabled { + --pf-topology__node__label__background--Fill: var(--pf-topology__node--m-disabled--Background--Fill); + --pf-topology__node__label__background--Stroke: var(--pf-topology__node--m-disabled--Background--Stroke); +} + +.pf-topology__group__action__label { + fill: #fff; + stroke-width: 1; + stroke: var(--pf-v5-global--active-color--100) +} + +.pf-topology__group.pf-m-selected.pf-m-danger .pf-topology__node__action-icon__icon { + color: white; +} + +.pf-topology__group__label rect.pf-topology__node__label__background { + fill: white; +} + +.pf-topology__group__label>text { + fill: black; + font-size: var(--pf-v5-global--FontSize--sm); + pointer-events: none; +} + +.pf-topology__group.pf-m-selected .pf-topology__group__label>text { + fill: white; + font-size: var(--pf-v5-global--FontSize--sm); + pointer-events: none; +} + +.action-icon-collapsed { + fill: white; + stroke: var(--pf-v5-global--active-color--100); + pointer-events: all; +} + +.action-icon-expanded { + fill: var(--pf-v5-global--active-color--100); +} + +g.pf-topology__node__action-icon.action-icon-collapsed .pf-topology__node__action-icon__icon { + color: var(--pf-v5-global--active-color--100); +} + +g.pf-topology__node__action-icon.action-icon-expanded .pf-topology__node__action-icon__icon { + color: white; +} + +svg.pf-v5-svg.list-lead-icon { + fill: var(--pf-v5-global--icon--Color--light); +} + +svg.pf-v5-svg.monitoring-lead-icon { + fill: var(--pf-v5-global--icon--Color--light); +} + +g.pf-topology-pipelines__pill.pf-m-danger.pf-m-selected.pf-m-selectable svg.pf-v5-svg.list-lead-icon { + fill: var(--pf-v5-global--icon--Color--dark--light); +} + +/* g.pf-topology-pipelines__pill.pf-m-selected.pf-m-selectable svg.pf-v5-svg.monitoring-lead-icon { + fill: var(--pf-v5-global--icon--Color--dark--light); +} */ + +.pf-topology-pipelines__pill.pf-m-selected:not(.pf-m-danger):not(.pf-m-success):not(.pf-m-warning):not(.pf-m-skipped):not(.pf-m-in-progress) .pf-topology-pipelines__pill-background { + fill: white; + stroke: var(--pf-v5-global--active-color--100); + stroke-width: 3px; +} + +.pf-topology-pipelines__pill.pf-m-selected:not(.pf-m-danger):not(.pf-m-success):not(.pf-m-warning):not(.pf-m-skipped):not(.pf-m-in-progress) .pf-topology-pipelines__pill-text { + fill: black; +} + +.pf-topology-pipelines__pill-text .pf-topology-pipelines__pill>.pf-topology-pipelines__pill-background { + stroke: none; +} + +.pf-topology-pipelines__pill .pf-m-danger .pf-m-selectable>.pf-v5-svg .list-lead-icon { + fill: var(--pf-topology-pipelines__pill-background--m-danger--Fill) +} + +.pf-topology__group__border:hover { + stroke: var(--pf-v5-global--active-color--100); +} + +.pf-topology__node__pipelines-action-icon__background { + fill: white; + stroke-width: 1; + color: var(--pf-v5-global--active-color--100); +} diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx new file mode 100644 index 00000000..9a53bd8f --- /dev/null +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx @@ -0,0 +1,143 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; + +import PipelinesDefaultGroupExpanded from './PipelinesDefaultGroupExpanded'; +import { OnSelect } from '@patternfly/react-table'; +import { + GraphElement, + LabelPosition, + BadgeLocation, + ShapeProps, + WithDndDragProps, + ConnectDragSource, + ConnectDropTarget, + Node, +} from '@patternfly/react-topology'; +import { isNode } from 'yaml'; +import PipelinesDefaultGroupCollapsed from "./PipelinesDefaultGroupCollapsed"; +import MyRectComponent from "./MyRectComponent"; + +interface PipelinesDefaultGroupProps { + /** Additional classes added to the group */ + className?: string; + /** The graph group node element to represent */ + element: GraphElement; + /** Flag if the node accepts drop operations */ + droppable?: boolean; + /** Flag if the current drag operation can be dropped on the node */ + canDrop?: boolean; + /** Flag if the node is the current drop target */ + dropTarget?: boolean; + /** Flag if the user is dragging the node */ + dragging?: boolean; + /** Flag if drag operation is a regroup operation */ + dragRegroupable?: boolean; + /** Flag if the user is hovering on the node */ + hover?: boolean; + /** Label for the node. Defaults to element.getLabel() */ + label?: string; // Defaults to element.getLabel() + /** Secondary label for the node */ + secondaryLabel?: string; + /** Flag to show the label */ + showLabel?: boolean; // Defaults to true + /** Position of the label, bottom or left. Defaults to element.getLabelPosition() or bottom */ + labelPosition?: LabelPosition; + /** The maximum length of the label before truncation */ + truncateLength?: number; + /** 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 */ + labelIcon?: string; + /** Padding for the label's icon */ + labelIconPadding?: number; + /** Text for the label's badge */ + badge?: string; + /** Color to use for the label's badge background */ + badgeColor?: string; + /** Color to use for the label's badge text */ + badgeTextColor?: string; + /** Color to use for the label's badge border */ + badgeBorderColor?: string; + /** Additional classes to use for the label's badge */ + badgeClassName?: string; + /** Location of the badge relative to the label's text, inner or below */ + badgeLocation?: BadgeLocation; + /** Flag if the group is collapsible */ + collapsible?: boolean; + /** Width of the collapsed group */ + collapsedWidth?: number; + /** Height of the collapsed group */ + collapsedHeight?: number; + /** Callback when the group is collapsed */ + onCollapseChange?: (group: Node, collapsed: boolean) => void; + /** Shape of the collapsed group */ + getCollapsedShape?: (node: Node) => React.FunctionComponent; + /** Shadow offset for the collapsed group */ + collapsedShadowOffset?: number; + /** Flag if the element selected. Part of WithSelectionProps */ + selected?: boolean; + /** Function to call when the element should become selected (or deselected). Part of WithSelectionProps */ + onSelect?: OnSelect; + /** A ref to add to the node for dragging. Part of WithDragNodeProps */ + dragNodeRef?: WithDndDragProps['dndDragRef']; + /** A ref to add to the node for drag and drop. Part of WithDndDragProps */ + dndDragRef?: ConnectDragSource; + /** A ref to add to the node for dropping. Part of WithDndDropProps */ + dndDropRef?: ConnectDropTarget; + /** Function to call to show a context menu for the node */ + onContextMenu?: (e: React.MouseEvent) => void; + /** Flag indicating that the context menu for the node is currently open */ + contextMenuOpen?: boolean; + /** Flag indicating whether to use hull layout or rect layout for expanded groups. Defaults to hull (true) */ + hulledOutline?: boolean; +} + +type DefaultGroupInnerProps = Omit & { element: Node }; + +const DefaultGroupInner: React.FunctionComponent = observer( + ({ className, element, onCollapseChange, ...rest }) => { + const handleCollapse = (group: Node, collapsed: boolean): void => { + if (collapsed && rest.collapsedWidth !== undefined && rest.collapsedHeight !== undefined) { + group.setBounds(group.getBounds().setSize(rest.collapsedWidth, rest.collapsedHeight)); + } + group.setCollapsed(collapsed); + onCollapseChange && onCollapseChange(group, collapsed); + }; + + const childCount = element.getAllNodeChildren().length; + + if (element.isCollapsed()) { + return ( + + + ); + } + return ( + + ); + }, +); + +const PipelinesDefaultGroup: React.FunctionComponent = ({ + element, + ...rest +}: PipelinesDefaultGroupProps) => { + // if (!isNode(element)) { + // throw new Error('DefaultGroup must be used only on Node elements'); + // } + + return ; +}; + +export default PipelinesDefaultGroup; diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx new file mode 100644 index 00000000..6a946f80 --- /dev/null +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx @@ -0,0 +1,172 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-topology/src/css/topology-components'; +import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-alt-icon'; +import { WithDragNodeProps, WithSelectionProps, WithDndDropProps, WithContextMenuProps, useDragNode } from "../../../behavior"; +import { CollapsibleGroupProps, Stadium, Layer, LabelBadge, PipelinesNodeLabel, NodeLabel } from "../../../components"; +import { NODE_SHADOW_FILTER_ID_HOVER } from "../../../components/nodes/NodeShadows"; +import { GROUPS_LAYER } from "../../../const"; +import { LabelPosition, BadgeLocation } from "../../../types"; +import { useHover, useSize, useCombineRefs, createSvgIdUrl } from "../../../utils"; + +type PipelinesDefaultGroupCollapsedProps = { + children?: React.ReactNode; + className?: string; + element: Node; + droppable?: boolean; + canDrop?: boolean; + dropTarget?: boolean; + dragging?: boolean; + hover?: boolean; + label?: string; // Defaults to element.getLabel() + secondaryLabel?: string; + showLabel?: boolean; // Defaults to true + labelPosition?: LabelPosition; // Defaults to bottom + truncateLength?: number; // Defaults to 13 + labelIconClass?: string; // Icon to show in label + labelIcon?: string; + labelIconPadding?: number; + badge?: string; + badgeColor?: string; + badgeTextColor?: string; + badgeBorderColor?: string; + badgeClassName?: string; + badgeLocation?: BadgeLocation; +} & CollapsibleGroupProps & WithDragNodeProps & WithSelectionProps & WithDndDropProps & WithContextMenuProps; + +const PipelinesDefaultGroupCollapsed: React.FunctionComponent = ({ + className, + element, + collapsible, + selected, + onSelect, + children, + hover, + label, + secondaryLabel, + showLabel = true, + truncateLength, + collapsedWidth, + collapsedHeight, + getCollapsedShape, + onCollapseChange, + collapsedShadowOffset = 8, + dndDropRef, + dragNodeRef, + canDrop, + dropTarget, + onContextMenu, + contextMenuOpen, + dragging, + labelPosition, + badge, + badgeColor, + badgeTextColor, + badgeBorderColor, + badgeClassName, + badgeLocation, + labelIconClass, + labelIcon, + labelIconPadding +}) => { + const [hovered, hoverRef] = useHover(); + const [labelHover, labelHoverRef] = useHover(); + const dragLabelRef = useDragNode()[1]; + const [shapeSize, shapeRef] = useSize([collapsedWidth, collapsedHeight]); + const refs = useCombineRefs(hoverRef, dragNodeRef, shapeRef); + const isHover = hover !== undefined ? hover : hovered; + const childCount = element.getAllNodeChildren().length; + const [badgeSize, badgeRef] = useSize([childCount]); + + const groupClassName = css( + styles.topologyGroup, + className, + canDrop && 'pf-m-highlight', + canDrop && dropTarget && 'pf-m-drop-target', + dragging && 'pf-m-dragging', + selected && 'pf-m-selected' + ); + + const filter = isHover || dragging || dropTarget ? createSvgIdUrl(NODE_SHADOW_FILTER_ID_HOVER) : undefined; + + return ( + + + + <> + + + + + + + + + + + {shapeSize && childCount && ( + + )} + {showLabel && ( + : undefined} + onActionIconClick={() => onCollapseChange(element, false)} + > + {label || element.getLabel()} + + )} + {children} + + ); +}; + +export default observer(PipelinesDefaultGroupCollapsed); diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx new file mode 100644 index 00000000..1744fbf1 --- /dev/null +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx @@ -0,0 +1,277 @@ +import * as React from 'react'; +import { observer } from 'mobx-react'; +import { polygonHull } from 'd3-polygon'; +import * as _ from 'lodash'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-topology/src/css/topology-components'; +import CollapseIcon from '@patternfly/react-icons/dist/esm/icons/compress-alt-icon'; +import { + BadgeLocation, + Layer, + Node, + NodeLabel, + useDragNode, + useSvgAnchor, + WithContextMenuProps, + WithDndDropProps, + WithDragNodeProps, + WithSelectionProps, + CollapsibleGroupProps, + hullPath, + maxPadding, + useCombineRefs, + useHover, + Rect, + GROUPS_LAYER, + TOP_LAYER, + NodeShape, + NodeStyle, + PointTuple, + isGraph, +} from '@patternfly/react-topology'; +import { PipelinesNodeLabel } from "../../../components"; +// import CustomNodeLabel from "../customNodes/CustomNodeLabel"; + +type DefaultGroupExpandedProps = { + className?: string; + element: Node; + droppable?: boolean; + canDrop?: boolean; + dropTarget?: boolean; + dragging?: boolean; + hover?: boolean; + label?: string; // Defaults to element.getLabel() + secondaryLabel?: string; + showLabel?: boolean; // Defaults to true + truncateLength?: number; // Defaults to 13 + badge?: string; + badgeColor?: string; + badgeTextColor?: string; + badgeBorderColor?: string; + badgeClassName?: string; + badgeLocation?: BadgeLocation; + labelIconClass?: string; // Icon to show in label + labelIcon?: string; + labelIconPadding?: number; + hulledOutline?: boolean; +} & CollapsibleGroupProps & + WithDragNodeProps & + WithSelectionProps & + WithDndDropProps & + WithContextMenuProps; + +type PointWithSize = [number, number, number]; + +// Return the point whose Y is the largest value. +// If multiple points are found, compute the center X between them +// export for testing only +export function computeLabelLocation(points: PointWithSize[]): PointWithSize { + let lowPoints: PointWithSize[]; + const threshold = 5; + + _.forEach(points, (p) => { + const delta = !lowPoints ? Infinity : Math.round(p[1]) - Math.round(lowPoints[0][1]); + if (delta > threshold) { + lowPoints = [p]; + } else if (Math.abs(delta) <= threshold) { + lowPoints.push(p); + } + }); + return [ + (_.minBy(lowPoints, (p) => p[0])[0] + _.maxBy(lowPoints, (p) => p[0])[0]) / 2, + lowPoints[0][1], + // use the max size value + _.maxBy(lowPoints, (p) => p[2])[2], + ]; +} + +const PipelinesDefaultGroupExpanded: React.FunctionComponent = ({ + className, + element, + collapsible, + selected, + onSelect, + hover, + label, + secondaryLabel, + showLabel = true, + truncateLength, + dndDropRef, + droppable, + canDrop, + dropTarget, + onContextMenu, + contextMenuOpen, + dragging, + dragNodeRef, + badge, + badgeColor, + badgeTextColor, + badgeBorderColor, + badgeClassName, + badgeLocation, + labelIconClass, + labelIcon, + labelIconPadding, + onCollapseChange, + hulledOutline = true, +}) => { + const [hovered, hoverRef] = useHover(); + const [labelHover, labelHoverRef] = useHover(); + const dragLabelRef = useDragNode()[1]; + const refs = useCombineRefs(hoverRef, dragNodeRef); + const isHover = hover !== undefined ? hover : hovered; + const anchorRef = useSvgAnchor(); + const outlineRef = useCombineRefs(dndDropRef, anchorRef); + const labelLocation = React.useRef(); + const pathRef = React.useRef(); + const boxRef = React.useRef(null); + + let parent = element.getParent(); + let altGroup = false; + while (!isGraph(parent)) { + altGroup = !altGroup; + parent = parent.getParent(); + } + + // cast to number and coerce + const padding = maxPadding(element.getStyle().padding ?? 17); + const hullPadding = (point: PointWithSize | PointTuple) => (point[2] || 0) + padding; + + if ( + !droppable || + (hulledOutline && !pathRef.current) || + (!hulledOutline && !boxRef.current) || + !labelLocation.current + ) { + const children = element.getNodes().filter((c) => c.isVisible()); + if (children.length === 0) { + return null; + } + const points: (PointWithSize | PointTuple)[] = []; + _.forEach(children, (c) => { + if (c.getNodeShape() === NodeShape.circle) { + const bounds = c.getBounds(); + const { width, height } = bounds; + const { x, y } = bounds.getCenter(); + const radius = Math.max(width, height) / 2; + points.push([x, y, radius] as PointWithSize); + } else { + // add all 4 corners + const { width, height, x, y } = c.getBounds(); + points.push([x, y, 0] as PointWithSize); + points.push([x + width, y, 0] as PointWithSize); + points.push([x, y + height, 0] as PointWithSize); + points.push([x + width, y + height, 0] as PointWithSize); + } + }); + + if (hulledOutline) { + const hullPoints: (PointWithSize | PointTuple)[] = + points.length > 2 ? polygonHull(points as PointTuple[]) : (points as PointTuple[]); + if (!hullPoints) { + return null; + } + + // change the box only when not dragging + pathRef.current = hullPath(hullPoints as PointTuple[], hullPadding); + + // Compute the location of the group label. + labelLocation.current = computeLabelLocation(hullPoints as PointWithSize[]); + } else { + boxRef.current = element.getBounds(); + labelLocation.current = [ + boxRef.current.x + boxRef.current.width / 2, + boxRef.current.y + boxRef.current.height, + 0, + ]; + } + } + + const groupClassName = css( + styles.topologyGroup, + className, + altGroup && 'pf-m-alt-group', + canDrop && 'pf-m-highlight', + dragging && 'pf-m-dragging', + selected && 'pf-m-selected', + ); + const innerGroupClassName = css( + styles.topologyGroup, + className, + altGroup && 'pf-m-alt-group', + canDrop && 'pf-m-highlight', + dragging && 'pf-m-dragging', + selected && 'pf-m-selected', + (isHover || labelHover) && 'pf-m-hover', + canDrop && dropTarget && 'pf-m-drop-target', + ); + + return ( + + + + {hulledOutline ? ( + + ) : ( + + )} + + + {showLabel && (label || element.getLabel()) && ( + + : undefined} + isExpanded + onActionIconClick={() => onCollapseChange(element, true)} + > + {label || element.getLabel()} + + + )} + + ); +}; + +export default observer(PipelinesDefaultGroupExpanded); diff --git a/packages/module/src/pipelines/components/groups/index.ts b/packages/module/src/pipelines/components/groups/index.ts index ff1e08c3..c4d5386b 100644 --- a/packages/module/src/pipelines/components/groups/index.ts +++ b/packages/module/src/pipelines/components/groups/index.ts @@ -1 +1,4 @@ export { default as DefaultTaskGroup } from './DefaultTaskGroup'; +export { default as PipelinesDefaultGroup } from './PipelinesDefaultGroup'; +export { default as PipelinesDefaultGroupCollapsed } from './PipelinesDefaultGroupCollapsed'; +export { default as PipelinesDefaultGroupExpanded } from './PipelinesDefaultGroupExpanded'; From 60a21fddda27554987323c64759c382b1a857d80 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:04:39 -0500 Subject: [PATCH 02/13] styling wip --- .../pipelinesDemo/useDemoPipelineNodes.tsx | 17 +- .../nodes/labels/PipelinesNodeLabel.tsx | 188 +++++++----------- .../groups/PipelinesDefaultGroupCollapsed.tsx | 35 +--- .../groups/PipelinesDefaultGroupExpanded.tsx | 115 +++++++---- 4 files changed, 157 insertions(+), 198 deletions(-) diff --git a/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx b/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx index 7cabf524..b0ea7e9e 100644 --- a/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx +++ b/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx @@ -4,9 +4,10 @@ import { DEFAULT_TASK_NODE_TYPE, DEFAULT_WHEN_OFFSET, DEFAULT_WHEN_SIZE, + LabelPosition, PipelineNodeModel, RunStatus, - WhenStatus, + WhenStatus } from '@patternfly/react-topology'; export const NODE_PADDING_VERTICAL = 45; @@ -156,7 +157,8 @@ export const useDemoPipelineNodes = ( collapsedWidth: 75, collapsedHeight: 42, collapsible: true, - }, + labelPosition: LabelPosition.top + } }); } } @@ -202,7 +204,8 @@ export const useDemoPipelineNodes = ( collapsedWidth: 75, collapsedHeight: 42, collapsible: true, - }, + labelPosition: LabelPosition.top + } }; acc.push(taskGroup); } @@ -233,8 +236,8 @@ export const useDemoPipelineNodes = ( taskProgress: '3/4', taskType: 'java', taskTopic: 'Environment', - columnGroup: TASK_STATUSES.length % STATUS_PER_ROW + 1, - taskJobType: 'cubes', + columnGroup: (TASK_STATUSES.length % STATUS_PER_ROW) + 1, + taskJobType: 'cubes' }; if (!layout) { @@ -262,8 +265,8 @@ export const useDemoPipelineNodes = ( taskProgress: '3/4', taskType: 'java', taskTopic: 'Environment', - columnGroup: TASK_STATUSES.length % STATUS_PER_ROW + 1, - taskJobType: 'link', + columnGroup: (TASK_STATUSES.length % STATUS_PER_ROW) + 1, + taskJobType: 'link' }; if (!layout) { diff --git a/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx b/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx index e495785f..f8cb9699 100644 --- a/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx +++ b/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx @@ -1,27 +1,16 @@ import * as React from 'react'; import { css } from '@patternfly/react-styles'; -import styles from '@patternfly/react-topology/src/css/topology-components'; -import { - LabelIcon, - LabelBadge, - LabelActionIcon, - LabelContextMenu, - BadgeLocation, - LabelPosition, - NodeStatus, - NodeShadows, - WithContextMenuProps, - WithDndDragProps, - createSvgIdUrl, - useCombineRefs, - useHover, - useSize, -} from '@patternfly/react-topology'; -import { Badge } from '@patternfly/react-core'; -// import { truncateMiddle } from "@patternfly/react-topology/src/utils/truncate-middle"; - -export const NODE_SHADOW_FILTER_ID_HOVER = 'NodeShadowsFilterId--hover'; -export const NODE_SHADOW_FILTER_ID_DANGER = 'NodeShadowsFilterId--danger'; +import styles from '../../../css/topology-components'; +import pipelinesStyles from '../../../css/topology-pipelines'; +import { truncateMiddle } from '../../../utils/truncate-middle'; +import { createSvgIdUrl, useCombineRefs, useHover, useSize } from '../../../utils'; +import { WithContextMenuProps, WithDndDragProps } from '../../../behavior'; +import NodeShadows, { NODE_SHADOW_FILTER_ID_DANGER, NODE_SHADOW_FILTER_ID_HOVER } from '../NodeShadows'; +import LabelBadge from './LabelBadge'; +import LabelContextMenu from './LabelContextMenu'; +import LabelIcon from './LabelIcon'; +import LabelActionIcon from './LabelActionIcon'; +import { BadgeLocation, LabelPosition, NodeStatus } from '../../../types'; type PipelinesNodeLabelProps = { children?: string; @@ -38,7 +27,6 @@ type PipelinesNodeLabelProps = { labelIconClass?: string; // Icon to show in label labelIcon?: React.ReactNode; labelIconPadding?: number; - isExpanded?: boolean; dragRef?: WithDndDragProps['dndDragRef']; hover?: boolean; dragging?: boolean; @@ -75,7 +63,6 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ badgeBorderColor, badgeClassName, badgeLocation = BadgeLocation.inner, - isExpanded = false, labelIconClass, labelIcon, labelIconPadding = 4, @@ -93,34 +80,14 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ ...other }) => { const [labelHover, labelHoverRef] = useHover(); - const [expandIconHovered, setExpandIconHovered] = React.useState(false); - - const refs = useCombineRefs( - dragRef as React.Ref, - (typeof truncateLength === 'number' ? labelHoverRef : undefined) as React.Ref, - ); + const refs = useCombineRefs(dragRef, typeof truncateLength === 'number' ? labelHoverRef : undefined); const [textSize, textRef] = useSize([children, truncateLength, className, labelHover]); - const [secondaryTextSize, secondaryTextRef] = useSize([ - secondaryLabel, - truncateLength, - className, - labelHover, - ]); + const [secondaryTextSize, secondaryTextRef] = useSize([secondaryLabel, truncateLength, className, labelHover]); const [badgeSize, badgeRef] = useSize([badge]); const [actionSize, actionRef] = useSize([actionIcon, paddingX]); const [contextSize, contextRef] = useSize([onContextMenu, paddingX]); - const onMouseEnter = (e) => { - console.log('mouse enetered'); - setExpandIconHovered(true); - }; - - const onMouseLeave = (e) => { - console.log('mouse letft'); - setExpandIconHovered(false); - }; - const { width, height, @@ -132,7 +99,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ actionStartX, contextStartX, iconSpace, - badgeSpace, + badgeSpace } = React.useMemo(() => { if (!textSize) { return { @@ -146,7 +113,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ actionStartX: 0, contextStartX: 0, iconSpace: 0, - badgeSpace: 0, + badgeSpace: 0 }; } const badgeSpace = badge && badgeSize && badgeLocation === BadgeLocation.inner ? badgeSize.width + paddingX : 0; @@ -154,7 +121,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ const iconSpace = labelIconClass || labelIcon ? (height + paddingY * 0.5) / 2 : 0; const actionSpace = actionIcon && actionSize ? actionSize.width : 0; const contextSpace = onContextMenu && contextSize ? contextSize.width : 0; - const primaryWidth = iconSpace + badgeSpace + paddingX + textSize.width + actionSpace + contextSpace + paddingX; + const primaryWidth = iconSpace + badgeSpace + paddingX + textSize.width + contextSpace + paddingX; const secondaryWidth = secondaryLabel && secondaryTextSize ? secondaryTextSize.width + 2 * paddingX : 0; const width = Math.max(primaryWidth, secondaryWidth); @@ -184,7 +151,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ badgeStartX = (width - badgeSize.width) / 2; badgeStartY = height + paddingY; } else { - badgeStartX = iconSpace + paddingX; + badgeStartX = iconSpace + textSize.width + paddingX * 2; badgeStartY = (height - badgeSize.height) / 2; } } @@ -200,7 +167,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ badgeStartX, badgeStartY, iconSpace, - badgeSpace: badgeSize && badgeLocation === BadgeLocation.inner ? badgeSpace : 0, + badgeSpace: badgeSize && badgeLocation === BadgeLocation.inner ? badgeSpace : 0 }; }, [ textSize, @@ -219,7 +186,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ secondaryTextSize, position, x, - y, + y ]); let filterId; @@ -230,73 +197,62 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ } return ( - <> - - - {textSize && ( - <> - - - )} - {textSize && (labelIconClass || labelIcon) && ( - - )} - - {truncateLength > 0 && !labelHover - ? truncateMiddle(children, { length: truncateLength }) - : children} - - {textSize && badge && ( - - )} - - setExpandIconHovered(true)} - onMouseLeave={() => setExpandIconHovered(false)} - > - + + {textSize && ( + - - + )} + {textSize && badge && ( + + )} + {textSize && (labelIconClass || labelIcon) && ( + + )} + + {truncateLength > 0 && !labelHover ? truncateMiddle(children, { length: truncateLength }) : children} + + {textSize && actionIcon && ( + + )} + ); }; diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx index 6a946f80..97258107 100644 --- a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx @@ -99,47 +99,18 @@ const PipelinesDefaultGroupCollapsed: React.FunctionComponent - - - - - {shapeSize && childCount && ( - - )} {showLabel && ( { + if (labelPosition === LabelPosition.top) { + points.forEach((p) => { + const delta = !highPoints ? -Infinity : Math.round(p[1]) - Math.round(highPoints[0][1]); + // If the difference is greater than the threshold, update the highest point + if (delta < -threshold) { + highPoints = [p]; + } else if (Math.abs(delta) <= threshold) { + if (!highPoints) { + highPoints = []; + } + highPoints.push(p); + } + }); + + // find min and max by x and y coordinates + const minX = highPoints.reduce((min, p) => Math.min(min, p[0]), Infinity); + const maxX = highPoints.reduce((max, p) => Math.max(max, p[0]), -Infinity); + const minY = highPoints.reduce((min, p) => Math.min(min, p[1]), Infinity); + // find max by size value + const maxSize = highPoints.reduce((max, p) => Math.max(max, p[2]), -Infinity); + + return [ + (minX + maxX) / 2, + minY, + // use the max size value + maxSize + ]; + } + + points.forEach((p) => { const delta = !lowPoints ? Infinity : Math.round(p[1]) - Math.round(lowPoints[0][1]); if (delta > threshold) { lowPoints = [p]; @@ -77,15 +107,20 @@ export function computeLabelLocation(points: PointWithSize[]): PointWithSize { lowPoints.push(p); } }); - return [ - (_.minBy(lowPoints, (p) => p[0])[0] + _.maxBy(lowPoints, (p) => p[0])[0]) / 2, - lowPoints[0][1], - // use the max size value - _.maxBy(lowPoints, (p) => p[2])[2], - ]; + + const minX = lowPoints.reduce((acc, point) => { + return Math.min(acc, point[0]); + }, Number.POSITIVE_INFINITY); + const maxX = lowPoints.reduce((acc, point) => { + return Math.max(acc, point[0]); + }, Number.NEGATIVE_INFINITY); + const maxSize = lowPoints.reduce((acc, point) => { + return Math.max(acc, point[2]); + }, Number.NEGATIVE_INFINITY); + return [(minX + maxX) / 2, lowPoints[0][1], maxSize]; } -const PipelinesDefaultGroupExpanded: React.FunctionComponent = ({ +const PipelinesDefaultGroupExpanded: React.FunctionComponent = ({ className, element, collapsible, @@ -112,9 +147,10 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent { const [hovered, hoverRef] = useHover(); const [labelHover, labelHoverRef] = useHover(); @@ -149,7 +185,7 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent { + children.forEach((c) => { if (c.getNodeShape() === NodeShape.circle) { const bounds = c.getBounds(); const { width, height } = bounds; @@ -177,14 +213,13 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent + - + {hulledOutline ? ( ) : ( @@ -238,12 +271,9 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent : undefined} - isExpanded onActionIconClick={() => onCollapseChange(element, true)} > {label || element.getLabel()} From c2603674d6521fae77d467d6bc3f2691e829625a Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Tue, 5 Mar 2024 10:31:24 -0500 Subject: [PATCH 03/13] update classnames wip --- .../nodes/labels/LabelActionIcon.tsx | 7 +- .../module/src/css/topology-pipelines.css | 76 +++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx index 1e0c671c..7ad35e76 100644 --- a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx +++ b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx @@ -2,6 +2,9 @@ import * as React from 'react'; import { useSize } from '../../../utils'; import { css } from '@patternfly/react-styles'; import styles from '../../../css/topology-components'; +import pipelineStyles from '../../../css/topology-pipelines'; +import { pipeline } from "stream"; + interface LabelActionIconProps { className?: string; @@ -38,7 +41,7 @@ const LabelActionIcon = React.forwardRef( {iconSize && ( ( /> )} diff --git a/packages/module/src/css/topology-pipelines.css b/packages/module/src/css/topology-pipelines.css index 070a9637..4d0e6828 100644 --- a/packages/module/src/css/topology-pipelines.css +++ b/packages/module/src/css/topology-pipelines.css @@ -555,3 +555,79 @@ .pf-topology-pipelines__status-icon-background.pf-m-idle.pf-m-selected { --pf-topology-pipelines__pill-background--Fill: var(--pf-topology-pipelines__pill-background--m-idle--Stroke); } + +.pf-topology__node__label__background.pf-m-disabled { + --pf-topology__node__label__background--Fill: var(--pf-topology__node--m-disabled--Background--Fill); + --pf-topology__node__label__background--Stroke: var(--pf-topology__node--m-disabled--Background--Stroke); +} + +.pf-topology__group__action__label { + fill: #fff; + stroke-width: 1; + stroke: var(--pf-v5-global--active-color--100) +} + +.pf-topology__group.pf-m-selected.pf-m-danger .pf-topology__node__action-icon__icon { + color: white; +} + +.pf-topology__group__label rect.pf-topology__node__label__background { + fill: white; +} + +.pf-topology__group__label>text { + fill: black; + font-size: var(--pf-v5-global--FontSize--sm); + pointer-events: none; +} + +.pf-topology__group.pf-m-selected .pf-topology__group__label>text { + fill: white; + font-size: var(--pf-v5-global--FontSize--sm); + pointer-events: none; +} + +.action-icon-collapsed { + fill: white; + stroke: var(--pf-v5-global--active-color--100); + pointer-events: all; +} + +.action-icon-expanded { + fill: var(--pf-v5-global--active-color--100); +} + +g.pf-topology__node__action-icon.action-icon-collapsed .pf-topology__node__action-icon__icon { + color: var(--pf-v5-global--active-color--100); +} + +g.pf-topology__node__action-icon.action-icon-expanded .pf-topology__node__action-icon__icon { + color: white; +} + +/* g.pf-topology-pipelines__pill.pf-m-selected.pf-m-selectable svg.pf-v5-svg.monitoring-lead-icon { + fill: var(--pf-v5-global--icon--Color--dark--light); +} */ + + +.pf-topology-pipelines__pill.pf-m-selected:not(.pf-m-danger):not(.pf-m-success):not(.pf-m-warning):not(.pf-m-skipped):not(.pf-m-in-progress) .pf-topology-pipelines__pill-text { + fill: black; +} + +.pf-topology-pipelines__pill-text .pf-topology-pipelines__pill>.pf-topology-pipelines__pill-background { + stroke: none; +} + +.pf-topology-pipelines__pill .pf-m-danger .pf-m-selectable>.pf-v5-svg .list-lead-icon { + fill: var(--pf-topology-pipelines__pill-background--m-danger--Fill) +} + +.pf-topology__group__border:hover { + stroke: var(--pf-v5-global--active-color--100); +} + +.pf-topology-pipelines__node__action-icon__background { + fill: white; + stroke-width: 1; + color: var(--pf-v5-global--active-color--100); +} From fd8d2326b52548c4004edc1705536abe5fa53fc3 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Tue, 5 Mar 2024 13:54:53 -0500 Subject: [PATCH 04/13] add proper styling, wip hover --- .../nodes/labels/PipelinesNodeLabel.tsx | 8 +- .../module/src/css/topology-components.css | 95 +------------------ .../module/src/css/topology-pipelines.css | 33 +------ .../groups/PipelinesDefaultGroup.tsx | 50 +++++----- .../groups/PipelinesDefaultGroupCollapsed.tsx | 2 +- .../groups/PipelinesDefaultGroupExpanded.tsx | 67 ++++--------- 6 files changed, 50 insertions(+), 205 deletions(-) diff --git a/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx b/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx index f8cb9699..e4b2d295 100644 --- a/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx +++ b/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx @@ -32,6 +32,7 @@ type PipelinesNodeLabelProps = { dragging?: boolean; edgeDragging?: boolean; dropTarget?: boolean; + isExpanded?: boolean; actionIcon?: React.ReactElement; actionIconClassName?: string; onActionIconClick?: (e: React.MouseEvent) => void; @@ -66,6 +67,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ labelIconClass, labelIcon, labelIconPadding = 4, + isExpanded = false, truncateLength, dragRef, hover, @@ -134,7 +136,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ startX = x + iconSpace; startY = y - height / 2; } else if (position === LabelPosition.left) { - startX = - width - paddingX; + startX = -width - paddingX; startY = y - height / 2 + paddingY; } else { startX = x - width / 2 + iconSpace / 2; @@ -241,14 +243,14 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ {textSize && actionIcon && ( )} diff --git a/packages/module/src/css/topology-components.css b/packages/module/src/css/topology-components.css index 1421b824..8c2d2e85 100644 --- a/packages/module/src/css/topology-components.css +++ b/packages/module/src/css/topology-components.css @@ -843,97 +843,4 @@ .pf-topology-default-create-connector__create__cursor { fill: var(--pf-topology__create-connector-color--Fill); -} - -.pf-topology__node__label__background.pf-m-disabled { - --pf-topology__node__label__background--Fill: var(--pf-topology__node--m-disabled--Background--Fill); - --pf-topology__node__label__background--Stroke: var(--pf-topology__node--m-disabled--Background--Stroke); -} - -.pf-topology__group__action__label { - fill: #fff; - stroke-width: 1; - stroke: var(--pf-v5-global--active-color--100) -} - -.pf-topology__group.pf-m-selected.pf-m-danger .pf-topology__node__action-icon__icon { - color: white; -} - -.pf-topology__group__label rect.pf-topology__node__label__background { - fill: white; -} - -.pf-topology__group__label>text { - fill: black; - font-size: var(--pf-v5-global--FontSize--sm); - pointer-events: none; -} - -.pf-topology__group.pf-m-selected .pf-topology__group__label>text { - fill: white; - font-size: var(--pf-v5-global--FontSize--sm); - pointer-events: none; -} - -.action-icon-collapsed { - fill: white; - stroke: var(--pf-v5-global--active-color--100); - pointer-events: all; -} - -.action-icon-expanded { - fill: var(--pf-v5-global--active-color--100); -} - -g.pf-topology__node__action-icon.action-icon-collapsed .pf-topology__node__action-icon__icon { - color: var(--pf-v5-global--active-color--100); -} - -g.pf-topology__node__action-icon.action-icon-expanded .pf-topology__node__action-icon__icon { - color: white; -} - -svg.pf-v5-svg.list-lead-icon { - fill: var(--pf-v5-global--icon--Color--light); -} - -svg.pf-v5-svg.monitoring-lead-icon { - fill: var(--pf-v5-global--icon--Color--light); -} - -g.pf-topology-pipelines__pill.pf-m-danger.pf-m-selected.pf-m-selectable svg.pf-v5-svg.list-lead-icon { - fill: var(--pf-v5-global--icon--Color--dark--light); -} - -/* g.pf-topology-pipelines__pill.pf-m-selected.pf-m-selectable svg.pf-v5-svg.monitoring-lead-icon { - fill: var(--pf-v5-global--icon--Color--dark--light); -} */ - -.pf-topology-pipelines__pill.pf-m-selected:not(.pf-m-danger):not(.pf-m-success):not(.pf-m-warning):not(.pf-m-skipped):not(.pf-m-in-progress) .pf-topology-pipelines__pill-background { - fill: white; - stroke: var(--pf-v5-global--active-color--100); - stroke-width: 3px; -} - -.pf-topology-pipelines__pill.pf-m-selected:not(.pf-m-danger):not(.pf-m-success):not(.pf-m-warning):not(.pf-m-skipped):not(.pf-m-in-progress) .pf-topology-pipelines__pill-text { - fill: black; -} - -.pf-topology-pipelines__pill-text .pf-topology-pipelines__pill>.pf-topology-pipelines__pill-background { - stroke: none; -} - -.pf-topology-pipelines__pill .pf-m-danger .pf-m-selectable>.pf-v5-svg .list-lead-icon { - fill: var(--pf-topology-pipelines__pill-background--m-danger--Fill) -} - -.pf-topology__group__border:hover { - stroke: var(--pf-v5-global--active-color--100); -} - -.pf-topology__node__pipelines-action-icon__background { - fill: white; - stroke-width: 1; - color: var(--pf-v5-global--active-color--100); -} +} \ No newline at end of file diff --git a/packages/module/src/css/topology-pipelines.css b/packages/module/src/css/topology-pipelines.css index 4d0e6828..b6087688 100644 --- a/packages/module/src/css/topology-pipelines.css +++ b/packages/module/src/css/topology-pipelines.css @@ -597,37 +597,6 @@ fill: var(--pf-v5-global--active-color--100); } -g.pf-topology__node__action-icon.action-icon-collapsed .pf-topology__node__action-icon__icon { - color: var(--pf-v5-global--active-color--100); -} - -g.pf-topology__node__action-icon.action-icon-expanded .pf-topology__node__action-icon__icon { - color: white; -} - -/* g.pf-topology-pipelines__pill.pf-m-selected.pf-m-selectable svg.pf-v5-svg.monitoring-lead-icon { - fill: var(--pf-v5-global--icon--Color--dark--light); -} */ - - -.pf-topology-pipelines__pill.pf-m-selected:not(.pf-m-danger):not(.pf-m-success):not(.pf-m-warning):not(.pf-m-skipped):not(.pf-m-in-progress) .pf-topology-pipelines__pill-text { - fill: black; -} - -.pf-topology-pipelines__pill-text .pf-topology-pipelines__pill>.pf-topology-pipelines__pill-background { - stroke: none; -} - -.pf-topology-pipelines__pill .pf-m-danger .pf-m-selectable>.pf-v5-svg .list-lead-icon { - fill: var(--pf-topology-pipelines__pill-background--m-danger--Fill) -} - -.pf-topology__group__border:hover { - stroke: var(--pf-v5-global--active-color--100); -} - -.pf-topology-pipelines__node__action-icon__background { +.action-icon-expanded > .pf-topology-pipelines__node__action-icon__icon > svg { fill: white; - stroke-width: 1; - color: var(--pf-v5-global--active-color--100); } diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx index 9a53bd8f..48d030c4 100644 --- a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx @@ -1,23 +1,16 @@ import * as React from 'react'; import { observer } from 'mobx-react'; - +import { OnSelect, WithDndDragProps, ConnectDragSource, ConnectDropTarget } from '../../../behavior'; +import { ShapeProps } from '../../../components'; +import { Dimensions } from '../../../geom'; +import { GraphElement, LabelPosition, BadgeLocation, isNode, Node } from '../../../types'; +import PipelinesDefaultGroupCollapsed from './PipelinesDefaultGroupCollapsed'; import PipelinesDefaultGroupExpanded from './PipelinesDefaultGroupExpanded'; -import { OnSelect } from '@patternfly/react-table'; -import { - GraphElement, - LabelPosition, - BadgeLocation, - ShapeProps, - WithDndDragProps, - ConnectDragSource, - ConnectDropTarget, - Node, -} from '@patternfly/react-topology'; -import { isNode } from 'yaml'; -import PipelinesDefaultGroupCollapsed from "./PipelinesDefaultGroupCollapsed"; -import MyRectComponent from "./MyRectComponent"; +import { Badge } from "@patternfly/react-core"; interface PipelinesDefaultGroupProps { + /** Additional content added to the node */ + children?: React.ReactNode; /** Additional classes added to the group */ className?: string; /** The graph group node element to represent */ @@ -40,7 +33,7 @@ interface PipelinesDefaultGroupProps { secondaryLabel?: string; /** Flag to show the label */ showLabel?: boolean; // Defaults to true - /** Position of the label, bottom or left. Defaults to element.getLabelPosition() or bottom */ + /** Position of the label, top or bottom. Defaults to element.getLabelPosition() or bottom */ labelPosition?: LabelPosition; /** The maximum length of the label before truncation */ truncateLength?: number; @@ -92,30 +85,31 @@ interface PipelinesDefaultGroupProps { hulledOutline?: boolean; } -type DefaultGroupInnerProps = Omit & { element: Node }; +type PipelinesDefaultGroupInnerProps = Omit & { element: Node }; -const DefaultGroupInner: React.FunctionComponent = observer( +const PipelinesDefaultGroupInner: React.FunctionComponent = observer( ({ className, element, onCollapseChange, ...rest }) => { + const childCount = element.getAllNodeChildren().length; const handleCollapse = (group: Node, collapsed: boolean): void => { if (collapsed && rest.collapsedWidth !== undefined && rest.collapsedHeight !== undefined) { - group.setBounds(group.getBounds().setSize(rest.collapsedWidth, rest.collapsedHeight)); + group.setDimensions(new Dimensions(rest.collapsedWidth, rest.collapsedHeight)); } group.setCollapsed(collapsed); onCollapseChange && onCollapseChange(group, collapsed); }; - const childCount = element.getAllNodeChildren().length; - if (element.isCollapsed()) { return ( - ); } return ( @@ -126,18 +120,18 @@ const DefaultGroupInner: React.FunctionComponent = obser {...rest} /> ); - }, + } ); const PipelinesDefaultGroup: React.FunctionComponent = ({ element, ...rest }: PipelinesDefaultGroupProps) => { - // if (!isNode(element)) { - // throw new Error('DefaultGroup must be used only on Node elements'); - // } + if (!isNode(element)) { + throw new Error('DefaultGroup must be used only on Node elements'); + } - return ; + return ; }; export default PipelinesDefaultGroup; diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx index 97258107..d7192b3b 100644 --- a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx @@ -7,7 +7,7 @@ import { WithDragNodeProps, WithSelectionProps, WithDndDropProps, WithContextMen import { CollapsibleGroupProps, Stadium, Layer, LabelBadge, PipelinesNodeLabel, NodeLabel } from "../../../components"; import { NODE_SHADOW_FILTER_ID_HOVER } from "../../../components/nodes/NodeShadows"; import { GROUPS_LAYER } from "../../../const"; -import { LabelPosition, BadgeLocation } from "../../../types"; +import { LabelPosition, BadgeLocation, Node } from "../../../types"; import { useHover, useSize, useCombineRefs, createSvgIdUrl } from "../../../utils"; type PipelinesDefaultGroupCollapsedProps = { diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx index ed71a679..3110d1f1 100644 --- a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx @@ -2,34 +2,14 @@ import * as React from 'react'; import { observer } from 'mobx-react'; import { polygonHull } from 'd3-polygon'; import { css } from '@patternfly/react-styles'; -import styles from '@patternfly/react-topology/src/css/topology-components'; +import styles from '../../../css/topology-components'; import CollapseIcon from '@patternfly/react-icons/dist/esm/icons/compress-alt-icon'; -import { - BadgeLocation, - Layer, - Node, - NodeLabel, - useDragNode, - useSvgAnchor, - WithContextMenuProps, - WithDndDropProps, - WithDragNodeProps, - WithSelectionProps, - CollapsibleGroupProps, - hullPath, - maxPadding, - useCombineRefs, - useHover, - Rect, - GROUPS_LAYER, - TOP_LAYER, - NodeShape, - NodeStyle, - PointTuple, - isGraph, - LabelPosition -} from '@patternfly/react-topology'; -import { PipelinesNodeLabel } from '../../../components'; +import { CollapsibleGroupProps, Layer, PipelinesNodeLabel } from "../../../components"; +import { WithDragNodeProps, WithSelectionProps, WithDndDropProps, WithContextMenuProps, useDragNode, useSvgAnchor } from "../../../behavior"; +import { GROUPS_LAYER, TOP_LAYER } from "../../../const"; +import { BadgeLocation, LabelPosition, isGraph, PointTuple, Node, NodeShape, NodeStyle } from "../../../types"; +import { useHover, useCombineRefs, maxPadding, hullPath } from "../../../utils"; +import Rect from '../../../geom/Rect'; type PipelinesDefaultGroupExpandedProps = { className?: string; @@ -99,7 +79,7 @@ export function computeLabelLocation(points: PointWithSize[], labelPosition?: La ]; } - points.forEach((p) => { + points.forEach(p => { const delta = !lowPoints ? Infinity : Math.round(p[1]) - Math.round(lowPoints[0][1]); if (delta > threshold) { lowPoints = [p]; @@ -117,7 +97,11 @@ export function computeLabelLocation(points: PointWithSize[], labelPosition?: La const maxSize = lowPoints.reduce((acc, point) => { return Math.max(acc, point[2]); }, Number.NEGATIVE_INFINITY); - return [(minX + maxX) / 2, lowPoints[0][1], maxSize]; + return [ + (minX + maxX) / 2, + lowPoints[0][1], + maxSize, + ]; } const PipelinesDefaultGroupExpanded: React.FunctionComponent = ({ @@ -150,7 +134,7 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent { const [hovered, hoverRef] = useHover(); const [labelHover, labelHoverRef] = useHover(); @@ -174,18 +158,13 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent().padding ?? 17); const hullPadding = (point: PointWithSize | PointTuple) => (point[2] || 0) + padding; - if ( - !droppable || - (hulledOutline && !pathRef.current) || - (!hulledOutline && !boxRef.current) || - !labelLocation.current - ) { - const children = element.getNodes().filter((c) => c.isVisible()); + if (!droppable || (hulledOutline && !pathRef.current) || (!hulledOutline && !boxRef.current) || !labelLocation.current) { + const children = element.getNodes().filter(c => c.isVisible()); if (children.length === 0) { return null; } const points: (PointWithSize | PointTuple)[] = []; - children.forEach((c) => { + children.forEach(c => { if (c.getNodeShape() === NodeShape.circle) { const bounds = c.getBounds(); const { width, height } = bounds; @@ -257,21 +236,14 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent ) : ( - + )} {showLabel && (label || element.getLabel()) && ( : undefined} onActionIconClick={() => onCollapseChange(element, true)} > From 56a8185a00fb08a1be149797abab27f9f1306ee9 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:27:18 -0500 Subject: [PATCH 05/13] clean up unused vars, add drop shadow to action icon --- .../pipelinesDemo/useDemoPipelineNodes.tsx | 2 +- .../src/demos/stylesDemo/StyleGroup.tsx | 26 +--------- .../nodes/labels/LabelActionIcon.tsx | 20 +++++--- .../nodes/labels/PipelinesNodeLabel.tsx | 48 ++++++------------- .../module/src/css/topology-pipelines.css | 4 ++ .../groups/PipelinesDefaultGroup.tsx | 1 - 6 files changed, 35 insertions(+), 66 deletions(-) diff --git a/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx b/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx index b0ea7e9e..83750083 100644 --- a/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx +++ b/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx @@ -202,7 +202,7 @@ export const useDemoPipelineNodes = ( label: `Group ${task.data.columnGroup}`, data: { collapsedWidth: 75, - collapsedHeight: 42, + collapsedHeight: 75, collapsible: true, labelPosition: LabelPosition.top } diff --git a/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx b/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx index d1b7ee51..2b919f28 100644 --- a/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx +++ b/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx @@ -34,34 +34,13 @@ type StyleGroupProps = { const StyleGroup: React.FunctionComponent = ({ element, - collapsedWidth = 75, - collapsedHeight = 60, + collapsedWidth, + collapsedHeight, ...rest }) => { - const groupElement = element as Node; const data = element.getData(); const detailsLevel = element.getGraph().getDetailsLevel(); - const getTypeIcon = (dataType?: DataTypes): any => { - switch (dataType) { - case DataTypes.Alternate: - return AlternateIcon; - default: - return DefaultIcon; - } - }; - - const renderIcon = (): React.ReactNode => { - const iconSize = Math.min(collapsedWidth, collapsedHeight) - ICON_PADDING * 2; -const label = element.getLabel(); - - return ( - - {label} - - ); - }; - const passedData = React.useMemo(() => { const newData = { ...data }; Object.keys(newData).forEach(key => { @@ -82,7 +61,6 @@ const label = element.getLabel(); {...rest} {...passedData} > - {groupElement.isCollapsed() ? renderIcon() : null} ); }; diff --git a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx index 7ad35e76..6d37fe12 100644 --- a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx +++ b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx @@ -1,15 +1,16 @@ import * as React from 'react'; -import { useSize } from '../../../utils'; +import { createSvgIdUrl, useHover, useSize } from '../../../utils'; import { css } from '@patternfly/react-styles'; import styles from '../../../css/topology-components'; import pipelineStyles from '../../../css/topology-pipelines'; -import { pipeline } from "stream"; +import { NODE_SHADOW_FILTER_ID_HOVER } from "../NodeShadows"; interface LabelActionIconProps { className?: string; icon: React.ReactElement; isIconExternal?: boolean; + hover?: boolean; onClick: (e: React.MouseEvent) => void; iconOffsetX?: number; iconOffsetY?: number; @@ -21,11 +22,15 @@ interface LabelActionIconProps { } const LabelActionIcon = React.forwardRef( - ({ icon, isIconExternal, onClick, className, x, y, paddingX, height, iconOffsetX = 0, iconOffsetY = 0 }, actionRef) => { + ({ icon, isIconExternal, onClick, className, x, y, paddingX, height}) => { const [iconSize, iconRef] = useSize([icon, paddingX]); const iconWidth = iconSize?.width ?? 0; const iconHeight = iconSize?.height ?? 0; - const iconY = (height - iconHeight) / 2; + const [hovered, hoverRef] = useHover(); + + + const centerX = x + height / 2 - iconWidth / 2; + const centerY = y + height / 2 - iconHeight / 2; const classes = css(styles.topologyNodeActionIcon, className); @@ -40,17 +45,18 @@ const LabelActionIcon = React.forwardRef( {iconSize && ( )} {icon} diff --git a/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx b/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx index e4b2d295..c896c59a 100644 --- a/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx +++ b/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx @@ -1,13 +1,11 @@ import * as React from 'react'; import { css } from '@patternfly/react-styles'; import styles from '../../../css/topology-components'; -import pipelinesStyles from '../../../css/topology-pipelines'; import { truncateMiddle } from '../../../utils/truncate-middle'; import { createSvgIdUrl, useCombineRefs, useHover, useSize } from '../../../utils'; import { WithContextMenuProps, WithDndDragProps } from '../../../behavior'; import NodeShadows, { NODE_SHADOW_FILTER_ID_DANGER, NODE_SHADOW_FILTER_ID_HOVER } from '../NodeShadows'; import LabelBadge from './LabelBadge'; -import LabelContextMenu from './LabelContextMenu'; import LabelIcon from './LabelIcon'; import LabelActionIcon from './LabelActionIcon'; import { BadgeLocation, LabelPosition, NodeStatus } from '../../../types'; @@ -22,7 +20,6 @@ type PipelinesNodeLabelProps = { position?: LabelPosition; cornerRadius?: number; status?: NodeStatus; - secondaryLabel?: string; truncateLength?: number; // Defaults to 13 labelIconClass?: string; // Icon to show in label labelIcon?: React.ReactNode; @@ -56,7 +53,6 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ x = 0, y = 0, position = LabelPosition.bottom, - secondaryLabel, status, badge, badgeColor, @@ -74,10 +70,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ dragging, edgeDragging, dropTarget, - onContextMenu, - contextMenuOpen, actionIcon, - actionIconClassName, onActionIconClick, ...other }) => { @@ -85,10 +78,8 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ const refs = useCombineRefs(dragRef, typeof truncateLength === 'number' ? labelHoverRef : undefined); const [textSize, textRef] = useSize([children, truncateLength, className, labelHover]); - const [secondaryTextSize, secondaryTextRef] = useSize([secondaryLabel, truncateLength, className, labelHover]); const [badgeSize, badgeRef] = useSize([badge]); const [actionSize, actionRef] = useSize([actionIcon, paddingX]); - const [contextSize, contextRef] = useSize([onContextMenu, paddingX]); const { width, @@ -99,9 +90,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ badgeStartX, badgeStartY, actionStartX, - contextStartX, iconSpace, - badgeSpace } = React.useMemo(() => { if (!textSize) { return { @@ -122,10 +111,8 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ const height = Math.max(textSize.height, badgeSize?.height ?? 0) + paddingY * 2; const iconSpace = labelIconClass || labelIcon ? (height + paddingY * 0.5) / 2 : 0; const actionSpace = actionIcon && actionSize ? actionSize.width : 0; - const contextSpace = onContextMenu && contextSize ? contextSize.width : 0; - const primaryWidth = iconSpace + badgeSpace + paddingX + textSize.width + contextSpace + paddingX; - const secondaryWidth = secondaryLabel && secondaryTextSize ? secondaryTextSize.width + 2 * paddingX : 0; - const width = Math.max(primaryWidth, secondaryWidth); + const primaryWidth = iconSpace + badgeSpace + paddingX + textSize.width + paddingX; + const width = primaryWidth; let startX: number; let startY: number; @@ -144,8 +131,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ } const actionStartX = iconSpace + badgeSpace + paddingX + textSize.width + paddingX; const contextStartX = actionStartX + actionSpace; - const backgroundHeight = - height + (secondaryLabel && secondaryTextSize ? secondaryTextSize.height + paddingY * 2 : 0); + const backgroundHeight = height; let badgeStartX = 0; let badgeStartY = 0; if (badgeSize) { @@ -182,10 +168,6 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ labelIcon, actionIcon, actionSize, - onContextMenu, - contextSize, - secondaryLabel, - secondaryTextSize, position, x, y @@ -241,18 +223,18 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ {truncateLength > 0 && !labelHover ? truncateMiddle(children, { length: truncateLength }) : children} {textSize && actionIcon && ( - + )} ); diff --git a/packages/module/src/css/topology-pipelines.css b/packages/module/src/css/topology-pipelines.css index b6087688..032712bf 100644 --- a/packages/module/src/css/topology-pipelines.css +++ b/packages/module/src/css/topology-pipelines.css @@ -596,6 +596,10 @@ .action-icon-expanded { fill: var(--pf-v5-global--active-color--100); } +.action-icon-expanded:hover { + fill: var(--pf-v5-global--primary-color--200); +} + .action-icon-expanded > .pf-topology-pipelines__node__action-icon__icon > svg { fill: white; diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx index 48d030c4..05583d1b 100644 --- a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx @@ -6,7 +6,6 @@ import { Dimensions } from '../../../geom'; import { GraphElement, LabelPosition, BadgeLocation, isNode, Node } from '../../../types'; import PipelinesDefaultGroupCollapsed from './PipelinesDefaultGroupCollapsed'; import PipelinesDefaultGroupExpanded from './PipelinesDefaultGroupExpanded'; -import { Badge } from "@patternfly/react-core"; interface PipelinesDefaultGroupProps { /** Additional content added to the node */ From 63370f3e8192ba9946df612757f181948650e0c3 Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Tue, 5 Mar 2024 17:31:02 -0500 Subject: [PATCH 06/13] remove css class to fix build --- .../src/components/nodes/labels/LabelActionIcon.tsx | 2 +- .../groups/PipelinesDefaultGroupCollapsed.tsx | 7 +------ .../groups/PipelinesDefaultGroupExpanded.tsx | 12 +++--------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx index 6d37fe12..1154d0c9 100644 --- a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx +++ b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx @@ -47,7 +47,7 @@ const LabelActionIcon = React.forwardRef( (hoverRef, dragNodeRef, shapeRef); const isHover = hover !== undefined ? hover : hovered; - const childCount = element.getAllNodeChildren().length; - const [badgeSize, badgeRef] = useSize([childCount]); const groupClassName = css( styles.topologyGroup, @@ -108,14 +106,13 @@ const PipelinesDefaultGroupCollapsed: React.FunctionComponent {showLabel && ( : undefined} onActionIconClick={() => onCollapseChange(element, false)} diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx index 3110d1f1..60337158 100644 --- a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx @@ -112,15 +112,12 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent + - + {hulledOutline ? ( ) : ( @@ -244,13 +241,12 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent : undefined} From 37beebd085436f5ab6ec50567be417be7bc985df Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:26:35 -0500 Subject: [PATCH 07/13] fix :hot build --- .../src/demos/stylesDemo/StyleGroup.tsx | 4 ---- .../src/components/groups/DefaultGroupExpanded.tsx | 2 +- .../components/nodes/labels/LabelActionIcon.tsx | 2 +- .../groups/PipelinesDefaultGroupCollapsed.tsx | 14 +++----------- .../groups/PipelinesDefaultGroupExpanded.tsx | 3 +-- 5 files changed, 6 insertions(+), 19 deletions(-) diff --git a/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx b/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx index 2b919f28..125684b4 100644 --- a/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx +++ b/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx @@ -10,10 +10,6 @@ import { WithSelectionProps, PipelinesDefaultGroup, } from '@patternfly/react-topology'; -import AlternateIcon from '@patternfly/react-icons/dist/esm/icons/regions-icon'; -import DefaultIcon from '@patternfly/react-icons/dist/esm/icons/builder-image-icon'; - -const ICON_PADDING = 20; export enum DataTypes { Default, diff --git a/packages/module/src/components/groups/DefaultGroupExpanded.tsx b/packages/module/src/components/groups/DefaultGroupExpanded.tsx index ff9ba920..82aec586 100644 --- a/packages/module/src/components/groups/DefaultGroupExpanded.tsx +++ b/packages/module/src/components/groups/DefaultGroupExpanded.tsx @@ -252,7 +252,7 @@ const DefaultGroupExpanded: React.FunctionComponent = {showLabel && (label || element.getLabel()) && ( ( (hoverRef, dragNodeRef, shapeRef); + const refs = useCombineRefs(hoverRef, dragNodeRef); const isHover = hover !== undefined ? hover : hovered; const groupClassName = css( @@ -86,8 +80,6 @@ const PipelinesDefaultGroupCollapsed: React.FunctionComponent diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx index 60337158..57acac68 100644 --- a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx @@ -240,8 +240,7 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent Date: Fri, 8 Mar 2024 09:15:38 -0500 Subject: [PATCH 08/13] add correct styling to collapsible groups --- .../pipelineComponentFactory.tsx | 4 +- .../pipelinesDemo/useDemoPipelineNodes.tsx | 1 - .../src/demos/stylesDemo/StyleGroup.tsx | 42 ++++++- .../demos/stylesDemo/StylePipelinesGroup.tsx | 64 +++++++++++ .../stylesDemo/stylesComponentFactory.tsx | 2 +- .../groups/DefaultGroupExpanded.tsx | 2 +- .../nodes/labels/LabelActionIcon.tsx | 10 +- .../nodes/labels/PipelinesNodeLabel.tsx | 5 +- .../module/src/css/topology-components.css | 2 +- .../module/src/css/topology-pipelines.css | 105 ++++++++++++++---- .../groups/PipelinesDefaultGroup.tsx | 7 +- .../groups/PipelinesDefaultGroupCollapsed.tsx | 8 +- .../groups/PipelinesDefaultGroupExpanded.tsx | 12 +- 13 files changed, 212 insertions(+), 52 deletions(-) create mode 100644 packages/demo-app-ts/src/demos/stylesDemo/StylePipelinesGroup.tsx diff --git a/packages/demo-app-ts/src/demos/pipelinesDemo/pipelineComponentFactory.tsx b/packages/demo-app-ts/src/demos/pipelinesDemo/pipelineComponentFactory.tsx index 13abf598..446bcd25 100644 --- a/packages/demo-app-ts/src/demos/pipelinesDemo/pipelineComponentFactory.tsx +++ b/packages/demo-app-ts/src/demos/pipelinesDemo/pipelineComponentFactory.tsx @@ -20,7 +20,7 @@ import { import DemoTaskNode from './DemoTaskNode'; import DemoFinallyNode from './DemoFinallyNode'; import DemoTaskGroupEdge from './DemoTaskGroupEdge'; -import StyleGroup from '../stylesDemo/StyleGroup'; +import StylePipelinesGroup from '../stylesDemo/StylePipelinesGroup'; export const GROUPED_EDGE_TYPE = 'GROUPED_EDGE'; @@ -53,7 +53,7 @@ const pipelineComponentFactory: ComponentFactory = ( case DEFAULT_FINALLY_NODE_TYPE: return withContextMenu(() => defaultMenu)(withSelection()(DemoFinallyNode)); case 'task-group': - return withSelection()(StyleGroup); + return withSelection()(StylePipelinesGroup); case 'finally-group': return DefaultTaskGroup; case DEFAULT_SPACER_NODE_TYPE: diff --git a/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx b/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx index 83750083..c2ec480f 100644 --- a/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx +++ b/packages/demo-app-ts/src/demos/pipelinesDemo/useDemoPipelineNodes.tsx @@ -152,7 +152,6 @@ export const useDemoPipelineNodes = ( group: true, label: 'Parallel tasks', data: { - selected: true, badge: 'Label', collapsedWidth: 75, collapsedHeight: 42, diff --git a/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx b/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx index 125684b4..44bc9be0 100644 --- a/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx +++ b/packages/demo-app-ts/src/demos/stylesDemo/StyleGroup.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { + DefaultGroup, GraphElement, Node, observer, @@ -7,9 +8,12 @@ import { ShapeProps, WithContextMenuProps, WithDragNodeProps, - WithSelectionProps, - PipelinesDefaultGroup, + WithSelectionProps } from '@patternfly/react-topology'; +import AlternateIcon from '@patternfly/react-icons/dist/esm/icons/regions-icon'; +import DefaultIcon from '@patternfly/react-icons/dist/esm/icons/builder-image-icon'; + +const ICON_PADDING = 20; export enum DataTypes { Default, @@ -30,13 +34,36 @@ type StyleGroupProps = { const StyleGroup: React.FunctionComponent = ({ element, - collapsedWidth, - collapsedHeight, + onContextMenu, + contextMenuOpen, + collapsedWidth = 75, + collapsedHeight = 75, ...rest }) => { + const groupElement = element as Node; const data = element.getData(); const detailsLevel = element.getGraph().getDetailsLevel(); + const getTypeIcon = (dataType?: DataTypes): any => { + switch (dataType) { + case DataTypes.Alternate: + return AlternateIcon; + default: + return DefaultIcon; + } + }; + + const renderIcon = (): React.ReactNode => { + const iconSize = Math.min(collapsedWidth, collapsedHeight) - ICON_PADDING * 2; + const Component = getTypeIcon(data.dataType); + + return ( + + + + ); + }; + const passedData = React.useMemo(() => { const newData = { ...data }; Object.keys(newData).forEach(key => { @@ -48,7 +75,9 @@ const StyleGroup: React.FunctionComponent = ({ }, [data]); return ( - = ({ {...rest} {...passedData} > - + {groupElement.isCollapsed() ? renderIcon() : null} + ); }; diff --git a/packages/demo-app-ts/src/demos/stylesDemo/StylePipelinesGroup.tsx b/packages/demo-app-ts/src/demos/stylesDemo/StylePipelinesGroup.tsx new file mode 100644 index 00000000..94ae6009 --- /dev/null +++ b/packages/demo-app-ts/src/demos/stylesDemo/StylePipelinesGroup.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; +import { + GraphElement, + Node, + observer, + ScaleDetailsLevel, + ShapeProps, + WithContextMenuProps, + WithDragNodeProps, + WithSelectionProps, + PipelinesDefaultGroup, +} from '@patternfly/react-topology'; + +export enum DataTypes { + Default, + Alternate +} + +type StylePipelinesGroupProps = { + element: GraphElement; + collapsible?: boolean; + collapsedWidth?: number; + collapsedHeight?: number; + onCollapseChange?: (group: Node, collapsed: boolean) => void; + getCollapsedShape?: (node: Node) => React.FunctionComponent; + collapsedShadowOffset?: number; // defaults to 10 +} & WithContextMenuProps & + WithDragNodeProps & + WithSelectionProps; + +const StylePipelinesGroup: React.FunctionComponent = ({ + element, + collapsedWidth, + collapsedHeight, + ...rest +}) => { + const data = element.getData(); + const detailsLevel = element.getGraph().getDetailsLevel(); + + const passedData = React.useMemo(() => { + const newData = { ...data }; + Object.keys(newData).forEach(key => { + if (newData[key] === undefined) { + delete newData[key]; + } + }); + return newData; + }, [data]); + + return ( + + + ); +}; + +export default observer(StylePipelinesGroup); diff --git a/packages/demo-app-ts/src/demos/stylesDemo/stylesComponentFactory.tsx b/packages/demo-app-ts/src/demos/stylesDemo/stylesComponentFactory.tsx index b747aa51..495c6fcb 100644 --- a/packages/demo-app-ts/src/demos/stylesDemo/stylesComponentFactory.tsx +++ b/packages/demo-app-ts/src/demos/stylesDemo/stylesComponentFactory.tsx @@ -140,4 +140,4 @@ const stylesComponentFactory: ComponentFactory = ( } }; -export default stylesComponentFactory; +export default stylesComponentFactory; \ No newline at end of file diff --git a/packages/module/src/components/groups/DefaultGroupExpanded.tsx b/packages/module/src/components/groups/DefaultGroupExpanded.tsx index 82aec586..ff9ba920 100644 --- a/packages/module/src/components/groups/DefaultGroupExpanded.tsx +++ b/packages/module/src/components/groups/DefaultGroupExpanded.tsx @@ -252,7 +252,7 @@ const DefaultGroupExpanded: React.FunctionComponent = {showLabel && (label || element.getLabel()) && ( ( const [iconSize, iconRef] = useSize([icon, paddingX]); const iconWidth = iconSize?.width ?? 0; const iconHeight = iconSize?.height ?? 0; - const [hovered, hoverRef] = useHover(); + // const [hovered, hoverRef] = useHover(); const centerX = x + height / 2 - iconWidth / 2; @@ -45,12 +43,10 @@ const LabelActionIcon = React.forwardRef( {iconSize && ( )} diff --git a/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx b/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx index c896c59a..4483f1a3 100644 --- a/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx +++ b/packages/module/src/components/nodes/labels/PipelinesNodeLabel.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { css } from '@patternfly/react-styles'; -import styles from '../../../css/topology-components'; +// import styles from '../../../css/topology-components'; +import pipelineStyles from '../../../css/topology-pipelines'; import { truncateMiddle } from '../../../utils/truncate-middle'; import { createSvgIdUrl, useCombineRefs, useHover, useSize } from '../../../utils'; import { WithContextMenuProps, WithDndDragProps } from '../../../behavior'; @@ -185,7 +186,7 @@ const PipelinesNodeLabel: React.FunctionComponent = ({ {textSize && ( text { - fill: var(--pf-topology__group__label__text--Fill); + fill: white; font-size: var(--pf-v5-global--FontSize--sm); pointer-events: none; } diff --git a/packages/module/src/css/topology-pipelines.css b/packages/module/src/css/topology-pipelines.css index 032712bf..b9f2a9c3 100644 --- a/packages/module/src/css/topology-pipelines.css +++ b/packages/module/src/css/topology-pipelines.css @@ -101,6 +101,9 @@ --pf-topology-pipelines__status-icon--m-selected--m-running--color: var(--pf-v5-global--Color--light-100); --pf-topology-pipelines__status-icon--m-selected--m-idle--color: var(--pf-v5-global--Color--light-100); + /* pipelines node label */ + --pf-topology-pipelines__node__label__background--fill: var(--pf-v5-global--Color--light-100); + --pf-topology-pipelines__node__label__background--m-selected--fill: var(--pf-v5-global--active-color--100); /* when expression */ --pf-topology-pipelines__when-expression-background--Stroke: var(--pf-v5-global--BorderColor--200); @@ -556,51 +559,113 @@ --pf-topology-pipelines__pill-background--Fill: var(--pf-topology-pipelines__pill-background--m-idle--Stroke); } -.pf-topology__node__label__background.pf-m-disabled { - --pf-topology__node__label__background--Fill: var(--pf-topology__node--m-disabled--Background--Fill); - --pf-topology__node__label__background--Stroke: var(--pf-topology__node--m-disabled--Background--Stroke); -} - .pf-topology__group__action__label { fill: #fff; stroke-width: 1; stroke: var(--pf-v5-global--active-color--100) } -.pf-topology__group.pf-m-selected.pf-m-danger .pf-topology__node__action-icon__icon { - color: white; -} - -.pf-topology__group__label rect.pf-topology__node__label__background { - fill: white; -} - -.pf-topology__group__label>text { +.pf-topology-pipelines__group__label>text { fill: black; font-size: var(--pf-v5-global--FontSize--sm); pointer-events: none; } -.pf-topology__group.pf-m-selected .pf-topology__group__label>text { +.pf-topology-pipelines__group.pf-m-selected .pf-topology-pipelines__group__label>text { fill: white; font-size: var(--pf-v5-global--FontSize--sm); pointer-events: none; } -.action-icon-collapsed { - fill: white; +.action-icon-collapsed > .pf-topology-pipelines__node__action-icon__icon > svg { + fill: var(--pf-v5-global--active-color--100);; +} + +.action-icon-collapsed .pf-topology-pipelines__node__action-icon__background:hover { stroke: var(--pf-v5-global--active-color--100); - pointer-events: all; + stroke-width: 2px; } -.action-icon-expanded { + +.pf-topology__node__action-icon.action-icon-expanded .pf-topology-pipelines__node__action-icon__background { fill: var(--pf-v5-global--active-color--100); } -.action-icon-expanded:hover { +.action-icon-expanded .pf-topology-pipelines__node__action-icon__background:hover { fill: var(--pf-v5-global--primary-color--200); } + + .action-icon-expanded > .pf-topology-pipelines__node__action-icon__icon > svg { fill: white; } + +.pf-topology-pipelines__node__background { + fill: var(--pf-topology__node__background--Fill); + stroke-width: var(--pf-topology__node__background--StrokeWidth); + stroke: var(--pf-topology__node__background--Stroke); +} + +.pf-topology-pipelines__node__action-icon { + cursor: pointer; + fill: var(--pf-v5-global--Color--light-100); +} + +.pf-topology-pipelines__node__label__background { + fill: var(--pf-topology-pipelines__node__label__background--fill); +} + +/* .pf-topology-pipelines__group.pf-m-selected .pf-topology-pipelines__node__label__background { + fill: var(--pf-topology-pipelines__node__label__background--m-selected--fill); +} */ + +.pf-topology-pipelines__group-inner.pf-m-selected .pf-topology__group__background { + --pf-topology__group__background--Fill: var(--pf-topology__group--m-selected--topology__group__background--Fill); + --pf-topology__group__background--Stroke: var(--pf-topology__group--m-selected--topology__group__background--Stroke); +} + +.pf-topology-pipelines__group-inner.pf-m-hover .pf-topology__group__background { + --pf-topology__group__background--Stroke: var(--pf-topology__group--m-hover--topology__group__background--Stroke); + --pf-topology__group__label__node__label__background--Stroke: var(--pf-topology__group--m-hover--label__node__label__background--Stroke); +} + +.pf-topology-pipelines__group-inner.pf-m-hover.pf-m-selected .pf-topology__group__background { + --pf-topology__group__background--Fill: var(--pf-topology__group--m-selected--topology__group__background--Fill); + --pf-topology__group__background--Stroke: var(--pf-topology__group--m-selected--m-hover--topology__group__background--Stroke); + --pf-topology__group__label__node__label__background--Stroke: var(--pf-topology__group--m-hover--label__node__label__background--Stroke); +} + +.pf-topology__node__action-icon { + fill: white; + stroke: var(--pf-v5-global--active-color--100) +} + +.action-icon-expanded svg { + fill: white; +} + +rect.pf-topology-pipelines__node__label__background.pf-m-selected + text { + fill: white; +} + +.pf-topology-pipelines__group.pf-m-selected .pf-topology-pipelines__node__label__background { + fill: var(--pf-v5-global--active-color--100); +} + +.pf-topology-pipelines__group.pf-m-selected .pf-topology-pipelines__node__label__background + text { + fill: white; +} + +.pf-topology-pipelines__group.pf-m-selected pf-topology-pipelines__group__label text { + fill: white; +} + +.pf-topology-pipelines__group.pf-m-selected .pf-topology-pipelines__group__background { + --pf-topology__group__background--Fill: var(--pf-topology__group--m-selected--topology__group__background--Fill); + --pf-topology__group__background--Stroke: var(--pf-topology__group--m-selected--topology__group__background--Stroke); +} + +/* .pf-topology-pipelines__group.pf-m-selected text { + fill: white; +} */ \ No newline at end of file diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx index 05583d1b..33c6bb18 100644 --- a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroup.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { observer } from 'mobx-react'; -import { OnSelect, WithDndDragProps, ConnectDragSource, ConnectDropTarget } from '../../../behavior'; +import { OnSelect, WithDndDragProps, ConnectDragSource, ConnectDropTarget, WithSelectionProps } from '../../../behavior'; import { ShapeProps } from '../../../components'; import { Dimensions } from '../../../geom'; import { GraphElement, LabelPosition, BadgeLocation, isNode, Node } from '../../../types'; @@ -84,7 +84,7 @@ interface PipelinesDefaultGroupProps { hulledOutline?: boolean; } -type PipelinesDefaultGroupInnerProps = Omit & { element: Node }; +type PipelinesDefaultGroupInnerProps = Omit & { element: Node } & WithSelectionProps; const PipelinesDefaultGroupInner: React.FunctionComponent = observer( ({ className, element, onCollapseChange, ...rest }) => { @@ -116,6 +116,9 @@ const PipelinesDefaultGroupInner: React.FunctionComponent ); diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx index 00fcf046..bc4a992f 100644 --- a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupCollapsed.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { observer } from 'mobx-react'; import { css } from '@patternfly/react-styles'; -import styles from '@patternfly/react-topology/src/css/topology-components'; +import styles from '@patternfly/react-topology/src/css/topology-pipelines'; import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-alt-icon'; import { WithDragNodeProps, WithSelectionProps, WithDndDropProps, WithContextMenuProps, useDragNode } from "../../../behavior"; import { CollapsibleGroupProps, Stadium, Layer, PipelinesNodeLabel } from "../../../components"; @@ -72,7 +72,7 @@ const PipelinesDefaultGroupCollapsed: React.FunctionComponent {showLabel && ( ().padding ?? 17); - const hullPadding = (point: PointWithSize | PointTuple) => (point[2] || 0) + padding; + const extraPadding = labelPosition === LabelPosition.top ? 15 : 0; // add extra padding if label on top + const hullPadding = (point: PointWithSize | PointTuple) => (point[2] || 0) + padding + extraPadding; if (!droppable || (hulledOutline && !pathRef.current) || (!hulledOutline && !boxRef.current) || !labelLocation.current) { const children = element.getNodes().filter(c => c.isVisible()); @@ -200,7 +202,7 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent Date: Fri, 8 Mar 2024 09:25:00 -0500 Subject: [PATCH 09/13] revert action label styling to default, adds isIconExternal prop --- .../src/components/nodes/labels/LabelActionIcon.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx index 08b3329b..a799d196 100644 --- a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx +++ b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx @@ -20,12 +20,11 @@ interface LabelActionIconProps { } const LabelActionIcon = React.forwardRef( - ({ icon, isIconExternal, onClick, className, x, y, paddingX, height}) => { + ({ icon, isIconExternal, onClick, className, x, y, paddingX, height, iconOffsetX = 0, iconOffsetY = 0 }) => { const [iconSize, iconRef] = useSize([icon, paddingX]); const iconWidth = iconSize?.width ?? 0; - const iconHeight = iconSize?.height ?? 0; - // const [hovered, hoverRef] = useHover(); - + const iconHeight = iconSize?.height ?? 0; + const iconY = (height - iconHeight) / 2; const centerX = x + height / 2 - iconWidth / 2; const centerY = y + height / 2 - iconHeight / 2; @@ -46,13 +45,13 @@ const LabelActionIcon = React.forwardRef( className={isIconExternal ? css(pipelineStyles.topologyPipelinesNodeActionIconBackground) : css(styles.topologyNodeActionIconBackground)} x={x} y={y} - width={height} + width={isIconExternal ? height :iconWidth + paddingX * 2} height={height} /> )} {icon} From 435f5c2d2a0cd48055ad033b23995819700d2aca Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Fri, 8 Mar 2024 10:34:53 -0500 Subject: [PATCH 10/13] add collapsible groups to subgroups layout --- .../pipelineGroupsDemo/DemoTaskGroup.tsx | 34 +++++++++++++++---- .../groups/PipelinesDefaultGroupExpanded.tsx | 8 ++--- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx index 09cc7430..28806320 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx @@ -3,37 +3,57 @@ import { observer } from 'mobx-react'; import { AnchorEnd, DagreLayoutOptions, - DefaultGroup, + PipelinesDefaultGroup, GraphElement, isNode, LabelPosition, Node, TOP_TO_BOTTOM, useAnchor, + WithContextMenuProps, + WithSelectionProps, + ShapeProps, + WithDragNodeProps, } from '@patternfly/react-topology'; import TaskGroupSourceAnchor from './TaskGroupSourceAnchor'; import TaskGroupTargetAnchor from './TaskGroupTargetAnchor'; -interface DemoTaskNodeProps { +type DemoTaskGroupProps = { element: GraphElement; -} + collapsible?: boolean; + collapsedWidth?: number; + collapsedHeight?: number; + onCollapseChange?: (group: Node, collapsed: boolean) => void; + getCollapsedShape?: (node: Node) => React.FunctionComponent; + collapsedShadowOffset?: number; // defaults to 10 +} & WithContextMenuProps & + WithDragNodeProps & + WithSelectionProps; -const DemoTaskGroup: React.FunctionComponent = ({ element, ...rest }) => { +const DemoTaskGroup: React.FunctionComponent = ({ element, collapsedWidth, collapsedHeight, ...rest }) => { const verticalLayout = (element.getGraph().getLayoutOptions?.() as DagreLayoutOptions)?.rankdir === TOP_TO_BOTTOM; useAnchor( - React.useCallback((node: Node) =>new TaskGroupSourceAnchor(node, verticalLayout), [verticalLayout]), + React.useCallback((node: Node) => new TaskGroupSourceAnchor(node, verticalLayout), [verticalLayout]), AnchorEnd.source ); useAnchor( - React.useCallback((node: Node) => new TaskGroupTargetAnchor(node, verticalLayout),[verticalLayout]), + React.useCallback((node: Node) => new TaskGroupTargetAnchor(node, verticalLayout), [verticalLayout]), AnchorEnd.target ); if (!isNode(element)) { return null; } return ( - + ); }; diff --git a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx index fb09494d..965cfb98 100644 --- a/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx +++ b/packages/module/src/pipelines/components/groups/PipelinesDefaultGroupExpanded.tsx @@ -154,7 +154,7 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent().padding ?? 17); - const extraPadding = labelPosition === LabelPosition.top ? 15 : 0; // add extra padding if label on top + const extraPadding = labelPosition === LabelPosition.top ? 25 : 25; // add extra padding if label on top const hullPadding = (point: PointWithSize | PointTuple) => (point[2] || 0) + padding + extraPadding; if (!droppable || (hulledOutline && !pathRef.current) || (!hulledOutline && !boxRef.current) || !labelLocation.current) { @@ -221,12 +221,12 @@ const PipelinesDefaultGroupExpanded: React.FunctionComponent From 2f6cf1e3eaa2d1e1206b717d085c7d2c3be5e32b Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:31:18 -0500 Subject: [PATCH 11/13] add ref to revert to default label styling --- .../module/src/components/nodes/labels/LabelActionIcon.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx index a799d196..8cc17eae 100644 --- a/packages/module/src/components/nodes/labels/LabelActionIcon.tsx +++ b/packages/module/src/components/nodes/labels/LabelActionIcon.tsx @@ -20,7 +20,7 @@ interface LabelActionIconProps { } const LabelActionIcon = React.forwardRef( - ({ icon, isIconExternal, onClick, className, x, y, paddingX, height, iconOffsetX = 0, iconOffsetY = 0 }) => { + ({ icon, isIconExternal, onClick, className, x, y, paddingX, height, iconOffsetX = 0, iconOffsetY = 0 }, actionRef) => { const [iconSize, iconRef] = useSize([icon, paddingX]); const iconWidth = iconSize?.width ?? 0; const iconHeight = iconSize?.height ?? 0; @@ -42,6 +42,7 @@ const LabelActionIcon = React.forwardRef( {iconSize && ( Date: Fri, 8 Mar 2024 14:14:32 -0500 Subject: [PATCH 12/13] add withSelection, update padding --- .../demos/pipelineGroupsDemo/createDemoPipelineGroupsNodes.ts | 2 +- .../demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/createDemoPipelineGroupsNodes.ts b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/createDemoPipelineGroupsNodes.ts index 383c99cd..9f66851b 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/createDemoPipelineGroupsNodes.ts +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/createDemoPipelineGroupsNodes.ts @@ -7,7 +7,7 @@ import { export const NODE_PADDING_VERTICAL = 15; export const NODE_PADDING_HORIZONTAL = 15; -export const GROUP_PADDING_VERTICAL = 15; +export const GROUP_PADDING_VERTICAL = 50; export const GROUP_PADDING_HORIZONTAL = 25; export const DEFAULT_TASK_WIDTH = 180; diff --git a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx index edc7b214..a4882a2e 100644 --- a/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx +++ b/packages/demo-app-ts/src/demos/pipelineGroupsDemo/pipelineGroupsComponentFactory.tsx @@ -22,7 +22,7 @@ const pipelineGroupsComponentFactory: ComponentFactory = ( } switch (type) { case 'Execution': - return DemoTaskGroup; + return withSelection()(DemoTaskGroup); case 'Task': return withSelection()(DemoTaskNode); case DEFAULT_SPACER_NODE_TYPE: From 302848431d3e344f0a155be6e2e081995f5602fa Mon Sep 17 00:00:00 2001 From: Jenny <32821331+jenny-s51@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:18:29 -0500 Subject: [PATCH 13/13] clean up css --- packages/module/src/css/topology-pipelines.css | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/module/src/css/topology-pipelines.css b/packages/module/src/css/topology-pipelines.css index b9f2a9c3..1c45b6e3 100644 --- a/packages/module/src/css/topology-pipelines.css +++ b/packages/module/src/css/topology-pipelines.css @@ -616,10 +616,6 @@ fill: var(--pf-topology-pipelines__node__label__background--fill); } -/* .pf-topology-pipelines__group.pf-m-selected .pf-topology-pipelines__node__label__background { - fill: var(--pf-topology-pipelines__node__label__background--m-selected--fill); -} */ - .pf-topology-pipelines__group-inner.pf-m-selected .pf-topology__group__background { --pf-topology__group__background--Fill: var(--pf-topology__group--m-selected--topology__group__background--Fill); --pf-topology__group__background--Stroke: var(--pf-topology__group--m-selected--topology__group__background--Stroke); @@ -637,16 +633,16 @@ } .pf-topology__node__action-icon { - fill: white; + fill: var(--pf-v5-global--Color--light-100);; stroke: var(--pf-v5-global--active-color--100) } .action-icon-expanded svg { - fill: white; + fill: var(--pf-v5-global--Color--light-100); } rect.pf-topology-pipelines__node__label__background.pf-m-selected + text { - fill: white; + fill: var(--pf-v5-global--Color--light-100); } .pf-topology-pipelines__group.pf-m-selected .pf-topology-pipelines__node__label__background { @@ -654,18 +650,14 @@ rect.pf-topology-pipelines__node__label__background.pf-m-selected + text { } .pf-topology-pipelines__group.pf-m-selected .pf-topology-pipelines__node__label__background + text { - fill: white; + fill: var(--pf-v5-global--Color--light-100); } .pf-topology-pipelines__group.pf-m-selected pf-topology-pipelines__group__label text { - fill: white; + fill: var(--pf-v5-global--Color--light-100); } .pf-topology-pipelines__group.pf-m-selected .pf-topology-pipelines__group__background { --pf-topology__group__background--Fill: var(--pf-topology__group--m-selected--topology__group__background--Fill); --pf-topology__group__background--Stroke: var(--pf-topology__group--m-selected--topology__group__background--Stroke); } - -/* .pf-topology-pipelines__group.pf-m-selected text { - fill: white; -} */ \ No newline at end of file