"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"
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}>
export interface Project {
name: string;
createdAt: string;
- href: string
+ modifiedAt: string;
+ uuid: string;
+ ownerUuid: string;
++ href: string;
+ icon?: any;
}
--- /dev/null
- 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;
--- /dev/null
- 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;
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);
});
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';
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)
);
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"