From cf035c0589f045849ba635a82b6857fdd6f618ed Mon Sep 17 00:00:00 2001 From: Daniel Kos Date: Thu, 31 May 2018 09:16:32 +0200 Subject: [PATCH] Add tree structure rendering Feature #13535 Arvados-DCO-1.1-Signed-off-by: Daniel Kos : --- package.json | 2 ++ src/components/tree/tree.tsx | 35 ++++++++++++++++++++------- src/index.tsx | 40 +++++++++++++++++++++++++++---- src/store/project-action.ts | 3 ++- src/store/project-reducer.ts | 26 +++++++++++++++++++- src/views/workbench/workbench.tsx | 22 ++++++++++------- tslint.json | 4 +++- yarn.lock | 6 ++++- 8 files changed, 113 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index b213bb5e2e..13f88a42c5 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@material-ui/core": "1.0.0", + "lodash": "4.17.10", "react": "16.3.2", "react-dom": "16.3.2", "react-redux": "5.0.7", @@ -32,6 +33,7 @@ "@types/react-router-dom": "4.2.6", "@types/react-router-redux": "5.0.14", "@types/redux-devtools": "3.0.44", + "@types/lodash": "4.14.109", "typescript": "2.8.3" }, "moduleNameMapper": { diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx index 0c99db8a50..e8ba710398 100644 --- a/src/components/tree/tree.tsx +++ b/src/components/tree/tree.tsx @@ -6,20 +6,39 @@ import * as React from 'react'; import List from "@material-ui/core/List/List"; import ListItem from "@material-ui/core/ListItem/ListItem"; import { ReactElement } from "react"; +import Collapse from "@material-ui/core/Collapse/Collapse"; + +export interface TreeItem { + data: T; + id: string; + open: boolean; + items?: Array>; +} interface TreeProps { - items: T[], - render: (item: T) => ReactElement<{}> + items?: Array>; + render: (item: T) => ReactElement<{}>; + toggleItem: (id: string) => any; + level?: number; } class Tree extends React.Component, {}> { - render() { - return - {this.props.items.map((it: T, idx: number) => - - {this.props.render(it)} + render(): ReactElement { + const level = this.props.level ? this.props.level : 0; + return + {this.props.items && this.props.items.map((it: TreeItem, idx: number) => +
+ this.props.toggleItem(it.id)} style={{paddingLeft: (level + 1) * 30}}> + {this.props.render(it.data)} - )} + {it.items && it.items.length > 0 && + + + } +
)}
} } diff --git a/src/index.tsx b/src/index.tsx index dacada2e6e..ae16dce350 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -12,14 +12,44 @@ import { Route, Router } from "react-router"; import createBrowserHistory from "history/createBrowserHistory"; import configureStore from "./store/store"; import { ConnectedRouter } from "react-router-redux"; +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> { + const projects = tree.map((t, idx) => ({ + id: `l${level}i${idx}`, + open: false, + data: { + name: t[0], + 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: [ - { name: 'Mouse genome', createdAt: '2018-05-01' }, - { name: 'Human body', createdAt: '2018-05-01' }, - { name: 'Secret operation', createdAt: '2018-05-01' } - ], + projects, router: { location: null } diff --git a/src/store/project-action.ts b/src/store/project-action.ts index 784adb3236..7cd3bacc24 100644 --- a/src/store/project-action.ts +++ b/src/store/project-action.ts @@ -7,7 +7,8 @@ import { Project } from "../models/project"; export const actions = { createProject: createStandardAction('@@project/create')(), - removeProject: createStandardAction('@@project/remove')() + removeProject: createStandardAction('@@project/remove')(), + toggleProjectTreeItem: createStandardAction('@@project/toggleTreeItem')() }; export type ProjectAction = ActionType; diff --git a/src/store/project-reducer.ts b/src/store/project-reducer.ts index 5e2d19212f..f1056f3c63 100644 --- a/src/store/project-reducer.ts +++ b/src/store/project-reducer.ts @@ -5,14 +5,38 @@ import { getType } from "typesafe-actions"; import { Project } from "../models/project"; import { actions, ProjectAction } from "./project-action"; +import { TreeItem } from "../components/tree/tree"; +import * as _ from 'lodash'; -type ProjectState = Project[]; +type ProjectState = Array>; + +function findTreeItem(tree: Array>, itemId: string): TreeItem | undefined { + let item; + for (const t of tree) { + item = t.id === itemId + ? t + : findTreeItem(t.items ? t.items : [], itemId); + if (item) { + break; + } + } + return item; +} const projectsReducer = (state: ProjectState = [], action: ProjectAction) => { switch (action.type) { case getType(actions.createProject): { return [...state, action.payload]; } + case getType(actions.toggleProjectTreeItem): { + const tree = _.cloneDeep(state); + const itemId = action.payload; + const item = findTreeItem(tree, itemId); + if (item) { + item.open = !item.open; + } + return tree; + } default: return state; } diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 365835a90a..7265380a2a 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -10,13 +10,16 @@ import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; import { connect } from "react-redux"; -import Tree from "../../components/tree/tree"; +import Tree, { TreeItem } from "../../components/tree/tree"; import { Project } from "../../models/project"; import { RootState } from "../../store/root-reducer"; import ProjectList from "../../components/project-list/project-list"; import { Route, Switch } from "react-router"; import { Link } from "react-router-dom"; +import { actions as projectActions } from "../../store/project-action"; +import ListItemText from "@material-ui/core/ListItemText/ListItemText"; + const drawerWidth = 240; type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'toolbar'; @@ -49,7 +52,8 @@ const styles: StyleRulesCallback = (theme: Theme) => ({ }); interface WorkbenchProps { - projects: Project[] + projects: Array>; + toggleProjectTreeItem: (id: string) => any; } interface WorkbenchState { @@ -58,7 +62,6 @@ interface WorkbenchState { class Workbench extends React.Component, WorkbenchState> { render() { const {classes} = this.props; - return (
@@ -74,9 +77,10 @@ class Workbench extends React.Component, W paper: classes.drawerPaper, }}>
- - {p.name} - }/> + } + />
@@ -92,10 +96,12 @@ class Workbench extends React.Component, W } } -export default connect( +export default connect( (state: RootState) => ({ projects: state.projects - }) + }), { + toggleProjectTreeItem: (id: string) => projectActions.toggleProjectTreeItem(id) + } )( withStyles(styles)(Workbench) ); diff --git a/tslint.json b/tslint.json index 18630ab5f9..1b26ab5f0f 100644 --- a/tslint.json +++ b/tslint.json @@ -9,7 +9,9 @@ "member-access": false, "jsx-boolean-value": false, "jsx-no-lambda": false, - "no-debugger": false + "no-debugger": false, + "no-console": false, + "no-shadowed-variable": false }, "linterOptions": { "exclude": [ diff --git a/yarn.lock b/yarn.lock index 93702cd00d..6d5ec0d581 100644 --- a/yarn.lock +++ b/yarn.lock @@ -70,6 +70,10 @@ csstype "^2.0.0" indefinite-observable "^1.0.1" +"@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.1.2": version "10.1.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.2.tgz#1b928a0baa408fc8ae3ac012cc81375addc147c6" @@ -5914,7 +5918,7 @@ react-redux@5.0.7: loose-envify "^1.1.0" prop-types "^15.6.0" -react-router-dom@^4.2.2: +react-router-dom@4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" dependencies: -- 2.30.2