From 258a4526f02e4f4980e575212c8ea744d2cf0a46 Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Tue, 24 Oct 2023 13:17:18 -0300 Subject: [PATCH 1/5] added property to set whether the outline is hull or rect --- .../demo-app-ts/src/components/StyleGroup.tsx | 1 + .../src/components/groups/DefaultGroup.tsx | 2 + .../groups/DefaultGroupExpanded.tsx | 79 +++++++++++-------- 3 files changed, 49 insertions(+), 33 deletions(-) diff --git a/packages/demo-app-ts/src/components/StyleGroup.tsx b/packages/demo-app-ts/src/components/StyleGroup.tsx index a566ac86..6e8e7333 100644 --- a/packages/demo-app-ts/src/components/StyleGroup.tsx +++ b/packages/demo-app-ts/src/components/StyleGroup.tsx @@ -84,6 +84,7 @@ const StyleGroup: React.FunctionComponent = ({ collapsedWidth={collapsedWidth} collapsedHeight={collapsedHeight} showLabel={detailsLevel === ScaleDetailsLevel.high} + hulledOutline={true} {...rest} {...passedData} > diff --git a/packages/module/src/components/groups/DefaultGroup.tsx b/packages/module/src/components/groups/DefaultGroup.tsx index a188d6ab..1beab616 100644 --- a/packages/module/src/components/groups/DefaultGroup.tsx +++ b/packages/module/src/components/groups/DefaultGroup.tsx @@ -77,6 +77,8 @@ interface DefaultGroupProps { 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 }; diff --git a/packages/module/src/components/groups/DefaultGroupExpanded.tsx b/packages/module/src/components/groups/DefaultGroupExpanded.tsx index 9087d741..84778c6a 100644 --- a/packages/module/src/components/groups/DefaultGroupExpanded.tsx +++ b/packages/module/src/components/groups/DefaultGroupExpanded.tsx @@ -19,6 +19,7 @@ import { WithSelectionProps } from '../../behavior'; import { CollapsibleGroupProps } from './types'; +import Rect from '../../geom/Rect'; type DefaultGroupExpandedProps = { className?: string; @@ -41,6 +42,7 @@ type DefaultGroupExpandedProps = { labelIconClass?: string; // Icon to show in label labelIcon?: string; labelIconPadding?: number; + hulledOutline?: boolean; } & CollapsibleGroupProps & WithDragNodeProps & WithSelectionProps & WithDndDropProps & WithContextMenuProps; type PointWithSize = [number, number, number]; @@ -96,7 +98,8 @@ const DefaultGroupExpanded: React.FunctionComponent = labelIconClass, labelIcon, labelIconPadding, - onCollapseChange + onCollapseChange, + hulledOutline, }) => { const [hovered, hoverRef] = useHover(); const [labelHover, labelHoverRef] = useHover(); @@ -107,6 +110,8 @@ const DefaultGroupExpanded: React.FunctionComponent = const outlineRef = useCombineRefs(dndDropRef, anchorRef); const labelLocation = React.useRef(); const pathRef = React.useRef(); + const boxRef = React.useRef(null); + const nodeElement = element as Node; let parent = element.getParent(); let altGroup = false; @@ -118,40 +123,47 @@ const DefaultGroupExpanded: React.FunctionComponent = // cast to number and coerce const padding = maxPadding(element.getStyle().padding ?? 17); const hullPadding = (point: PointWithSize | PointTuple) => (point[2] || 0) + padding; - - if (!droppable || !pathRef.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) { + if (!droppable || !pathRef.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); + } + }); + const hullPoints: (PointWithSize | PointTuple)[] = + points.length > 2 ? polygonHull(points as PointTuple[]) : (points as PointTuple[]); + if (!hullPoints) { + return null; } - }); - 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); + // 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[]); + // Compute the location of the group label. + labelLocation.current = computeLabelLocation(hullPoints as PointWithSize[]); + } + } else { + if (!droppable || !boxRef.current) { + // change the box only when not dragging + boxRef.current = nodeElement.getBounds(); + } } const groupClassName = css( @@ -177,7 +189,8 @@ const DefaultGroupExpanded: React.FunctionComponent = - + {hulledOutline && } + {!hulledOutline && } {showLabel && (label || element.getLabel()) && ( From c41a3440af24a0fcf5b4dd76c5046b9419ff539e Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Date: Tue, 24 Oct 2023 13:46:05 -0400 Subject: [PATCH 2/5] Fix for label location --- .../demo-app-ts/src/components/StyleGroup.tsx | 2 +- .../groups/DefaultGroupExpanded.tsx | 62 ++++++++++--------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/demo-app-ts/src/components/StyleGroup.tsx b/packages/demo-app-ts/src/components/StyleGroup.tsx index 6e8e7333..80a11a0e 100644 --- a/packages/demo-app-ts/src/components/StyleGroup.tsx +++ b/packages/demo-app-ts/src/components/StyleGroup.tsx @@ -84,7 +84,7 @@ const StyleGroup: React.FunctionComponent = ({ collapsedWidth={collapsedWidth} collapsedHeight={collapsedHeight} showLabel={detailsLevel === ScaleDetailsLevel.high} - hulledOutline={true} + hulledOutline={false} {...rest} {...passedData} > diff --git a/packages/module/src/components/groups/DefaultGroupExpanded.tsx b/packages/module/src/components/groups/DefaultGroupExpanded.tsx index 84778c6a..f409abc4 100644 --- a/packages/module/src/components/groups/DefaultGroupExpanded.tsx +++ b/packages/module/src/components/groups/DefaultGroupExpanded.tsx @@ -123,30 +123,31 @@ const DefaultGroupExpanded: React.FunctionComponent = // cast to number and coerce const padding = maxPadding(element.getStyle().padding ?? 17); const hullPadding = (point: PointWithSize | PointTuple) => (point[2] || 0) + padding; - - if (hulledOutline) { - if (!droppable || !pathRef.current || !labelLocation.current) { - const children = element.getNodes().filter(c => c.isVisible()); - if (children.length === 0) { - return null; + + if (!droppable || !pathRef.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); } - 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) { @@ -158,11 +159,9 @@ const DefaultGroupExpanded: React.FunctionComponent = // Compute the location of the group label. labelLocation.current = computeLabelLocation(hullPoints as PointWithSize[]); - } - } else { - if (!droppable || !boxRef.current) { - // change the box only when not dragging + } else { boxRef.current = nodeElement.getBounds(); + labelLocation.current = [boxRef.current.x + boxRef.current.width / 2, boxRef.current.y + boxRef.current.height, 0]; } } @@ -189,15 +188,18 @@ const DefaultGroupExpanded: React.FunctionComponent = - {hulledOutline && } - {!hulledOutline && } + {hulledOutline ? ( + + ) : ( + + )} {showLabel && (label || element.getLabel()) && ( Date: Wed, 25 Oct 2023 09:41:23 -0300 Subject: [PATCH 3/5] hulled outline set to true --- packages/demo-app-ts/src/components/StyleGroup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/demo-app-ts/src/components/StyleGroup.tsx b/packages/demo-app-ts/src/components/StyleGroup.tsx index 80a11a0e..6e8e7333 100644 --- a/packages/demo-app-ts/src/components/StyleGroup.tsx +++ b/packages/demo-app-ts/src/components/StyleGroup.tsx @@ -84,7 +84,7 @@ const StyleGroup: React.FunctionComponent = ({ collapsedWidth={collapsedWidth} collapsedHeight={collapsedHeight} showLabel={detailsLevel === ScaleDetailsLevel.high} - hulledOutline={false} + hulledOutline={true} {...rest} {...passedData} > From e294bba108a14a67f6808d8aed33d454449d03da Mon Sep 17 00:00:00 2001 From: Leandro Beretta Date: Wed, 25 Oct 2023 10:58:35 -0300 Subject: [PATCH 4/5] addressing feedback --- packages/demo-app-ts/src/components/StyleGroup.tsx | 1 - .../module/src/components/groups/DefaultGroupExpanded.tsx | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/demo-app-ts/src/components/StyleGroup.tsx b/packages/demo-app-ts/src/components/StyleGroup.tsx index 6e8e7333..a566ac86 100644 --- a/packages/demo-app-ts/src/components/StyleGroup.tsx +++ b/packages/demo-app-ts/src/components/StyleGroup.tsx @@ -84,7 +84,6 @@ const StyleGroup: React.FunctionComponent = ({ collapsedWidth={collapsedWidth} collapsedHeight={collapsedHeight} showLabel={detailsLevel === ScaleDetailsLevel.high} - hulledOutline={true} {...rest} {...passedData} > diff --git a/packages/module/src/components/groups/DefaultGroupExpanded.tsx b/packages/module/src/components/groups/DefaultGroupExpanded.tsx index f409abc4..198ef5b7 100644 --- a/packages/module/src/components/groups/DefaultGroupExpanded.tsx +++ b/packages/module/src/components/groups/DefaultGroupExpanded.tsx @@ -99,7 +99,7 @@ const DefaultGroupExpanded: React.FunctionComponent = labelIcon, labelIconPadding, onCollapseChange, - hulledOutline, + hulledOutline = true, }) => { const [hovered, hoverRef] = useHover(); const [labelHover, labelHoverRef] = useHover(); @@ -111,7 +111,6 @@ const DefaultGroupExpanded: React.FunctionComponent = const labelLocation = React.useRef(); const pathRef = React.useRef(); const boxRef = React.useRef(null); - const nodeElement = element as Node; let parent = element.getParent(); let altGroup = false; @@ -160,7 +159,7 @@ const DefaultGroupExpanded: React.FunctionComponent = // Compute the location of the group label. labelLocation.current = computeLabelLocation(hullPoints as PointWithSize[]); } else { - boxRef.current = nodeElement.getBounds(); + boxRef.current = element.getBounds(); labelLocation.current = [boxRef.current.x + boxRef.current.width / 2, boxRef.current.y + boxRef.current.height, 0]; } } From 8e26e7af8a1920fb091d40b500f169e3cf1e9ccf Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Date: Fri, 10 Nov 2023 09:13:59 -0500 Subject: [PATCH 5/5] Fix for dragging out of rect group --- packages/demo-app-ts/src/data/generator.ts | 26 +++++++++++++++++-- .../demo-app-ts/src/demos/TopologyPackage.tsx | 4 +-- .../src/utils/useTopologyOptions.tsx | 8 ++++++ .../groups/DefaultGroupExpanded.tsx | 2 +- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/demo-app-ts/src/data/generator.ts b/packages/demo-app-ts/src/data/generator.ts index 3917a208..eca3bdbc 100644 --- a/packages/demo-app-ts/src/data/generator.ts +++ b/packages/demo-app-ts/src/data/generator.ts @@ -33,6 +33,7 @@ export interface GeneratorNodeOptions { nodeIcons?: boolean; smallNodes?: boolean; contextMenus?: boolean; + hulledOutline?: boolean; } export interface GeneratorEdgeOptions { @@ -53,7 +54,8 @@ export const DefaultNodeOptions: GeneratorNodeOptions = { nodeBadges: false, nodeIcons: false, smallNodes: false, - contextMenus: false + contextMenus: false, + hulledOutline: true }; export const DefaultEdgeOptions: GeneratorEdgeOptions = { @@ -76,6 +78,7 @@ export const getNodeOptions = ( showStatusDecorator?: boolean; showDecorators?: boolean; showContextMenu?: boolean; + hulledOutline?: boolean; labelIconClass?: string; labelIcon?: React.ComponentClass; } => { @@ -91,6 +94,7 @@ export const getNodeOptions = ( showStatusDecorator: nodeCreationOptions.statusDecorators, showDecorators: nodeCreationOptions.showDecorators, showContextMenu: nodeCreationOptions.contextMenus, + hulledOutline: nodeCreationOptions.hulledOutline, labelIconClass, labelIcon }; @@ -129,6 +133,23 @@ export const generateEdge = ( tagStatus: options.edgeStatuses[index % options.edgeStatuses.length] }); +export const updateGroup = (group: NodeModel, nodeCreationOptions: GeneratorNodeOptions): NodeModel => { + return { + ...group, + data: { + badge: nodeCreationOptions.nodeBadges ? 'GN' : undefined, + badgeColor: '#F2F0FC', + badgeTextColor: '#5752d1', + badgeBorderColor: '#CBC1FF', + collapsedWidth: 75, + collapsedHeight: 75, + showContextMenu: nodeCreationOptions.contextMenus, + collapsible: true, + hulledOutline: nodeCreationOptions.hulledOutline + } + }; +}; + export const generateDataModel = ( numNodes: number, numGroups: number, @@ -167,7 +188,8 @@ export const generateDataModel = ( collapsedWidth: 75, collapsedHeight: 75, showContextMenu: nodeCreationOptions.contextMenus, - collapsible: true + collapsible: true, + hulledOutline: nodeOptions.hulledOutline } }; if (level === groupDepth) { diff --git a/packages/demo-app-ts/src/demos/TopologyPackage.tsx b/packages/demo-app-ts/src/demos/TopologyPackage.tsx index 1f2fa567..67f27cdc 100644 --- a/packages/demo-app-ts/src/demos/TopologyPackage.tsx +++ b/packages/demo-app-ts/src/demos/TopologyPackage.tsx @@ -26,7 +26,7 @@ import { import stylesComponentFactory from '../components/stylesComponentFactory'; import defaultLayoutFactory from '../layouts/defaultLayoutFactory'; import defaultComponentFactory from '../components/defaultComponentFactory'; -import { generateDataModel, generateEdge, generateNode } from '../data/generator'; +import { generateDataModel, generateEdge, generateNode, updateGroup } from '../data/generator'; import { useTopologyOptions } from '../utils/useTopologyOptions'; import { Tab, Tabs, TabTitleText } from '@patternfly/react-core'; @@ -157,7 +157,7 @@ const TopologyViewComponent: React.FunctionComponent if (nodes.length) { const updatedNodes: NodeModel[] = nodes.map((node, index) => { if (node.group) { - return node; + return updateGroup(node, nodeOptions); } return { ...node, diff --git a/packages/demo-app-ts/src/utils/useTopologyOptions.tsx b/packages/demo-app-ts/src/utils/useTopologyOptions.tsx index d9cb27a7..5c7c23a5 100644 --- a/packages/demo-app-ts/src/utils/useTopologyOptions.tsx +++ b/packages/demo-app-ts/src/utils/useTopologyOptions.tsx @@ -177,6 +177,14 @@ export const useTopologyOptions = ( > Context Menus + setNodeOptions((prev) => ({ ...prev, hulledOutline: !prev.hulledOutline }))} + > + Rectangle Groups + ); diff --git a/packages/module/src/components/groups/DefaultGroupExpanded.tsx b/packages/module/src/components/groups/DefaultGroupExpanded.tsx index 198ef5b7..1ad41478 100644 --- a/packages/module/src/components/groups/DefaultGroupExpanded.tsx +++ b/packages/module/src/components/groups/DefaultGroupExpanded.tsx @@ -123,7 +123,7 @@ const DefaultGroupExpanded: React.FunctionComponent = const padding = maxPadding(element.getStyle().padding ?? 17); const hullPadding = (point: PointWithSize | PointTuple) => (point[2] || 0) + padding; - if (!droppable || !pathRef.current || !labelLocation.current) { + if (!droppable || (hulledOutline && !pathRef.current) || (!hulledOutline && !boxRef.current) || !labelLocation.current) { const children = element.getNodes().filter(c => c.isVisible()); if (children.length === 0) { return null;