import {
Breadcrumb,
} from '../Breadcrumb';
import classNames from 'classnames';
import {
warn,
} from 'colorful-logging';
import {
IBreadcrumbItem,
} from './IBreadcrumbItem';
import {
IBreadcrumbTrailOwnProps,
} from './IBreadcrumbTrailOwnProps';
import {
IBreadcrumbTrailState,
} from './IBreadcrumbTrailState';
import {
IVisibilityTree,
} from './IVisibilityTree';
import {
ReactNodeWithoutNullOrUndefined,
} from '../../typeAliases/ReactNodeWithoutNullOrUndefined';
import {
TreeSelector,
} from './TreeSelector';
import * as React from 'react';
export class BreadcrumbTrail extends React.PureComponent<
IBreadcrumbTrailOwnProps,
IBreadcrumbTrailState
> {
private readonly assembleVisibilityTree = (
children: ReactNodeWithoutNullOrUndefined,
index: number,
): IVisibilityTree => ({
children: React.Children.map(children, (child) => {
let treeChildren: readonly IVisibilityTree[] = [];
if (React.isValidElement(child)) {
if (Array.isArray((child as any).props.children)) {
treeChildren = React.Children.map(
(child as any).props.children,
(child) => this.assembleVisibilityTree(child, index + 1),
);
} else if (typeof (child as any).props.collectVisibilityTree === 'function') {
treeChildren = (child as any).props.collectVisibilityTree();
}
}
const tree: IVisibilityTree = {
children: Object.freeze(treeChildren),
open: false,
visible: index + 1 <= 1,
};
return Object.freeze(tree);
}),
open: true,
visible: index <= 1,
});
public readonly state: IBreadcrumbTrailState = {
trail: [
{ name: this.props.name || 'Start' },
],
visibilityTree: this.assembleVisibilityTree(this.props.children, 0),
};
private readonly treeSelector: TreeSelector = [ 0 ];
public readonly render = () => {
const {
className,
children,
listComponent: ListComponent,
} = this.props;
const { trail } = this.state;
return (
{children({
getBreadcrumbProps: this.getBreadcrumbProps,
treeSelector: this.treeSelector,
visibilityTree: this.state.visibilityTree,
})}
);
};
private readonly addBreadcrumb = (crumb: IBreadcrumbItem) => {
this.setState({ trail: this.state.trail.concat([ crumb ]) });
if (crumb.treeSelector) {
this.setVisibilityTreePropsAt(
crumb.treeSelector.slice(0, crumb.treeSelector.length - 1),
{ open: true },
);
}
};
private readonly clickBreadcrumb = (index: number) => this.setState({
trail: this.state.trail.slice(0, index + 1),
visibilityTree: this.trimVisibilityTree(
this.state.visibilityTree,
index,
),
});
private readonly getBreadcrumbProps = () => ({
addBreadcrumb: this.addBreadcrumb,
breadcrumbTrail: this.state.trail,
removeBreadcrumb: this.removeBreadcrumb,
visibilityTree: this.state.visibilityTree,
});
private readonly removeBreadcrumb = () => this.setState({
trail: this.state.trail.slice(0, this.state.trail.length - 1),
visibilityTree: this.trimVisibilityTree(
this.state.visibilityTree,
Math.max(0, this.state.trail.length - 2),
),
});
private readonly setVisibilityTreePropsAt = (
treeSelector: readonly number[],
updatedProps: Partial,
) => {
const updatedVizTree = { ...this.state.visibilityTree };
let last = updatedVizTree;
let treeToSet = updatedVizTree;
for (const item of treeSelector) {
treeToSet.children = (treeToSet.children as IVisibilityTree[]).map(
(child) => ({ ...child })
);
treeToSet = treeToSet.children[item];
if (!treeToSet) {
last.children = last.children.slice(0, item).concat([
{
children: [],
open: false,
visible: false,
},
]).concat(last.children.slice(item + 1));
treeToSet = last.children[item];
if (!treeToSet) {
warn('Tree selector could not be resolved to mutate menu breadcrumb trail.');
return;
}
}
last = treeToSet;
}
Object.keys(updatedProps).forEach((key) => (
treeToSet[key] = updatedProps[key]
));
this.setState({ visibilityTree: updatedVizTree });
};
private readonly trimVisibilityTree = (
tree: IVisibilityTree,
trimIndex: number,
depthIndex = 0,
): IVisibilityTree => {
let open;
let visible;
if (trimIndex === 0) {
open = depthIndex === 0;
visible = depthIndex <= 1;
} else {
if (depthIndex < trimIndex) {
open = tree.open;
} else {
open = false;
}
visible = depthIndex < trimIndex && tree.visible;
}
return {
children: (tree.children as IVisibilityTree[]).map((tree) => (
this.trimVisibilityTree(tree, trimIndex, depthIndex + 1)
)),
open,
visible,
};
};
}