--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export const formatDate = (isoDate: string) => {
+ const date = new Date(isoDate);
+ return date.toLocaleString();
+};
+
+export const formatFileSize = (size?: number) => {
+ if (typeof size === "number") {
+ for (const { base, unit } of fileSizes) {
+ if (size >= base) {
+ return `${(size / base).toFixed()} ${unit}`;
+ }
+ }
+ }
+ return "";
+};
+
+const fileSizes = [
+ {
+ base: 1000000000000,
+ unit: "TB"
+ },
+ {
+ base: 1000000000,
+ unit: "GB"
+ },
+ {
+ base: 1000000,
+ unit: "MB"
+ },
+ {
+ base: 1000,
+ unit: "KB"
+ },
+ {
+ base: 1,
+ unit: "B"
+ }
+];
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { DataTable, DataTableProps, DataColumn, ColumnSelector } from "../../components/data-table";
+import { Typography, Grid, ListItem, Divider, List, ListItemIcon, ListItemText, Paper, Toolbar } from '@material-ui/core';
+import IconButton, { IconButtonProps } from '@material-ui/core/IconButton';
+import MoreVertIcon from "@material-ui/icons/MoreVert";
+import Popover from '../popover/popover';
+import { formatFileSize, formatDate } from '../../common/formatters';
+import { DataItem } from './data-item';
+
+interface DataExplorerProps {
+ items: DataItem[];
+ onItemClick: (item: DataItem) => void;
+}
+
+type DataExplorerState = Pick<DataTableProps<DataItem>, "columns">;
+
+class DataExplorer extends React.Component<DataExplorerProps, DataExplorerState> {
+ state: DataExplorerState = {
+ columns: [
+ {
+ name: "Name",
+ selected: true,
+ render: item => this.renderName(item)
+ },
+ {
+ name: "Status",
+ selected: true,
+ render: item => renderStatus(item.status)
+ },
+ {
+ name: "Type",
+ selected: true,
+ render: item => renderType(item.type)
+ },
+ {
+ name: "Owner",
+ selected: true,
+ render: item => renderOwner(item.owner)
+ },
+ {
+ name: "File size",
+ selected: true,
+ render: (item) => renderFileSize(item.fileSize)
+ },
+ {
+ name: "Last modified",
+ selected: true,
+ render: item => renderDate(item.lastModified)
+ },
+ {
+ name: "Actions",
+ selected: true,
+ configurable: false,
+ renderHeader: () => null,
+ render: renderItemActions
+ }
+ ]
+ };
+
+ render() {
+ return <Paper>
+ <Toolbar>
+ <Grid container justify="flex-end">
+ <ColumnSelector
+ columns={this.state.columns}
+ onColumnToggle={this.toggleColumn} />
+ </Grid>
+ </Toolbar>
+ <DataTable
+ columns={this.state.columns}
+ items={this.props.items} />
+ <Toolbar />
+ </Paper>;
+ }
+
+ toggleColumn = (column: DataColumn<DataItem>) => {
+ 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 });
+ }
+
+ renderName = (item: DataItem) =>
+ <Grid
+ container
+ alignItems="center"
+ wrap="nowrap"
+ spacing={16}
+ onClick={() => this.props.onItemClick(item)}>
+ <Grid item>
+ {renderIcon(item)}
+ </Grid>
+ <Grid item>
+ <Typography color="primary">
+ {item.name}
+ </Typography>
+ </Grid>
+ </Grid>
+
+}
+
+const renderIcon = (dataItem: DataItem) => {
+ switch (dataItem.type) {
+ case "arvados#group":
+ return <i className="fas fa-folder fa-lg" />;
+ case "arvados#groupList":
+ return <i className="fas fa-th fa-lg" />;
+ default:
+ return <i />;
+ }
+};
+
+const renderDate = (date: string) =>
+ <Typography noWrap>
+ {formatDate(date)}
+ </Typography>;
+
+const renderFileSize = (fileSize?: number) =>
+ <Typography noWrap>
+ {formatFileSize(fileSize)}
+ </Typography>;
+
+const renderOwner = (owner: string) =>
+ <Typography noWrap color="primary">
+ {owner}
+ </Typography>;
+
+const renderType = (type: string) =>
+ <Typography noWrap>
+ {type}
+ </Typography>;
+
+const renderStatus = (status?: string) =>
+ <Typography noWrap align="center">
+ {status || "-"}
+ </Typography>;
+
+const renderItemActions = () =>
+ <Grid container justify="flex-end">
+ <Popover triggerComponent={ItemActionsTrigger}>
+ <List dense>
+ {[{
+ icon: "fas fa-users",
+ label: "Share"
+ },
+ {
+ icon: "fas fa-sign-out-alt",
+ label: "Move to"
+ },
+ {
+ icon: "fas fa-star",
+ label: "Add to favourite"
+ },
+ {
+ icon: "fas fa-edit",
+ label: "Rename"
+ },
+ {
+ icon: "fas fa-copy",
+ label: "Make a copy"
+ },
+ {
+ icon: "fas fa-download",
+ label: "Download"
+ }].map(renderAction)}
+ < Divider />
+ {renderAction({ icon: "fas fa-trash-alt", label: "Remove" })}
+ </List>
+ </Popover>
+ </Grid>;
+
+const renderAction = (action: { label: string, icon: string }, index?: number) =>
+ <ListItem button key={index}>
+ <ListItemIcon>
+ <i className={action.icon} />
+ </ListItemIcon>
+ <ListItemText>
+ {action.label}
+ </ListItemText>
+ </ListItem>;
+
+const ItemActionsTrigger: React.SFC<IconButtonProps> = (props) =>
+ <IconButton {...props}>
+ <MoreVertIcon />
+ </IconButton>;
+
+export default DataExplorer;
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface DataItem {
+ name: string;
+ type: string;
+ owner: string;
+ lastModified: string;
+ fileSize?: number;
+ status?: string;
+}
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export { default as DataExplorer } from "./data-explorer";
+export * from "./data-item";
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { mount, configure } from "enzyme";
+import * as Adapter from "enzyme-adapter-react-16";
+import ColumnSelector, { ColumnSelectorProps, ColumnSelectorTrigger } from "./column-selector";
+import { DataColumn } from "../data-column";
+import { ListItem, Checkbox } from "@material-ui/core";
+
+configure({ adapter: new Adapter() });
+
+describe("<ColumnSelector />", () => {
+ it("shows only configurable columns", () => {
+ const columns: Array<DataColumn<void>> = [
+ {
+ name: "Column 1",
+ render: () => <span />,
+ selected: true
+ },
+ {
+ name: "Column 2",
+ render: () => <span />,
+ selected: true,
+ configurable: true,
+ },
+ {
+ name: "Column 3",
+ render: () => <span />,
+ selected: true,
+ configurable: false
+ }
+ ];
+ const columnsConfigurator = mount(<ColumnSelector columns={columns} onColumnToggle={jest.fn()} />);
+ columnsConfigurator.find(ColumnSelectorTrigger).simulate("click");
+ expect(columnsConfigurator.find(ListItem)).toHaveLength(2);
+ });
+
+ it("renders checked checkboxes next to selected columns", () => {
+ const columns: Array<DataColumn<void>> = [
+ {
+ name: "Column 1",
+ render: () => <span />,
+ selected: true
+ },
+ {
+ name: "Column 2",
+ render: () => <span />,
+ selected: false
+ },
+ {
+ name: "Column 3",
+ render: () => <span />,
+ selected: true
+ }
+ ];
+ const columnsConfigurator = mount(<ColumnSelector columns={columns} onColumnToggle={jest.fn()} />);
+ columnsConfigurator.find(ColumnSelectorTrigger).simulate("click");
+ expect(columnsConfigurator.find(Checkbox).at(0).prop("checked")).toBe(true);
+ expect(columnsConfigurator.find(Checkbox).at(1).prop("checked")).toBe(false);
+ expect(columnsConfigurator.find(Checkbox).at(2).prop("checked")).toBe(true);
+ });
+
+ it("calls onColumnToggle with clicked column", () => {
+ const columns: Array<DataColumn<void>> = [
+ {
+ name: "Column 1",
+ render: () => <span />,
+ selected: true
+ }
+ ];
+ const onColumnToggle = jest.fn();
+ const columnsConfigurator = mount(<ColumnSelector columns={columns} onColumnToggle={onColumnToggle} />);
+ columnsConfigurator.find(ColumnSelectorTrigger).simulate("click");
+ columnsConfigurator.find(ListItem).simulate("click");
+ expect(onColumnToggle).toHaveBeenCalledWith(columns[0]);
+ });
+});
\ No newline at end of file
--- /dev/null
+// 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, Paper, List, Checkbox, ListItemText, ListItem } from '@material-ui/core';
+import MenuIcon from "@material-ui/icons/Menu";
+import { DataColumn, isColumnConfigurable } from '../data-column';
+import Popover from "../../popover/popover";
+import { IconButtonProps } from '@material-ui/core/IconButton';
+
+export interface ColumnSelectorProps {
+ columns: Array<DataColumn<any>>;
+ onColumnToggle: (column: DataColumn<any>) => void;
+}
+
+const ColumnSelector: React.SFC<ColumnSelectorProps & WithStyles<CssRules>> = ({ columns, onColumnToggle, classes }) =>
+ <Popover triggerComponent={ColumnSelectorTrigger}>
+ <Paper>
+ <List dense>
+ {columns
+ .filter(isColumnConfigurable)
+ .map((column, index) => (
+ <ListItem
+ button
+ key={index}
+ onClick={() => onColumnToggle(column)}>
+ <Checkbox
+ disableRipple
+ color="primary"
+ checked={column.selected}
+ className={classes.checkbox} />
+ <ListItemText>
+ {column.name}
+ </ListItemText>
+ </ListItem>
+ ))}
+ </List>
+ </Paper>
+ </Popover>;
+
+export const ColumnSelectorTrigger: React.SFC<IconButtonProps> = (props) =>
+ <IconButton {...props}>
+ <MenuIcon />
+ </IconButton>;
+
+type CssRules = "checkbox";
+
+const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
+ checkbox: {
+ width: 24,
+ height: 24
+ }
+});
+
+export default withStyles(styles)(ColumnSelector);
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface DataColumn<T> {
+ name: string;
+ selected: boolean;
+ configurable?: boolean;
+ key?: React.Key;
+ render: (item: T) => React.ReactElement<void>;
+ renderHeader?: () => React.ReactElement<void> | null;
+}
+
+export const isColumnConfigurable = <T>(column: DataColumn<T>) => {
+ return column.configurable === undefined || column.configurable;
+};
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { mount, configure } from "enzyme";
+import * as Adapter from "enzyme-adapter-react-16";
+import DataTable from "./data-table";
+import { DataColumn } from "./data-column";
+import { TableHead, TableCell, Typography, TableBody, Button } from "@material-ui/core";
+
+configure({ adapter: new Adapter() });
+
+describe("<DataTable />", () => {
+ it("shows only selected columns", () => {
+ const columns: Array<DataColumn<string>> = [
+ {
+ name: "Column 1",
+ render: () => <span />,
+ selected: true
+ },
+ {
+ name: "Column 2",
+ render: () => <span />,
+ selected: true
+ },
+ {
+ name: "Column 3",
+ render: () => <span />,
+ selected: false
+ }
+ ];
+ const dataTable = mount(<DataTable columns={columns} items={["item 1"]}/>);
+ expect(dataTable.find(TableHead).find(TableCell)).toHaveLength(2);
+ });
+
+ it("renders column name", () => {
+ const columns: Array<DataColumn<string>> = [
+ {
+ name: "Column 1",
+ render: () => <span />,
+ selected: true
+ }
+ ];
+ const dataTable = mount(<DataTable columns={columns} items={["item 1"]}/>);
+ expect(dataTable.find(TableHead).find(TableCell).text()).toBe("Column 1");
+ });
+
+ it("uses renderHeader instead of name prop", () => {
+ const columns: Array<DataColumn<string>> = [
+ {
+ name: "Column 1",
+ renderHeader: () => <span>Column Header</span>,
+ render: () => <span />,
+ selected: true
+ }
+ ];
+ const dataTable = mount(<DataTable columns={columns} items={["item 1"]}/>);
+ expect(dataTable.find(TableHead).find(TableCell).text()).toBe("Column Header");
+ });
+
+ it("passes column key prop to corresponding cells", () => {
+ const columns: Array<DataColumn<string>> = [
+ {
+ name: "Column 1",
+ key: "column-1-key",
+ render: () => <span />,
+ selected: true
+ }
+ ];
+ const dataTable = mount(<DataTable columns={columns} items={["item 1"]}/>);
+ expect(dataTable.find(TableHead).find(TableCell).key()).toBe("column-1-key");
+ expect(dataTable.find(TableBody).find(TableCell).key()).toBe("column-1-key");
+ });
+
+ it("shows information that items array is empty", () => {
+ const columns: Array<DataColumn<string>> = [
+ {
+ name: "Column 1",
+ render: () => <span />,
+ selected: true
+ }
+ ];
+ const dataTable = mount(<DataTable columns={columns} items={[]}/>);
+ expect(dataTable.find(Typography).text()).toBe("No items");
+ });
+
+ it("renders items", () => {
+ const columns: Array<DataColumn<string>> = [
+ {
+ name: "Column 1",
+ render: (item) => <Typography>{item}</Typography>,
+ selected: true
+ },
+ {
+ name: "Column 2",
+ render: (item) => <Button>{item}</Button>,
+ selected: true
+ }
+ ];
+ const dataTable = mount(<DataTable columns={columns} items={["item 1"]}/>);
+ expect(dataTable.find(TableBody).find(Typography).text()).toBe("item 1");
+ expect(dataTable.find(TableBody).find(Button).text()).toBe("item 1");
+ });
+
+
+});
\ No newline at end of file
--- /dev/null
+// 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 } from '@material-ui/core';
+import { DataColumn } from './data-column';
+
+export interface DataTableProps<T> {
+ items: T[];
+ columns: Array<DataColumn<T>>;
+ onItemClick?: (item: T) => void;
+}
+
+class DataTable<T> extends React.Component<DataTableProps<T> & WithStyles<CssRules>> {
+ render() {
+ const { items, columns, classes, onItemClick } = this.props;
+ return <div className={classes.tableContainer}>
+ {items.length > 0 ?
+ <Table>
+ <TableHead>
+ <TableRow>
+ {columns
+ .filter(column => column.selected)
+ .map(({ name, renderHeader, key }, index) =>
+ <TableCell key={key || index}>
+ {renderHeader ? renderHeader() : name}
+ </TableCell>
+ )}
+ </TableRow>
+ </TableHead>
+ <TableBody className={classes.tableBody}>
+ {items
+ .map((item, index) =>
+ <TableRow
+ hover
+ key={index}
+ onClick={() => onItemClick && onItemClick(item)}>
+ {columns
+ .filter(column => column.selected)
+ .map((column, index) => (
+ <TableCell key={column.key || index}>
+ {column.render(item)}
+ </TableCell>
+ ))}
+ </TableRow>
+ )}
+ </TableBody>
+ </Table> : <Typography
+ className={classes.noItemsInfo}
+ variant="body2"
+ gutterBottom>
+ No items
+ </Typography>}
+ </div>;
+ }
+}
+
+type CssRules = "tableBody" | "tableContainer" | "noItemsInfo";
+
+const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
+ tableContainer: {
+ overflowX: 'auto'
+ },
+ tableBody: {
+ background: theme.palette.background.paper
+ },
+ noItemsInfo: {
+ textAlign: "center",
+ padding: theme.spacing.unit
+ }
+});
+
+export default withStyles(styles)(DataTable);
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export * from "./data-column";
+export * from "./column-selector/column-selector";
+export { default as ColumnSelector } from "./column-selector/column-selector";
+export * from "./data-table";
+export { default as DataTable } from "./data-table";
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { mount, configure } from "enzyme";
+import * as Adapter from "enzyme-adapter-react-16";
+
+import Popover, { DefaultTrigger } from "./popover";
+import Button, { ButtonProps } from "@material-ui/core/Button";
+
+configure({ adapter: new Adapter() });
+
+describe("<Popover />", () => {
+ it("opens on default trigger click", () => {
+ const popover = mount(<Popover />);
+ popover.find(DefaultTrigger).simulate("click");
+ expect(popover.state().anchorEl).toBeDefined();
+ });
+
+ it("renders custom trigger", () => {
+ const popover = mount(<Popover triggerComponent={CustomTrigger} />);
+ expect(popover.find(Button).text()).toBe("Open popover");
+ });
+
+ it("opens on custom trigger click", () => {
+ const popover = mount(<Popover triggerComponent={CustomTrigger} />);
+ popover.find(CustomTrigger).simulate("click");
+ expect(popover.state().anchorEl).toBeDefined();
+ });
+
+ it("renders children when opened", () => {
+ const popover = mount(
+ <Popover>
+ <CustomTrigger />
+ </Popover>
+ );
+ popover.find(DefaultTrigger).simulate("click");
+ expect(popover.find(CustomTrigger)).toHaveLength(1);
+ });
+
+ it("does not close if closeOnContentClick is not set", () => {
+ const popover = mount(
+ <Popover>
+ <CustomTrigger />
+ </Popover>
+ );
+ popover.find(DefaultTrigger).simulate("click");
+ popover.find(CustomTrigger).simulate("click");
+ expect(popover.state().anchorEl).toBeDefined();
+ });
+ it("closes on content click if closeOnContentClick is set", () => {
+ const popover = mount(
+ <Popover closeOnContentClick>
+ <CustomTrigger />
+ </Popover>
+ );
+ popover.find(DefaultTrigger).simulate("click");
+ popover.find(CustomTrigger).simulate("click");
+ expect(popover.state().anchorEl).toBeUndefined();
+ });
+
+});
+
+const CustomTrigger: React.SFC<ButtonProps> = (props) => (
+ <Button {...props}>
+ Open popover
+ </Button>
+);
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { Popover as MaterialPopover } from '@material-ui/core';
+
+import { PopoverOrigin } from '@material-ui/core/Popover';
+import IconButton, { IconButtonProps } from '@material-ui/core/IconButton';
+
+export interface PopoverProps {
+ triggerComponent?: React.ComponentType<{ onClick: (event: React.MouseEvent<any>) => void }>;
+ closeOnContentClick?: boolean;
+}
+
+
+class Popover extends React.Component<PopoverProps> {
+
+ state = {
+ anchorEl: undefined
+ };
+
+ transformOrigin: PopoverOrigin = {
+ vertical: "top",
+ horizontal: "right",
+ };
+
+ render() {
+ const Trigger = this.props.triggerComponent || DefaultTrigger;
+ return (
+ <>
+ <Trigger onClick={this.handleTriggerClick} />
+ <MaterialPopover
+ anchorEl={this.state.anchorEl}
+ open={Boolean(this.state.anchorEl)}
+ onClose={this.handleClose}
+ onClick={this.handleSelfClick}
+ transformOrigin={this.transformOrigin}
+ anchorOrigin={this.transformOrigin}
+ >
+ {this.props.children}
+ </MaterialPopover>
+ </>
+ );
+ }
+
+ handleClose = () => {
+ this.setState({ anchorEl: undefined });
+ }
+
+ handleTriggerClick = (event: React.MouseEvent<any>) => {
+ this.setState({ anchorEl: event.currentTarget });
+ }
+
+ handleSelfClick = () => {
+ if (this.props.closeOnContentClick) {
+ this.handleClose();
+ }
+ }
+
+}
+
+export const DefaultTrigger: React.SFC<IconButtonProps> = (props) => (
+ <IconButton {...props}>
+ <i className="fas" />
+ </IconButton>
+);
+
+export default Popover;
uuid: string;
ownerUuid: string;
href: string;
+ kind: string;
}
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;
createdAt: '2018-01-01',
modifiedAt: '2018-01-01',
ownerUuid: 'owner-test123',
- uuid: 'test123'
+ uuid: 'test123',
+ kind: ""
};
const state = projectsReducer(initialState, actions.CREATE_PROJECT(project));
createdAt: '2018-01-01',
modifiedAt: '2018-01-01',
ownerUuid: 'owner-test123',
- uuid: 'test123'
+ uuid: 'test123',
+ kind: ""
};
const projects = [project, project];
export type ProjectState = Array<TreeItem<Project>>;
-function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
+export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
let item;
for (const t of tree) {
item = t.id === itemId
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { DataTableProps } from "../../components/data-table";
+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 { DataExplorer, DataItem } from '../../components/data-explorer';
+import { TreeItem } from '../../components/tree/tree';
+
+interface DataExplorerViewDataProps {
+ projects: ProjectState;
+}
+
+type DataExplorerViewProps = DataExplorerViewDataProps & RouteComponentProps<{ name: string }> & DispatchProp;
+
+type DataExplorerViewState = Pick<DataTableProps<Project>, "columns">;
+
+interface MappedProjectItem extends DataItem {
+ uuid: string;
+}
+
+class DataExplorerView extends React.Component<DataExplorerViewProps, DataExplorerViewState> {
+
+ render() {
+ const project = findTreeItem(this.props.projects, this.props.match.params.name);
+ const projectItems = project && project.items || [];
+ return (
+ <DataExplorer
+ items={projectItems.map(mapTreeItem)}
+ onItemClick={this.goToProject}
+ />
+ );
+ }
+
+ goToProject = (project: MappedProjectItem) => {
+ this.props.dispatch(push(`/project/${project.uuid}`));
+ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(project.uuid));
+ }
+
+}
+
+const mapTreeItem = (item: TreeItem<Project>): MappedProjectItem => ({
+ name: item.data.name,
+ type: item.data.kind,
+ owner: item.data.ownerUuid,
+ lastModified: item.data.modifiedAt,
+ uuid: item.data.uuid
+});
+
+
+export default connect(
+ (state: RootState) => ({
+ projects: state.projects
+ })
+)(DataExplorerView);
import { StyleRulesCallback, Theme, WithStyles, withStyles } from '@material-ui/core/styles';
import Drawer from '@material-ui/core/Drawer';
-import AppBar from '@material-ui/core/AppBar';
-import Toolbar from '@material-ui/core/Toolbar';
-import Typography from '@material-ui/core/Typography';
import { connect, DispatchProp } from "react-redux";
-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 MainAppBar, { MainAppBarActionProps, MainAppBarMenuItems, MainAppBarMenuItem } from '../../components/main-app-bar/main-app-bar';
+import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../components/main-app-bar/main-app-bar';
import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs';
import { push } from 'react-router-redux';
import projectActions from "../../store/project/project-action";
import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
import { Project } from "../../models/project";
import { projectService } from '../../services/services';
+import DataExplorer from '../data-explorer/data-explorer';
const drawerWidth = 240;
toggleProjectTreeItem = (itemId: string, status: TreeItemStatus) => {
if (status === TreeItemStatus.Loaded) {
- this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(itemId));
+ this.openProjectItem(itemId);
} else {
- this.props.dispatch<any>(projectService.getProjectList(itemId)).then(() => {
- this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(itemId));
- });
+ this.props.dispatch<any>(projectService.getProjectList(itemId)).then(() => this.openProjectItem(itemId));
}
}
+ openProjectItem = (itemId: string) => {
+ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(itemId));
+ this.props.dispatch(push(`/project/${itemId}`));
+ }
+
render() {
const { classes, user } = this.props;
return (
toggleProjectTreeItem={this.toggleProjectTreeItem} />
</Drawer>}
<main className={classes.content}>
+ <div className={classes.toolbar} />
<div className={classes.toolbar} />
<Switch>
- <Route path="/project/:name" component={ProjectList} />
+ <Route path="/project/:name" component={DataExplorer} />
</Switch>
</main>
</div>