Skip to content

Commit

Permalink
feat(group labels): Add ability to show group labels on hover (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-phillips-18 authored Jun 7, 2024
1 parent 018c394 commit 280ca91
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 98 deletions.
43 changes: 18 additions & 25 deletions packages/demo-app-ts/src/demos/pipelineGroupsDemo/DemoTaskGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ import {
EdgeCreationTypes,
useHover,
ScaleDetailsLevel,
DEFAULT_LAYER,
Layer,
TOP_LAYER,
GROUPS_LAYER,
RunStatus
} from '@patternfly/react-topology';
import { DEFAULT_TASK_HEIGHT, GROUP_TASK_WIDTH } from './createDemoPipelineGroupsNodes';
Expand All @@ -38,29 +34,26 @@ const DemoTaskGroup: React.FunctionComponent<DemoTaskGroupProps> = ({ element, .
if (!isNode(element)) {
return null;
}
const groupLayer = element.isCollapsed() ? DEFAULT_LAYER : GROUPS_LAYER;

return (
<Layer id={detailsLevel !== ScaleDetailsLevel.high && hover ? TOP_LAYER : groupLayer}>
<g ref={hoverRef}>
<DefaultTaskGroup
labelPosition={verticalLayout ? LabelPosition.top : LabelPosition.bottom}
collapsible
collapsedWidth={GROUP_TASK_WIDTH}
collapsedHeight={DEFAULT_TASK_HEIGHT}
element={element as Node}
recreateLayoutOnCollapseChange
getEdgeCreationTypes={getEdgeCreationTypes}
scaleNode={hover && detailsLevel !== ScaleDetailsLevel.high}
showLabel={detailsLevel === ScaleDetailsLevel.high}
hideDetailsAtMedium
showStatusState
status={data.status}
hiddenDetailsShownStatuses={[RunStatus.Succeeded]}
{...rest}
/>
</g>
</Layer>
<g id="group-hover-ref" ref={hoverRef}>
<DefaultTaskGroup
labelPosition={verticalLayout ? LabelPosition.top : LabelPosition.bottom}
collapsible
collapsedWidth={GROUP_TASK_WIDTH}
collapsedHeight={DEFAULT_TASK_HEIGHT}
element={element as Node}
recreateLayoutOnCollapseChange
getEdgeCreationTypes={getEdgeCreationTypes}
scaleNode={hover && detailsLevel !== ScaleDetailsLevel.high}
showLabelOnHover
hideDetailsAtMedium
showStatusState
status={data.status}
hiddenDetailsShownStatuses={[RunStatus.Succeeded]}
{...rest}
/>
</g>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const DemoGroup: React.FunctionComponent<DemoGroupProps> = ({ element, onContext
collapsedHeight={DEFAULT_NODE_SIZE}
showLabel={detailsLevel === ScaleDetailsLevel.high}
hulledOutline={options.hulledOutline}
showLabelOnHover
>
{groupElement.isCollapsed() ? renderIcon() : null}
</DefaultGroup>
Expand Down
2 changes: 2 additions & 0 deletions packages/module/src/components/groups/DefaultGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ interface DefaultGroupProps {
secondaryLabel?: string;
/** Flag to show the label */
showLabel?: boolean; // Defaults to true
/** Flag to show the label when hovering (effects expanded only) */
showLabelOnHover?: boolean;
/** Position of the label, top or bottom. Defaults to element.getLabelPosition() or bottom */
labelPosition?: LabelPosition;
/** The maximum length of the label before truncation */
Expand Down
80 changes: 45 additions & 35 deletions packages/module/src/components/groups/DefaultGroupExpanded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type DefaultGroupExpandedProps = {
label?: string; // Defaults to element.getLabel()
secondaryLabel?: string;
showLabel?: boolean; // Defaults to true
showLabelOnHover?: boolean;
truncateLength?: number; // Defaults to 13
badge?: string;
badgeColor?: string;
Expand Down Expand Up @@ -119,6 +120,7 @@ const DefaultGroupExpanded: React.FunctionComponent<DefaultGroupExpandedProps> =
label,
secondaryLabel,
showLabel = true,
showLabelOnHover,
truncateLength,
dndDropRef,
droppable,
Expand All @@ -141,11 +143,11 @@ const DefaultGroupExpanded: React.FunctionComponent<DefaultGroupExpandedProps> =
onCollapseChange,
hulledOutline = true
}) => {
const [hovered, hoverRef] = useHover();
const [labelHover, labelHoverRef] = useHover();
const [hovered, hoverRef] = useHover(200, 500);
const [labelHover, labelHoverRef] = useHover(0);
const dragLabelRef = useDragNode()[1];
const refs = useCombineRefs<SVGPathElement>(hoverRef, dragNodeRef);
const isHover = hover !== undefined ? hover : hovered;
const isHover = hover !== undefined ? hover : hovered || labelHover;
const anchorRef = useSvgAnchor();
const outlineRef = useCombineRefs(dndDropRef, anchorRef);
const labelLocation = React.useRef<PointWithSize>();
Expand Down Expand Up @@ -239,8 +241,46 @@ const DefaultGroupExpanded: React.FunctionComponent<DefaultGroupExpandedProps> =
? labelLocation.current[1] - outlinePadding - labelGap * 2
: labelLocation.current[1] + outlinePadding + labelGap;

const scale = element.getGraph().getScale();
const medScale = element.getGraph().getDetailsLevelThresholds().medium;
const labelScale = !showLabel && showLabelOnHover && isHover ? Math.min(1 / scale, 1 / medScale) : 1;
const labelPositionScale = 1 / labelScale;

const groupLabel =
(showLabel || (showLabelOnHover && isHover)) && (label || element.getLabel()) ? (
<g ref={labelHoverRef} transform={isHover ? `scale(${labelScale})` : undefined}>
<NodeLabel
className={styles.topologyGroupLabel}
x={startX * labelPositionScale}
y={startY * labelPositionScale}
paddingX={8}
paddingY={5}
dragRef={dragNodeRef ? dragLabelRef : undefined}
status={element.getNodeStatus()}
secondaryLabel={secondaryLabel}
truncateLength={truncateLength}
badge={badge}
badgeColor={badgeColor}
badgeTextColor={badgeTextColor}
badgeBorderColor={badgeBorderColor}
badgeClassName={badgeClassName}
badgeLocation={badgeLocation}
labelIconClass={labelIconClass}
labelIcon={labelIcon}
labelIconPadding={labelIconPadding}
onContextMenu={onContextMenu}
contextMenuOpen={contextMenuOpen}
hover={isHover || labelHover}
actionIcon={collapsible ? <CollapseIcon /> : undefined}
onActionIconClick={() => onCollapseChange(element, true)}
>
{label || element.getLabel()}
</NodeLabel>
</g>
) : null;

return (
<g ref={labelHoverRef} onContextMenu={onContextMenu} onClick={onSelect} className={groupClassName}>
<g onContextMenu={onContextMenu} onClick={onSelect} className={groupClassName}>
<Layer id={GROUPS_LAYER}>
<g ref={refs} onContextMenu={onContextMenu} onClick={onSelect} className={innerGroupClassName}>
{hulledOutline ? (
Expand All @@ -256,38 +296,8 @@ const DefaultGroupExpanded: React.FunctionComponent<DefaultGroupExpandedProps> =
/>
)}
</g>
{groupLabel && isHover ? <Layer id={TOP_LAYER}>{groupLabel}</Layer> : groupLabel}
</Layer>
{showLabel && (label || element.getLabel()) && (
<Layer id={isHover ? TOP_LAYER : undefined}>
<NodeLabel
className={styles.topologyGroupLabel}
x={startX}
y={startY}
paddingX={8}
paddingY={5}
dragRef={dragNodeRef ? dragLabelRef : undefined}
status={element.getNodeStatus()}
secondaryLabel={secondaryLabel}
truncateLength={truncateLength}
badge={badge}
badgeColor={badgeColor}
badgeTextColor={badgeTextColor}
badgeBorderColor={badgeBorderColor}
badgeClassName={badgeClassName}
badgeLocation={badgeLocation}
labelIconClass={labelIconClass}
labelIcon={labelIcon}
labelIconPadding={labelIconPadding}
onContextMenu={onContextMenu}
contextMenuOpen={contextMenuOpen}
hover={isHover || labelHover}
actionIcon={collapsible ? <CollapseIcon /> : undefined}
onActionIconClick={() => onCollapseChange(element, true)}
>
{label || element.getLabel()}
</NodeLabel>
</Layer>
)}
</g>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export interface DefaultTaskGroupProps {
scaleNode?: boolean;
/** Flag to hide details at medium scale */
hideDetailsAtMedium?: boolean;
/** Flag to show the label when hovering and details are hidden (effects expanded only) */
showLabelOnHover?: boolean;
/** Flag if the user is hovering on the node */
hover?: boolean;
/** Label for the node. Defaults to element.getLabel() */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import NodeLabel from '../../../components/nodes/labels/NodeLabel';
import { Layer } from '../../../components/layers';
import { GROUPS_LAYER, TOP_LAYER } from '../../../const';
import { maxPadding, useCombineRefs, useHover } from '../../../utils';
import { AnchorEnd, isGraph, LabelPosition, Node, NodeStyle } from '../../../types';
import { AnchorEnd, isGraph, LabelPosition, Node, NodeStyle, ScaleDetailsLevel } from '../../../types';
import { useAnchor, useDragNode } from '../../../behavior';
import { DagreLayoutOptions, TOP_TO_BOTTOM } from '../../../layouts';
import TaskGroupSourceAnchor from '../anchors/TaskGroupSourceAnchor';
Expand All @@ -26,6 +26,8 @@ const DefaultTaskGroupExpanded: React.FunctionComponent<Omit<DefaultTaskGroupPro
label,
secondaryLabel,
showLabel = true,
showLabelOnHover,
hideDetailsAtMedium,
truncateLength,
canDrop,
dropTarget,
Expand All @@ -46,14 +48,15 @@ const DefaultTaskGroupExpanded: React.FunctionComponent<Omit<DefaultTaskGroupPro
onCollapseChange,
labelPosition
}) => {
const [hovered, hoverRef] = useHover();
const [labelHover, labelHoverRef] = useHover();
const [hovered, hoverRef] = useHover(200, 500);
const [labelHover, labelHoverRef] = useHover(0);
const dragLabelRef = useDragNode()[1];
const refs = useCombineRefs<SVGPathElement>(hoverRef, dragNodeRef);
const isHover = hover !== undefined ? hover : hovered;
const isHover = hover !== undefined ? hover : hovered || labelHover;
const verticalLayout = (element.getGraph().getLayoutOptions?.() as DagreLayoutOptions)?.rankdir === TOP_TO_BOTTOM;
const groupLabelPosition = labelPosition ?? element.getLabelPosition() ?? LabelPosition.bottom;
let parent = element.getParent();
const detailsLevel = element.getGraph().getDetailsLevel();
let altGroup = false;
while (!isGraph(parent)) {
altGroup = !altGroup;
Expand Down Expand Up @@ -121,12 +124,53 @@ const DefaultTaskGroupExpanded: React.FunctionComponent<Omit<DefaultTaskGroupPro
canDrop && 'pf-m-highlight',
dragging && 'pf-m-dragging',
selected && 'pf-m-selected',
(isHover || labelHover) && 'pf-m-hover',
isHover && 'pf-m-hover',
canDrop && dropTarget && 'pf-m-drop-target'
);

const scale = element.getGraph().getScale();
const medScale = element.getGraph().getDetailsLevelThresholds().medium;
const labelScale = detailsLevel !== ScaleDetailsLevel.high ? Math.min(1 / scale, 1 / medScale) : 1;
const labelPositionScale = detailsLevel !== ScaleDetailsLevel.high ? 1 / labelScale : 1;

const groupLabel =
showLabel &&
(!hideDetailsAtMedium || detailsLevel === ScaleDetailsLevel.high || (isHover && showLabelOnHover)) &&
(label || element.getLabel()) ? (
<g ref={labelHoverRef} transform={isHover ? `scale(${labelScale})` : undefined}>
<NodeLabel
className={styles.topologyGroupLabel}
x={labelX * labelPositionScale}
y={labelY * labelPositionScale}
position={labelPosition}
paddingX={8}
paddingY={5}
dragRef={dragNodeRef ? dragLabelRef : undefined}
status={element.getNodeStatus()}
secondaryLabel={secondaryLabel}
truncateLength={truncateLength}
badge={badge}
badgeColor={badgeColor}
badgeTextColor={badgeTextColor}
badgeBorderColor={badgeBorderColor}
badgeClassName={badgeClassName}
badgeLocation={badgeLocation}
labelIconClass={labelIconClass}
labelIcon={labelIcon}
labelIconPadding={labelIconPadding}
onContextMenu={onContextMenu}
contextMenuOpen={contextMenuOpen}
hover={isHover}
actionIcon={collapsible ? <CollapseIcon /> : undefined}
onActionIconClick={() => onCollapseChange(element, true)}
>
{label || element.getLabel()}
</NodeLabel>
</g>
) : null;

return (
<g ref={labelHoverRef} onContextMenu={onContextMenu} onClick={onSelect} className={groupClassName}>
<g onContextMenu={onContextMenu} onClick={onSelect} className={groupClassName}>
<Layer id={GROUPS_LAYER}>
<g ref={refs} onContextMenu={onContextMenu} onClick={onSelect} className={innerGroupClassName}>
<rect
Expand All @@ -137,39 +181,8 @@ const DefaultTaskGroupExpanded: React.FunctionComponent<Omit<DefaultTaskGroupPro
className={styles.topologyGroupBackground}
/>
</g>
{groupLabel && isHover ? <Layer id={TOP_LAYER}>{groupLabel}</Layer> : groupLabel}
</Layer>
{showLabel && (label || element.getLabel()) && (
<Layer id={isHover ? TOP_LAYER : undefined}>
<NodeLabel
className={styles.topologyGroupLabel}
x={labelX}
y={labelY}
position={labelPosition}
paddingX={8}
paddingY={5}
dragRef={dragNodeRef ? dragLabelRef : undefined}
status={element.getNodeStatus()}
secondaryLabel={secondaryLabel}
truncateLength={truncateLength}
badge={badge}
badgeColor={badgeColor}
badgeTextColor={badgeTextColor}
badgeBorderColor={badgeBorderColor}
badgeClassName={badgeClassName}
badgeLocation={badgeLocation}
labelIconClass={labelIconClass}
labelIcon={labelIcon}
labelIconPadding={labelIconPadding}
onContextMenu={onContextMenu}
contextMenuOpen={contextMenuOpen}
hover={isHover || labelHover}
actionIcon={collapsible ? <CollapseIcon /> : undefined}
onActionIconClick={() => onCollapseChange(element, true)}
>
{label || element.getLabel()}
</NodeLabel>
</Layer>
)}
</g>
);
}
Expand Down

0 comments on commit 280ca91

Please sign in to comment.