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=5e88476747d9fb0c77ef76d63430192fa4b77f22 merge-conflicts Feature ##13598 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- diff --git a/src/components/side-panel/side-panel.tsx b/src/components/side-panel/side-panel.tsx new file mode 100644 index 00000000..36e4c74d --- /dev/null +++ b/src/components/side-panel/side-panel.tsx @@ -0,0 +1,111 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import { ReactElement } from 'react'; +import { StyleRulesCallback, Theme, WithStyles, withStyles } from '@material-ui/core/styles'; +import List from "@material-ui/core/List/List"; +import ListItem from "@material-ui/core/ListItem/ListItem"; +import ListItemText from "@material-ui/core/ListItemText/ListItemText"; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import Collapse from "@material-ui/core/Collapse/Collapse"; + +import { Typography } from '@material-ui/core'; + +export interface SidePanelItem { + id: string; + name: string; + icon: string; + active?: boolean; + open?: boolean; +} + +interface SidePanelProps { + toggleOpen: (id: string) => void; + toggleActive: (id: string) => void; + sidePanelItems: SidePanelItem[]; +} + +class SidePanel extends React.Component> { + render(): ReactElement { + const { classes, toggleOpen, toggleActive, sidePanelItems } = this.props; + const { listItemText, leftSidePanelContainer, row, list, icon, projectIcon, active, activeArrow, inactiveArrow, arrowTransition, arrowRotate } = classes; + return ( +
+ + {sidePanelItems.map(it => ( + + toggleActive(it.id)}> + + {it.name === "Projects" ? toggleOpen(it.id)} className={`${it.active ? activeArrow : inactiveArrow} + ${it.open ? `fas fa-caret-down ${arrowTransition}` : `fas fa-caret-down ${arrowRotate}`}`} /> : null} + + + + {it.name}} /> + + + {it.name === "Projects" ? ( + + {this.props.children} + ) : null} + + ))} + +
+ ); + } +} + +type CssRules = 'active' | 'listItemText' | 'row' | 'leftSidePanelContainer' | 'list' | 'icon' | 'projectIcon' | + 'activeArrow' | 'inactiveArrow' | 'arrowRotate' | 'arrowTransition'; + +const styles: StyleRulesCallback = (theme: Theme) => ({ + active: { + color: '#4285F6', + }, + listItemText: { + padding: '0px', + }, + row: { + display: 'flex', + alignItems: 'center', + }, + activeArrow: { + color: '#4285F6', + position: 'absolute', + }, + inactiveArrow: { + position: 'absolute', + }, + arrowTransition: { + transition: 'all 0.1s ease', + }, + arrowRotate: { + transition: 'all 0.1s ease', + transform: 'rotate(-90deg)', + }, + leftSidePanelContainer: { + overflowY: 'auto', + minWidth: '240px', + whiteSpace: 'nowrap', + marginTop: '38px', + display: 'flex', + flexGrow: 1, + }, + list: { + paddingBottom: '5px', + paddingTop: '5px', + paddingLeft: '14px', + minWidth: '240px', + }, + icon: { + minWidth: '20px', + }, + projectIcon: { + marginLeft: '17px', + } +}); + +export default withStyles(styles)(SidePanel); \ No newline at end of file diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx index 6731950c..2c19a831 100644 --- a/src/components/tree/tree.tsx +++ b/src/components/tree/tree.tsx @@ -9,38 +9,6 @@ import { StyleRulesCallback, Theme, withStyles, WithStyles } from '@material-ui/ 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, @@ -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, 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, 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)} @@ -91,7 +60,8 @@ class Tree extends React.Component & WithStyles, {}> { }
)} @@ -99,5 +69,36 @@ class Tree extends React.Component & WithStyles, {}> { } } +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 --git a/src/index.tsx b/src/index.tsx index cf1610f8..ba395e8b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -26,7 +26,8 @@ const store = configureStore({ }, auth: { user: undefined - } + }, + sidePanel: [] }, history); store.dispatch(authActions.INIT()); diff --git a/src/store/project/project-action.ts b/src/store/project/project-action.ts index 728b1cc9..3c264d3e 100644 --- a/src/store/project/project-action.ts +++ b/src/store/project/project-action.ts @@ -1,29 +1,33 @@ // 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; diff --git a/src/store/project/project-reducer.test.ts b/src/store/project/project-reducer.test.ts index f964e0ea..e8d6afc6 100644 --- a/src/store/project/project-reducer.test.ts +++ b/src/store/project/project-reducer.test.ts @@ -38,21 +38,144 @@ describe('project-reducer', () => { 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); }); }); diff --git a/src/store/project/project-reducer.ts b/src/store/project/project-reducer.ts index 4f7545fc..48db05df 100644 --- a/src/store/project/project-reducer.ts +++ b/src/store/project/project-reducer.ts @@ -2,10 +2,11 @@ // // 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>; @@ -83,17 +84,29 @@ const projectsReducer = (state: ProjectState = [], action: ProjectAction) => { 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 --git a/src/store/side-panel/side-panel-action.ts b/src/store/side-panel/side-panel-action.ts new file mode 100644 index 00000000..32fa653b --- /dev/null +++ b/src/store/side-panel/side-panel-action.ts @@ -0,0 +1,17 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { default as unionize, ofType, UnionOf } from "unionize"; + +const actions = unionize({ + TOGGLE_SIDE_PANEL_ITEM_OPEN: ofType(), + TOGGLE_SIDE_PANEL_ITEM_ACTIVE: ofType(), + RESET_SIDE_PANEL_ACTIVITY: ofType(), +}, { + tag: 'type', + value: 'payload' +}); + +export type SidePanelAction = UnionOf; +export default actions; \ No newline at end of file diff --git a/src/store/side-panel/side-panel-reducer.test.ts b/src/store/side-panel/side-panel-reducer.test.ts new file mode 100644 index 00000000..942c16eb --- /dev/null +++ b/src/store/side-panel/side-panel-reducer.test.ts @@ -0,0 +1,81 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import sidePanelReducer from "./side-panel-reducer"; +import actions from "./side-panel-action"; + +describe('side-panel-reducer', () => { + + it('should toggle activity on side-panel', () => { + const initialState = [ + { + id: "1", + name: "Projects", + icon: "fas fa-th fa-fw", + open: false, + active: false, + } + ]; + const project = [ + { + id: "1", + name: "Projects", + icon: "fas fa-th fa-fw", + open: false, + active: true, + } + ]; + + const state = sidePanelReducer(initialState, actions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(initialState[0].id)); + expect(state).toEqual(project); + }); + + it('should open side-panel item', () => { + const initialState = [ + { + id: "1", + name: "Projects", + icon: "fas fa-th fa-fw", + open: false, + active: false, + } + ]; + const project = [ + { + id: "1", + name: "Projects", + icon: "fas fa-th fa-fw", + open: true, + active: false, + } + ]; + + const state = sidePanelReducer(initialState, actions.TOGGLE_SIDE_PANEL_ITEM_OPEN(initialState[0].id)); + expect(state).toEqual(project); + }); + + it('should remove activity on side-panel item', () => { + const initialState = [ + { + id: "1", + name: "Projects", + icon: "fas fa-th fa-fw", + open: false, + active: true, + } + ]; + const project = [ + { + id: "1", + name: "Projects", + icon: "fas fa-th fa-fw", + open: false, + active: false, + } + ]; + + const state = sidePanelReducer(initialState, actions.RESET_SIDE_PANEL_ACTIVITY(initialState[0].id)); + expect(state).toEqual(project); + }); +}); \ No newline at end of file diff --git a/src/store/side-panel/side-panel-reducer.ts b/src/store/side-panel/side-panel-reducer.ts new file mode 100644 index 00000000..8051017c --- /dev/null +++ b/src/store/side-panel/side-panel-reducer.ts @@ -0,0 +1,84 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as _ from "lodash"; + +import actions, { SidePanelAction } from './side-panel-action'; +import { SidePanelItem } from '../../components/side-panel/side-panel'; + +export type SidePanelState = SidePanelItem[]; + +const sidePanelReducer = (state: SidePanelState = sidePanelData, action: SidePanelAction) => { + if (state.length === 0) { + return sidePanelData; + } else { + return actions.match(action, { + TOGGLE_SIDE_PANEL_ITEM_OPEN: itemId => state.map(it => itemId === it.id && it.open === false ? {...it, open: true} : {...it, open: false}), + TOGGLE_SIDE_PANEL_ITEM_ACTIVE: itemId => { + const sidePanel = _.cloneDeep(state); + resetSidePanelActivity(sidePanel); + sidePanel.map(it => { + if (it.id === itemId) { + it.active = true; + } + }); + return sidePanel; + }, + RESET_SIDE_PANEL_ACTIVITY: () => { + const sidePanel = _.cloneDeep(state); + resetSidePanelActivity(sidePanel); + return sidePanel; + }, + default: () => state + }); + } +}; + +export const sidePanelData = [ + { + id: "1", + name: "Projects", + icon: "fas fa-th fa-fw", + open: false, + active: false, + }, + { + id: "2", + name: "Shared with me", + icon: "fas fa-users fa-fw", + active: false, + }, + { + id: "3", + name: "Workflows", + icon: "fas fa-cogs fa-fw", + active: false, + }, + { + id: "4", + name: "Recent open", + icon: "icon-time fa-fw", + active: false, + }, + { + id: "5", + name: "Favorites", + icon: "fas fa-star fa-fw", + active: false, + }, + { + id: "6", + name: "Trash", + icon: "fas fa-trash-alt fa-fw", + active: false, + } +]; + +function resetSidePanelActivity(sidePanel: SidePanelItem[]) { + for (const t of sidePanel) { + t.active = false; + } +} + +export default sidePanelReducer; diff --git a/src/store/store.ts b/src/store/store.ts index 6b9c31ff..6089caf3 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -6,26 +6,30 @@ import { createStore, applyMiddleware, compose, Middleware, combineReducers } fr 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 --git a/src/views-components/project-tree/project-tree.test.tsx b/src/views-components/project-tree/project-tree.test.tsx index d5312130..1ba3abb8 100644 --- a/src/views-components/project-tree/project-tree.test.tsx +++ b/src/views-components/project-tree/project-tree.test.tsx @@ -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 +48,7 @@ describe("ProjectTree component", () => { uuid: "uuid", ownerUuid: "ownerUuid", href: "href", + kind: 'example' }, id: "3", open: false, @@ -61,6 +63,7 @@ describe("ProjectTree component", () => { uuid: "uuid", ownerUuid: "ownerUuid", href: "href", + kind: 'example' }, id: "3", open: false, @@ -68,7 +71,7 @@ describe("ProjectTree component", () => { status: 1 } ]; - const wrapper = mount( { }} />); + const wrapper = mount(); expect(wrapper.find(ListItemIcon)).toHaveLength(2); }); @@ -83,6 +86,7 @@ describe("ProjectTree component", () => { uuid: "uuid", ownerUuid: "ownerUuid", href: "href", + kind: 'example' }, id: "3", open: true, @@ -97,6 +101,7 @@ describe("ProjectTree component", () => { uuid: "uuid", ownerUuid: "ownerUuid", href: "href", + kind: 'example' }, id: "3", open: true, @@ -106,7 +111,7 @@ describe("ProjectTree component", () => { ] } ]; - const wrapper = mount( { }} />); + const wrapper = mount(); expect(wrapper.find(Collapse)).toHaveLength(1); }); @@ -120,13 +125,14 @@ describe("ProjectTree component", () => { 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 --git a/src/views-components/project-tree/project-tree.tsx b/src/views-components/project-tree/project-tree.tsx index fd32ff04..f51b65e0 100644 --- a/src/views-components/project-tree/project-tree.tsx +++ b/src/views-components/project-tree/project-tree.tsx @@ -12,6 +12,36 @@ import Typography from '@material-ui/core/Typography'; 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, 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) => ({ @@ -27,42 +57,10 @@ const styles: StyleRulesCallback = (theme: Theme) => ({ 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 --git a/src/views/workbench/workbench.test.tsx b/src/views/workbench/workbench.test.tsx index 7b9b74d0..69257922 100644 --- a/src/views/workbench/workbench.test.tsx +++ b/src/views/workbench/workbench.test.tsx @@ -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 --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index bac0b473..4f9843cb 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -3,7 +3,6 @@ // 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"; @@ -20,6 +19,9 @@ 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; @@ -45,6 +47,8 @@ const styles: StyleRulesCallback = (theme: Theme) => ({ drawerPaper: { position: 'relative', width: drawerWidth, + display: 'flex', + flexDirection: 'column', }, contentWrapper: { backgroundColor: theme.palette.background.default, @@ -64,6 +68,7 @@ const styles: StyleRulesCallback = (theme: Theme) => ({ interface WorkbenchDataProps { projects: Array>; user?: User; + sidePanelItems: SidePanelItem[]; } interface WorkbenchActionProps { @@ -125,7 +130,7 @@ class Workbench extends React.Component { mainAppBarActions: MainAppBarActionProps = { onBreadcrumbClick: ({ itemId, status }: NavBreadcrumb) => { - this.toggleProjectTreeItem(itemId, status); + this.toggleProjectTreeItemOpen(itemId, status); }, onSearch: searchText => { this.setState({ searchText }); @@ -134,15 +139,45 @@ class Workbench extends React.Component { 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(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, 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({ @@ -152,12 +187,12 @@ class Workbench extends React.Component { 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 (
@@ -176,9 +211,15 @@ class Workbench extends React.Component { paper: classes.drawerPaper, }}>
- + + + }
@@ -195,7 +236,8 @@ class Workbench extends React.Component { export default connect( (state: RootState) => ({ projects: state.projects, - user: state.auth.user + user: state.auth.user, + sidePanelItems: state.sidePanel, }) )( withStyles(styles)(Workbench)