From 973fce01f05a50d4aa0169a88281f20476b305b2 Mon Sep 17 00:00:00 2001 From: Michal Klobukowski Date: Wed, 13 Jun 2018 16:28:16 +0200 Subject: [PATCH] Create data-explorer and project-explorer prototype Feature #13601 Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski --- src/components/data-explorer/column.ts | 9 +++ .../columns-configurator.tsx | 74 +++++++++++++++++ .../data-explorer/data-explorer.tsx | 76 ++++++++++++++++++ src/models/project.ts | 1 + .../project-service/project-service.ts | 3 +- src/store/project/project-reducer.test.ts | 6 +- src/store/project/project-reducer.ts | 2 +- .../project-explorer/project-explorer.tsx | 80 +++++++++++++++++++ src/views/workbench/workbench.tsx | 5 +- 9 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 src/components/data-explorer/column.ts create mode 100644 src/components/data-explorer/columns-configurator/columns-configurator.tsx create mode 100644 src/components/data-explorer/data-explorer.tsx create mode 100644 src/views/project-explorer/project-explorer.tsx diff --git a/src/components/data-explorer/column.ts b/src/components/data-explorer/column.ts new file mode 100644 index 00000000..bbbc6ef9 --- /dev/null +++ b/src/components/data-explorer/column.ts @@ -0,0 +1,9 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +export interface Column { + header: string; + selected: boolean; + render: (item: T) => React.ReactElement; +} \ No newline at end of file diff --git a/src/components/data-explorer/columns-configurator/columns-configurator.tsx b/src/components/data-explorer/columns-configurator/columns-configurator.tsx new file mode 100644 index 00000000..e0680e98 --- /dev/null +++ b/src/components/data-explorer/columns-configurator/columns-configurator.tsx @@ -0,0 +1,74 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import { WithStyles, StyleRulesCallback, Theme, withStyles, IconButton, Popover, Paper, List, Checkbox, ListItemText, ListItem } from '@material-ui/core'; +import ColumnsIcon from "@material-ui/icons/ViewWeek"; +import { Column } from '../column'; +import { PopoverOrigin } from '@material-ui/core/Popover'; + +export interface ColumnsConfiguratorProps { + columns: Array>; + onColumnToggle: (column: Column) => void +} + + +class ColumnsConfigurator extends React.Component> { + + state = { + anchorEl: undefined + } + + transformOrigin: PopoverOrigin = { + vertical: "top", + horizontal: "right", + } + + render() { + const { columns, onColumnToggle } = this.props; + return ( + <> + + + + + { + columns.map((column, index) => ( + onColumnToggle(column)}> + + {column.header} + + + )) + } + + + + + ); + } + + handleClose = () => { + this.setState({ anchorEl: undefined }); + } + + handleClick = (event: React.MouseEvent) => { + this.setState({ anchorEl: event.currentTarget }); + } + +} + +type CssRules = "root"; + +const styles: StyleRulesCallback = (theme: Theme) => ({ + root: {} +}); + +export default withStyles(styles)(ColumnsConfigurator); diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx new file mode 100644 index 00000000..9090bff2 --- /dev/null +++ b/src/components/data-explorer/data-explorer.tsx @@ -0,0 +1,76 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import { Table, TableBody, TableRow, TableCell, TableHead, StyleRulesCallback, Theme, WithStyles, withStyles, Typography, Grid } from '@material-ui/core'; +import { Column } from './column'; +import ColumnsConfigurator from "./columns-configurator/columns-configurator"; + +export interface DataExplorerProps { + items: T[]; + columns: Array>; + onColumnToggle: (column: Column) => void; + onItemClick: (item: T) => void; +} + +class DataExplorer extends React.Component & WithStyles> { + render() { + const { items, columns, classes, onItemClick, onColumnToggle } = this.props; + return ( +
+ + + +
+ { + items.length > 0 ? ( + + + + { + columns.filter(column => column.selected).map((column, index) => ( + {column.header} + )) + } + + + + { + items.map((item, index) => ( + onItemClick(item)}> + { + columns.filter(column => column.selected).map((column, index) => ( + + {column.render(item)} + + )) + } + + )) + } + +
+ ) : ( + No items + ) + } + +
+
+ ); + } +} + +type CssRules = "tableBody" | "tableContainer"; + +const styles: StyleRulesCallback = (theme: Theme) => ({ + tableContainer: { + overflowX: 'auto' + }, + tableBody: { + background: theme.palette.background.paper + } +}); + +export default withStyles(styles)(DataExplorer); diff --git a/src/models/project.ts b/src/models/project.ts index 83fb59bd..830621b4 100644 --- a/src/models/project.ts +++ b/src/models/project.ts @@ -9,4 +9,5 @@ export interface Project { uuid: string; ownerUuid: string; href: string; + kind: string; } diff --git a/src/services/project-service/project-service.ts b/src/services/project-service/project-service.ts index 9350dabd..d454a7b6 100644 --- a/src/services/project-service/project-service.ts +++ b/src/services/project-service/project-service.ts @@ -48,7 +48,8 @@ export default class ProjectService { modifiedAt: g.modified_at, href: g.href, uuid: g.uuid, - ownerUuid: g.owner_uuid + ownerUuid: g.owner_uuid, + kind: g.kind } as Project)); dispatch(actions.PROJECTS_SUCCESS({projects, parentItemId: parentUuid})); return projects; diff --git a/src/store/project/project-reducer.test.ts b/src/store/project/project-reducer.test.ts index 9c1ed3b4..f6dfea7e 100644 --- a/src/store/project/project-reducer.test.ts +++ b/src/store/project/project-reducer.test.ts @@ -14,7 +14,8 @@ describe('project-reducer', () => { 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,7 +30,8 @@ describe('project-reducer', () => { createdAt: '2018-01-01', modifiedAt: '2018-01-01', ownerUuid: 'owner-test123', - uuid: 'test123' + uuid: 'test123', + kind: "" }; const projects = [project, project]; diff --git a/src/store/project/project-reducer.ts b/src/store/project/project-reducer.ts index 887cf89b..909038e7 100644 --- a/src/store/project/project-reducer.ts +++ b/src/store/project/project-reducer.ts @@ -9,7 +9,7 @@ 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 diff --git a/src/views/project-explorer/project-explorer.tsx b/src/views/project-explorer/project-explorer.tsx new file mode 100644 index 00000000..de866c2c --- /dev/null +++ b/src/views/project-explorer/project-explorer.tsx @@ -0,0 +1,80 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from 'react'; +import DataExplorer, { DataExplorerProps } from "../../components/data-explorer/data-explorer"; +import { RouteComponentProps } from 'react-router'; +import { Project } from '../../models/project'; +import { ProjectState, findTreeItem } from '../../store/project/project-reducer'; +import { RootState } from '../../store/store'; +import { connect, DispatchProp } from 'react-redux'; +import { push } from 'react-router-redux'; +import projectActions from "../../store/project/project-action" +import { Typography } from '@material-ui/core'; +import { Column } from '../../components/data-explorer/column'; + +interface ProjectExplorerViewDataProps { + projects: ProjectState +} + +type ProjectExplorerViewProps = ProjectExplorerViewDataProps & RouteComponentProps<{ name: string }> & DispatchProp; + +type ProjectExplorerViewState = Pick, "columns">; + +class ProjectExplorerView extends React.Component { + + state: ProjectExplorerViewState = { + columns: [ + { header: "Name", selected: true, render: item => {renderIcon(item.kind)} {item.name} }, + { header: "Created at", selected: true, render: item => {formatDate(item.createdAt)} }, + { header: "Modified at", selected: true, render: item => {formatDate(item.modifiedAt)} }, + { header: "UUID", selected: true, render: item => {item.uuid} }, + { header: "Owner UUID", selected: true, render: item => {item.ownerUuid} }, + { header: "URL", selected: true, render: item => {item.href} } + ] + } + + render() { + const project = findTreeItem(this.props.projects, this.props.match.params.name); + const projectItems = project && project.items || []; + return ( + item.data)} onItemClick={this.goToProject} onColumnToggle={this.toggleColumn} /> + ); + } + + + goToProject = (project: Project) => { + this.props.dispatch(push(`/project/${project.uuid}`)); + this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(project.uuid)); + } + + toggleColumn = (column: Column) => { + const index = this.state.columns.indexOf(column); + const columns = this.state.columns.slice(0); + columns.splice(index, 1, { ...column, selected: !column.selected }); + this.setState({ columns }); + } +} + +const formatDate = (isoDate: string) => { + const date = new Date(isoDate); + return date.toLocaleString(); +}; + +const renderIcon = (kind: string) => { + switch (kind) { + case "arvados#group": + return ; + case "arvados#groupList": + return ; + default: + return ; + } +}; + +export default connect( + (state: RootState) => ({ + projects: state.projects + }) +)(ProjectExplorerView); diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index d18d113b..f4539471 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -28,6 +28,8 @@ import ProjectTree from '../../components/project-tree/project-tree'; import { TreeItem } from "../../components/tree/tree"; import { Project } from "../../models/project"; import { projectService } from '../../services/services'; +import ProjectExplorer from '../project-explorer/project-explorer'; +import { push } from 'react-router-redux'; const drawerWidth = 240; @@ -107,6 +109,7 @@ class Workbench extends React.Component { toggleProjectTreeItem = (itemId: string) => { this.props.dispatch(projectService.getProjectList(itemId)).then(() => { this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(itemId)); + this.props.dispatch(push(`/project/${itemId}`)) }); }; @@ -171,7 +174,7 @@ class Workbench extends React.Component {
- +
-- 2.30.2