refs #13535 Merge branch '13535-tree-component' into 13610-projects-hierarchy
authorDaniel Kos <daniel.kos@contractors.roche.com>
Tue, 12 Jun 2018 19:38:39 +0000 (21:38 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Tue, 12 Jun 2018 19:38:39 +0000 (21:38 +0200)
# Conflicts:
# package.json
# src/components/tree/tree.tsx
# src/index.tsx
# src/models/project.ts
# src/store/project-action.ts
# src/store/project-reducer.ts
# src/views/workbench/workbench.test.tsx
# src/views/workbench/workbench.tsx
# tslint.json
# yarn.lock

Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

1  2 
package.json
src/index.tsx
src/models/project.ts
src/store/project/project-action.ts
src/store/project/project-reducer.ts
src/views/workbench/workbench.test.tsx
src/views/workbench/workbench.tsx
yarn.lock

diff --cc package.json
index ac890d8d89460f5e42f70ab33b4f43c8f67c67da,e4ba28ac3a31c69ae993720f6692a0e506d0779b..399750a3d77c8ca005d95bf8c8e1f02a2fe063d3
@@@ -3,11 -3,10 +3,12 @@@
    "version": "0.1.0",
    "private": true,
    "dependencies": {
 -    "@material-ui/core": "1.0.0",
 +    "@material-ui/core": "1.2.0",
 +    "@material-ui/icons": "^1.1.0",
+     "lodash": "4.17.10",
 -    "react": "16.3.2",
 -    "react-dom": "16.3.2",
 +    "axios": "0.18.0",
 +    "react": "16.4.0",
 +    "react-dom": "16.4.0",
      "react-redux": "5.0.7",
      "react-router": "4.2.0",
      "react-router-dom": "4.2.2",
      "lint": "tslint src/** -t verbose"
    },
    "devDependencies": {
 -    "@types/jest": "22.2.3",
 -    "@types/lodash": "4.14.109",
 -    "@types/node": "10.1.2",
 -    "@types/react": "16.3.14",
+     "@types/enzyme": "^3.1.10",
+     "@types/enzyme-adapter-react-16": "^1.0.2",
 +    "@types/jest": "23.0.0",
 +    "@types/node": "10.3.0",
 +    "@types/react": "16.3.16",
      "@types/react-dom": "16.0.5",
 -    "@types/react-redux": "6.0.0",
 -    "@types/react-router": "4.0.25",
 -    "@types/react-router-dom": "4.2.6",
 -    "@types/react-router-redux": "5.0.14",
 +    "@types/react-redux": "6.0.1",
 +    "@types/react-router": "4.0.26",
 +    "@types/react-router-dom": "4.2.7",
 +    "@types/react-router-redux": "5.0.15",
      "@types/redux-devtools": "3.0.44",
 -    "typescript": "2.8.3"
+     "enzyme": "^3.3.0",
+     "enzyme-adapter-react-16": "^1.1.1",
 +    "jest-localstorage-mock": "2.2.0",
 +    "redux-devtools": "3.4.1",
 +    "typescript": "2.9.1"
    },
    "moduleNameMapper": {
      "^~/(.*)$": "<rootDir>/src/$1"
diff --cc src/index.tsx
index 0cb67f1d82771a985d0b0637078c7d8dbb62463d,9cc33feb8eba3de42328434b80fea6253dd14814..173296817b317294d0fe75f8907c0468dd388835
@@@ -12,25 -12,51 +12,44 @@@ 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 authActions from "./store/auth/auth-action";
 +import { projectService } from "./services/services";
+ import { TreeItem } from "./components/tree/tree";
+ import { Project } from "./models/project";
  
 -const sampleProjects = [
 -    [
 -        'Project 1', [
 -            ['Project 1.1', [['Project 1.1.1'], ['Project 1.1.2']]],
 -            ['Project 1.2', [['Project 1.2.1'], ['Project 1.2.2'], ['Project 1.2.3']]]
 -        ]
 -    ],
 -    [
 -        'Project 2'
 -    ],
 -    [
 -        'Project 3', [['Project 3.1'], ['Project 3.2']]
 -    ]
 -];
 -
 -
+ function buildProjectTree(tree: any[], level = 0): Array<TreeItem<Project>> {
+     const projects = tree.map((t, idx) => ({
+         id: `l${level}i${idx}${t[0]}`,
+         open: false,
+         active: false,
+         data: {
+             name: t[0],
+             icon: level === 0 ? <i className="fas fa-th"/> : <i className="fas fa-folder"/>,
+             createdAt: '2018-05-05',
+         },
+         items: t.length > 1 ? buildProjectTree(t[1], level + 1) : []
+     }));
+     return projects;
+ }
 -
 -
  const history = createBrowserHistory();
+ const projects = buildProjectTree(sampleProjects);
  const store = configureStore({
 -    projects,
 +    projects: [
 +    ],
      router: {
          location: null
 +    },
 +    auth: {
 +        user: undefined
      }
  }, history);
  
 +store.dispatch(authActions.INIT());
 +store.dispatch<any>(projectService.getProjectList());
 +
++
  const App = () =>
      <Provider store={store}>
          <ConnectedRouter history={history}>
index 83862c94001a575f55136faa8b1d93653a44f415,d730bcdfd8c02ce676f368044dbf876367593a9c..f4faf7d64e14acddce651b3bc64b71a2d47ba78c
@@@ -5,8 -5,5 +5,9 @@@
  export interface Project {
      name: string;
      createdAt: string;
-     href: string
 +    modifiedAt: string;
 +    uuid: string;
 +    ownerUuid: string;
++    href: string;
+     icon?: any;
  }
index c88edb60df8d0c764563ee48c027289859214567,0000000000000000000000000000000000000000..2101619515a0c197855425667b28910581165df7
mode 100644,000000..100644
--- /dev/null
@@@ -1,19 -1,0 +1,20 @@@
-     PROJECTS_SUCCESS: ofType<Project[]>()
 +// Copyright (C) The Arvados Authors. All rights reserved.
 +//
 +// SPDX-License-Identifier: AGPL-3.0
 +
 +import { Project } from "../../models/project";
 +import { default as unionize, ofType, UnionOf } from "unionize";
 +
 +const actions = unionize({
 +    CREATE_PROJECT: ofType<Project>(),
 +    REMOVE_PROJECT: ofType<string>(),
 +    PROJECTS_REQUEST: {},
++    PROJECTS_SUCCESS: ofType<Project[]>(),
++    TOGGLE_PROJECT_TREE_ITEM: ofType<string>()
 +}, {
 +    tag: 'type',
 +    value: 'payload'
 +});
 +
 +export type ProjectAction = UnionOf<typeof actions>;
 +export default actions;
index 477cb1e4688e309e243d78cece928313f6215146,0000000000000000000000000000000000000000..458177c0431c9d240ec0b08664014615ea095b36
mode 100644,000000..100644
--- /dev/null
@@@ -1,22 -1,0 +1,57 @@@
- export type ProjectState = Project[];
 +// Copyright (C) The Arvados Authors. All rights reserved.
 +//
 +// SPDX-License-Identifier: AGPL-3.0
 +
 +import { Project } from "../../models/project";
 +import actions, { ProjectAction } from "./project-action";
++import { TreeItem } from "../../components/tree/tree";
++import * as _ from "lodash";
 +
++export type ProjectState = Array<TreeItem<Project>>;
++
++function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
++    let item;
++    for (const t of tree) {
++        item = t.id === itemId
++            ? t
++            : findTreeItem(t.items ? t.items : [], itemId);
++        if (item) {
++            break;
++        }
++    }
++    return item;
++}
++
++function resetTreeActivity<T>(tree: Array<TreeItem<T>>): boolean | undefined {
++    let item;
++    for (const leaf of tree) {
++        item = leaf.active === true
++            ? leaf.active = false
++            : resetTreeActivity(leaf.items ? leaf.items : []);
++    }
++    return item;
++}
 +
 +const projectsReducer = (state: ProjectState = [], action: ProjectAction) => {
 +    return actions.match(action, {
 +        CREATE_PROJECT: project => [...state, project],
 +        REMOVE_PROJECT: () => state,
 +        PROJECTS_REQUEST: () => state,
 +        PROJECTS_SUCCESS: projects => {
 +            return projects;
 +        },
++        TOGGLE_PROJECT_TREE_ITEM: itemId => {
++            const tree = _.cloneDeep(state);
++            resetTreeActivity(tree);
++            const item = findTreeItem(tree, itemId);
++            if (item) {
++                item.open = !item.open;
++                item.active = true;
++            }
++            return tree;
++        },
 +        default: () => state
 +    });
 +};
 +
 +export default projectsReducer;
index fc89609acc43b45767fcebb0cec2f0518953423f,b1657bc35f6de244ffc71db154bf467a1d9d6d54..7b9b74d095c65a8a1ad760a2c4c87f360cb13836
@@@ -11,7 -14,13 +14,13 @@@ const history = createBrowserHistory()
  
  it('renders without crashing', () => {
      const div = document.createElement('div');
 -    const store = configureStore({ projects: [], router: { location: null } }, createBrowserHistory());
 +    const store = configureStore({ projects: [], router: { location: null }, auth: {} }, createBrowserHistory());
-     ReactDOM.render(<Provider store={store}><Workbench/></Provider>, div);
+     ReactDOM.render(
+         <Provider store={store}>
+             <ConnectedRouter history={history}>
+                 <Workbench/>
+             </ConnectedRouter>
+         </Provider>,
+     div);
      ReactDOM.unmountComponentAtNode(div);
  });
index 8c3145b823e1950a58aa2bca49129ab23c0e8023,6f39ac78e05c3752dee124e6447f98490eedc4d5..7cccfe3034698057db006cde792dc0e4d201a915
@@@ -15,16 -14,10 +15,19 @@@ import { Project } from "../../models/p
  import ProjectList from "../../components/project-list/project-list";
  import { Route, Switch } from "react-router";
  import { Link } from "react-router-dom";
 +import Button from "@material-ui/core/Button/Button";
 +import authActions from "../../store/auth/auth-action";
 +import IconButton from "@material-ui/core/IconButton/IconButton";
 +import Menu from "@material-ui/core/Menu/Menu";
 +import MenuItem from "@material-ui/core/MenuItem/MenuItem";
 +import { AccountCircle } from "@material-ui/icons";
 +import { User } from "../../models/user";
 +import Grid from "@material-ui/core/Grid/Grid";
 +import { RootState } from "../../store/store";
  
+ import { actions as projectActions } from "../../store/project-action";
+ import ProjectTree, { WorkbenchProps } from '../../components/project-tree/project-tree';
  const drawerWidth = 240;
  
  type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'toolbar';
@@@ -153,15 -66,18 +156,15 @@@ class Workbench extends React.Component
                      classes={{
                          paper: classes.drawerPaper,
                      }}>
 -                    <div className={classes.toolbar} />
 -                    <ProjectTree
 +                    <div className={classes.toolbar}/>
-                     <Tree items={this.props.projects} render={(p: Project) =>
-                         <Link to={`/project/${p.name}`}>{p.name}</Link>
-                     }/>
++                          <ProjectTree
+                         projects={this.props.projects}
 -                        toggleProjectTreeItem={this.props.toggleProjectTreeItem} />
 -                </Drawer>
++                        toggleProjectTreeItem={this.props.toggleProjectTreeItem}/>
 +                </Drawer>}
                  <main className={classes.content}>
 -                    <div className={classes.toolbar} />
 +                    <div className={classes.toolbar}/>
                      <Switch>
 -                        <Route exact path="/">
 -                            <Typography noWrap>Hello new workbench!</Typography>
 -                        </Route>
 -                        <Route path="/project/:name" component={ProjectList} />
 +                        <Route path="/project/:name" component={ProjectList}/>
                      </Switch>
                  </main>
              </div>
      }
  }
  
 -export default connect(
 +export default connect<WorkbenchDataProps>(
      (state: RootState) => ({
 -        projects: state.projects
 -    }), {
 +        projects: state.projects,
 +        user: state.auth.user
-     })
++    }){
+         toggleProjectTreeItem: (id: string) => projectActions.toggleProjectTreeItem(id)
+     }
  )(
      withStyles(styles)(Workbench)
  );
diff --cc yarn.lock
index 7b9820bb5807c253e3aa0f5c2c5539f9d0053d6d,eadc3467022385e2b4cc66f4fab138d1f56c8d40..f3950fae03611dd1acd889ff4423ffb71c7c9dd0
+++ b/yarn.lock
      react-transition-group "^2.2.1"
      recompose "^0.26.0 || ^0.27.0"
      scroll "^2.0.3"
 -    warning "^3.0.0"
 +    warning "^4.0.1"
 +
 +"@material-ui/icons@^1.1.0":
 +  version "1.1.0"
 +  resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-1.1.0.tgz#4d025df7b0ba6ace8d6710079ed76013a4d26595"
 +  dependencies:
 +    recompose "^0.26.0 || ^0.27.0"
  
+ "@types/cheerio@*":
+   version "0.22.7"
+   resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.7.tgz#4a92eafedfb2b9f4437d3a4410006d81114c66ce"
+ "@types/enzyme-adapter-react-16@^1.0.2":
+   version "1.0.2"
+   resolved "https://registry.yarnpkg.com/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.2.tgz#15ae37c64d6221a6f4b3a4aacc357cf773859de4"
+   dependencies:
+     "@types/enzyme" "*"
+ "@types/enzyme@*", "@types/enzyme@^3.1.10":
+   version "3.1.10"
+   resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.1.10.tgz#28108a9864e65699751469551a803a35d2e26160"
+   dependencies:
+     "@types/cheerio" "*"
+     "@types/react" "*"
  "@types/history@*":
    version "4.6.2"
    resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0"
      csstype "^2.0.0"
      indefinite-observable "^1.0.1"
  
 -"@types/node@*", "@types/node@10.1.2":
 -  version "10.1.2"
 -  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.2.tgz#1b928a0baa408fc8ae3ac012cc81375addc147c6"
+ "@types/lodash@4.14.109":
+   version "4.14.109"
+   resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.109.tgz#b1c4442239730bf35cabaf493c772b18c045886d"
 +"@types/node@*", "@types/node@10.3.0":
 +  version "10.3.0"
 +  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.3.0.tgz#078516315a84d56216b5d4fed8f75d59d3b16cac"
  
  "@types/react-dom@16.0.5":
    version "16.0.5"