Skip to content

Commit 6d0ff64

Browse files
Merge pull request #203 from jeff-phillips-18/expand-collapse-all
feat(ControlBar): Add expand/collapse all buttons
2 parents 90aa075 + f4b67a9 commit 6d0ff64

7 files changed

Lines changed: 174 additions & 8 deletions

File tree

packages/demo-app-ts/src/demos/DemoControlBar.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ import {
77
action
88
} from '@patternfly/react-topology';
99

10-
const DemoControlBar: React.FC = () => {
10+
const DemoControlBar: React.FC<{ collapseAllCallback?: (collapseAll: boolean) => void }> = ({
11+
collapseAllCallback
12+
}) => {
1113
const controller = useVisualizationController();
1214

1315
return (
1416
<TopologyControlBar
1517
controlButtons={createTopologyControlButtons({
1618
...defaultControlButtonsOptions,
19+
expandAll: !!collapseAllCallback,
20+
collapseAll: !!collapseAllCallback,
1721
zoomInCallback: action(() => {
1822
controller.getGraph().scaleBy(4 / 3);
1923
}),
@@ -27,6 +31,12 @@ const DemoControlBar: React.FC = () => {
2731
controller.getGraph().reset();
2832
controller.getGraph().layout();
2933
}),
34+
expandAllCallback: action(() => {
35+
collapseAllCallback(false);
36+
}),
37+
collapseAllCallback: action(() => {
38+
collapseAllCallback(true);
39+
}),
3040
legend: false
3141
})}
3242
/>

packages/demo-app-ts/src/demos/pipelineGroupsDemo/PipelineGroupsDemo.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ import {
2525
Edge
2626
} from '@patternfly/react-topology';
2727
import pipelineGroupsComponentFactory from './pipelineGroupsComponentFactory';
28-
import { createComplexDemoPipelineGroupsNodes, createDemoPipelineGroupsNodes } from './createDemoPipelineGroupsNodes';
28+
import {
29+
createComplexDemoPipelineGroupsNodes,
30+
createDemoPipelineGroupsNodes,
31+
DEFAULT_TASK_HEIGHT,
32+
GROUP_TASK_WIDTH
33+
} from './createDemoPipelineGroupsNodes';
2934
import { PipelineGroupsDemoContext, PipelineGroupsDemoModel } from './PipelineGroupsDemoContext';
3035
import OptionsBar from './OptionsBar';
3136
import DemoControlBar from '../DemoControlBar';
@@ -78,8 +83,48 @@ const TopologyPipelineGroups: React.FC<{ nodes: PipelineNodeModel[] }> = observe
7883
);
7984
}, [controller, nodes, options.verticalLayout]);
8085

86+
const collapseAllCallback = React.useCallback(
87+
(collapseAll: boolean) => {
88+
// First, expand/collapse all nodes
89+
collapseAll ? controller.getGraph().collapseAll() : controller.getGraph().expandAll();
90+
// We must recreate the model based on what is visible
91+
const model = controller.toModel();
92+
93+
// Get all the non-spacer nodes, mark them all visible again
94+
const nodes = model.nodes
95+
.filter((n) => n.type !== DEFAULT_SPACER_NODE_TYPE)
96+
.map((n) => ({
97+
...n,
98+
visible: true
99+
}));
100+
101+
// If collapsing, set the size of the collapsed group nodes
102+
if (collapseAll) {
103+
nodes.forEach((node) => {
104+
if (node.group && node.collapsed) {
105+
node.width = GROUP_TASK_WIDTH;
106+
node.height = DEFAULT_TASK_HEIGHT;
107+
}
108+
});
109+
}
110+
// Determine the new set of nodes, including the spacer nodes
111+
const pipelineNodes = addSpacerNodes(nodes);
112+
113+
// Determine the new edges
114+
const edges = getEdgesFromNodes(pipelineNodes, DEFAULT_SPACER_NODE_TYPE, 'edge', 'edge');
115+
// Apply the new model and run the layout
116+
controller.fromModel({ nodes: pipelineNodes, edges }, true);
117+
controller.getGraph().layout();
118+
controller.getGraph().fit(80);
119+
},
120+
[controller]
121+
);
122+
81123
return (
82-
<TopologyView contextToolbar={<OptionsBar />} controlBar={<DemoControlBar />}>
124+
<TopologyView
125+
contextToolbar={<OptionsBar />}
126+
controlBar={<DemoControlBar collapseAllCallback={collapseAllCallback} />}
127+
>
83128
<VisualizationSurface state={{ selectedIds }} />
84129
</TopologyView>
85130
);

packages/module/src/components/TopologyControlBar/TopologyControlBar.tsx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@ import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
1212
import ExpandArrowsAltIcon from '@patternfly/react-icons/dist/esm/icons/expand-arrows-alt-icon';
1313
import SearchPlusIcon from '@patternfly/react-icons/dist/esm/icons/search-plus-icon';
1414
import SearchMinusIcon from '@patternfly/react-icons/dist/esm/icons/search-minus-icon';
15+
import CollapseIcon from '@patternfly/react-icons/dist/esm/icons/compress-alt-icon';
16+
import ExpandAltIcon from '@patternfly/react-icons/dist/esm/icons/expand-alt-icon';
17+
1518
import '../../css/topology-controlbar';
1619

1720
/* ID's for common control buttons */
1821
export const ZOOM_IN = 'zoom-in';
1922
export const ZOOM_OUT = 'zoom-out';
2023
export const FIT_TO_SCREEN = 'fit-to-screen';
2124
export const RESET_VIEW = 'reset-view';
25+
export const EXPAND_ALL = 'expand-all';
26+
export const COLLAPSE_ALL = 'collapse-all';
2227
export const LEGEND = 'legend';
2328

2429
/* Data needed for each control button */
@@ -66,6 +71,22 @@ export interface TopologyControlButtonsOptions {
6671
resetViewDisabled: boolean;
6772
resetViewHidden: boolean;
6873

74+
expandAll: boolean;
75+
expandAllIcon: React.ReactNode;
76+
expandAllTip: React.ReactNode;
77+
expandAllAriaLabel: string;
78+
expandAllCallback: (id: any) => void;
79+
expandAllDisabled: boolean;
80+
expandAllHidden: boolean;
81+
82+
collapseAll: boolean;
83+
collapseAllIcon: React.ReactNode;
84+
collapseAllTip: React.ReactNode;
85+
collapseAllAriaLabel: string;
86+
collapseAllCallback: (id: any) => void;
87+
collapseAllDisabled: boolean;
88+
collapseAllHidden: boolean;
89+
6990
legend: boolean;
7091
legendIcon: React.ReactNode;
7192
legendTip: string;
@@ -111,6 +132,22 @@ export const defaultControlButtonsOptions: TopologyControlButtonsOptions = {
111132
resetViewDisabled: false,
112133
resetViewHidden: false,
113134

135+
expandAll: false,
136+
expandAllIcon: <ExpandAltIcon />,
137+
expandAllTip: 'Expand All',
138+
expandAllAriaLabel: 'Expand All',
139+
expandAllCallback: null,
140+
expandAllDisabled: false,
141+
expandAllHidden: false,
142+
143+
collapseAll: false,
144+
collapseAllIcon: <CollapseIcon />,
145+
collapseAllTip: 'Collapse All',
146+
collapseAllAriaLabel: 'Collapse All',
147+
collapseAllCallback: null,
148+
collapseAllDisabled: false,
149+
collapseAllHidden: false,
150+
114151
legend: true,
115152
legendIcon: 'Legend',
116153
legendTip: '',
@@ -156,6 +193,22 @@ export const createTopologyControlButtons = ({
156193
resetViewDisabled = defaultControlButtonsOptions.resetViewDisabled,
157194
resetViewHidden = defaultControlButtonsOptions.resetViewHidden,
158195

196+
expandAll = defaultControlButtonsOptions.expandAll,
197+
expandAllIcon = defaultControlButtonsOptions.expandAllIcon,
198+
expandAllTip = defaultControlButtonsOptions.expandAllTip,
199+
expandAllAriaLabel = defaultControlButtonsOptions.expandAllAriaLabel,
200+
expandAllCallback = defaultControlButtonsOptions.expandAllCallback,
201+
expandAllDisabled = defaultControlButtonsOptions.expandAllDisabled,
202+
expandAllHidden = defaultControlButtonsOptions.expandAllHidden,
203+
204+
collapseAll = defaultControlButtonsOptions.collapseAll,
205+
collapseAllIcon = defaultControlButtonsOptions.collapseAllIcon,
206+
collapseAllTip = defaultControlButtonsOptions.collapseAllTip,
207+
collapseAllAriaLabel = defaultControlButtonsOptions.collapseAllAriaLabel,
208+
collapseAllCallback = defaultControlButtonsOptions.collapseAllCallback,
209+
collapseAllDisabled = defaultControlButtonsOptions.collapseAllDisabled,
210+
collapseAllHidden = defaultControlButtonsOptions.collapseAllHidden,
211+
159212
legend = defaultControlButtonsOptions.legend,
160213
legendIcon = defaultControlButtonsOptions.legendIcon,
161214
legendTip = defaultControlButtonsOptions.legendTip,
@@ -216,6 +269,30 @@ export const createTopologyControlButtons = ({
216269
});
217270
}
218271

272+
if (expandAll) {
273+
controlButtons.push({
274+
id: EXPAND_ALL,
275+
icon: expandAllIcon,
276+
tooltip: expandAllTip,
277+
ariaLabel: expandAllAriaLabel,
278+
callback: expandAllCallback,
279+
disabled: expandAllDisabled,
280+
hidden: expandAllHidden
281+
});
282+
}
283+
284+
if (collapseAll) {
285+
controlButtons.push({
286+
id: COLLAPSE_ALL,
287+
icon: collapseAllIcon,
288+
tooltip: collapseAllTip,
289+
ariaLabel: collapseAllAriaLabel,
290+
callback: collapseAllCallback,
291+
disabled: collapseAllDisabled,
292+
hidden: collapseAllHidden
293+
});
294+
}
295+
219296
if (customButtons) {
220297
controlButtons.push(...customButtons);
221298
}

packages/module/src/elements/BaseGraph.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,34 @@ export default class BaseGraph<E extends GraphModel = GraphModel, D = any>
226226
this.setPosition(new Point(0, 0));
227227
}
228228

229+
setAllChildrenCollapsedState(parent: Node, collapsed: boolean): void {
230+
// eslint-disable-next-line no-console
231+
console.log(parent.getAllNodeChildren(false));
232+
parent.getAllNodeChildren(false).forEach((node) => {
233+
if (node.isGroup()) {
234+
node.setCollapsed(collapsed);
235+
}
236+
});
237+
}
238+
239+
expandAll(): void {
240+
this.getNodes().forEach((node) => {
241+
if (node.isGroup()) {
242+
node.setCollapsed(false);
243+
this.setAllChildrenCollapsedState(node, false);
244+
}
245+
});
246+
}
247+
248+
collapseAll(): void {
249+
this.getNodes().forEach((node) => {
250+
if (node.isGroup()) {
251+
node.setCollapsed(true);
252+
this.setAllChildrenCollapsedState(node, true);
253+
}
254+
});
255+
}
256+
229257
scaleBy(scale: number, location?: Point): void {
230258
const b = this.getBounds();
231259
let { x, y } = b;

packages/module/src/elements/BaseNode.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,15 @@ export default class BaseNode<E extends NodeModel = NodeModel, D = any>
157157
return super.getChildren();
158158
}
159159

160-
// Return all child leaf nodes regardless of collapse status or child groups' collapsed status
161-
getAllNodeChildren(): Node[] {
160+
// Return all child nodes regardless of collapse status or child groups' collapsed status
161+
getAllNodeChildren(leafOnly: boolean = true): Node[] {
162162
return super.getChildren().reduce((total, nexChild) => {
163163
if (isNode(nexChild)) {
164164
if (nexChild.isGroup()) {
165-
return total.concat(nexChild.getAllNodeChildren());
165+
total.push(...nexChild.getAllNodeChildren(leafOnly));
166+
if (leafOnly) {
167+
return total;
168+
}
166169
}
167170
total.push(nexChild);
168171
}

packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { getEdgesFromNodes, getSpacerNodes } from '../../utils';
99
import DefaultTaskGroupCollapsed from './DefaultTaskGroupCollapsed';
1010
import DefaultTaskGroupExpanded from './DefaultTaskGroupExpanded';
1111
import { RunStatus } from '../../types';
12+
import { DEFAULT_SPACER_NODE_TYPE } from '../../const';
1213

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

147148
const pipelineNodes = model.nodes
148-
.filter((n) => n.type !== creationTypes.spacerNodeType)
149+
.filter((n) => n.type !== (creationTypes.spacerNodeType || DEFAULT_SPACER_NODE_TYPE))
149150
.map((n) => ({
150151
...n,
151152
visible: true

packages/module/src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export interface Node<E extends NodeModel = NodeModel, D = any> extends GraphEle
232232
setNodeStatus(shape: NodeStatus): void;
233233
getSourceEdges(): Edge[];
234234
getTargetEdges(): Edge[];
235-
getAllNodeChildren(): Node[]; // Return all children regardless of collapse status or child groups' collapsed status
235+
getAllNodeChildren(leafOnly?: boolean): Node[]; // Return all children regardless of collapse status or child groups' collapsed status
236236
getPositionableChildren(): Node[]; // Return all children that can be positioned (collapsed groups are positionable)
237237
isDimensionsInitialized(): boolean;
238238
isPositioned(): boolean;
@@ -287,6 +287,8 @@ export interface Graph<E extends GraphModel = GraphModel, D = any> extends Graph
287287
fit(padding?: number): void;
288288
panIntoView(element: Node, options?: { offset?: number; minimumVisible?: number }): void;
289289
isNodeInView(element: Node, options?: { padding: number }): boolean;
290+
expandAll(): void;
291+
collapseAll(): void;
290292
}
291293

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

0 commit comments

Comments
 (0)