Skip to content

Commit

Permalink
feat(ControlBar): Add expand/collapse all buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-phillips-18 committed May 29, 2024
1 parent 90aa075 commit f4b67a9
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 8 deletions.
12 changes: 11 additions & 1 deletion packages/demo-app-ts/src/demos/DemoControlBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ import {
action
} from '@patternfly/react-topology';

const DemoControlBar: React.FC = () => {
const DemoControlBar: React.FC<{ collapseAllCallback?: (collapseAll: boolean) => void }> = ({
collapseAllCallback
}) => {
const controller = useVisualizationController();

return (
<TopologyControlBar
controlButtons={createTopologyControlButtons({
...defaultControlButtonsOptions,
expandAll: !!collapseAllCallback,
collapseAll: !!collapseAllCallback,
zoomInCallback: action(() => {
controller.getGraph().scaleBy(4 / 3);
}),
Expand All @@ -27,6 +31,12 @@ const DemoControlBar: React.FC = () => {
controller.getGraph().reset();
controller.getGraph().layout();
}),
expandAllCallback: action(() => {
collapseAllCallback(false);
}),
collapseAllCallback: action(() => {
collapseAllCallback(true);
}),
legend: false
})}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ import {
Edge
} from '@patternfly/react-topology';
import pipelineGroupsComponentFactory from './pipelineGroupsComponentFactory';
import { createComplexDemoPipelineGroupsNodes, createDemoPipelineGroupsNodes } from './createDemoPipelineGroupsNodes';
import {
createComplexDemoPipelineGroupsNodes,
createDemoPipelineGroupsNodes,
DEFAULT_TASK_HEIGHT,
GROUP_TASK_WIDTH
} from './createDemoPipelineGroupsNodes';
import { PipelineGroupsDemoContext, PipelineGroupsDemoModel } from './PipelineGroupsDemoContext';
import OptionsBar from './OptionsBar';
import DemoControlBar from '../DemoControlBar';
Expand Down Expand Up @@ -78,8 +83,48 @@ const TopologyPipelineGroups: React.FC<{ nodes: PipelineNodeModel[] }> = observe
);
}, [controller, nodes, options.verticalLayout]);

const collapseAllCallback = React.useCallback(
(collapseAll: boolean) => {
// First, expand/collapse all nodes
collapseAll ? controller.getGraph().collapseAll() : controller.getGraph().expandAll();
// We must recreate the model based on what is visible
const model = controller.toModel();

// Get all the non-spacer nodes, mark them all visible again
const nodes = model.nodes
.filter((n) => n.type !== DEFAULT_SPACER_NODE_TYPE)
.map((n) => ({
...n,
visible: true
}));

// If collapsing, set the size of the collapsed group nodes
if (collapseAll) {
nodes.forEach((node) => {
if (node.group && node.collapsed) {
node.width = GROUP_TASK_WIDTH;
node.height = DEFAULT_TASK_HEIGHT;
}
});
}
// Determine the new set of nodes, including the spacer nodes
const pipelineNodes = addSpacerNodes(nodes);

// Determine the new edges
const edges = getEdgesFromNodes(pipelineNodes, DEFAULT_SPACER_NODE_TYPE, 'edge', 'edge');
// Apply the new model and run the layout
controller.fromModel({ nodes: pipelineNodes, edges }, true);
controller.getGraph().layout();
controller.getGraph().fit(80);
},
[controller]
);

return (
<TopologyView contextToolbar={<OptionsBar />} controlBar={<DemoControlBar />}>
<TopologyView
contextToolbar={<OptionsBar />}
controlBar={<DemoControlBar collapseAllCallback={collapseAllCallback} />}
>
<VisualizationSurface state={{ selectedIds }} />
</TopologyView>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
import ExpandArrowsAltIcon from '@patternfly/react-icons/dist/esm/icons/expand-arrows-alt-icon';
import SearchPlusIcon from '@patternfly/react-icons/dist/esm/icons/search-plus-icon';
import SearchMinusIcon from '@patternfly/react-icons/dist/esm/icons/search-minus-icon';
import CollapseIcon from '@patternfly/react-icons/dist/esm/icons/compress-alt-icon';
import ExpandAltIcon from '@patternfly/react-icons/dist/esm/icons/expand-alt-icon';

import '../../css/topology-controlbar';

/* ID's for common control buttons */
export const ZOOM_IN = 'zoom-in';
export const ZOOM_OUT = 'zoom-out';
export const FIT_TO_SCREEN = 'fit-to-screen';
export const RESET_VIEW = 'reset-view';
export const EXPAND_ALL = 'expand-all';
export const COLLAPSE_ALL = 'collapse-all';
export const LEGEND = 'legend';

/* Data needed for each control button */
Expand Down Expand Up @@ -66,6 +71,22 @@ export interface TopologyControlButtonsOptions {
resetViewDisabled: boolean;
resetViewHidden: boolean;

expandAll: boolean;
expandAllIcon: React.ReactNode;
expandAllTip: React.ReactNode;
expandAllAriaLabel: string;
expandAllCallback: (id: any) => void;
expandAllDisabled: boolean;
expandAllHidden: boolean;

collapseAll: boolean;
collapseAllIcon: React.ReactNode;
collapseAllTip: React.ReactNode;
collapseAllAriaLabel: string;
collapseAllCallback: (id: any) => void;
collapseAllDisabled: boolean;
collapseAllHidden: boolean;

legend: boolean;
legendIcon: React.ReactNode;
legendTip: string;
Expand Down Expand Up @@ -111,6 +132,22 @@ export const defaultControlButtonsOptions: TopologyControlButtonsOptions = {
resetViewDisabled: false,
resetViewHidden: false,

expandAll: false,
expandAllIcon: <ExpandAltIcon />,
expandAllTip: 'Expand All',
expandAllAriaLabel: 'Expand All',
expandAllCallback: null,
expandAllDisabled: false,
expandAllHidden: false,

collapseAll: false,
collapseAllIcon: <CollapseIcon />,
collapseAllTip: 'Collapse All',
collapseAllAriaLabel: 'Collapse All',
collapseAllCallback: null,
collapseAllDisabled: false,
collapseAllHidden: false,

legend: true,
legendIcon: 'Legend',
legendTip: '',
Expand Down Expand Up @@ -156,6 +193,22 @@ export const createTopologyControlButtons = ({
resetViewDisabled = defaultControlButtonsOptions.resetViewDisabled,
resetViewHidden = defaultControlButtonsOptions.resetViewHidden,

expandAll = defaultControlButtonsOptions.expandAll,
expandAllIcon = defaultControlButtonsOptions.expandAllIcon,
expandAllTip = defaultControlButtonsOptions.expandAllTip,
expandAllAriaLabel = defaultControlButtonsOptions.expandAllAriaLabel,
expandAllCallback = defaultControlButtonsOptions.expandAllCallback,
expandAllDisabled = defaultControlButtonsOptions.expandAllDisabled,
expandAllHidden = defaultControlButtonsOptions.expandAllHidden,

collapseAll = defaultControlButtonsOptions.collapseAll,
collapseAllIcon = defaultControlButtonsOptions.collapseAllIcon,
collapseAllTip = defaultControlButtonsOptions.collapseAllTip,
collapseAllAriaLabel = defaultControlButtonsOptions.collapseAllAriaLabel,
collapseAllCallback = defaultControlButtonsOptions.collapseAllCallback,
collapseAllDisabled = defaultControlButtonsOptions.collapseAllDisabled,
collapseAllHidden = defaultControlButtonsOptions.collapseAllHidden,

legend = defaultControlButtonsOptions.legend,
legendIcon = defaultControlButtonsOptions.legendIcon,
legendTip = defaultControlButtonsOptions.legendTip,
Expand Down Expand Up @@ -216,6 +269,30 @@ export const createTopologyControlButtons = ({
});
}

if (expandAll) {
controlButtons.push({
id: EXPAND_ALL,
icon: expandAllIcon,
tooltip: expandAllTip,
ariaLabel: expandAllAriaLabel,
callback: expandAllCallback,
disabled: expandAllDisabled,
hidden: expandAllHidden
});
}

if (collapseAll) {
controlButtons.push({
id: COLLAPSE_ALL,
icon: collapseAllIcon,
tooltip: collapseAllTip,
ariaLabel: collapseAllAriaLabel,
callback: collapseAllCallback,
disabled: collapseAllDisabled,
hidden: collapseAllHidden
});
}

if (customButtons) {
controlButtons.push(...customButtons);
}
Expand Down
28 changes: 28 additions & 0 deletions packages/module/src/elements/BaseGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,34 @@ export default class BaseGraph<E extends GraphModel = GraphModel, D = any>
this.setPosition(new Point(0, 0));
}

setAllChildrenCollapsedState(parent: Node, collapsed: boolean): void {
// eslint-disable-next-line no-console
console.log(parent.getAllNodeChildren(false));
parent.getAllNodeChildren(false).forEach((node) => {
if (node.isGroup()) {
node.setCollapsed(collapsed);
}
});
}

expandAll(): void {
this.getNodes().forEach((node) => {
if (node.isGroup()) {
node.setCollapsed(false);
this.setAllChildrenCollapsedState(node, false);
}
});
}

collapseAll(): void {
this.getNodes().forEach((node) => {
if (node.isGroup()) {
node.setCollapsed(true);
this.setAllChildrenCollapsedState(node, true);
}
});
}

scaleBy(scale: number, location?: Point): void {
const b = this.getBounds();
let { x, y } = b;
Expand Down
9 changes: 6 additions & 3 deletions packages/module/src/elements/BaseNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,15 @@ export default class BaseNode<E extends NodeModel = NodeModel, D = any>
return super.getChildren();
}

// Return all child leaf nodes regardless of collapse status or child groups' collapsed status
getAllNodeChildren(): Node[] {
// Return all child nodes regardless of collapse status or child groups' collapsed status
getAllNodeChildren(leafOnly: boolean = true): Node[] {
return super.getChildren().reduce((total, nexChild) => {
if (isNode(nexChild)) {
if (nexChild.isGroup()) {
return total.concat(nexChild.getAllNodeChildren());
total.push(...nexChild.getAllNodeChildren(leafOnly));
if (leafOnly) {
return total;
}
}
total.push(nexChild);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getEdgesFromNodes, getSpacerNodes } from '../../utils';
import DefaultTaskGroupCollapsed from './DefaultTaskGroupCollapsed';
import DefaultTaskGroupExpanded from './DefaultTaskGroupExpanded';
import { RunStatus } from '../../types';
import { DEFAULT_SPACER_NODE_TYPE } from '../../const';

export interface EdgeCreationTypes {
spacerNodeType?: string;
Expand Down Expand Up @@ -145,7 +146,7 @@ const DefaultTaskGroupInner: React.FunctionComponent<PipelinesDefaultGroupInnerP
const creationTypes: EdgeCreationTypes = getEdgeCreationTypes ? getEdgeCreationTypes() : {};

const pipelineNodes = model.nodes
.filter((n) => n.type !== creationTypes.spacerNodeType)
.filter((n) => n.type !== (creationTypes.spacerNodeType || DEFAULT_SPACER_NODE_TYPE))
.map((n) => ({
...n,
visible: true
Expand Down
4 changes: 3 additions & 1 deletion packages/module/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export interface Node<E extends NodeModel = NodeModel, D = any> extends GraphEle
setNodeStatus(shape: NodeStatus): void;
getSourceEdges(): Edge[];
getTargetEdges(): Edge[];
getAllNodeChildren(): Node[]; // Return all children regardless of collapse status or child groups' collapsed status
getAllNodeChildren(leafOnly?: boolean): Node[]; // Return all children regardless of collapse status or child groups' collapsed status
getPositionableChildren(): Node[]; // Return all children that can be positioned (collapsed groups are positionable)
isDimensionsInitialized(): boolean;
isPositioned(): boolean;
Expand Down Expand Up @@ -287,6 +287,8 @@ export interface Graph<E extends GraphModel = GraphModel, D = any> extends Graph
fit(padding?: number): void;
panIntoView(element: Node, options?: { offset?: number; minimumVisible?: number }): void;
isNodeInView(element: Node, options?: { padding: number }): boolean;
expandAll(): void;
collapseAll(): void;
}

export const isGraph = (element: GraphElement): element is Graph => element && element.getKind() === ModelKind.graph;
Expand Down

0 comments on commit f4b67a9

Please sign in to comment.