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

feat(ControlBar): Add expand/collapse all buttons #203

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading