From: Pawel Kowalczyk Date: Mon, 25 Jun 2018 10:05:09 +0000 (+0200) Subject: merge-conflicts X-Git-Tag: 1.2.0~68^2~2 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/c90e813adcec89899d9db95843295a84fb058c3e?hp=-c merge-conflicts Feature ##13598 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- c90e813adcec89899d9db95843295a84fb058c3e diff --combined src/components/tree/tree.tsx index 5dd45878,6731950c..2c19a831 --- a/src/components/tree/tree.tsx +++ b/src/components/tree/tree.tsx @@@ -9,6 -9,38 +9,6 @@@ import { StyleRulesCallback, Theme, wit import { ReactElement } from "react"; import Collapse from "@material-ui/core/Collapse/Collapse"; import CircularProgress from '@material-ui/core/CircularProgress'; -import { inherits } from 'util'; - -type CssRules = 'list' | 'activeArrow' | 'inactiveArrow' | 'arrowRotate' | 'arrowTransition' | 'loader' | 'arrowVisibility'; - -const styles: StyleRulesCallback = (theme: Theme) => ({ - list: { - paddingBottom: '3px', - paddingTop: '3px', - }, - activeArrow: { - color: '#4285F6', - position: 'absolute', - }, - inactiveArrow: { - position: 'absolute', - }, - arrowTransition: { - transition: 'all 0.1s ease', - }, - arrowRotate: { - transition: 'all 0.1s ease', - transform: 'rotate(-90deg)', - }, - arrowVisibility: { - opacity: 0, - }, - loader: { - position: 'absolute', - transform: 'translate(0px)', - top: '3px' - } -}); export enum TreeItemStatus { Initial, @@@ -29,28 -61,27 +29,28 @@@ export interface TreeItem interface TreeProps { items?: Array>; render: (item: TreeItem, level?: number) => ReactElement<{}>; - toggleItem: (id: string, status: TreeItemStatus) => any; + toggleItemOpen: (id: string, status: TreeItemStatus) => void; - toggleItemActive: (id: string) => void; ++ toggleItemActive: (id: string, status: TreeItemStatus) => void; level?: number; } class Tree extends React.Component & WithStyles, {}> { renderArrow(status: TreeItemStatus, arrowClass: string, open: boolean, id: string) { - return this.props.toggleItem(id, status)} + const { arrowTransition, arrowVisibility, arrowRotate } = this.props.classes; + return this.props.toggleItemOpen(id, status)} className={` - ${arrowClass} - ${status === TreeItemStatus.Pending ? this.props.classes.arrowVisibility : ''} - ${open ? `fas fa-caret-down ${this.props.classes.arrowTransition}` : `fas fa-caret-down ${this.props.classes.arrowRotate}`}`} />; + ${arrowClass} + ${status === TreeItemStatus.Pending ? arrowVisibility : ''} + ${open ? `fas fa-caret-down ${arrowTransition}` : `fas fa-caret-down ${arrowRotate}`}`} />; } render(): ReactElement { const level = this.props.level ? this.props.level : 0; - const { classes, render, toggleItem, items } = this.props; + const { classes, render, toggleItemOpen, items, toggleItemActive } = this.props; const { list, inactiveArrow, activeArrow, loader } = classes; return {items && items.map((it: TreeItem, idx: number) =>
- toggleItemActive(it.id)}> - ++ toggleItemActive(it.id, it.status)}> {it.status === TreeItemStatus.Pending ? : null} {it.toggled && it.items && it.items.length === 0 ? null : this.renderArrow(it.status, it.active ? activeArrow : inactiveArrow, it.open, it.id)} {render(it, level)} @@@ -60,8 -91,7 +60,8 @@@ }
)} @@@ -69,36 -99,5 +69,36 @@@ } } +type CssRules = 'list' | 'activeArrow' | 'inactiveArrow' | 'arrowRotate' | 'arrowTransition' | 'loader' | 'arrowVisibility'; + +const styles: StyleRulesCallback = (theme: Theme) => ({ + list: { + paddingBottom: '3px', + paddingTop: '3px', + }, + activeArrow: { + color: '#4285F6', + position: 'absolute', + }, + inactiveArrow: { + position: 'absolute', + }, + arrowTransition: { + transition: 'all 0.1s ease', + }, + arrowRotate: { + transition: 'all 0.1s ease', + transform: 'rotate(-90deg)', + }, + arrowVisibility: { + opacity: 0, + }, + loader: { + position: 'absolute', + transform: 'translate(0px)', + top: '3px' + } +}); + const StyledTree = withStyles(styles)(Tree); export default StyledTree; diff --combined src/index.tsx index 1807bd8d,cf1610f8..ba395e8b --- a/src/index.tsx +++ b/src/index.tsx @@@ -11,10 -11,10 +11,10 @@@ import { Route } from "react-router" import createBrowserHistory from "history/createBrowserHistory"; import configureStore from "./store/store"; import { ConnectedRouter } from "react-router-redux"; - import ApiToken from "./components/api-token/api-token"; + import ApiToken from "./views-components/api-token/api-token"; import authActions from "./store/auth/auth-action"; - import { authService, projectService } from "./services/services"; - import { sidePanelData } from './store/side-panel/side-panel-reducer'; + import { authService } from "./services/services"; + import { getProjectList } from "./store/project/project-action"; const history = createBrowserHistory(); @@@ -26,13 -26,12 +26,13 @@@ const store = configureStore( }, auth: { user: undefined - } + }, + sidePanel: [] }, history); store.dispatch(authActions.INIT()); const rootUuid = authService.getRootUuid(); - store.dispatch(projectService.getProjectList(rootUuid)); + store.dispatch(getProjectList(rootUuid)); const App = () => diff --combined src/store/project/project-action.ts index a58edd3c,728b1cc9..3c264d3e --- a/src/store/project/project-action.ts +++ b/src/store/project/project-action.ts @@@ -1,22 -1,30 +1,34 @@@ // Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: AGPL-3.0 +import { default as unionize, ofType, UnionOf } from "unionize"; import { Project } from "../../models/project"; -import { default as unionize, ofType, UnionOf } from "unionize"; + import { projectService } from "../../services/services"; + import { Dispatch } from "redux"; const actions = unionize({ CREATE_PROJECT: ofType(), REMOVE_PROJECT: ofType(), -- PROJECTS_REQUEST: ofType(), ++ PROJECTS_REQUEST: ofType(), PROJECTS_SUCCESS: ofType<{ projects: Project[], parentItemId?: string }>(), - TOGGLE_PROJECT_TREE_ITEM: ofType() + TOGGLE_PROJECT_TREE_ITEM_OPEN: ofType(), + TOGGLE_PROJECT_TREE_ITEM_ACTIVE: ofType(), + RESET_PROJECT_TREE_ACTIVITY: ofType(), }, { -- tag: 'type', -- value: 'payload' --}); ++ tag: 'type', ++ value: 'payload' ++ }); + + export const getProjectList = (parentUuid?: string) => (dispatch: Dispatch): Promise => { - dispatch(actions.PROJECTS_REQUEST()); - return projectService.getProjectList(parentUuid).then(projects => { - dispatch(actions.PROJECTS_SUCCESS({projects, parentItemId: parentUuid})); - return projects; - }); ++ if (parentUuid) { ++ dispatch(actions.PROJECTS_REQUEST(parentUuid)); ++ return projectService.getProjectList(parentUuid).then(projects => { ++ dispatch(actions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid })); ++ return projects; ++ }); ++ } return Promise.resolve([]); + }; export type ProjectAction = UnionOf; export default actions; diff --combined src/store/project/project-reducer.test.ts index e5cd57e2,f964e0ea..e8d6afc6 --- a/src/store/project/project-reducer.test.ts +++ b/src/store/project/project-reducer.test.ts @@@ -2,8 -2,9 +2,9 @@@ // // SPDX-License-Identifier: AGPL-3.0 - import projectsReducer from "./project-reducer"; + import projectsReducer, { getTreePath } 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', () => { @@@ -14,7 -15,8 +15,8 @@@ createdAt: '2018-01-01', modifiedAt: '2018-01-01', ownerUuid: 'owner-test123', - uuid: 'test123' + uuid: 'test123', + kind: "" }; const state = projectsReducer(initialState, actions.CREATE_PROJECT(project)); @@@ -29,143 -31,77 +31,200 @@@ createdAt: '2018-01-01', modifiedAt: '2018-01-01', ownerUuid: 'owner-test123', - uuid: 'test123' + uuid: 'test123', + kind: "" }; const projects = [project, project]; const state = projectsReducer(initialState, actions.PROJECTS_SUCCESS({ projects, parentItemId: undefined })); expect(state).toEqual([{ + active: false, + open: false, + id: "test123", + items: [], + data: project, + status: 0 + }, { + active: false, + open: false, + id: "test123", + items: [], + data: project, + status: 0 + } + ]); + }); + + it('should remove activity on projects list', () => { + const initialState = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", + open: true, + active: true, + status: 1 + } + ]; + const project = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", + open: true, active: false, - open: false, - id: "test123", - items: [], - data: project, - status: 0 - }, { + status: 1 + } + ]; + + const state = projectsReducer(initialState, actions.RESET_PROJECT_TREE_ACTIVITY(initialState[0].id)); + expect(state).toEqual(project); + }); + + it('should toggle project tree item activity', () => { + const initialState = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", + open: true, + active: false, + status: 1 + } + ]; + const project = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", + open: true, + active: true, + status: 1 + } + ]; + + const state = projectsReducer(initialState, actions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(initialState[0].id)); + expect(state).toEqual(project); + }); + + + it('should close project tree item ', () => { + const initialState = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", + open: true, active: false, + status: 1, + toggled: false, + } + ]; + const project = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", open: false, - id: "test123", - items: [], - data: project, - status: 0 + active: false, + status: 1, + toggled: true } - ]); + ]; + + const state = projectsReducer(initialState, actions.TOGGLE_PROJECT_TREE_ITEM_OPEN(initialState[0].id)); + expect(state).toEqual(project); }); }); + + describe("findTreeBranch", () => { + + const createTreeItem = (id: string, items?: Array>): TreeItem => ({ + 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> = [ + 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 = getTreePath(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> = [ + 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(getTreePath(tree, "3")).toHaveLength(0); + }); + + }); diff --combined src/store/project/project-reducer.ts index 43117ef0,4f7545fc..48db05df --- a/src/store/project/project-reducer.ts +++ b/src/store/project/project-reducer.ts @@@ -2,15 -2,14 +2,15 @@@ // // SPDX-License-Identifier: AGPL-3.0 +import * as _ from "lodash"; + import { Project } from "../../models/project"; import actions, { ProjectAction } from "./project-action"; import { TreeItem, TreeItemStatus } from "../../components/tree/tree"; -import * as _ from "lodash"; export type ProjectState = Array>; - function findTreeItem(tree: Array>, itemId: string): TreeItem | undefined { + export function findTreeItem(tree: Array>, itemId: string): TreeItem | undefined { let item; for (const t of tree) { item = t.id === itemId @@@ -23,6 -22,20 +23,20 @@@ return item; } + export function getTreePath(tree: Array>, itemId: string): Array> { + for(const item of tree){ + if(item.id === itemId){ + return [item]; + } else { + const branch = getTreePath(item.items || [], itemId); + if(branch.length > 0){ + return [item, ...branch]; + } + } + } + return []; + } + function resetTreeActivity(tree: Array>) { for (const t of tree) { t.active = false; @@@ -70,29 -83,17 +84,29 @@@ const projectsReducer = (state: Project PROJECTS_SUCCESS: ({ projects, parentItemId }) => { return updateProjectTree(state, projects, parentItemId); }, - TOGGLE_PROJECT_TREE_ITEM: itemId => { + TOGGLE_PROJECT_TREE_ITEM_OPEN: itemId => { const tree = _.cloneDeep(state); - resetTreeActivity(tree); const item = findTreeItem(tree, itemId); if (item) { + item.toggled = true; item.open = !item.open; + } + return tree; + }, + TOGGLE_PROJECT_TREE_ITEM_ACTIVE: itemId => { + const tree = _.cloneDeep(state); + resetTreeActivity(tree); + const item = findTreeItem(tree, itemId); + if (item) { item.active = true; - item.toggled = true; } return tree; }, + RESET_PROJECT_TREE_ACTIVITY: () => { + const tree = _.cloneDeep(state); + resetTreeActivity(tree); + return tree; + }, default: () => state }); }; diff --combined src/store/store.ts index 49e0b5f7,6b9c31ff..6089caf3 --- a/src/store/store.ts +++ b/src/store/store.ts @@@ -6,28 -6,26 +6,30 @@@ import { createStore, applyMiddleware, import { routerMiddleware, routerReducer, RouterState } from "react-router-redux"; import thunkMiddleware from 'redux-thunk'; import { History } from "history"; + import projectsReducer, { ProjectState } from "./project/project-reducer"; +import sidePanelReducer, { SidePanelState } from './side-panel/side-panel-reducer'; import authReducer, { AuthState } from "./auth/auth-reducer"; + import collectionsReducer from "./collection/collection-reducer"; const composeEnhancers = (process.env.NODE_ENV === 'development' && - window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || + window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose; export interface RootState { auth: AuthState; projects: ProjectState; router: RouterState; + sidePanel: SidePanelState; } const rootReducer = combineReducers({ auth: authReducer, projects: projectsReducer, + collections: collectionsReducer, - router: routerReducer + router: routerReducer, + sidePanel: sidePanelReducer }); diff --combined src/views-components/project-tree/project-tree.test.tsx index 932a29cc,d5312130..1ba3abb8 --- a/src/views-components/project-tree/project-tree.test.tsx +++ b/src/views-components/project-tree/project-tree.test.tsx @@@ -11,7 -11,7 +11,7 @@@ import { Collapse } from '@material-ui/ import CircularProgress from '@material-ui/core/CircularProgress'; import ProjectTree from './project-tree'; - import { TreeItem } from '../tree/tree'; + import { TreeItem } from '../../components/tree/tree'; import { Project } from '../../models/project'; Enzyme.configure({ adapter: new Adapter() }); @@@ -26,13 -26,13 +26,14 @@@ describe("ProjectTree component", () = uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: true, active: true, status: 1 }; -- const wrapper = mount( { }} />); ++ const wrapper = mount(); expect(wrapper.find(ListItemIcon)).toHaveLength(1); }); @@@ -47,6 -47,6 +48,7 @@@ uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: false, @@@ -61,6 -61,6 +63,7 @@@ uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: false, @@@ -68,7 -68,7 +71,7 @@@ status: 1 } ]; -- const wrapper = mount( { }} />); ++ const wrapper = mount(); expect(wrapper.find(ListItemIcon)).toHaveLength(2); }); @@@ -83,6 -83,6 +86,7 @@@ uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: true, @@@ -97,6 -97,6 +101,7 @@@ uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: true, @@@ -106,7 -106,7 +111,7 @@@ ] } ]; -- const wrapper = mount( { }} />); ++ const wrapper = mount(); expect(wrapper.find(Collapse)).toHaveLength(1); }); @@@ -120,13 -120,13 +125,14 @@@ uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: false, active: true, status: 1 }; -- const wrapper = mount( { }} />); ++ const wrapper = mount(); expect(wrapper.find(CircularProgress)).toHaveLength(1); }); diff --combined src/views-components/project-tree/project-tree.tsx index 7406f7f3,fd32ff04..f51b65e0 --- a/src/views-components/project-tree/project-tree.tsx +++ b/src/views-components/project-tree/project-tree.tsx @@@ -9,39 -9,9 +9,39 @@@ import ListItemText from "@material-ui/ import ListItemIcon from '@material-ui/core/ListItemIcon'; import Typography from '@material-ui/core/Typography'; - import Tree, { TreeItem, TreeItemStatus } from '../tree/tree'; + import Tree, { TreeItem, TreeItemStatus } from '../../components/tree/tree'; import { Project } from '../../models/project'; +export interface ProjectTreeProps { + projects: Array>; + toggleOpen: (id: string, status: TreeItemStatus) => void; - toggleActive: (id: string) => void; ++ toggleActive: (id: string, status: TreeItemStatus) => void; +} + +class ProjectTree extends React.Component> { + render(): ReactElement { + const { classes, projects, toggleOpen, toggleActive } = this.props; + const { active, listItemText, row, treeContainer } = classes; + return ( +
+ ) => + + + + + {project.data.name} + } /> + + } /> +
+ ); + } +} + type CssRules = 'active' | 'listItemText' | 'row' | 'treeContainer'; const styles: StyleRulesCallback = (theme: Theme) => ({ @@@ -57,10 -27,42 +57,10 @@@ marginLeft: '20px', }, treeContainer: { - marginTop: '37px', - overflowX: 'visible', - overflowY: 'auto', minWidth: '240px', whiteSpace: 'nowrap', + marginLeft: '13px', } }); -export interface ProjectTreeProps { - projects: Array>; - toggleProjectTreeItem: (id: string, status: TreeItemStatus) => void; -} - -class ProjectTree extends React.Component> { - render(): ReactElement { - const {classes, projects} = this.props; - const {active, listItemText, row, treeContainer} = classes; - return ( -
- , level: number) => - - - {level === 0 ? : } - - - {project.data.name} - - }/> - - }/> -
- ); - } -} - export default withStyles(styles)(ProjectTree); diff --combined src/views/workbench/workbench.test.tsx index 7b9b74d0,7b9b74d0..69257922 --- a/src/views/workbench/workbench.test.tsx +++ b/src/views/workbench/workbench.test.tsx @@@ -14,7 -14,7 +14,7 @@@ const history = createBrowserHistory() it('renders without crashing', () => { const div = document.createElement('div'); -- const store = configureStore({ projects: [], router: { location: null }, auth: {} }, createBrowserHistory()); ++ const store = configureStore({ projects: [], router: { location: null }, auth: {}, sidePanel: [] }, createBrowserHistory()); ReactDOM.render( diff --combined src/views/workbench/workbench.tsx index 9e274325,bac0b473..4f9843cb --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@@ -3,29 -3,28 +3,30 @@@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; - import { StyleRulesCallback, Theme, WithStyles, withStyles } from '@material-ui/core/styles'; import Drawer from '@material-ui/core/Drawer'; import { connect, DispatchProp } from "react-redux"; - - import ProjectList from "../../components/project-list/project-list"; import { Route, Switch } from "react-router"; import authActions from "../../store/auth/auth-action"; import { User } from "../../models/user"; import { RootState } from "../../store/store"; - import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../components/main-app-bar/main-app-bar'; + import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../views-components/main-app-bar/main-app-bar'; import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs'; import { push } from 'react-router-redux'; - import projectActions from "../../store/project/project-action"; - import sidePanelActions from '../../store/side-panel/side-panel-action'; - import ProjectTree from '../../components/project-tree/project-tree'; + import projectActions, { getProjectList } from "../../store/project/project-action"; + import ProjectTree from '../../views-components/project-tree/project-tree'; import { TreeItem, TreeItemStatus } from "../../components/tree/tree"; import { Project } from "../../models/project"; + import { getTreePath } from '../../store/project/project-reducer'; + import ProjectPanel from '../project-panel/project-panel'; ++import sidePanelActions from '../../store/side-panel/side-panel-action'; +import { projectService } from '../../services/services'; +import SidePanel, { SidePanelItem } from '../../components/side-panel/side-panel'; const drawerWidth = 240; + const appBarHeight = 102; - type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'toolbar'; + type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'contentWrapper' | 'toolbar'; const styles: StyleRulesCallback = (theme: Theme) => ({ root: { @@@ -46,15 -45,18 +47,20 @@@ drawerPaper: { position: 'relative', width: drawerWidth, + display: 'flex', + flexDirection: 'column', }, - content: { - flexGrow: 1, + contentWrapper: { backgroundColor: theme.palette.background.default, - padding: theme.spacing.unit * 3, - height: '100%', + display: "flex", + flexGrow: 1, minWidth: 0, + paddingTop: appBarHeight + }, + content: { + padding: theme.spacing.unit * 3, + overflowY: "auto", + flexGrow: 1 }, toolbar: theme.mixins.toolbar }); @@@ -62,7 -64,6 +68,7 @@@ interface WorkbenchDataProps { projects: Array>; user?: User; + sidePanelItems: SidePanelItem[]; } interface WorkbenchActionProps { @@@ -71,7 -72,8 +77,8 @@@ type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & DispatchProp & WithStyles; interface NavBreadcrumb extends Breadcrumb { - path: string; + itemId: string; + status: TreeItemStatus; } interface NavMenuItem extends MainAppBarMenuItem { @@@ -93,15 -95,7 +100,7 @@@ class Workbench extends React.Component state = { anchorEl: null, searchText: "", - breadcrumbs: [ - { - label: "Projects", - path: "/projects" - }, { - label: "Project 1", - path: "/projects/project-1" - } - ], + breadcrumbs: [], menuItems: { accountMenu: [ { @@@ -130,7 -124,9 +129,9 @@@ mainAppBarActions: MainAppBarActionProps = { - onBreadcrumbClick: (breadcrumb: NavBreadcrumb) => this.props.dispatch(push(breadcrumb.path)), + onBreadcrumbClick: ({ itemId, status }: NavBreadcrumb) => { - this.toggleProjectTreeItem(itemId, status); ++ this.toggleProjectTreeItemOpen(itemId, status); + }, onSearch: searchText => { this.setState({ searchText }); this.props.dispatch(push(`/search?q=${searchText}`)); @@@ -138,34 -134,30 +139,60 @@@ onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action() }; - toggleProjectTreeItem = (itemId: string, status: TreeItemStatus) => { + toggleProjectTreeItemOpen = (itemId: string, status: TreeItemStatus) => { if (status === TreeItemStatus.Loaded) { + this.openProjectItem(itemId); + this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(itemId)); + this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); } else { - this.props.dispatch(projectService.getProjectList(itemId)).then(() => { - this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(itemId)); - this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); - }); + this.props.dispatch(getProjectList(itemId)) - .then(() => this.openProjectItem(itemId)); ++ .then(() => { ++ this.openProjectItem(itemId); ++ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(itemId)); ++ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); ++ }); } } - toggleProjectTreeItemActive = (itemId: string) => { - this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); - this.props.dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(itemId)); ++ toggleProjectTreeItemActive = (itemId: string, status: TreeItemStatus) => { ++ if (status === TreeItemStatus.Loaded) { ++ this.openProjectItem(itemId); ++ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); ++ this.props.dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(itemId)); ++ } else { ++ this.props.dispatch(getProjectList(itemId)) ++ .then(() => { ++ this.openProjectItem(itemId); ++ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); ++ this.props.dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(itemId)); ++ }); ++ } + } + + toggleSidePanelOpen = (itemId: string) => { + this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId)); + } + + toggleSidePanelActive = (itemId: string) => { + this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId)); + this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId)); + } + + openProjectItem = (itemId: string) => { + const branch = getTreePath(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(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); + this.props.dispatch(push(`/project/${itemId}`)); + } + render() { - const { classes, user } = this.props; + const { classes, user, projects, sidePanelItems } = this.props; return (
@@@ -184,21 -176,16 +211,22 @@@ paper: classes.drawerPaper, }}>
- - - - ++ ++ ++ } -
-
- - - +
+
+ + + +
); @@@ -208,8 -195,7 +236,8 @@@ export default connect( (state: RootState) => ({ projects: state.projects, - user: state.auth.user + user: state.auth.user, + sidePanelItems: state.sidePanel, }) )( withStyles(styles)(Workbench)