Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add support for expandable and collapsible groups #3

Closed
wants to merge 13 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -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<ShapeProps>;
collapsedShadowOffset?: number; // defaults to 10
} & WithContextMenuProps &
WithDragNodeProps &
WithSelectionProps;

const DemoTaskGroup: React.FunctionComponent<DemoTaskNodeProps> = ({ element, ...rest }) => {
const DemoTaskGroup: React.FunctionComponent<DemoTaskGroupProps> = ({ 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 (
<DefaultGroup hulledOutline={false} labelPosition={verticalLayout ? LabelPosition.top : LabelPosition.bottom} element={element as Node} {...rest} />
<PipelinesDefaultGroup
hulledOutline={false}
labelPosition={verticalLayout ? LabelPosition.top : LabelPosition.bottom}
collapsible
collapsedWidth={collapsedWidth}
collapsedHeight={collapsedHeight}
element={element as Node}
{...rest}
/>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import DemoTaskNode from './DemoTaskNode';
import DemoFinallyNode from './DemoFinallyNode';
import DemoTaskGroupEdge from './DemoTaskGroupEdge';
import StylePipelinesGroup from '../stylesDemo/StylePipelinesGroup';

export const GROUPED_EDGE_TYPE = 'GROUPED_EDGE';

Expand Down Expand Up @@ -52,7 +53,7 @@ const pipelineComponentFactory: ComponentFactory = (
case DEFAULT_FINALLY_NODE_TYPE:
return withContextMenu(() => defaultMenu)(withSelection()(DemoFinallyNode));
case 'task-group':
return DefaultTaskGroup;
return withSelection()(StylePipelinesGroup);
case 'finally-group':
return DefaultTaskGroup;
case DEFAULT_SPACER_NODE_TYPE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -149,7 +150,14 @@ export const useDemoPipelineNodes = (
type: 'task-group',
children: parallelTasks.map(t => t.id),
group: true,
label: 'Parallel tasks'
label: 'Parallel tasks',
data: {
badge: 'Label',
collapsedWidth: 75,
collapsedHeight: 42,
collapsible: true,
labelPosition: LabelPosition.top
}
});
}
}
Expand Down Expand Up @@ -190,7 +198,13 @@ export const useDemoPipelineNodes = (
type: 'task-group',
children: [],
group: true,
label: `Group ${task.data.columnGroup}`
label: `Group ${task.data.columnGroup}`,
data: {
collapsedWidth: 75,
collapsedHeight: 75,
collapsible: true,
labelPosition: LabelPosition.top
}
};
acc.push(taskGroup);
}
Expand Down Expand Up @@ -221,8 +235,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) {
Expand Down Expand Up @@ -250,8 +264,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) {
Expand Down
64 changes: 64 additions & 0 deletions packages/demo-app-ts/src/demos/stylesDemo/StylePipelinesGroup.tsx
Original file line number Diff line number Diff line change
@@ -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<ShapeProps>;
collapsedShadowOffset?: number; // defaults to 10
} & WithContextMenuProps &
WithDragNodeProps &
WithSelectionProps;

const StylePipelinesGroup: React.FunctionComponent<StylePipelinesGroupProps> = ({
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 (
<PipelinesDefaultGroup
element={element}
collapsible
collapsedWidth={collapsedWidth}
collapsedHeight={collapsedHeight}
showLabel={detailsLevel === ScaleDetailsLevel.high}
{...rest}
{...passedData}
>
</PipelinesDefaultGroup>
);
};

export default observer(StylePipelinesGroup);
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,4 @@ const stylesComponentFactory: ComponentFactory = (
}
};

export default stylesComponentFactory;
export default stylesComponentFactory;
18 changes: 12 additions & 6 deletions packages/module/src/components/nodes/labels/LabelActionIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ 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';

interface LabelActionIconProps {
className?: string;
icon: React.ReactElement;
isIconExternal?: boolean;
hover?: boolean;
onClick: (e: React.MouseEvent) => void;
iconOffsetX?: number;
iconOffsetY?: number;
Expand All @@ -17,12 +20,15 @@ interface LabelActionIconProps {
}

const LabelActionIcon = React.forwardRef<SVGRectElement, LabelActionIconProps>(
({ 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;
const iconHeight = iconSize?.height ?? 0;
const iconY = (height - iconHeight) / 2;

const centerX = x + height / 2 - iconWidth / 2;
const centerY = y + height / 2 - iconHeight / 2;

const classes = css(styles.topologyNodeActionIcon, className);

const handleClick = (e: React.MouseEvent) => {
Expand All @@ -37,16 +43,16 @@ const LabelActionIcon = React.forwardRef<SVGRectElement, LabelActionIconProps>(
{iconSize && (
<rect
ref={actionRef}
className={css(styles.topologyNodeActionIconBackground)}
className={isIconExternal ? css(pipelineStyles.topologyPipelinesNodeActionIconBackground) : css(styles.topologyNodeActionIconBackground)}
x={x}
y={y}
width={iconWidth + paddingX * 2}
width={isIconExternal ? height :iconWidth + paddingX * 2}
height={height}
/>
)}
<g
className={css(styles.topologyNodeActionIconIcon)}
transform={`translate(${x + paddingX + iconOffsetX}, ${y + iconY + iconOffsetY})`}
className={isIconExternal ? css(pipelineStyles.topologyPipelinesNodeActionIconIcon) : css(styles.topologyNodeActionIconIcon)}
transform={isIconExternal ? `translate(${centerX}, ${centerY})` : `translate(${x + paddingX + iconOffsetX}, ${y + iconY + iconOffsetY})`}
ref={iconRef}
>
{icon}
Expand Down
Loading
Loading