--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export default class Popper {
+ static placements = [
+ 'auto',
+ 'auto-end',
+ 'auto-start',
+ 'bottom',
+ 'bottom-end',
+ 'bottom-start',
+ 'left',
+ 'left-end',
+ 'left-start',
+ 'right',
+ 'right-end',
+ 'right-start',
+ 'top',
+ 'top-end',
+ 'top-start'
+ ];
+
+ constructor() {
+ return {
+ destroy: jest.fn(),
+ scheduleUpdate: jest.fn()
+ };
+ }
+}
\ No newline at end of file
it("renders one item", () => {
const items = [
- {label: 'breadcrumb 1'}
+ { label: 'breadcrumb 1' }
];
- const breadcrumbs = mount(<Breadcrumbs items={items} onClick={onClick} />);
+ const breadcrumbs = mount(<Breadcrumbs items={items} onClick={onClick} />);
expect(breadcrumbs.find(Button)).toHaveLength(1);
expect(breadcrumbs.find(ChevronRightIcon)).toHaveLength(0);
});
-
+
it("renders multiple items", () => {
const items = [
- {label: 'breadcrumb 1'},
- {label: 'breadcrumb 2'}
+ { label: 'breadcrumb 1' },
+ { label: 'breadcrumb 2' }
];
- const breadcrumbs = mount(<Breadcrumbs items={items} onClick={onClick} />);
+ const breadcrumbs = mount(<Breadcrumbs items={items} onClick={onClick} />);
expect(breadcrumbs.find(Button)).toHaveLength(2);
expect(breadcrumbs.find(ChevronRightIcon)).toHaveLength(1);
});
-
+
it("calls onClick with clicked item", () => {
const items = [
- {label: 'breadcrumb 1'},
- {label: 'breadcrumb 2'}
+ { label: 'breadcrumb 1' },
+ { label: 'breadcrumb 2' }
];
- const breadcrumbs = mount(<Breadcrumbs items={items} onClick={onClick} />);
+ const breadcrumbs = mount(<Breadcrumbs items={items} onClick={onClick} />);
breadcrumbs.find(Button).at(1).simulate('click');
expect(onClick).toBeCalledWith(items[1]);
});
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { Button, Grid, StyleRulesCallback, WithStyles } from '@material-ui/core';
+import { Button, Grid, StyleRulesCallback, WithStyles, Typography, Tooltip } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import { withStyles } from '@material-ui/core';
}
const Breadcrumbs: React.SFC<BreadcrumbsProps & WithStyles<CssRules>> = ({ classes, onClick, items }) => {
- return <Grid container alignItems="center">
+ return <Grid container alignItems="center" wrap="nowrap">
{
items.map((item, index) => {
const isLastItem = index === items.length - 1;
return (
<React.Fragment key={index}>
- <Button
- color="inherit"
- className={isLastItem ? classes.currentItem : classes.item}
- onClick={() => onClick(item)}
- >
- {item.label}
- </Button>
+ <Tooltip title={item.label}>
+ <Button
+ color="inherit"
+ className={isLastItem ? classes.currentItem : classes.item}
+ onClick={() => onClick(item)}
+ >
+ <Typography
+ noWrap
+ color="inherit"
+ className={classes.label}
+ >
+ {item.label}
+ </Typography>
+ </Button>
+ </Tooltip>
{
!isLastItem && <ChevronRightIcon color="inherit" className={classes.item} />
}
</Grid>;
};
-type CssRules = "item" | "currentItem";
+type CssRules = "item" | "currentItem" | "label";
const styles: StyleRulesCallback<CssRules> = theme => {
const { unit } = theme.spacing;
},
currentItem: {
opacity: 1
+ },
+ label: {
+ textTransform: "none"
}
};
};
//
// SPDX-License-Identifier: AGPL-3.0
-import projectsReducer from "./project-reducer";
+import projectsReducer, { findTreeBranch } from "./project-reducer";
import actions from "./project-action";
+import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
describe('project-reducer', () => {
it('should add new project to the list', () => {
};
const projects = [project, project];
- const state = projectsReducer(initialState, actions.PROJECTS_SUCCESS({projects, parentItemId: undefined}));
+ const state = projectsReducer(initialState, actions.PROJECTS_SUCCESS({ projects, parentItemId: undefined }));
expect(state).toEqual([{
active: false,
open: false,
]);
});
});
+
+describe("findTreeBranch", () => {
+
+ const createTreeItem = (id: string, items?: Array<TreeItem<string>>): TreeItem<string> => ({
+ id,
+ items,
+ active: false,
+ data: "",
+ open: false,
+ status: TreeItemStatus.Initial
+ });
+
+ it("should return an array that matches path to the given item", () => {
+ const tree: Array<TreeItem<string>> = [
+ createTreeItem("1", [
+ createTreeItem("1.1", [
+ createTreeItem("1.1.1"),
+ createTreeItem("1.1.2")
+ ])
+ ]),
+ createTreeItem("2", [
+ createTreeItem("2.1", [
+ createTreeItem("2.1.1"),
+ createTreeItem("2.1.2")
+ ])
+ ])
+ ];
+ const branch = findTreeBranch(tree, "2.1.1");
+ expect(branch.map(item => item.id)).toEqual(["2", "2.1", "2.1.1"]);
+ });
+
+ it("should return empty array if item is not found", () => {
+ const tree: Array<TreeItem<string>> = [
+ createTreeItem("1", [
+ createTreeItem("1.1", [
+ createTreeItem("1.1.1"),
+ createTreeItem("1.1.2")
+ ])
+ ]),
+ createTreeItem("2", [
+ createTreeItem("2.1", [
+ createTreeItem("2.1.1"),
+ createTreeItem("2.1.2")
+ ])
+ ])
+ ];
+ expect(findTreeBranch(tree, "3")).toHaveLength(0);
+ });
+
+});
return item;
}
+export function findTreeBranch<T>(tree: Array<TreeItem<T>>, itemId: string): Array<TreeItem<T>> {
+ for(const item of tree){
+ if(item.id === itemId){
+ return [item];
+ } else {
+ const branch = findTreeBranch(item.items || [], itemId);
+ if(branch.length > 0){
+ return [item, ...branch];
+ }
+ }
+ }
+ return [];
+}
+
function resetTreeActivity<T>(tree: Array<TreeItem<T>>) {
for (const t of tree) {
t.active = false;
import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
import { Project } from "../../models/project";
import { projectService } from '../../services/services';
+import { findTreeBranch } from '../../store/project/project-reducer';
import DataExplorer from '../data-explorer/data-explorer';
const drawerWidth = 240;
type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & DispatchProp & WithStyles<CssRules>;
interface NavBreadcrumb extends Breadcrumb {
- path: string;
+ itemId: string;
+ status: TreeItemStatus;
}
interface NavMenuItem extends MainAppBarMenuItem {
state = {
anchorEl: null,
searchText: "",
- breadcrumbs: [
- {
- label: "Projects",
- path: "/projects"
- }, {
- label: "Project 1",
- path: "/projects/project-1"
- }
- ],
+ breadcrumbs: [],
menuItems: {
accountMenu: [
{
mainAppBarActions: MainAppBarActionProps = {
- onBreadcrumbClick: (breadcrumb: NavBreadcrumb) => this.props.dispatch(push(breadcrumb.path)),
+ onBreadcrumbClick: ({ itemId, status }: NavBreadcrumb) => {
+ this.toggleProjectTreeItem(itemId, status);
+ },
onSearch: searchText => {
this.setState({ searchText });
this.props.dispatch(push(`/search?q=${searchText}`));
}
openProjectItem = (itemId: string) => {
+ const branch = findTreeBranch(this.props.projects, itemId);
+ this.setState({
+ breadcrumbs: branch.map(item => ({
+ label: item.data.name,
+ itemId: item.data.uuid,
+ status: item.status
+ }))
+ });
this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(itemId));
this.props.dispatch(push(`/project/${itemId}`));
}