diff --git a/packages/demo-app-ts/src/data/generator.ts b/packages/demo-app-ts/src/data/generator.ts index 3917a20..eca3bdb 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 1f2fa56..67f27cd 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 d9cb27a..5c7c23a 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/DefaultGroup.tsx b/packages/module/src/components/groups/DefaultGroup.tsx index a188d6a..1beab61 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 9087d74..1ad4147 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 = true, }) => { const [hovered, hoverRef] = useHover(); const [labelHover, labelHoverRef] = useHover(); @@ -107,6 +110,7 @@ const DefaultGroupExpanded: React.FunctionComponent = 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; @@ -119,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; @@ -141,17 +145,23 @@ const DefaultGroupExpanded: React.FunctionComponent = 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; - } - // change the box only when not dragging - pathRef.current = hullPath(hullPoints as PointTuple[], hullPadding); + if (hulledOutline) { + const hullPoints: (PointWithSize | PointTuple)[] = + points.length > 2 ? polygonHull(points as PointTuple[]) : (points as PointTuple[]); + if (!hullPoints) { + return null; + } - // Compute the location of the group label. - labelLocation.current = computeLabelLocation(hullPoints as PointWithSize[]); + // 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( @@ -177,14 +187,18 @@ const DefaultGroupExpanded: React.FunctionComponent = - + {hulledOutline ? ( + + ) : ( + + )} {showLabel && (label || element.getLabel()) && (