[ARVADOS-WORKBENCH2] created: 1.1.4-339-g180c2c3
Git user
git at public.curoverse.com
Sun Jul 22 16:34:17 EDT 2018
at 180c2c37b635cbb7a33257d2ee9b4395553ce5e7 (commit)
commit 180c2c37b635cbb7a33257d2ee9b4395553ce5e7
Author: Daniel Kos <daniel.kos at contractors.roche.com>
Date: Sun Jul 22 22:34:00 2018 +0200
Remove default exports
No issue #
Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos at contractors.roche.com>
diff --git a/src/common/api/common-resource-service.test.ts b/src/common/api/common-resource-service.test.ts
index 7093b59..8346624 100644
--- a/src/common/api/common-resource-service.test.ts
+++ b/src/common/api/common-resource-service.test.ts
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import CommonResourceService from "./common-resource-service";
+import { CommonResourceService } from "./common-resource-service";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
diff --git a/src/common/api/common-resource-service.ts b/src/common/api/common-resource-service.ts
index 4c05392..2541fea 100644
--- a/src/common/api/common-resource-service.ts
+++ b/src/common/api/common-resource-service.ts
@@ -3,8 +3,8 @@
// SPDX-License-Identifier: AGPL-3.0
import * as _ from "lodash";
-import FilterBuilder from "./filter-builder";
-import OrderBuilder from "./order-builder";
+import { FilterBuilder } from "./filter-builder";
+import { OrderBuilder } from "./order-builder";
import { AxiosInstance } from "axios";
import { Resource } from "../../models/resource";
@@ -26,7 +26,7 @@ export interface ListResults<T> {
itemsAvailable: number;
}
-export default class CommonResourceService<T extends Resource> {
+export class CommonResourceService<T extends Resource> {
static mapResponseKeys = (response: any): Promise<any> =>
CommonResourceService.mapKeys(_.camelCase)(response.data)
@@ -92,6 +92,5 @@ export default class CommonResourceService<T extends Resource> {
update(uuid: string) {
throw new Error("Not implemented");
}
-
}
diff --git a/src/common/api/filter-builder.test.ts b/src/common/api/filter-builder.test.ts
index 3424393..d129a80 100644
--- a/src/common/api/filter-builder.test.ts
+++ b/src/common/api/filter-builder.test.ts
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import FilterBuilder from "./filter-builder";
+import { FilterBuilder } from "./filter-builder";
describe("FilterBuilder", () => {
diff --git a/src/common/api/filter-builder.ts b/src/common/api/filter-builder.ts
index 38c4fee..28ad060 100644
--- a/src/common/api/filter-builder.ts
+++ b/src/common/api/filter-builder.ts
@@ -5,8 +5,7 @@
import * as _ from "lodash";
import { Resource } from "../../models/resource";
-export default class FilterBuilder<T extends Resource = Resource> {
-
+export class FilterBuilder<T extends Resource = Resource> {
static create<T extends Resource = Resource>(resourcePrefix = "") {
return new FilterBuilder<T>(resourcePrefix);
}
@@ -61,5 +60,4 @@ export default class FilterBuilder<T extends Resource = Resource> {
}
return this;
}
-
}
diff --git a/src/common/api/order-builder.test.ts b/src/common/api/order-builder.test.ts
index b80756d..f53bddb 100644
--- a/src/common/api/order-builder.test.ts
+++ b/src/common/api/order-builder.test.ts
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import OrderBuilder from "./order-builder";
+import { OrderBuilder } from "./order-builder";
describe("OrderBuilder", () => {
it("should build correct order query", () => {
diff --git a/src/common/api/order-builder.ts b/src/common/api/order-builder.ts
index b5a2e80..ed99054 100644
--- a/src/common/api/order-builder.ts
+++ b/src/common/api/order-builder.ts
@@ -5,14 +5,14 @@
import * as _ from "lodash";
import { Resource } from "../../models/resource";
-export default class OrderBuilder<T extends Resource = Resource> {
+export class OrderBuilder<T extends Resource = Resource> {
static create<T extends Resource = Resource>(prefix?: string){
return new OrderBuilder<T>([], prefix);
}
private constructor(
- private order: string[] = [],
+ private order: string[] = [],
private prefix = ""){}
private addRule (direction: string, attribute: keyof T) {
diff --git a/src/common/api/url-builder.ts b/src/common/api/url-builder.ts
index e5786a2..0587c83 100644
--- a/src/common/api/url-builder.ts
+++ b/src/common/api/url-builder.ts
@@ -2,25 +2,25 @@
//
// SPDX-License-Identifier: AGPL-3.0
-export default class UrlBuilder {
- private url: string = "";
- private query: string = "";
+export class UrlBuilder {
+ private readonly url: string = "";
+ private query: string = "";
- constructor(host: string) {
- this.url = host;
- }
+ constructor(host: string) {
+ this.url = host;
+ }
- public addParam(param: string, value: string) {
- if (this.query.length === 0) {
- this.query += "?";
- } else {
- this.query += "&";
- }
- this.query += `${param}=${value}`;
- return this;
- }
+ public addParam(param: string, value: string) {
+ if (this.query.length === 0) {
+ this.query += "?";
+ } else {
+ this.query += "&";
+ }
+ this.query += `${param}=${value}`;
+ return this;
+ }
- public get() {
- return this.url + this.query;
- }
+ public get() {
+ return this.url + this.query;
+ }
}
diff --git a/src/common/url.ts b/src/common/url.ts
new file mode 100644
index 0000000..1824f26
--- /dev/null
+++ b/src/common/url.ts
@@ -0,0 +1,6 @@
+export function getUrlParameter(search: string, name: string) {
+ const safeName = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
+ const regex = new RegExp('[\\?&]' + safeName + '=([^&#]*)');
+ const results = regex.exec(search);
+ return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
+}
diff --git a/src/components/attribute/attribute.tsx b/src/components/attribute/attribute.tsx
index 4fb1d11..e2da980 100644
--- a/src/components/attribute/attribute.tsx
+++ b/src/components/attribute/attribute.tsx
@@ -7,37 +7,6 @@ import Typography from '@material-ui/core/Typography';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
import { ArvadosTheme } from '../../common/custom-theme';
-interface AttributeDataProps {
- label: string;
- value?: string | number;
- link?: string;
-}
-
-type AttributeProps = AttributeDataProps & WithStyles<CssRules>;
-
-class Attribute extends React.Component<AttributeProps> {
-
- hasLink() {
- return !!this.props.link;
- }
-
- render() {
- const { label, link, value, children, classes } = this.props;
- return <Typography component="div" className={classes.attribute}>
- <Typography component="span" className={classes.label}>{label}</Typography>
- { this.hasLink() ? (
- <a href='{link}' className={classes.link} target='_blank'>{value}</a>
- ) : (
- <Typography component="span" className={classes.value}>
- {value}
- {children}
- </Typography>
- )}
- </Typography>;
- }
-
-}
-
type CssRules = 'attribute' | 'label' | 'value' | 'link';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
@@ -64,4 +33,23 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
}
});
-export default withStyles(styles)(Attribute);
\ No newline at end of file
+interface AttributeDataProps {
+ label: string;
+ value?: string | number;
+ link?: string;
+ children?: React.ReactNode;
+}
+
+type AttributeProps = AttributeDataProps & WithStyles<CssRules>;
+
+export const Attribute = withStyles(styles)(({ label, link, value, children, classes }: AttributeProps) =>
+ <Typography component="div" className={classes.attribute}>
+ <Typography component="span" className={classes.label}>{label}</Typography>
+ { link
+ ? <a href={link} className={classes.link} target='_blank'>{value}</a>
+ : <Typography component="span" className={classes.value}>
+ {value}
+ {children}
+ </Typography> }
+ </Typography>
+);
diff --git a/src/components/breadcrumbs/breadcrumbs.test.tsx b/src/components/breadcrumbs/breadcrumbs.test.tsx
index ef3f888..ea3d5ac 100644
--- a/src/components/breadcrumbs/breadcrumbs.test.tsx
+++ b/src/components/breadcrumbs/breadcrumbs.test.tsx
@@ -6,7 +6,7 @@ import * as React from "react";
import { mount, configure } from "enzyme";
import * as Adapter from "enzyme-adapter-react-16";
-import Breadcrumbs from "./breadcrumbs";
+import { Breadcrumbs } from "./breadcrumbs";
import { Button } from "@material-ui/core";
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
@@ -50,4 +50,4 @@ describe("<Breadcrumbs />", () => {
});
-});
\ No newline at end of file
+});
diff --git a/src/components/breadcrumbs/breadcrumbs.tsx b/src/components/breadcrumbs/breadcrumbs.tsx
index cfcfd40..da549db 100644
--- a/src/components/breadcrumbs/breadcrumbs.tsx
+++ b/src/components/breadcrumbs/breadcrumbs.tsx
@@ -11,56 +11,52 @@ export interface Breadcrumb {
label: string;
}
+type CssRules = "item" | "currentItem" | "label";
+
+const styles: StyleRulesCallback<CssRules> = theme => ({
+ item: {
+ opacity: 0.6
+ },
+ currentItem: {
+ opacity: 1
+ },
+ label: {
+ textTransform: "none"
+ }
+});
+
interface BreadcrumbsProps {
items: Breadcrumb[];
onClick: (breadcrumb: Breadcrumb) => void;
onContextMenu: (event: React.MouseEvent<HTMLElement>, breadcrumb: Breadcrumb) => void;
}
-const Breadcrumbs: React.SFC<BreadcrumbsProps & WithStyles<CssRules>> = ({ classes, onClick, onContextMenu, items }) => {
- return <Grid container alignItems="center" wrap="nowrap">
- {
- items.map((item, index) => {
- const isLastItem = index === items.length - 1;
- return (
- <React.Fragment key={index}>
- <Tooltip title={item.label}>
- <Button
+export const Breadcrumbs = withStyles(styles)(
+ ({ classes, onClick, onContextMenu, items }: BreadcrumbsProps & WithStyles<CssRules>) =>
+ <Grid container alignItems="center" wrap="nowrap">
+ {
+ items.map((item, index) => {
+ const isLastItem = index === items.length - 1;
+ return (
+ <React.Fragment key={index}>
+ <Tooltip title={item.label}>
+ <Button
+ color="inherit"
+ className={isLastItem ? classes.currentItem : classes.item}
+ onClick={() => onClick(item)}
+ onContextMenu={event => onContextMenu(event, item)}>
+ <Typography
+ noWrap
color="inherit"
- className={isLastItem ? classes.currentItem : classes.item}
- onClick={() => onClick(item)}
- onContextMenu={event => onContextMenu(event, item)}>
- <Typography
- noWrap
- color="inherit"
- className={classes.label}>
- {item.label}
- </Typography>
- </Button>
- </Tooltip>
- {!isLastItem && <ChevronRightIcon color="inherit" className={classes.item} />}
- </React.Fragment>
- );
- })
- }
- </Grid>;
-};
-
-type CssRules = "item" | "currentItem" | "label";
-
-const styles: StyleRulesCallback<CssRules> = theme => {
- return {
- item: {
- opacity: 0.6
- },
- currentItem: {
- opacity: 1
- },
- label: {
- textTransform: "none"
- }
- };
-};
-
-export default withStyles(styles)(Breadcrumbs);
-
+ className={classes.label}>
+ {item.label}
+ </Typography>
+ </Button>
+ </Tooltip>
+ {!isLastItem && <ChevronRightIcon color="inherit" className={classes.item} />}
+ </React.Fragment>
+ );
+ })
+ }
+ </Grid>
+);
diff --git a/src/components/column-selector/column-selector.test.tsx b/src/components/column-selector/column-selector.test.tsx
index c2835ad..01dba85 100644
--- a/src/components/column-selector/column-selector.test.tsx
+++ b/src/components/column-selector/column-selector.test.tsx
@@ -5,7 +5,7 @@
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 { ColumnSelector, ColumnSelectorTrigger } from "./column-selector";
import { ListItem, Checkbox } from "@material-ui/core";
import { DataColumns } from "../data-table/data-table";
diff --git a/src/components/column-selector/column-selector.tsx b/src/components/column-selector/column-selector.tsx
index b5dd43b..0f496e2 100644
--- a/src/components/column-selector/column-selector.tsx
+++ b/src/components/column-selector/column-selector.tsx
@@ -3,19 +3,32 @@
// 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 { WithStyles, StyleRulesCallback, withStyles, IconButton, Paper, List, Checkbox, ListItemText, ListItem } from '@material-ui/core';
import MenuIcon from "@material-ui/icons/Menu";
import { DataColumn, isColumnConfigurable } from '../data-table/data-column';
-import Popover from "../popover/popover";
+import { Popover } from "../popover/popover";
import { IconButtonProps } from '@material-ui/core/IconButton';
import { DataColumns } from '../data-table/data-table';
+import { ArvadosTheme } from "../../common/custom-theme";
-export interface ColumnSelectorProps {
+interface ColumnSelectorDataProps {
columns: DataColumns<any>;
onColumnToggle: (column: DataColumn<any>) => void;
}
-const ColumnSelector: React.SFC<ColumnSelectorProps & WithStyles<CssRules>> = ({ columns, onColumnToggle, classes }) =>
+type CssRules = "checkbox";
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ checkbox: {
+ width: 24,
+ height: 24
+ }
+});
+
+export type ColumnSelectorProps = ColumnSelectorDataProps & WithStyles<CssRules>;
+
+export const ColumnSelector = withStyles(styles)(
+ ({ columns, onColumnToggle, classes }: ColumnSelectorProps) =>
<Popover triggerComponent={ColumnSelectorTrigger}>
<Paper>
<List dense>
@@ -38,20 +51,10 @@ const ColumnSelector: React.SFC<ColumnSelectorProps & WithStyles<CssRules>> = ({
))}
</List>
</Paper>
- </Popover>;
+ </Popover>
+);
-export const ColumnSelectorTrigger: React.SFC<IconButtonProps> = (props) =>
+export const ColumnSelectorTrigger = (props: IconButtonProps) =>
<IconButton {...props}>
<MenuIcon />
</IconButton>;
-
-type CssRules = "checkbox";
-
-const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
- checkbox: {
- width: 24,
- height: 24
- }
-});
-
-export default withStyles(styles)(ColumnSelector);
diff --git a/src/components/context-menu/context-menu.test.tsx b/src/components/context-menu/context-menu.test.tsx
index 95a219f..a245253 100644
--- a/src/components/context-menu/context-menu.test.tsx
+++ b/src/components/context-menu/context-menu.test.tsx
@@ -5,7 +5,7 @@
import * as React from "react";
import { mount, configure, shallow } from "enzyme";
import * as Adapter from "enzyme-adapter-react-16";
-import ContextMenu from "./context-menu";
+import { ContextMenu } from "./context-menu";
import { ListItem } from "@material-ui/core";
import { ShareIcon } from "../icon/icon";
@@ -33,4 +33,4 @@ describe("<ContextMenu />", () => {
contextMenu.find(ListItem).at(2).simulate("click");
expect(onItemClick).toHaveBeenCalledWith(items[1][0]);
});
-});
\ No newline at end of file
+});
diff --git a/src/components/context-menu/context-menu.tsx b/src/components/context-menu/context-menu.tsx
index ccdc01b..2103a2a 100644
--- a/src/components/context-menu/context-menu.tsx
+++ b/src/components/context-menu/context-menu.tsx
@@ -20,7 +20,7 @@ export interface ContextMenuProps {
onClose: () => void;
}
-export default class ContextMenu extends React.PureComponent<ContextMenuProps> {
+export class ContextMenu extends React.PureComponent<ContextMenuProps> {
render() {
const { anchorEl, items, onClose, onItemClick} = this.props;
return <Popover
diff --git a/src/components/data-explorer/data-explorer.test.tsx b/src/components/data-explorer/data-explorer.test.tsx
index 5d4877f..616a9c1 100644
--- a/src/components/data-explorer/data-explorer.test.tsx
+++ b/src/components/data-explorer/data-explorer.test.tsx
@@ -6,10 +6,10 @@ import * as React from "react";
import { configure, mount } from "enzyme";
import * as Adapter from 'enzyme-adapter-react-16';
-import DataExplorer from "./data-explorer";
-import ColumnSelector from "../column-selector/column-selector";
-import DataTable from "../data-table/data-table";
-import SearchInput from "../search-input/search-input";
+import { DataExplorer } from "./data-explorer";
+import { ColumnSelector } from "../column-selector/column-selector";
+import { DataTable } from "../data-table/data-table";
+import { SearchInput } from "../search-input/search-input";
import { TablePagination } from "@material-ui/core";
configure({ adapter: new Adapter() });
@@ -109,4 +109,4 @@ const mockDataExplorerProps = () => ({
onChangePage: jest.fn(),
onChangeRowsPerPage: jest.fn(),
onContextMenu: jest.fn()
-});
\ No newline at end of file
+});
diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index 1073ddd..4699fd6 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -3,15 +3,27 @@
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, Theme, WithStyles, TablePagination, IconButton } from '@material-ui/core';
+import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, WithStyles, TablePagination, IconButton } from '@material-ui/core';
import MoreVertIcon from "@material-ui/icons/MoreVert";
-import ColumnSelector from "../column-selector/column-selector";
-import DataTable, { DataColumns } from "../data-table/data-table";
+import { ColumnSelector } from "../column-selector/column-selector";
+import { DataTable, DataColumns } from "../data-table/data-table";
import { DataColumn } from "../data-table/data-column";
import { DataTableFilterItem } from '../data-table-filters/data-table-filters';
-import SearchInput from '../search-input/search-input';
+import { SearchInput } from '../search-input/search-input';
+import { ArvadosTheme } from "../../common/custom-theme";
-interface DataExplorerProps<T> {
+type CssRules = "searchBox" | "toolbar";
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ searchBox: {
+ paddingBottom: theme.spacing.unit * 2
+ },
+ toolbar: {
+ paddingTop: theme.spacing.unit * 2
+ }
+});
+
+interface DataExplorerDataProps<T> {
items: T[];
itemsAvailable: number;
columns: DataColumns<T>;
@@ -31,33 +43,35 @@ interface DataExplorerProps<T> {
extractKey?: (item: T) => React.Key;
}
-class DataExplorer<T> extends React.Component<DataExplorerProps<T> & WithStyles<CssRules>> {
+type DataExplorerProps<T> = DataExplorerDataProps<T> & WithStyles<CssRules>;
- render() {
- return <Paper>
- <Toolbar className={this.props.classes.toolbar}>
- <Grid container justify="space-between" wrap="nowrap" alignItems="center">
- <div className={this.props.classes.searchBox}>
- <SearchInput
- value={this.props.searchValue}
- onSearch={this.props.onSearch} />
- </div>
- <ColumnSelector
- columns={this.props.columns}
- onColumnToggle={this.props.onColumnToggle} />
- </Grid>
- </Toolbar>
- <DataTable
- columns={[...this.props.columns, this.contextMenuColumn]}
- items={this.props.items}
- onRowClick={(_, item: T) => this.props.onRowClick(item)}
- onContextMenu={this.props.onContextMenu}
- onRowDoubleClick={(_, item: T) => this.props.onRowDoubleClick(item)}
- onFiltersChange={this.props.onFiltersChange}
- onSortToggle={this.props.onSortToggle}
- extractKey={this.props.extractKey} />
- <Toolbar>
- {this.props.items.length > 0 &&
+export const DataExplorer = withStyles(styles)(
+ class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>> {
+ render() {
+ return <Paper>
+ <Toolbar className={this.props.classes.toolbar}>
+ <Grid container justify="space-between" wrap="nowrap" alignItems="center">
+ <div className={this.props.classes.searchBox}>
+ <SearchInput
+ value={this.props.searchValue}
+ onSearch={this.props.onSearch}/>
+ </div>
+ <ColumnSelector
+ columns={this.props.columns}
+ onColumnToggle={this.props.onColumnToggle}/>
+ </Grid>
+ </Toolbar>
+ <DataTable
+ columns={[...this.props.columns, this.contextMenuColumn]}
+ items={this.props.items}
+ onRowClick={(_, item: T) => this.props.onRowClick(item)}
+ onContextMenu={this.props.onContextMenu}
+ onRowDoubleClick={(_, item: T) => this.props.onRowDoubleClick(item)}
+ onFiltersChange={this.props.onFiltersChange}
+ onSortToggle={this.props.onSortToggle}
+ extractKey={this.props.extractKey}/>
+ <Toolbar>
+ {this.props.items.length > 0 &&
<Grid container justify="flex-end">
<TablePagination
count={this.props.itemsAvailable}
@@ -69,45 +83,32 @@ class DataExplorer<T> extends React.Component<DataExplorerProps<T> & WithStyles<
component="div"
/>
</Grid>}
- </Toolbar>
- </Paper>;
- }
+ </Toolbar>
+ </Paper>;
+ }
- changePage = (event: React.MouseEvent<HTMLButtonElement> | null, page: number) => {
- this.props.onChangePage(page);
- }
+ changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
+ this.props.onChangePage(page);
+ }
- changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = (event) => {
- this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
- }
+ changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = (event) => {
+ this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
+ }
- renderContextMenuTrigger = (item: T) =>
- <Grid container justify="flex-end">
- <IconButton onClick={event => this.props.onContextMenu(event, item)}>
- <MoreVertIcon />
- </IconButton>
- </Grid>
+ renderContextMenuTrigger = (item: T) =>
+ <Grid container justify="flex-end">
+ <IconButton onClick={event => this.props.onContextMenu(event, item)}>
+ <MoreVertIcon/>
+ </IconButton>
+ </Grid>
- contextMenuColumn = {
- name: "Actions",
- selected: true,
- key: "context-actions",
- renderHeader: () => null,
- render: this.renderContextMenuTrigger,
- width: "auto"
- };
-
-}
-
-type CssRules = "searchBox" | "toolbar";
-
-const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
- searchBox: {
- paddingBottom: theme.spacing.unit * 2
- },
- toolbar: {
- paddingTop: theme.spacing.unit * 2
+ contextMenuColumn = {
+ name: "Actions",
+ selected: true,
+ key: "context-actions",
+ renderHeader: () => null,
+ render: this.renderContextMenuTrigger,
+ width: "auto"
+ };
}
-});
-
-export default withStyles(styles)(DataExplorer);
+);
diff --git a/src/components/data-table-filters/data-table-filters.test.tsx b/src/components/data-table-filters/data-table-filters.test.tsx
index b2daebe..b0a84b5 100644
--- a/src/components/data-table-filters/data-table-filters.test.tsx
+++ b/src/components/data-table-filters/data-table-filters.test.tsx
@@ -4,7 +4,7 @@
import * as React from "react";
import { mount, configure } from "enzyme";
-import DataTableFilter, { DataTableFilterItem } from "./data-table-filters";
+import { DataTableFilters, DataTableFilterItem } from "./data-table-filters";
import * as Adapter from 'enzyme-adapter-react-16';
import { Checkbox, ButtonBase, ListItem, Button, ListItemText } from "@material-ui/core";
@@ -19,12 +19,12 @@ describe("<DataTableFilter />", () => {
name: "Filter 2",
selected: false
}];
- const dataTableFilter = mount(<DataTableFilter name="" filters={filters} />);
+ const dataTableFilter = mount(<DataTableFilters name="" filters={filters} />);
dataTableFilter.find(ButtonBase).simulate("click");
expect(dataTableFilter.find(Checkbox).at(0).prop("checked")).toBeTruthy();
expect(dataTableFilter.find(Checkbox).at(1).prop("checked")).toBeFalsy();
});
-
+
it("updates filters after filters prop change", () => {
const filters = [{
name: "Filter 1",
@@ -34,7 +34,7 @@ describe("<DataTableFilter />", () => {
name: "Filter 2",
selected: true
}];
- const dataTableFilter = mount(<DataTableFilter name="" filters={filters} />);
+ const dataTableFilter = mount(<DataTableFilters name="" filters={filters} />);
dataTableFilter.find(ButtonBase).simulate("click");
expect(dataTableFilter.find(Checkbox).prop("checked")).toBeTruthy();
dataTableFilter.find(ListItem).simulate("click");
@@ -53,7 +53,7 @@ describe("<DataTableFilter />", () => {
selected: false
}];
const onChange = jest.fn();
- const dataTableFilter = mount(<DataTableFilter name="" filters={filters} onChange={onChange} />);
+ const dataTableFilter = mount(<DataTableFilters name="" filters={filters} onChange={onChange} />);
dataTableFilter.find(ButtonBase).simulate("click");
dataTableFilter.find(ListItem).at(1).simulate("click");
dataTableFilter.find(Button).at(0).simulate("click");
@@ -65,4 +65,4 @@ describe("<DataTableFilter />", () => {
selected: true
}]);
});
-});
\ No newline at end of file
+});
diff --git a/src/components/data-table-filters/data-table-filters.tsx b/src/components/data-table-filters/data-table-filters.tsx
index bede5ae..d288a5a 100644
--- a/src/components/data-table-filters/data-table-filters.tsx
+++ b/src/components/data-table-filters/data-table-filters.tsx
@@ -23,133 +23,6 @@ import {
import * as classnames from "classnames";
import { DefaultTransformOrigin } from "../popover/helpers";
-export interface DataTableFilterItem {
- name: string;
- selected: boolean;
-}
-
-export interface DataTableFilterProps {
- name: string;
- filters: DataTableFilterItem[];
- onChange?: (filters: DataTableFilterItem[]) => void;
-}
-
-interface DataTableFilterState {
- anchorEl?: HTMLElement;
- filters: DataTableFilterItem[];
- prevFilters: DataTableFilterItem[];
-}
-
-class DataTableFilter extends React.Component<DataTableFilterProps & WithStyles<CssRules>, DataTableFilterState> {
- state: DataTableFilterState = {
- anchorEl: undefined,
- filters: [],
- prevFilters: []
- };
- icon = React.createRef<HTMLElement>();
-
- render() {
- const { name, classes, children } = this.props;
- const isActive = this.state.filters.some(f => f.selected);
- return <>
- <ButtonBase
- className={classnames([classes.root, { [classes.active]: isActive }])}
- component="span"
- onClick={this.open}
- disableRipple>
- {children}
- <i className={classnames(["fas fa-filter", classes.icon])}
- data-fa-transform="shrink-3"
- ref={this.icon} />
- </ButtonBase>
- <Popover
- anchorEl={this.state.anchorEl}
- open={!!this.state.anchorEl}
- anchorOrigin={DefaultTransformOrigin}
- transformOrigin={DefaultTransformOrigin}
- onClose={this.cancel}>
- <Card>
- <CardContent>
- <Typography variant="caption">
- {name}
- </Typography>
- </CardContent>
- <List dense>
- {this.state.filters.map((filter, index) =>
- <ListItem
- button
- key={index}
- onClick={this.toggleFilter(filter)}>
- <Checkbox
- disableRipple
- color="primary"
- checked={filter.selected}
- className={classes.checkbox} />
- <ListItemText>
- {filter.name}
- </ListItemText>
- </ListItem>
- )}
- </List>
- <CardActions>
- <Button
- color="primary"
- variant="raised"
- size="small"
- onClick={this.submit}>
- Ok
- </Button>
- <Button
- color="primary"
- variant="outlined"
- size="small"
- onClick={this.cancel}>
- Cancel
- </Button>
- </CardActions >
- </Card>
- </Popover>
- </>;
- }
-
- static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState {
- return props.filters !== state.prevFilters
- ? { ...state, filters: props.filters, prevFilters: props.filters }
- : state;
- }
-
- open = () => {
- this.setState({ anchorEl: this.icon.current || undefined });
- }
-
- submit = () => {
- const { onChange } = this.props;
- if (onChange) {
- onChange(this.state.filters);
- }
- this.setState({ anchorEl: undefined });
- }
-
- cancel = () => {
- this.setState(prev => ({
- ...prev,
- filters: prev.prevFilters,
- anchorEl: undefined
- }));
- }
-
- toggleFilter = (toggledFilter: DataTableFilterItem) => () => {
- this.setState(prev => ({
- ...prev,
- filters: prev.filters.map(filter =>
- filter === toggledFilter
- ? { ...filter, selected: !filter.selected }
- : filter)
- }));
- }
-}
-
-
export type CssRules = "root" | "icon" | "active" | "checkbox";
const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
@@ -185,4 +58,130 @@ const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
}
});
-export default withStyles(styles)(DataTableFilter);
+export interface DataTableFilterItem {
+ name: string;
+ selected: boolean;
+}
+
+export interface DataTableFilterProps {
+ name: string;
+ filters: DataTableFilterItem[];
+ onChange?: (filters: DataTableFilterItem[]) => void;
+}
+
+interface DataTableFilterState {
+ anchorEl?: HTMLElement;
+ filters: DataTableFilterItem[];
+ prevFilters: DataTableFilterItem[];
+}
+
+export const DataTableFilters = withStyles(styles)(
+ class extends React.Component<DataTableFilterProps & WithStyles<CssRules>, DataTableFilterState> {
+ state: DataTableFilterState = {
+ anchorEl: undefined,
+ filters: [],
+ prevFilters: []
+ };
+ icon = React.createRef<HTMLElement>();
+
+ render() {
+ const { name, classes, children } = this.props;
+ const isActive = this.state.filters.some(f => f.selected);
+ return <>
+ <ButtonBase
+ className={classnames([classes.root, { [classes.active]: isActive }])}
+ component="span"
+ onClick={this.open}
+ disableRipple>
+ {children}
+ <i className={classnames(["fas fa-filter", classes.icon])}
+ data-fa-transform="shrink-3"
+ ref={this.icon} />
+ </ButtonBase>
+ <Popover
+ anchorEl={this.state.anchorEl}
+ open={!!this.state.anchorEl}
+ anchorOrigin={DefaultTransformOrigin}
+ transformOrigin={DefaultTransformOrigin}
+ onClose={this.cancel}>
+ <Card>
+ <CardContent>
+ <Typography variant="caption">
+ {name}
+ </Typography>
+ </CardContent>
+ <List dense>
+ {this.state.filters.map((filter, index) =>
+ <ListItem
+ button
+ key={index}
+ onClick={this.toggleFilter(filter)}>
+ <Checkbox
+ disableRipple
+ color="primary"
+ checked={filter.selected}
+ className={classes.checkbox} />
+ <ListItemText>
+ {filter.name}
+ </ListItemText>
+ </ListItem>
+ )}
+ </List>
+ <CardActions>
+ <Button
+ color="primary"
+ variant="raised"
+ size="small"
+ onClick={this.submit}>
+ Ok
+ </Button>
+ <Button
+ color="primary"
+ variant="outlined"
+ size="small"
+ onClick={this.cancel}>
+ Cancel
+ </Button>
+ </CardActions >
+ </Card>
+ </Popover>
+ </>;
+ }
+
+ static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState {
+ return props.filters !== state.prevFilters
+ ? { ...state, filters: props.filters, prevFilters: props.filters }
+ : state;
+ }
+
+ open = () => {
+ this.setState({ anchorEl: this.icon.current || undefined });
+ }
+
+ submit = () => {
+ const { onChange } = this.props;
+ if (onChange) {
+ onChange(this.state.filters);
+ }
+ this.setState({ anchorEl: undefined });
+ }
+
+ cancel = () => {
+ this.setState(prev => ({
+ ...prev,
+ filters: prev.prevFilters,
+ anchorEl: undefined
+ }));
+ }
+
+ toggleFilter = (toggledFilter: DataTableFilterItem) => () => {
+ this.setState(prev => ({
+ ...prev,
+ filters: prev.filters.map(filter =>
+ filter === toggledFilter
+ ? { ...filter, selected: !filter.selected }
+ : filter)
+ }));
+ }
+ }
+);
diff --git a/src/components/data-table/data-table.test.tsx b/src/components/data-table/data-table.test.tsx
index ec84aca..7e460c8 100644
--- a/src/components/data-table/data-table.test.tsx
+++ b/src/components/data-table/data-table.test.tsx
@@ -6,8 +6,8 @@ import * as React from "react";
import { mount, configure } from "enzyme";
import { TableHead, TableCell, Typography, TableBody, Button, TableSortLabel } from "@material-ui/core";
import * as Adapter from "enzyme-adapter-react-16";
-import DataTable, { DataColumns } from "./data-table";
-import DataTableFilters from "../data-table-filters/data-table-filters";
+import { DataTable, DataColumns } from "./data-table";
+import { DataTableFilters } from "../data-table-filters/data-table-filters";
import { SortDirection } from "./data-column";
configure({ adapter: new Adapter() });
@@ -169,6 +169,4 @@ describe("<DataTable />", () => {
dataTable.find(DataTableFilters).prop("onChange")([]);
expect(onFiltersChange).toHaveBeenCalledWith([], columns[0]);
});
-
-
-});
\ No newline at end of file
+});
diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx
index e0e3048..829bc84 100644
--- a/src/components/data-table/data-table.tsx
+++ b/src/components/data-table/data-table.tsx
@@ -5,11 +5,11 @@
import * as React from 'react';
import { Table, TableBody, TableRow, TableCell, TableHead, TableSortLabel, StyleRulesCallback, Theme, WithStyles, withStyles, Typography } from '@material-ui/core';
import { DataColumn, SortDirection } from './data-column';
-import DataTableFilters, { DataTableFilterItem } from "../data-table-filters/data-table-filters";
+import { DataTableFilters, DataTableFilterItem } from "../data-table-filters/data-table-filters";
export type DataColumns<T, F extends DataTableFilterItem = DataTableFilterItem> = Array<DataColumn<T, F>>;
-export interface DataTableProps<T> {
+export interface DataTableDataProps<T> {
items: T[];
columns: DataColumns<T>;
onRowClick: (event: React.MouseEvent<HTMLTableRowElement>, item: T) => void;
@@ -20,80 +20,6 @@ export interface DataTableProps<T> {
extractKey?: (item: T) => React.Key;
}
-class DataTable<T> extends React.Component<DataTableProps<T> & WithStyles<CssRules>> {
- render() {
- const { items, classes } = this.props;
- return <div
- className={classes.tableContainer}>
- <Table>
- <TableHead>
- <TableRow>
- {this.mapVisibleColumns(this.renderHeadCell)}
- </TableRow>
- </TableHead>
- <TableBody className={classes.tableBody}>
- {items.map(this.renderBodyRow)}
- </TableBody>
- </Table>
- </div>;
- }
-
- renderHeadCell = (column: DataColumn<T>, index: number) => {
- const { name, key, renderHeader, filters, sortDirection } = column;
- const { onSortToggle, onFiltersChange } = this.props;
- return <TableCell key={key || index} style={{ width: column.width, minWidth: column.width }}>
- {renderHeader ?
- renderHeader() :
- filters
- ? <DataTableFilters
- name={`${name} filters`}
- onChange={filters =>
- onFiltersChange &&
- onFiltersChange(filters, column)}
- filters={filters}>
- {name}
- </DataTableFilters>
- : sortDirection
- ? <TableSortLabel
- active={sortDirection !== SortDirection.None}
- direction={sortDirection !== SortDirection.None ? sortDirection : undefined}
- onClick={() =>
- onSortToggle &&
- onSortToggle(column)}>
- {name}
- </TableSortLabel>
- : <span>
- {name}
- </span>}
- </TableCell>;
- }
-
- renderBodyRow = (item: T, index: number) => {
- const { onRowClick, onRowDoubleClick, extractKey } = this.props;
- return <TableRow
- hover
- key={extractKey ? extractKey(item) : index}
- onClick={event => onRowClick && onRowClick(event, item)}
- onContextMenu={this.handleRowContextMenu(item)}
- onDoubleClick={event => onRowDoubleClick && onRowDoubleClick(event, item) }>
- {this.mapVisibleColumns((column, index) => (
- <TableCell key={column.key || index}>
- {column.render(item)}
- </TableCell>
- ))}
- </TableRow>;
- }
-
- mapVisibleColumns = (fn: (column: DataColumn<T>, index: number) => React.ReactElement<any>) => {
- return this.props.columns.filter(column => column.selected).map(fn);
- }
-
- handleRowContextMenu = (item: T) =>
- (event: React.MouseEvent<HTMLElement>) =>
- this.props.onContextMenu(event, item)
-
-}
-
type CssRules = "tableBody" | "tableContainer" | "noItemsInfo";
const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
@@ -110,4 +36,80 @@ const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
}
});
-export default withStyles(styles)(DataTable);
+type DataTableProps<T> = DataTableDataProps<T> & WithStyles<CssRules>;
+
+export const DataTable = withStyles(styles)(
+ class Component<T> extends React.Component<DataTableProps<T>> {
+ render() {
+ const { items, classes } = this.props;
+ return <div
+ className={classes.tableContainer}>
+ <Table>
+ <TableHead>
+ <TableRow>
+ {this.mapVisibleColumns(this.renderHeadCell)}
+ </TableRow>
+ </TableHead>
+ <TableBody className={classes.tableBody}>
+ {items.map(this.renderBodyRow)}
+ </TableBody>
+ </Table>
+ </div>;
+ }
+
+ renderHeadCell = (column: DataColumn<T>, index: number) => {
+ const { name, key, renderHeader, filters, sortDirection } = column;
+ const { onSortToggle, onFiltersChange } = this.props;
+ return <TableCell key={key || index} style={{ width: column.width, minWidth: column.width }}>
+ {renderHeader ?
+ renderHeader() :
+ filters
+ ? <DataTableFilters
+ name={`${name} filters`}
+ onChange={filters =>
+ onFiltersChange &&
+ onFiltersChange(filters, column)}
+ filters={filters}>
+ {name}
+ </DataTableFilters>
+ : sortDirection
+ ? <TableSortLabel
+ active={sortDirection !== SortDirection.None}
+ direction={sortDirection !== SortDirection.None ? sortDirection : undefined}
+ onClick={() =>
+ onSortToggle &&
+ onSortToggle(column)}>
+ {name}
+ </TableSortLabel>
+ : <span>
+ {name}
+ </span>}
+ </TableCell>;
+ }
+
+ renderBodyRow = (item: T, index: number) => {
+ const { onRowClick, onRowDoubleClick, extractKey } = this.props;
+ return <TableRow
+ hover
+ key={extractKey ? extractKey(item) : index}
+ onClick={event => onRowClick && onRowClick(event, item)}
+ onContextMenu={this.handleRowContextMenu(item)}
+ onDoubleClick={event => onRowDoubleClick && onRowDoubleClick(event, item) }>
+ {this.mapVisibleColumns((column, index) => (
+ <TableCell key={column.key || index}>
+ {column.render(item)}
+ </TableCell>
+ ))}
+ </TableRow>;
+ }
+
+ mapVisibleColumns = (fn: (column: DataColumn<T>, index: number) => React.ReactElement<any>) => {
+ return this.props.columns.filter(column => column.selected).map(fn);
+ }
+
+ handleRowContextMenu = (item: T) =>
+ (event: React.MouseEvent<HTMLElement>) =>
+ this.props.onContextMenu(event, item)
+
+ }
+);
diff --git a/src/components/details-panel-factory/details-panel-factory.tsx b/src/components/details-panel-factory/details-panel-factory.tsx
index bb7d855..9fb8f32 100644
--- a/src/components/details-panel-factory/details-panel-factory.tsx
+++ b/src/components/details-panel-factory/details-panel-factory.tsx
@@ -2,16 +2,16 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import ProjectItem from './items/project-item';
-import CollectionItem from './items/collection-item';
-import ProcessItem from './items/process-item';
-import AbstractItem from './items/abstract-item';
-import EmptyItem from './items/empty-item';
+import { ProjectItem } from './items/project-item';
+import { CollectionItem } from './items/collection-item';
+import { ProcessItem } from './items/process-item';
+import { AbstractItem } from './items/abstract-item';
+import { EmptyItem } from './items/empty-item';
import { DetailsPanelResource } from '../../views-components/details-panel/details-panel';
import { EmptyResource } from '../../models/empty';
import { ResourceKind } from '../../models/resource';
-export default class DetailsPanelFactory {
+export class DetailsPanelFactory {
static createItem(res: DetailsPanelResource): AbstractItem {
switch (res.kind) {
case ResourceKind.Project:
@@ -24,4 +24,4 @@ export default class DetailsPanelFactory {
return new EmptyItem(res as EmptyResource);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/components/details-panel-factory/items/abstract-item.tsx b/src/components/details-panel-factory/items/abstract-item.tsx
index d403870..5455d5c 100644
--- a/src/components/details-panel-factory/items/abstract-item.tsx
+++ b/src/components/details-panel-factory/items/abstract-item.tsx
@@ -4,20 +4,18 @@
import * as React from 'react';
import { DetailsPanelResource } from '../../../views-components/details-panel/details-panel';
-import { IconType } from '../../icon/icon';
-
-export default abstract class AbstractItem<T extends DetailsPanelResource = DetailsPanelResource> {
+export abstract class AbstractItem<T extends DetailsPanelResource = DetailsPanelResource> {
constructor(protected item: T) {}
getTitle(): string {
return this.item.name;
}
-
+
abstract getIcon(className?: string): React.ReactElement<any>;
abstract buildDetails(): React.ReactElement<any>;
-
+
buildActivity(): React.ReactElement<any> {
return <div/>;
}
-}
\ No newline at end of file
+}
diff --git a/src/components/details-panel-factory/items/collection-item.tsx b/src/components/details-panel-factory/items/collection-item.tsx
index dab8101..5616a6b 100644
--- a/src/components/details-panel-factory/items/collection-item.tsx
+++ b/src/components/details-panel-factory/items/collection-item.tsx
@@ -4,14 +4,14 @@
import * as React from 'react';
import { CollectionIcon } from '../../icon/icon';
-import Attribute from '../../attribute/attribute';
-import AbstractItem from './abstract-item';
+import { Attribute } from '../../attribute/attribute';
+import { AbstractItem } from './abstract-item';
import { CollectionResource } from '../../../models/collection';
import { formatDate } from '../../../common/formatters';
import { resourceLabel } from '../../../common/labels';
import { ResourceKind } from '../../../models/resource';
-export default class CollectionItem extends AbstractItem<CollectionResource> {
+export class CollectionItem extends AbstractItem<CollectionResource> {
getIcon(className?: string) {
return <CollectionIcon className={className} />;
@@ -32,4 +32,4 @@ export default class CollectionItem extends AbstractItem<CollectionResource> {
<Attribute label='Content size' value='54 MB' />
</div>;
}
-}
\ No newline at end of file
+}
diff --git a/src/components/details-panel-factory/items/empty-item.tsx b/src/components/details-panel-factory/items/empty-item.tsx
index 0edfe84..b9a4872 100644
--- a/src/components/details-panel-factory/items/empty-item.tsx
+++ b/src/components/details-panel-factory/items/empty-item.tsx
@@ -4,12 +4,12 @@
import * as React from 'react';
import { DefaultIcon, ProjectsIcon } from '../../icon/icon';
-import AbstractItem from './abstract-item';
-import EmptyState from '../../empty-state/empty-state';
+import { AbstractItem } from './abstract-item';
+import { EmptyState } from '../../empty-state/empty-state';
import { EmptyResource } from '../../../models/empty';
-export default class EmptyItem extends AbstractItem<EmptyResource> {
-
+export class EmptyItem extends AbstractItem<EmptyResource> {
+
getIcon(className?: string) {
return <ProjectsIcon className={className} />;
}
@@ -18,4 +18,4 @@ export default class EmptyItem extends AbstractItem<EmptyResource> {
return <EmptyState icon={DefaultIcon}
message='Select a file or folder to view its details.' />;
}
-}
\ No newline at end of file
+}
diff --git a/src/components/details-panel-factory/items/process-item.tsx b/src/components/details-panel-factory/items/process-item.tsx
index 215c531..1071efc 100644
--- a/src/components/details-panel-factory/items/process-item.tsx
+++ b/src/components/details-panel-factory/items/process-item.tsx
@@ -4,14 +4,14 @@
import * as React from 'react';
import { ProcessIcon } from '../../icon/icon';
-import Attribute from '../../attribute/attribute';
-import AbstractItem from './abstract-item';
+import { Attribute } from '../../attribute/attribute';
+import { AbstractItem } from './abstract-item';
import { ProcessResource } from '../../../models/process';
import { formatDate } from '../../../common/formatters';
import { ResourceKind } from '../../../models/resource';
import { resourceLabel } from '../../../common/labels';
-export default class ProcessItem extends AbstractItem<ProcessResource> {
+export class ProcessItem extends AbstractItem<ProcessResource> {
getIcon(className?: string){
return <ProcessIcon className={className} />;
@@ -26,7 +26,7 @@ export default class ProcessItem extends AbstractItem<ProcessResource> {
{/* Missing attr */}
<Attribute label='Status' value={this.item.state} />
<Attribute label='Last modified' value={formatDate(this.item.modifiedAt)} />
-
+
{/* Missing attrs */}
<Attribute label='Started at' value={formatDate(this.item.createdAt)} />
<Attribute label='Finished at' value={formatDate(this.item.expiresAt)} />
@@ -35,11 +35,11 @@ export default class ProcessItem extends AbstractItem<ProcessResource> {
<Attribute label='Outputs' link={this.item.outputPath} value={this.item.outputPath} />
<Attribute label='UUID' link={this.item.uuid} value={this.item.uuid} />
<Attribute label='Container UUID' link={this.item.containerUuid} value={this.item.containerUuid} />
-
+
<Attribute label='Priority' value={this.item.priority} />
<Attribute label='Runtime Constraints' value={this.item.runtimeConstraints} />
{/* Link but we dont have view */}
<Attribute label='Docker Image locator' link={this.item.containerImage} value={this.item.containerImage} />
</div>;
}
-}
\ No newline at end of file
+}
diff --git a/src/components/details-panel-factory/items/project-item.tsx b/src/components/details-panel-factory/items/project-item.tsx
index ae694e5..702aa17 100644
--- a/src/components/details-panel-factory/items/project-item.tsx
+++ b/src/components/details-panel-factory/items/project-item.tsx
@@ -4,14 +4,14 @@
import * as React from 'react';
import { ProjectIcon } from '../../icon/icon';
-import Attribute from '../../attribute/attribute';
-import AbstractItem from './abstract-item';
+import { Attribute } from '../../attribute/attribute';
+import { AbstractItem } from './abstract-item';
import { ProjectResource } from '../../../models/project';
import { formatDate } from '../../../common/formatters';
import { ResourceKind } from '../../../models/resource';
import { resourceLabel } from '../../../common/labels';
-export default class ProjectItem extends AbstractItem<ProjectResource> {
+export class ProjectItem extends AbstractItem<ProjectResource> {
getIcon(className?: string) {
return <ProjectIcon className={className} />;
@@ -30,4 +30,4 @@ export default class ProjectItem extends AbstractItem<ProjectResource> {
<Attribute label='Description' value={this.item.description} />
</div>;
}
-}
\ No newline at end of file
+}
diff --git a/src/components/dropdown-menu/dropdown-menu.test.tsx b/src/components/dropdown-menu/dropdown-menu.test.tsx
index 3396469..da232bd 100644
--- a/src/components/dropdown-menu/dropdown-menu.test.tsx
+++ b/src/components/dropdown-menu/dropdown-menu.test.tsx
@@ -4,7 +4,7 @@
import * as React from "react";
import { shallow, configure } from "enzyme";
-import DropdownMenu from "./dropdown-menu";
+import { DropdownMenu } from "./dropdown-menu";
import * as Adapter from 'enzyme-adapter-react-16';
import { MenuItem, IconButton, Menu } from "@material-ui/core";
import { PaginationRightArrowIcon } from "../icon/icon";
@@ -32,11 +32,11 @@ describe("<DropdownMenu />", () => {
dropdownMenu.find(IconButton).simulate("click", {currentTarget: {}});
expect(dropdownMenu.state().anchorEl).toBeDefined();
});
-
+
it("closes on menu click", () => {
const dropdownMenu = shallow(<DropdownMenu id="test-menu" icon={<PaginationRightArrowIcon />} />);
dropdownMenu.find(Menu).simulate("click", {currentTarget: {}});
expect(dropdownMenu.state().anchorEl).toBeUndefined();
});
-});
\ No newline at end of file
+});
diff --git a/src/components/dropdown-menu/dropdown-menu.tsx b/src/components/dropdown-menu/dropdown-menu.tsx
index 98c29c6..73b279b 100644
--- a/src/components/dropdown-menu/dropdown-menu.tsx
+++ b/src/components/dropdown-menu/dropdown-menu.tsx
@@ -12,8 +12,11 @@ interface DropdownMenuProps {
icon: React.ReactElement<any>;
}
-class DropdownMenu extends React.Component<DropdownMenuProps> {
+interface DropdownMenuState {
+ anchorEl: any;
+}
+export class DropdownMenu extends React.Component<DropdownMenuProps, DropdownMenuState> {
state = {
anchorEl: undefined
};
@@ -57,6 +60,3 @@ class DropdownMenu extends React.Component<DropdownMenuProps> {
this.setState({ anchorEl: event.currentTarget });
}
}
-
-
-export default DropdownMenu;
diff --git a/src/components/empty-state/empty-state.tsx b/src/components/empty-state/empty-state.tsx
index 8a36213..75a2aa6 100644
--- a/src/components/empty-state/empty-state.tsx
+++ b/src/components/empty-state/empty-state.tsx
@@ -8,30 +8,6 @@ import { WithStyles, withStyles, StyleRulesCallback } from '@material-ui/core/st
import { ArvadosTheme } from 'src/common/custom-theme';
import { IconType } from '../icon/icon';
-export interface EmptyStateDataProps {
- message: string;
- icon: IconType;
- details?: string;
-}
-
-type EmptyStateProps = EmptyStateDataProps & WithStyles<CssRules>;
-
-class EmptyState extends React.Component<EmptyStateProps, {}> {
-
- render() {
- const { classes, message, details, icon: Icon, children } = this.props;
- return (
- <Typography className={classes.container} component="div">
- <Icon className={classes.icon} />
- <Typography variant="body1" gutterBottom>{message}</Typography>
- { details && <Typography gutterBottom>{details}</Typography> }
- { children && <Typography gutterBottom>{children}</Typography> }
- </Typography>
- );
- }
-
-}
-
type CssRules = 'container' | 'icon';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
container: {
@@ -43,4 +19,26 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
}
});
-export default withStyles(styles)(EmptyState);
\ No newline at end of file
+export interface EmptyStateDataProps {
+ message: string;
+ icon: IconType;
+ details?: string;
+}
+
+type EmptyStateProps = EmptyStateDataProps & WithStyles<CssRules>;
+
+export const EmptyState = withStyles(styles)(
+ class extends React.Component<EmptyStateProps, {}> {
+ render() {
+ const {classes, message, details, icon: Icon, children} = this.props;
+ return (
+ <Typography className={classes.container} component="div">
+ <Icon className={classes.icon}/>
+ <Typography variant="body1" gutterBottom>{message}</Typography>
+ {details && <Typography gutterBottom>{details}</Typography>}
+ {children && <Typography gutterBottom>{children}</Typography>}
+ </Typography>
+ );
+ }
+ }
+);
diff --git a/src/components/list-item-text-icon/list-item-text-icon.tsx b/src/components/list-item-text-icon/list-item-text-icon.tsx
index f140d86..8f9d474 100644
--- a/src/components/list-item-text-icon/list-item-text-icon.tsx
+++ b/src/components/list-item-text-icon/list-item-text-icon.tsx
@@ -9,40 +9,8 @@ import { ListItemIcon, ListItemText, Typography } from '@material-ui/core';
import { IconType } from '../icon/icon';
import * as classnames from "classnames";
-export interface ListItemTextIconDataProps {
- icon: IconType;
- name: string;
- isActive?: boolean;
- hasMargin?: boolean;
-}
-
-type ListItemTextIconProps = ListItemTextIconDataProps & WithStyles<CssRules>;
-
-class ListItemTextIcon extends React.Component<ListItemTextIconProps, {}> {
- render() {
- const { classes, isActive, hasMargin, name, icon: Icon } = this.props;
- return (
- <Typography component='span' className={classes.root}>
- <ListItemIcon className={classnames({
- [classes.hasMargin]: hasMargin,
- [classes.active]: isActive
- })}>
- <Icon />
- </ListItemIcon>
- <ListItemText primary={
- <Typography variant='body1' className={classnames(classes.listItemText, {
- [classes.active]: isActive
- })}>
- {name}
- </Typography>
- } />
- </Typography>
- );
- }
-}
-
type CssRules = 'root' | 'listItemText' | 'hasMargin' | 'active';
-
+
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
display: 'flex',
@@ -59,4 +27,36 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
},
});
-export default withStyles(styles)(ListItemTextIcon);
\ No newline at end of file
+export interface ListItemTextIconDataProps {
+ icon: IconType;
+ name: string;
+ isActive?: boolean;
+ hasMargin?: boolean;
+}
+
+type ListItemTextIconProps = ListItemTextIconDataProps & WithStyles<CssRules>;
+
+export const ListItemTextIcon = withStyles(styles)(
+ class extends React.Component<ListItemTextIconProps, {}> {
+ render() {
+ const { classes, isActive, hasMargin, name, icon: Icon } = this.props;
+ return (
+ <Typography component='span' className={classes.root}>
+ <ListItemIcon className={classnames({
+ [classes.hasMargin]: hasMargin,
+ [classes.active]: isActive
+ })}>
+ <Icon />
+ </ListItemIcon>
+ <ListItemText primary={
+ <Typography variant='body1' className={classnames(classes.listItemText, {
+ [classes.active]: isActive
+ })}>
+ {name}
+ </Typography>
+ } />
+ </Typography>
+ );
+ }
+ }
+);
diff --git a/src/components/popover/popover.test.tsx b/src/components/popover/popover.test.tsx
index fa24c0c..37007ab 100644
--- a/src/components/popover/popover.test.tsx
+++ b/src/components/popover/popover.test.tsx
@@ -6,7 +6,7 @@ import * as React from "react";
import { mount, configure } from "enzyme";
import * as Adapter from "enzyme-adapter-react-16";
-import Popover, { DefaultTrigger } from "./popover";
+import { Popover, DefaultTrigger } from "./popover";
import Button, { ButtonProps } from "@material-ui/core/Button";
configure({ adapter: new Adapter() });
@@ -38,7 +38,7 @@ describe("<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>
@@ -66,4 +66,4 @@ const CustomTrigger: React.SFC<ButtonProps> = (props) => (
<Button {...props}>
Open popover
</Button>
-);
\ No newline at end of file
+);
diff --git a/src/components/popover/popover.tsx b/src/components/popover/popover.tsx
index c8d4033..9f3cd78 100644
--- a/src/components/popover/popover.tsx
+++ b/src/components/popover/popover.tsx
@@ -13,9 +13,7 @@ export interface PopoverProps {
closeOnContentClick?: boolean;
}
-
-class Popover extends React.Component<PopoverProps> {
-
+export class Popover extends React.Component<PopoverProps> {
state = {
anchorEl: undefined
};
@@ -57,7 +55,6 @@ class Popover extends React.Component<PopoverProps> {
this.handleClose();
}
}
-
}
export const DefaultTrigger: React.SFC<IconButtonProps> = (props) => (
@@ -65,5 +62,3 @@ export const DefaultTrigger: React.SFC<IconButtonProps> = (props) => (
<i className="fas" />
</IconButton>
);
-
-export default Popover;
diff --git a/src/components/search-bar/search-bar.test.tsx b/src/components/search-bar/search-bar.test.tsx
index 2479e40..07b5ebf 100644
--- a/src/components/search-bar/search-bar.test.tsx
+++ b/src/components/search-bar/search-bar.test.tsx
@@ -4,7 +4,7 @@
import * as React from "react";
import { mount, configure } from "enzyme";
-import SearchBar, { DEFAULT_SEARCH_DEBOUNCE } from "./search-bar";
+import { SearchBar, DEFAULT_SEARCH_DEBOUNCE } from "./search-bar";
import * as Adapter from 'enzyme-adapter-react-16';
@@ -61,7 +61,7 @@ describe("<SearchBar />", () => {
jest.advanceTimersByTime(DEFAULT_SEARCH_DEBOUNCE);
expect(onSearch).toBeCalledWith("current value");
});
-
+
it("calls onSearch after the time specified in props has passed", () => {
const searchBar = mount(<SearchBar value="" onSearch={onSearch} debounce={2000}/>);
searchBar.find("input").simulate("change", { target: { value: "current value" } });
@@ -70,7 +70,7 @@ describe("<SearchBar />", () => {
jest.advanceTimersByTime(1000);
expect(onSearch).toBeCalledWith("current value");
});
-
+
it("calls onSearch only once after no change happened during the specified time", () => {
const searchBar = mount(<SearchBar value="" onSearch={onSearch} debounce={1000}/>);
searchBar.find("input").simulate("change", { target: { value: "current value" } });
@@ -79,7 +79,7 @@ describe("<SearchBar />", () => {
jest.advanceTimersByTime(1000);
expect(onSearch).toHaveBeenCalledTimes(1);
});
-
+
it("calls onSearch again after the specified time has passed since previous call", () => {
const searchBar = mount(<SearchBar value="" onSearch={onSearch} debounce={1000}/>);
searchBar.find("input").simulate("change", { target: { value: "current value" } });
@@ -91,9 +91,7 @@ describe("<SearchBar />", () => {
jest.advanceTimersByTime(1000);
expect(onSearch).toBeCalledWith("latest value");
expect(onSearch).toHaveBeenCalledTimes(2);
-
- });
+ });
});
-
-});
\ No newline at end of file
+});
diff --git a/src/components/search-bar/search-bar.tsx b/src/components/search-bar/search-bar.tsx
index 62c8cc3..de9e7de 100644
--- a/src/components/search-bar/search-bar.tsx
+++ b/src/components/search-bar/search-bar.tsx
@@ -6,80 +6,6 @@ import * as React from 'react';
import { IconButton, Paper, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core';
import SearchIcon from '@material-ui/icons/Search';
-interface SearchBarDataProps {
- value: string;
-}
-
-interface SearchBarActionProps {
- onSearch: (value: string) => any;
- debounce?: number;
-}
-
-type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
-
-interface SearchBarState {
- value: string;
-}
-
-export const DEFAULT_SEARCH_DEBOUNCE = 1000;
-
-class SearchBar extends React.Component<SearchBarProps> {
-
- state: SearchBarState = {
- value: ""
- };
-
- timeout: number;
-
- render() {
- const { classes } = this.props;
- return <Paper className={classes.container}>
- <form onSubmit={this.handleSubmit}>
- <input
- className={classes.input}
- onChange={this.handleChange}
- placeholder="Search"
- value={this.state.value}
- />
- <IconButton className={classes.button}>
- <SearchIcon />
- </IconButton>
- </form>
- </Paper>;
- }
-
- componentDidMount() {
- this.setState({value: this.props.value});
- }
-
- componentWillReceiveProps(nextProps: SearchBarProps) {
- if (nextProps.value !== this.props.value) {
- this.setState({ value: nextProps.value });
- }
- }
-
- componentWillUnmount() {
- clearTimeout(this.timeout);
- }
-
- handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
- event.preventDefault();
- clearTimeout(this.timeout);
- this.props.onSearch(this.state.value);
- }
-
- handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- clearTimeout(this.timeout);
- this.setState({ value: event.target.value });
- this.timeout = window.setTimeout(
- () => this.props.onSearch(this.state.value),
- this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
- );
-
- }
-
-}
-
type CssRules = 'container' | 'input' | 'button';
const styles: StyleRulesCallback<CssRules> = theme => {
@@ -106,4 +32,76 @@ const styles: StyleRulesCallback<CssRules> = theme => {
};
};
-export default withStyles(styles)(SearchBar);
\ No newline at end of file
+interface SearchBarDataProps {
+ value: string;
+}
+
+interface SearchBarActionProps {
+ onSearch: (value: string) => any;
+ debounce?: number;
+}
+
+type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
+
+interface SearchBarState {
+ value: string;
+}
+
+export const DEFAULT_SEARCH_DEBOUNCE = 1000;
+
+export const SearchBar = withStyles(styles)(
+ class extends React.Component<SearchBarProps> {
+ state: SearchBarState = {
+ value: ""
+ };
+
+ timeout: number;
+
+ render() {
+ const {classes} = this.props;
+ return <Paper className={classes.container}>
+ <form onSubmit={this.handleSubmit}>
+ <input
+ className={classes.input}
+ onChange={this.handleChange}
+ placeholder="Search"
+ value={this.state.value}
+ />
+ <IconButton className={classes.button}>
+ <SearchIcon/>
+ </IconButton>
+ </form>
+ </Paper>;
+ }
+
+ componentDidMount() {
+ this.setState({value: this.props.value});
+ }
+
+ componentWillReceiveProps(nextProps: SearchBarProps) {
+ if (nextProps.value !== this.props.value) {
+ this.setState({value: nextProps.value});
+ }
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ clearTimeout(this.timeout);
+ this.props.onSearch(this.state.value);
+ }
+
+ handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ clearTimeout(this.timeout);
+ this.setState({value: event.target.value});
+ this.timeout = window.setTimeout(
+ () => this.props.onSearch(this.state.value),
+ this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
+ );
+
+ }
+ }
+);
diff --git a/src/components/search-input/search-input.test.tsx b/src/components/search-input/search-input.test.tsx
index b07445a..a91f9b1 100644
--- a/src/components/search-input/search-input.test.tsx
+++ b/src/components/search-input/search-input.test.tsx
@@ -4,7 +4,7 @@
import * as React from "react";
import { mount, configure } from "enzyme";
-import SearchInput, { DEFAULT_SEARCH_DEBOUNCE } from "./search-input";
+import { SearchInput, DEFAULT_SEARCH_DEBOUNCE } from "./search-input";
import * as Adapter from 'enzyme-adapter-react-16';
@@ -61,7 +61,7 @@ describe("<SearchInput />", () => {
jest.advanceTimersByTime(DEFAULT_SEARCH_DEBOUNCE);
expect(onSearch).toBeCalledWith("current value");
});
-
+
it("calls onSearch after the time specified in props has passed", () => {
const searchInput = mount(<SearchInput value="" onSearch={onSearch} debounce={2000}/>);
searchInput.find("input").simulate("change", { target: { value: "current value" } });
@@ -70,7 +70,7 @@ describe("<SearchInput />", () => {
jest.advanceTimersByTime(1000);
expect(onSearch).toBeCalledWith("current value");
});
-
+
it("calls onSearch only once after no change happened during the specified time", () => {
const searchInput = mount(<SearchInput value="" onSearch={onSearch} debounce={1000}/>);
searchInput.find("input").simulate("change", { target: { value: "current value" } });
@@ -79,7 +79,7 @@ describe("<SearchInput />", () => {
jest.advanceTimersByTime(1000);
expect(onSearch).toHaveBeenCalledTimes(1);
});
-
+
it("calls onSearch again after the specified time has passed since previous call", () => {
const searchInput = mount(<SearchInput value="" onSearch={onSearch} debounce={1000}/>);
searchInput.find("input").simulate("change", { target: { value: "current value" } });
@@ -91,9 +91,9 @@ describe("<SearchInput />", () => {
jest.advanceTimersByTime(1000);
expect(onSearch).toBeCalledWith("latest value");
expect(onSearch).toHaveBeenCalledTimes(2);
-
+
});
});
-});
\ No newline at end of file
+});
diff --git a/src/components/search-input/search-input.tsx b/src/components/search-input/search-input.tsx
index edc82d5..dc02cd3 100644
--- a/src/components/search-input/search-input.tsx
+++ b/src/components/search-input/search-input.tsx
@@ -3,87 +3,9 @@
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { IconButton, Paper, StyleRulesCallback, withStyles, WithStyles, FormControl, InputLabel, Input, InputAdornment, FormHelperText } from '@material-ui/core';
+import { IconButton, StyleRulesCallback, withStyles, WithStyles, FormControl, InputLabel, Input, InputAdornment } from '@material-ui/core';
import SearchIcon from '@material-ui/icons/Search';
-interface SearchInputDataProps {
- value: string;
-}
-
-interface SearchInputActionProps {
- onSearch: (value: string) => any;
- debounce?: number;
-}
-
-type SearchInputProps = SearchInputDataProps & SearchInputActionProps & WithStyles<CssRules>;
-
-interface SearchInputState {
- value: string;
-}
-
-export const DEFAULT_SEARCH_DEBOUNCE = 1000;
-
-class SearchInput extends React.Component<SearchInputProps> {
-
- state: SearchInputState = {
- value: ""
- };
-
- timeout: number;
-
- render() {
- const { classes } = this.props;
- return <form onSubmit={this.handleSubmit}>
- <FormControl>
- <InputLabel>Search</InputLabel>
- <Input
- type="text"
- value={this.state.value}
- onChange={this.handleChange}
- endAdornment={
- <InputAdornment position="end">
- <IconButton
- onClick={this.handleSubmit}>
- <SearchIcon />
- </IconButton>
- </InputAdornment>
- } />
- </FormControl>
- </form>;
- }
-
- componentDidMount() {
- this.setState({ value: this.props.value });
- }
-
- componentWillReceiveProps(nextProps: SearchInputProps) {
- if (nextProps.value !== this.props.value) {
- this.setState({ value: nextProps.value });
- }
- }
-
- componentWillUnmount() {
- clearTimeout(this.timeout);
- }
-
- handleSubmit = (event: React.FormEvent<HTMLElement>) => {
- event.preventDefault();
- clearTimeout(this.timeout);
- this.props.onSearch(this.state.value);
- }
-
- handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- clearTimeout(this.timeout);
- this.setState({ value: event.target.value });
- this.timeout = window.setTimeout(
- () => this.props.onSearch(this.state.value),
- this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
- );
-
- }
-
-}
-
type CssRules = 'container' | 'input' | 'button';
const styles: StyleRulesCallback<CssRules> = theme => {
@@ -110,4 +32,80 @@ const styles: StyleRulesCallback<CssRules> = theme => {
};
};
-export default withStyles(styles)(SearchInput);
\ No newline at end of file
+interface SearchInputDataProps {
+ value: string;
+}
+
+interface SearchInputActionProps {
+ onSearch: (value: string) => any;
+ debounce?: number;
+}
+
+type SearchInputProps = SearchInputDataProps & SearchInputActionProps & WithStyles<CssRules>;
+
+interface SearchInputState {
+ value: string;
+}
+
+export const DEFAULT_SEARCH_DEBOUNCE = 1000;
+
+export const SearchInput = withStyles(styles)(
+ class extends React.Component<SearchInputProps> {
+ state: SearchInputState = {
+ value: ""
+ };
+
+ timeout: number;
+
+ render() {
+ const { classes } = this.props;
+ return <form onSubmit={this.handleSubmit}>
+ <FormControl>
+ <InputLabel>Search</InputLabel>
+ <Input
+ type="text"
+ value={this.state.value}
+ onChange={this.handleChange}
+ endAdornment={
+ <InputAdornment position="end">
+ <IconButton
+ onClick={this.handleSubmit}>
+ <SearchIcon/>
+ </IconButton>
+ </InputAdornment>
+ }/>
+ </FormControl>
+ </form>;
+ }
+
+ componentDidMount() {
+ this.setState({ value: this.props.value });
+ }
+
+ componentWillReceiveProps(nextProps: SearchInputProps) {
+ if (nextProps.value !== this.props.value) {
+ this.setState({ value: nextProps.value });
+ }
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ }
+
+ handleSubmit = (event: React.FormEvent<HTMLElement>) => {
+ event.preventDefault();
+ clearTimeout(this.timeout);
+ this.props.onSearch(this.state.value);
+ }
+
+ handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ clearTimeout(this.timeout);
+ this.setState({ value: event.target.value });
+ this.timeout = window.setTimeout(
+ () => this.props.onSearch(this.state.value),
+ this.props.debounce || DEFAULT_SEARCH_DEBOUNCE
+ );
+
+ }
+ }
+);
diff --git a/src/components/side-panel/side-panel.tsx b/src/components/side-panel/side-panel.tsx
index 165bd56..4240b1b 100644
--- a/src/components/side-panel/side-panel.tsx
+++ b/src/components/side-panel/side-panel.tsx
@@ -6,75 +6,10 @@ import * as React from 'react';
import { ReactElement } from 'react';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
import { ArvadosTheme } from '../../common/custom-theme';
-import { List, ListItem, ListItemText, ListItemIcon, Collapse, Typography } from "@material-ui/core";
+import { List, ListItem, ListItemIcon, Collapse } from "@material-ui/core";
import { SidePanelRightArrowIcon, IconType } from '../icon/icon';
import * as classnames from "classnames";
-import ListItemTextIcon from '../list-item-text-icon/list-item-text-icon';
-
-export interface SidePanelItem {
- id: string;
- name: string;
- icon: IconType;
- active?: boolean;
- open?: boolean;
- margin?: boolean;
- openAble?: boolean;
-}
-
-interface SidePanelProps {
- toggleOpen: (id: string) => void;
- toggleActive: (id: string) => void;
- sidePanelItems: SidePanelItem[];
- onContextMenu: (event: React.MouseEvent<HTMLElement>, item: SidePanelItem) => void;
-}
-
-class SidePanel extends React.Component<SidePanelProps & WithStyles<CssRules>> {
- render(): ReactElement<any> {
- const { classes, toggleOpen, toggleActive, sidePanelItems, children } = this.props;
- const { root, row, list, toggableIconContainer } = classes;
- return (
- <div className={root}>
- <List>
- {sidePanelItems.map(it => (
- <span key={it.name}>
- <ListItem button className={list} onClick={() => toggleActive(it.id)} onContextMenu={this.handleRowContextMenu(it)}>
- <span className={row}>
- {it.openAble ? (
- <i onClick={() => toggleOpen(it.id)} className={toggableIconContainer}>
- <ListItemIcon className={this.getToggableIconClassNames(it.open, it.active)}>
- < SidePanelRightArrowIcon />
- </ListItemIcon>
- </i>
- ) : null}
- <ListItemTextIcon icon={it.icon} name={it.name} isActive={it.active} hasMargin={it.margin} />
- </span>
- </ListItem>
- {it.openAble ? (
- <Collapse in={it.open} timeout="auto" unmountOnExit>
- {children}
- </Collapse>
- ) : null}
- </span>
- ))}
- </List>
- </div>
- );
- }
-
- getToggableIconClassNames = (isOpen?: boolean, isActive ?: boolean) => {
- const { classes } = this.props;
- return classnames(classes.toggableIcon, {
- [classes.iconOpen]: isOpen,
- [classes.iconClose]: !isOpen,
- [classes.active]: isActive
- });
- }
-
- handleRowContextMenu = (item: SidePanelItem) =>
- (event: React.MouseEvent<HTMLElement>) =>
- item.openAble ? this.props.onContextMenu(event, item) : null
-
-}
+import { ListItemTextIcon } from '../list-item-text-icon/list-item-text-icon';
type CssRules = 'active' | 'row' | 'root' | 'list' | 'iconClose' | 'iconOpen' | 'toggableIconContainer' | 'toggableIcon';
@@ -115,4 +50,73 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
}
});
-export default withStyles(styles)(SidePanel);
\ No newline at end of file
+export interface SidePanelItem {
+ id: string;
+ name: string;
+ icon: IconType;
+ active?: boolean;
+ open?: boolean;
+ margin?: boolean;
+ openAble?: boolean;
+}
+
+interface SidePanelDataProps {
+ toggleOpen: (id: string) => void;
+ toggleActive: (id: string) => void;
+ sidePanelItems: SidePanelItem[];
+ onContextMenu: (event: React.MouseEvent<HTMLElement>, item: SidePanelItem) => void;
+}
+
+type SidePanelProps = SidePanelDataProps & WithStyles<CssRules>;
+
+export const SidePanel = withStyles(styles)(
+ class extends React.Component<SidePanelProps> {
+ render(): ReactElement<any> {
+ const { classes, toggleOpen, toggleActive, sidePanelItems, children } = this.props;
+ const { root, row, list, toggableIconContainer } = classes;
+ return (
+ <div className={root}>
+ <List>
+ {sidePanelItems.map(it => (
+ <span key={it.name}>
+ <ListItem button className={list} onClick={() => toggleActive(it.id)}
+ onContextMenu={this.handleRowContextMenu(it)}>
+ <span className={row}>
+ {it.openAble ? (
+ <i onClick={() => toggleOpen(it.id)} className={toggableIconContainer}>
+ <ListItemIcon
+ className={this.getToggableIconClassNames(it.open, it.active)}>
+ < SidePanelRightArrowIcon/>
+ </ListItemIcon>
+ </i>
+ ) : null}
+ <ListItemTextIcon icon={it.icon} name={it.name} isActive={it.active}
+ hasMargin={it.margin}/>
+ </span>
+ </ListItem>
+ {it.openAble ? (
+ <Collapse in={it.open} timeout="auto" unmountOnExit>
+ {children}
+ </Collapse>
+ ) : null}
+ </span>
+ ))}
+ </List>
+ </div>
+ );
+ }
+
+ getToggableIconClassNames = (isOpen?: boolean, isActive ?: boolean) => {
+ const { classes } = this.props;
+ return classnames(classes.toggableIcon, {
+ [classes.iconOpen]: isOpen,
+ [classes.iconClose]: !isOpen,
+ [classes.active]: isActive
+ });
+ }
+
+ handleRowContextMenu = (item: SidePanelItem) =>
+ (event: React.MouseEvent<HTMLElement>) =>
+ item.openAble ? this.props.onContextMenu(event, item) : null
+ }
+);
diff --git a/src/components/tree/tree.test.tsx b/src/components/tree/tree.test.tsx
index 9ac0511..58484c3 100644
--- a/src/components/tree/tree.test.tsx
+++ b/src/components/tree/tree.test.tsx
@@ -7,7 +7,7 @@ import * as Enzyme from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
import ListItem from "@material-ui/core/ListItem/ListItem";
-import Tree, { TreeItem } from './tree';
+import { Tree, TreeItem } from './tree';
import { ProjectResource } from '../../models/project';
import { mockProjectResource } from '../../models/test-utils';
diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx
index 16f3ab2..e4d8c72 100644
--- a/src/components/tree/tree.tsx
+++ b/src/components/tree/tree.tsx
@@ -11,6 +11,38 @@ import Collapse from "@material-ui/core/Collapse/Collapse";
import CircularProgress from '@material-ui/core/CircularProgress';
import { ArvadosTheme } from '../../common/custom-theme';
+type CssRules = 'list' | 'activeArrow' | 'inactiveArrow' | 'arrowRotate' | 'arrowTransition' | 'loader' | 'arrowVisibility';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ list: {
+ paddingBottom: '3px',
+ paddingTop: '3px',
+ },
+ activeArrow: {
+ color: theme.palette.primary.main,
+ position: 'absolute',
+ },
+ inactiveArrow: {
+ color: theme.palette.grey["700"],
+ position: 'absolute',
+ },
+ arrowTransition: {
+ transition: 'all 0.1s ease',
+ },
+ arrowRotate: {
+ transition: 'all 0.1s ease',
+ transform: 'rotate(-90deg)',
+ },
+ arrowVisibility: {
+ opacity: 0,
+ },
+ loader: {
+ position: 'absolute',
+ transform: 'translate(0px)',
+ top: '3px'
+ }
+});
+
export enum TreeItemStatus {
Initial,
Pending,
@@ -36,78 +68,48 @@ interface TreeProps<T> {
onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
}
-class Tree<T> extends React.Component<TreeProps<T> & WithStyles<CssRules>, {}> {
- render(): ReactElement<any> {
- const level = this.props.level ? this.props.level : 0;
- const { classes, render, toggleItemOpen, items, toggleItemActive, onContextMenu } = this.props;
- const { list, inactiveArrow, activeArrow, loader } = classes;
- return <List component="div" className={list}>
- {items && items.map((it: TreeItem<T>, idx: number) =>
- <div key={`item/${level}/${idx}`}>
- <ListItem button className={list} style={{ paddingLeft: (level + 1) * 20 }} onClick={() => toggleItemActive(it.id, it.status)} onContextMenu={this.handleRowContextMenu(it)}>
- {it.status === TreeItemStatus.Pending ? <CircularProgress size={10} className={loader} /> : null}
- {it.toggled && it.items && it.items.length === 0 ? null : this.renderArrow(it.status, it.active ? activeArrow : inactiveArrow, it.open, it.id)}
- {render(it, level)}
- </ListItem>
- {it.items && it.items.length > 0 &&
+export const Tree = withStyles(styles)(
+ class Component<T> extends React.Component<TreeProps<T> & WithStyles<CssRules>, {}> {
+ render(): ReactElement<any> {
+ const level = this.props.level ? this.props.level : 0;
+ const { classes, render, toggleItemOpen, items, toggleItemActive, onContextMenu } = this.props;
+ const { list, inactiveArrow, activeArrow, loader } = classes;
+ return <List component="div" className={list}>
+ {items && items.map((it: TreeItem<T>, idx: number) =>
+ <div key={`item/${level}/${idx}`}>
+ <ListItem button className={list} style={{ paddingLeft: (level + 1) * 20 }}
+ onClick={() => toggleItemActive(it.id, it.status)}
+ onContextMenu={this.handleRowContextMenu(it)}>
+ {it.status === TreeItemStatus.Pending ?
+ <CircularProgress size={10} className={loader}/> : null}
+ {it.toggled && it.items && it.items.length === 0 ? null : this.renderArrow(it.status, it.active ? activeArrow : inactiveArrow, it.open, it.id)}
+ {render(it, level)}
+ </ListItem>
+ {it.items && it.items.length > 0 &&
<Collapse in={it.open} timeout="auto" unmountOnExit>
- <StyledTree
+ <Tree
items={it.items}
render={render}
toggleItemOpen={toggleItemOpen}
toggleItemActive={toggleItemActive}
level={level + 1}
- onContextMenu={onContextMenu} />
+ onContextMenu={onContextMenu}/>
</Collapse>}
- </div>)}
- </List>;
- }
+ </div>)}
+ </List>;
+ }
- renderArrow(status: TreeItemStatus, arrowClass: string, open: boolean, id: string) {
- const { arrowTransition, arrowVisibility, arrowRotate } = this.props.classes;
- return <i onClick={() => this.props.toggleItemOpen(id, status)}
- className={`
- ${arrowClass}
- ${status === TreeItemStatus.Pending ? arrowVisibility : ''}
- ${open ? `fas fa-caret-down ${arrowTransition}` : `fas fa-caret-down ${arrowRotate}`}`} />;
- }
+ renderArrow(status: TreeItemStatus, arrowClass: string, open: boolean, id: string) {
+ const { arrowTransition, arrowVisibility, arrowRotate } = this.props.classes;
+ return <i onClick={() => this.props.toggleItemOpen(id, status)}
+ className={`
+ ${arrowClass}
+ ${status === TreeItemStatus.Pending ? arrowVisibility : ''}
+ ${open ? `fas fa-caret-down ${arrowTransition}` : `fas fa-caret-down ${arrowRotate}`}`}/>;
+ }
- handleRowContextMenu = (item: TreeItem<T>) =>
- (event: React.MouseEvent<HTMLElement>) =>
- this.props.onContextMenu(event, item)
-}
-
-type CssRules = 'list' | 'activeArrow' | 'inactiveArrow' | 'arrowRotate' | 'arrowTransition' | 'loader' | 'arrowVisibility';
-
-const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
- list: {
- paddingBottom: '3px',
- paddingTop: '3px',
- },
- activeArrow: {
- color: theme.palette.primary.main,
- position: 'absolute',
- },
- inactiveArrow: {
- color: theme.palette.grey["700"],
- position: 'absolute',
- },
- arrowTransition: {
- transition: 'all 0.1s ease',
- },
- arrowRotate: {
- transition: 'all 0.1s ease',
- transform: 'rotate(-90deg)',
- },
- arrowVisibility: {
- opacity: 0,
- },
- loader: {
- position: 'absolute',
- transform: 'translate(0px)',
- top: '3px'
+ handleRowContextMenu = (item: TreeItem<T>) =>
+ (event: React.MouseEvent<HTMLElement>) =>
+ this.props.onContextMenu(event, item)
}
-});
-
-const StyledTree = withStyles(styles)(Tree);
-export default StyledTree;
+);
diff --git a/src/index.tsx b/src/index.tsx
index 1022496..6d53e0d 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -5,20 +5,26 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from "react-redux";
-import Workbench from './views/workbench/workbench';
+import { Workbench } from './views/workbench/workbench';
import './index.css';
import { Route } from "react-router";
import createBrowserHistory from "history/createBrowserHistory";
-import configureStore from "./store/store";
+import { configureStore } from "./store/store";
import { ConnectedRouter } from "react-router-redux";
-import ApiToken from "./views-components/api-token/api-token";
-import authActions from "./store/auth/auth-action";
+import { ApiToken } from "./views-components/api-token/api-token";
+import { authActions } from "./store/auth/auth-action";
import { authService } from "./services/services";
import { getProjectList } from "./store/project/project-action";
import { MuiThemeProvider } from '@material-ui/core/styles';
import { CustomTheme } from './common/custom-theme';
import { fetchConfig } from './common/config';
import { setBaseUrl } from './common/api/server-api';
+import { addMenuActionSet, ContextMenuKind } from "./views-components/context-menu/context-menu";
+import { rootProjectActionSet } from "./views-components/context-menu/action-sets/root-project-action-set";
+import { projectActionSet } from "./views-components/context-menu/action-sets/project-action-set";
+
+addMenuActionSet(ContextMenuKind.RootProject, rootProjectActionSet);
+addMenuActionSet(ContextMenuKind.Project, projectActionSet);
fetchConfig()
.then(config => {
diff --git a/src/services/auth-service/auth-service.ts b/src/services/auth-service/auth-service.ts
index 5b21a61..1879e6a 100644
--- a/src/services/auth-service/auth-service.ts
+++ b/src/services/auth-service/auth-service.ts
@@ -22,7 +22,7 @@ export interface UserDetailsResponse {
is_admin: boolean;
}
-export default class AuthService {
+export class AuthService {
constructor(protected serverApi: AxiosInstance) { }
diff --git a/src/services/groups-service/groups-service.test.ts b/src/services/groups-service/groups-service.test.ts
index 2562a59..c3be8bd 100644
--- a/src/services/groups-service/groups-service.test.ts
+++ b/src/services/groups-service/groups-service.test.ts
@@ -4,7 +4,7 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
-import GroupsService from "./groups-service";
+import { GroupsService } from "./groups-service";
describe("GroupsService", () => {
diff --git a/src/services/groups-service/groups-service.ts b/src/services/groups-service/groups-service.ts
index 1318dac..dfaf11d 100644
--- a/src/services/groups-service/groups-service.ts
+++ b/src/services/groups-service/groups-service.ts
@@ -3,9 +3,9 @@
// SPDX-License-Identifier: AGPL-3.0
import * as _ from "lodash";
-import CommonResourceService, { ListResults } from "../../common/api/common-resource-service";
-import FilterBuilder from "../../common/api/filter-builder";
-import OrderBuilder from "../../common/api/order-builder";
+import { CommonResourceService, ListResults } from "../../common/api/common-resource-service";
+import { FilterBuilder } from "../../common/api/filter-builder";
+import { OrderBuilder } from "../../common/api/order-builder";
import { AxiosInstance } from "axios";
import { GroupResource } from "../../models/group";
import { CollectionResource } from "../../models/collection";
@@ -25,7 +25,7 @@ export type GroupContentsResource =
ProjectResource |
ProcessResource;
-export default class GroupsService<T extends GroupResource = GroupResource> extends CommonResourceService<T> {
+export class GroupsService<T extends GroupResource = GroupResource> extends CommonResourceService<T> {
constructor(serverApi: AxiosInstance) {
super(serverApi, "groups");
@@ -50,4 +50,4 @@ export enum GroupContentsResourcePrefix {
Collection = "collections",
Project = "groups",
Process = "container_requests"
-}
\ No newline at end of file
+}
diff --git a/src/services/project-service/project-service.test.ts b/src/services/project-service/project-service.test.ts
index 76da3d8..f915c2d 100644
--- a/src/services/project-service/project-service.test.ts
+++ b/src/services/project-service/project-service.test.ts
@@ -3,9 +3,8 @@
// SPDX-License-Identifier: AGPL-3.0
import axios from "axios";
-import MockAdapter from "axios-mock-adapter/types";
-import ProjectService from "./project-service";
-import FilterBuilder from "../../common/api/filter-builder";
+import { ProjectService } from "./project-service";
+import { FilterBuilder } from "../../common/api/filter-builder";
import { ProjectResource } from "../../models/project";
describe("CommonResourceService", () => {
@@ -35,5 +34,5 @@ describe("CommonResourceService", () => {
}
});
});
-
+
});
diff --git a/src/services/project-service/project-service.ts b/src/services/project-service/project-service.ts
index 9ce9e21..f759547 100644
--- a/src/services/project-service/project-service.ts
+++ b/src/services/project-service/project-service.ts
@@ -2,13 +2,13 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import GroupsService, { ContentsArguments } from "../groups-service/groups-service";
+import { GroupsService, ContentsArguments } from "../groups-service/groups-service";
import { ProjectResource } from "../../models/project";
import { GroupClass } from "../../models/group";
import { ListArguments } from "../../common/api/common-resource-service";
-import FilterBuilder from "../../common/api/filter-builder";
+import { FilterBuilder } from "../../common/api/filter-builder";
-export default class ProjectService extends GroupsService<ProjectResource> {
+export class ProjectService extends GroupsService<ProjectResource> {
create(data: Partial<ProjectResource>) {
const projectData = { ...data, groupClass: GroupClass.Project };
@@ -33,4 +33,4 @@ export default class ProjectService extends GroupsService<ProjectResource> {
.addEqual("groupClass", GroupClass.Project));
}
-}
\ No newline at end of file
+}
diff --git a/src/services/services.ts b/src/services/services.ts
index 88f6ffa..57f07d6 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import AuthService from "./auth-service/auth-service";
-import GroupsService from "./groups-service/groups-service";
+import { AuthService } from "./auth-service/auth-service";
+import { GroupsService } from "./groups-service/groups-service";
import { serverApi } from "../common/api/server-api";
-import ProjectService from "./project-service/project-service";
+import { ProjectService } from "./project-service/project-service";
export const authService = new AuthService(serverApi);
export const groupsService = new GroupsService(serverApi);
diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts
index a6e6f79..e9930a0 100644
--- a/src/store/auth/auth-action.ts
+++ b/src/store/auth/auth-action.ts
@@ -7,7 +7,7 @@ import { Dispatch } from "redux";
import { authService } from "../../services/services";
import { User } from "../../models/user";
-const actions = unionize({
+export const authActions = unionize({
SAVE_API_TOKEN: ofType<string>(),
LOGIN: {},
LOGOUT: {},
@@ -20,13 +20,11 @@ const actions = unionize({
});
export const getUserDetails = () => (dispatch: Dispatch): Promise<User> => {
- dispatch(actions.USER_DETAILS_REQUEST());
+ dispatch(authActions.USER_DETAILS_REQUEST());
return authService.getUserDetails().then(details => {
- dispatch(actions.USER_DETAILS_SUCCESS(details));
+ dispatch(authActions.USER_DETAILS_SUCCESS(details));
return details;
});
};
-
-export type AuthAction = UnionOf<typeof actions>;
-export default actions;
+export type AuthAction = UnionOf<typeof authActions>;
diff --git a/src/store/auth/auth-reducer.test.ts b/src/store/auth/auth-reducer.test.ts
index 2e7c1a2..ea08e58 100644
--- a/src/store/auth/auth-reducer.test.ts
+++ b/src/store/auth/auth-reducer.test.ts
@@ -2,8 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import authReducer from "./auth-reducer";
-import actions from "./auth-action";
+import { authReducer } from "./auth-reducer";
+import { authActions } from "./auth-action";
import {
API_TOKEN_KEY,
USER_EMAIL_KEY,
@@ -23,7 +23,7 @@ describe('auth-reducer', () => {
it('should return default state on initialisation', () => {
const initialState = undefined;
- const state = authReducer(initialState, actions.INIT());
+ const state = authReducer(initialState, authActions.INIT());
expect(state).toEqual({
apiToken: undefined,
user: undefined
@@ -40,7 +40,7 @@ describe('auth-reducer', () => {
localStorage.setItem(USER_UUID_KEY, "uuid");
localStorage.setItem(USER_OWNER_UUID_KEY, "ownerUuid");
- const state = authReducer(initialState, actions.INIT());
+ const state = authReducer(initialState, authActions.INIT());
expect(state).toEqual({
apiToken: "token",
user: {
@@ -56,7 +56,7 @@ describe('auth-reducer', () => {
it('should store token in local storage', () => {
const initialState = undefined;
- const state = authReducer(initialState, actions.SAVE_API_TOKEN("token"));
+ const state = authReducer(initialState, authActions.SAVE_API_TOKEN("token"));
expect(state).toEqual({
apiToken: "token",
user: undefined
@@ -76,7 +76,7 @@ describe('auth-reducer', () => {
ownerUuid: "ownerUuid"
};
- const state = authReducer(initialState, actions.USER_DETAILS_SUCCESS(user));
+ const state = authReducer(initialState, authActions.USER_DETAILS_SUCCESS(user));
expect(state).toEqual({
apiToken: undefined,
user: {
@@ -94,7 +94,7 @@ describe('auth-reducer', () => {
it('should fire external url to login', () => {
const initialState = undefined;
window.location.assign = jest.fn();
- authReducer(initialState, actions.LOGIN());
+ authReducer(initialState, authActions.LOGIN());
expect(window.location.assign).toBeCalledWith(
`${API_HOST}/login?return_to=${window.location.protocol}//${window.location.host}/token`
);
@@ -103,7 +103,7 @@ describe('auth-reducer', () => {
it('should fire external url to logout', () => {
const initialState = undefined;
window.location.assign = jest.fn();
- authReducer(initialState, actions.LOGOUT());
+ authReducer(initialState, authActions.LOGOUT());
expect(window.location.assign).toBeCalledWith(
`${API_HOST}/logout?return_to=${location.protocol}//${location.host}`
);
diff --git a/src/store/auth/auth-reducer.ts b/src/store/auth/auth-reducer.ts
index f6974fd..366385d 100644
--- a/src/store/auth/auth-reducer.ts
+++ b/src/store/auth/auth-reducer.ts
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import actions, { AuthAction } from "./auth-action";
+import { authActions, AuthAction } from "./auth-action";
import { User } from "../../models/user";
import { authService } from "../../services/services";
import { removeServerApiAuthorizationHeader, setServerApiAuthorizationHeader } from "../../common/api/server-api";
@@ -12,8 +12,8 @@ export interface AuthState {
apiToken?: string;
}
-const authReducer = (state: AuthState = {}, action: AuthAction) => {
- return actions.match(action, {
+export const authReducer = (state: AuthState = {}, action: AuthAction) => {
+ return authActions.match(action, {
SAVE_API_TOKEN: (token: string) => {
authService.saveApiToken(token);
setServerApiAuthorizationHeader(token);
@@ -45,5 +45,3 @@ const authReducer = (state: AuthState = {}, action: AuthAction) => {
default: () => state
});
};
-
-export default authReducer;
diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts
index 8630f9c..8e5eb1e 100644
--- a/src/store/context-menu/context-menu-actions.ts
+++ b/src/store/context-menu/context-menu-actions.ts
@@ -5,7 +5,7 @@
import { default as unionize, ofType, UnionOf } from "unionize";
import { ContextMenuPosition, ContextMenuResource } from "./context-menu-reducer";
-const actions = unionize({
+export const contextMenuActions = unionize({
OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
CLOSE_CONTEXT_MENU: ofType<{}>()
}, {
@@ -13,5 +13,4 @@ const actions = unionize({
value: 'payload'
});
-export type ContextMenuAction = UnionOf<typeof actions>;
-export default actions;
\ No newline at end of file
+export type ContextMenuAction = UnionOf<typeof contextMenuActions>;
diff --git a/src/store/context-menu/context-menu-reducer.ts b/src/store/context-menu/context-menu-reducer.ts
index 147f094..b20ad72 100644
--- a/src/store/context-menu/context-menu-reducer.ts
+++ b/src/store/context-menu/context-menu-reducer.ts
@@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0
import { ResourceKind } from "../../models/resource";
-import actions, { ContextMenuAction } from "./context-menu-actions";
+import { contextMenuActions, ContextMenuAction } from "./context-menu-actions";
export interface ContextMenuState {
position: ContextMenuPosition;
@@ -24,11 +24,10 @@ const initialState = {
position: { x: 0, y: 0 }
};
-const reducer = (state: ContextMenuState = initialState, action: ContextMenuAction) =>
- actions.match(action, {
+export const contextMenuReducer = (state: ContextMenuState = initialState, action: ContextMenuAction) =>
+ contextMenuActions.match(action, {
default: () => state,
OPEN_CONTEXT_MENU: ({resource, position}) => ({ resource, position }),
CLOSE_CONTEXT_MENU: () => ({ position: state.position })
});
-export default reducer;
diff --git a/src/store/data-explorer/data-explorer-action.ts b/src/store/data-explorer/data-explorer-action.ts
index 3d7ba53..053f419 100644
--- a/src/store/data-explorer/data-explorer-action.ts
+++ b/src/store/data-explorer/data-explorer-action.ts
@@ -6,7 +6,7 @@ import { default as unionize, ofType, UnionOf } from "unionize";
import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
import { DataColumns } from "../../components/data-table/data-table";
-const actions = unionize({
+export const dataExplorerActions = unionize({
RESET_PAGINATION: ofType<{ id: string }>(),
REQUEST_ITEMS: ofType<{ id: string }>(),
SET_COLUMNS: ofType<{ id: string, columns: DataColumns<any> }>(),
@@ -19,6 +19,4 @@ const actions = unionize({
SET_SEARCH_VALUE: ofType<{ id: string, searchValue: string }>(),
}, { tag: "type", value: "payload" });
-export type DataExplorerAction = UnionOf<typeof actions>;
-
-export default actions;
+export type DataExplorerAction = UnionOf<typeof dataExplorerActions>;
diff --git a/src/store/data-explorer/data-explorer-reducer.test.tsx b/src/store/data-explorer/data-explorer-reducer.test.tsx
index 0eb3c32..5b9f68f 100644
--- a/src/store/data-explorer/data-explorer-reducer.test.tsx
+++ b/src/store/data-explorer/data-explorer-reducer.test.tsx
@@ -2,10 +2,11 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import dataExplorerReducer, { initialDataExplorer } from "./data-explorer-reducer";
-import actions from "./data-explorer-action";
+import { dataExplorerReducer, initialDataExplorer } from "./data-explorer-reducer";
+import { dataExplorerActions } from "./data-explorer-action";
import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
import { DataColumns } from "../../components/data-table/data-table";
+import { SortDirection } from "../../components/data-table/data-column";
describe('data-explorer-reducer', () => {
it('should set columns', () => {
@@ -15,7 +16,7 @@ describe('data-explorer-reducer', () => {
selected: true
}];
const state = dataExplorerReducer(undefined,
- actions.SET_COLUMNS({ id: "Data explorer", columns }));
+ dataExplorerActions.SET_COLUMNS({ id: "Data explorer", columns }));
expect(state["Data explorer"].columns).toEqual(columns);
});
@@ -24,15 +25,15 @@ describe('data-explorer-reducer', () => {
name: "Column 1",
render: jest.fn(),
selected: true,
- sortDirection: "asc"
+ sortDirection: SortDirection.Asc
}, {
name: "Column 2",
render: jest.fn(),
selected: true,
- sortDirection: "none",
+ sortDirection: SortDirection.None,
}];
const state = dataExplorerReducer({ "Data explorer": { ...initialDataExplorer, columns } },
- actions.TOGGLE_SORT({ id: "Data explorer", columnName: "Column 2" }));
+ dataExplorerActions.TOGGLE_SORT({ id: "Data explorer", columnName: "Column 2" }));
expect(state["Data explorer"].columns[0].sortDirection).toEqual("none");
expect(state["Data explorer"].columns[1].sortDirection).toEqual("asc");
});
@@ -49,25 +50,31 @@ describe('data-explorer-reducer', () => {
selected: true
}];
const state = dataExplorerReducer({ "Data explorer": { ...initialDataExplorer, columns } },
- actions.SET_FILTERS({ id: "Data explorer", columnName: "Column 1", filters }));
+ dataExplorerActions.SET_FILTERS({ id: "Data explorer", columnName: "Column 1", filters }));
expect(state["Data explorer"].columns[0].filters).toEqual(filters);
});
it('should set items', () => {
const state = dataExplorerReducer({ "Data explorer": undefined },
- actions.SET_ITEMS({ id: "Data explorer", items: ["Item 1", "Item 2"] }));
+ dataExplorerActions.SET_ITEMS({
+ id: "Data explorer",
+ items: ["Item 1", "Item 2"],
+ page: 0,
+ rowsPerPage: 10,
+ itemsAvailable: 100
+ }));
expect(state["Data explorer"].items).toEqual(["Item 1", "Item 2"]);
});
it('should set page', () => {
const state = dataExplorerReducer({ "Data explorer": undefined },
- actions.SET_PAGE({ id: "Data explorer", page: 2 }));
+ dataExplorerActions.SET_PAGE({ id: "Data explorer", page: 2 }));
expect(state["Data explorer"].page).toEqual(2);
});
-
+
it('should set rows per page', () => {
const state = dataExplorerReducer({ "Data explorer": undefined },
- actions.SET_ROWS_PER_PAGE({ id: "Data explorer", rowsPerPage: 5 }));
+ dataExplorerActions.SET_ROWS_PER_PAGE({ id: "Data explorer", rowsPerPage: 5 }));
expect(state["Data explorer"].rowsPerPage).toEqual(5);
});
});
diff --git a/src/store/data-explorer/data-explorer-reducer.ts b/src/store/data-explorer/data-explorer-reducer.ts
index 0112617..c112454 100644
--- a/src/store/data-explorer/data-explorer-reducer.ts
+++ b/src/store/data-explorer/data-explorer-reducer.ts
@@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0
import { DataColumn, toggleSortDirection, resetSortDirection } from "../../components/data-table/data-column";
-import actions, { DataExplorerAction } from "./data-explorer-action";
+import { dataExplorerActions, DataExplorerAction } from "./data-explorer-action";
import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
import { DataColumns } from "../../components/data-table/data-table";
@@ -29,8 +29,8 @@ export const initialDataExplorer: DataExplorer = {
export type DataExplorerState = Record<string, DataExplorer | undefined>;
-const dataExplorerReducer = (state: DataExplorerState = {}, action: DataExplorerAction) =>
- actions.match(action, {
+export const dataExplorerReducer = (state: DataExplorerState = {}, action: DataExplorerAction) =>
+ dataExplorerActions.match(action, {
RESET_PAGINATION: ({ id }) =>
update(state, id, explorer => ({ ...explorer, page: 0 })),
@@ -61,8 +61,6 @@ const dataExplorerReducer = (state: DataExplorerState = {}, action: DataExplorer
default: () => state
});
-export default dataExplorerReducer;
-
export const getDataExplorer = (state: DataExplorerState, id: string) =>
state[id] || initialDataExplorer;
diff --git a/src/store/details-panel/details-panel-action.ts b/src/store/details-panel/details-panel-action.ts
index 630428c..ba330f2 100644
--- a/src/store/details-panel/details-panel-action.ts
+++ b/src/store/details-panel/details-panel-action.ts
@@ -3,28 +3,26 @@
// SPDX-License-Identifier: AGPL-3.0
import { unionize, ofType, UnionOf } from "unionize";
-import CommonResourceService from "../../common/api/common-resource-service";
+import { CommonResourceService } from "../../common/api/common-resource-service";
import { Dispatch } from "redux";
import { serverApi } from "../../common/api/server-api";
import { Resource, ResourceKind } from "../../models/resource";
-const actions = unionize({
+export const detailsPanelActions = unionize({
TOGGLE_DETAILS_PANEL: ofType<{}>(),
LOAD_DETAILS: ofType<{ uuid: string, kind: ResourceKind }>(),
LOAD_DETAILS_SUCCESS: ofType<{ item: Resource }>(),
}, { tag: 'type', value: 'payload' });
-export default actions;
-
-export type DetailsPanelAction = UnionOf<typeof actions>;
+export type DetailsPanelAction = UnionOf<typeof detailsPanelActions>;
export const loadDetails = (uuid: string, kind: ResourceKind) =>
(dispatch: Dispatch) => {
- dispatch(actions.LOAD_DETAILS({ uuid, kind }));
+ dispatch(detailsPanelActions.LOAD_DETAILS({ uuid, kind }));
getService(kind)
.get(uuid)
.then(project => {
- dispatch(actions.LOAD_DETAILS_SUCCESS({ item: project }));
+ dispatch(detailsPanelActions.LOAD_DETAILS_SUCCESS({ item: project }));
});
};
diff --git a/src/store/details-panel/details-panel-reducer.ts b/src/store/details-panel/details-panel-reducer.ts
index f57b9f1..97de4a9 100644
--- a/src/store/details-panel/details-panel-reducer.ts
+++ b/src/store/details-panel/details-panel-reducer.ts
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import actions, { DetailsPanelAction } from "./details-panel-action";
+import { detailsPanelActions, DetailsPanelAction } from "./details-panel-action";
import { Resource } from "../../models/resource";
export interface DetailsPanelState {
@@ -15,12 +15,10 @@ const initialState = {
isOpened: false
};
-const reducer = (state: DetailsPanelState = initialState, action: DetailsPanelAction) =>
- actions.match(action, {
+export const detailsPanelReducer = (state: DetailsPanelState = initialState, action: DetailsPanelAction) =>
+ detailsPanelActions.match(action, {
default: () => state,
LOAD_DETAILS: () => state,
LOAD_DETAILS_SUCCESS: ({ item }) => ({ ...state, item }),
TOGGLE_DETAILS_PANEL: () => ({ ...state, isOpened: !state.isOpened })
});
-
-export default reducer;
diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts
index d7630d7..3920b5a 100644
--- a/src/store/navigation/navigation-action.ts
+++ b/src/store/navigation/navigation-action.ts
@@ -3,11 +3,11 @@
// SPDX-License-Identifier: AGPL-3.0
import { Dispatch } from "redux";
-import projectActions, { getProjectList } from "../project/project-action";
+import { projectActions, getProjectList } from "../project/project-action";
import { push } from "react-router-redux";
import { TreeItemStatus } from "../../components/tree/tree";
import { findTreeItem } from "../project/project-reducer";
-import dataExplorerActions from "../data-explorer/data-explorer-action";
+import { dataExplorerActions } from "../data-explorer/data-explorer-action";
import { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel";
import { RootState } from "../store";
import { Resource, ResourceKind } from "../../models/resource";
diff --git a/src/store/project-panel/project-panel-middleware.ts b/src/store/project-panel/project-panel-middleware.ts
index 80fb7fa..7ea2ffa 100644
--- a/src/store/project-panel/project-panel-middleware.ts
+++ b/src/store/project-panel/project-panel-middleware.ts
@@ -3,22 +3,21 @@
// SPDX-License-Identifier: AGPL-3.0
import { Middleware } from "redux";
-import actions from "../data-explorer/data-explorer-action";
+import { dataExplorerActions } from "../data-explorer/data-explorer-action";
import { PROJECT_PANEL_ID, columns, ProjectPanelFilter, ProjectPanelColumnNames } from "../../views/project-panel/project-panel";
import { groupsService } from "../../services/services";
import { RootState } from "../store";
-import { getDataExplorer, DataExplorerState } from "../data-explorer/data-explorer-reducer";
+import { getDataExplorer } from "../data-explorer/data-explorer-reducer";
import { resourceToDataItem, ProjectPanelItem } from "../../views/project-panel/project-panel-item";
-import FilterBuilder from "../../common/api/filter-builder";
+import { FilterBuilder } from "../../common/api/filter-builder";
import { DataColumns } from "../../components/data-table/data-table";
import { ProcessResource } from "../../models/process";
-import { CollectionResource } from "../../models/collection";
-import OrderBuilder from "../../common/api/order-builder";
+import { OrderBuilder } from "../../common/api/order-builder";
import { GroupContentsResource, GroupContentsResourcePrefix } from "../../services/groups-service/groups-service";
import { SortDirection } from "../../components/data-table/data-column";
export const projectPanelMiddleware: Middleware = store => next => {
- next(actions.SET_COLUMNS({ id: PROJECT_PANEL_ID, columns }));
+ next(dataExplorerActions.SET_COLUMNS({ id: PROJECT_PANEL_ID, columns }));
return action => {
@@ -30,23 +29,23 @@ export const projectPanelMiddleware: Middleware = store => next => {
}
};
- actions.match(action, {
+ dataExplorerActions.match(action, {
SET_PAGE: handleProjectPanelAction(() => {
- store.dispatch(actions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
+ store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
}),
SET_ROWS_PER_PAGE: handleProjectPanelAction(() => {
- store.dispatch(actions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
+ store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
}),
SET_FILTERS: handleProjectPanelAction(() => {
- store.dispatch(actions.RESET_PAGINATION({ id: PROJECT_PANEL_ID }));
- store.dispatch(actions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
+ store.dispatch(dataExplorerActions.RESET_PAGINATION({ id: PROJECT_PANEL_ID }));
+ store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
}),
TOGGLE_SORT: handleProjectPanelAction(() => {
- store.dispatch(actions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
+ store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
}),
SET_SEARCH_VALUE: handleProjectPanelAction(() => {
- store.dispatch(actions.RESET_PAGINATION({ id: PROJECT_PANEL_ID }));
- store.dispatch(actions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
+ store.dispatch(dataExplorerActions.RESET_PAGINATION({ id: PROJECT_PANEL_ID }));
+ store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
}),
REQUEST_ITEMS: handleProjectPanelAction(() => {
const state = store.getState() as RootState;
@@ -77,7 +76,7 @@ export const projectPanelMiddleware: Middleware = store => next => {
.concat(getSearchFilter(dataExplorer.searchValue))
})
.then(response => {
- store.dispatch(actions.SET_ITEMS({
+ store.dispatch(dataExplorerActions.SET_ITEMS({
id: PROJECT_PANEL_ID,
items: response.items.map(resourceToDataItem),
itemsAvailable: response.itemsAvailable,
@@ -86,7 +85,7 @@ export const projectPanelMiddleware: Middleware = store => next => {
}));
});
} else {
- store.dispatch(actions.SET_ITEMS({
+ store.dispatch(dataExplorerActions.SET_ITEMS({
id: PROJECT_PANEL_ID,
items: [],
itemsAvailable: 0,
diff --git a/src/store/project/project-action.ts b/src/store/project/project-action.ts
index f7708eb..2a7a5c1 100644
--- a/src/store/project/project-action.ts
+++ b/src/store/project/project-action.ts
@@ -6,10 +6,10 @@ import { default as unionize, ofType, UnionOf } from "unionize";
import { ProjectResource } from "../../models/project";
import { projectService } from "../../services/services";
import { Dispatch } from "redux";
-import FilterBuilder from "../../common/api/filter-builder";
+import { FilterBuilder } from "../../common/api/filter-builder";
import { RootState } from "../store";
-const actions = unionize({
+export const projectActions = unionize({
OPEN_PROJECT_CREATOR: ofType<{ ownerUuid: string }>(),
CLOSE_PROJECT_CREATOR: ofType<{}>(),
CREATE_PROJECT: ofType<Partial<ProjectResource>>(),
@@ -27,13 +27,13 @@ const actions = unionize({
});
export const getProjectList = (parentUuid: string = '') => (dispatch: Dispatch) => {
- dispatch(actions.PROJECTS_REQUEST(parentUuid));
+ dispatch(projectActions.PROJECTS_REQUEST(parentUuid));
return projectService.list({
filters: FilterBuilder
.create<ProjectResource>()
.addEqual("ownerUuid", parentUuid)
}).then(({ items: projects }) => {
- dispatch(actions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid }));
+ dispatch(projectActions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid }));
return projects;
});
};
@@ -42,12 +42,11 @@ export const createProject = (project: Partial<ProjectResource>) =>
(dispatch: Dispatch, getState: () => RootState) => {
const { ownerUuid } = getState().projects.creator;
const projectData = { ownerUuid, ...project };
- dispatch(actions.CREATE_PROJECT(projectData));
+ dispatch(projectActions.CREATE_PROJECT(projectData));
return projectService
.create(projectData)
- .then(project => dispatch(actions.CREATE_PROJECT_SUCCESS(project)))
- .catch(() => dispatch(actions.CREATE_PROJECT_ERROR("Could not create a project")));
+ .then(project => dispatch(projectActions.CREATE_PROJECT_SUCCESS(project)))
+ .catch(() => dispatch(projectActions.CREATE_PROJECT_ERROR("Could not create a project")));
};
-export type ProjectAction = UnionOf<typeof actions>;
-export default actions;
+export type ProjectAction = UnionOf<typeof projectActions>;
diff --git a/src/store/project/project-reducer.test.ts b/src/store/project/project-reducer.test.ts
index dbac4e9..c8eed87 100644
--- a/src/store/project/project-reducer.test.ts
+++ b/src/store/project/project-reducer.test.ts
@@ -2,8 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import projectsReducer, { getTreePath } from "./project-reducer";
-import actions from "./project-action";
+import { projectsReducer, getTreePath } from "./project-reducer";
+import { projectActions } from "./project-action";
import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
import { mockProjectResource } from "../../models/test-utils";
@@ -13,7 +13,7 @@ describe('project-reducer', () => {
const initialState = undefined;
const projects = [mockProjectResource({ uuid: "1" }), mockProjectResource({ uuid: "2" })];
- const state = projectsReducer(initialState, actions.PROJECTS_SUCCESS({ projects, parentItemId: undefined }));
+ const state = projectsReducer(initialState, projectActions.PROJECTS_SUCCESS({ projects, parentItemId: undefined }));
expect(state).toEqual({
items: [{
active: false,
@@ -64,7 +64,7 @@ describe('project-reducer', () => {
creator: { opened: false, pending: false, ownerUuid: "" },
};
- const state = projectsReducer(initialState, actions.RESET_PROJECT_TREE_ACTIVITY(initialState.items[0].id));
+ const state = projectsReducer(initialState, projectActions.RESET_PROJECT_TREE_ACTIVITY(initialState.items[0].id));
expect(state).toEqual(project);
});
@@ -93,7 +93,7 @@ describe('project-reducer', () => {
creator: { opened: false, pending: false, ownerUuid: "" },
};
- const state = projectsReducer(initialState, actions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(initialState.items[0].id));
+ const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(initialState.items[0].id));
expect(state).toEqual(project);
});
@@ -124,7 +124,7 @@ describe('project-reducer', () => {
creator: { opened: false, pending: false, ownerUuid: "" },
};
- const state = projectsReducer(initialState, actions.TOGGLE_PROJECT_TREE_ITEM_OPEN(initialState.items[0].id));
+ const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(initialState.items[0].id));
expect(state).toEqual(project);
});
});
diff --git a/src/store/project/project-reducer.ts b/src/store/project/project-reducer.ts
index c008370..40356c0 100644
--- a/src/store/project/project-reducer.ts
+++ b/src/store/project/project-reducer.ts
@@ -4,7 +4,7 @@
import * as _ from "lodash";
-import actions, { ProjectAction } from "./project-action";
+import { projectActions, ProjectAction } from "./project-action";
import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
import { ProjectResource } from "../../models/project";
@@ -111,8 +111,8 @@ const initialState: ProjectState = {
};
-const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => {
- return actions.match(action, {
+export const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => {
+ return projectActions.match(action, {
OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true, pending: false }),
CLOSE_PROJECT_CREATOR: () => updateCreator(state, { opened: false }),
CREATE_PROJECT: () => updateCreator(state, { opened: false, pending: true }),
@@ -174,5 +174,3 @@ const projectsReducer = (state: ProjectState = initialState, action: ProjectActi
default: () => state
});
};
-
-export default projectsReducer;
diff --git a/src/store/side-panel/side-panel-action.ts b/src/store/side-panel/side-panel-action.ts
index 6a83946..0dd6aad 100644
--- a/src/store/side-panel/side-panel-action.ts
+++ b/src/store/side-panel/side-panel-action.ts
@@ -4,7 +4,7 @@
import { default as unionize, ofType, UnionOf } from "unionize";
-const actions = unionize({
+export const sidePanelActions = unionize({
TOGGLE_SIDE_PANEL_ITEM_OPEN: ofType<string>(),
TOGGLE_SIDE_PANEL_ITEM_ACTIVE: ofType<string>(),
RESET_SIDE_PANEL_ACTIVITY: ofType<{}>(),
@@ -13,5 +13,4 @@ const actions = unionize({
value: 'payload'
});
-export type SidePanelAction = UnionOf<typeof actions>;
-export default actions;
\ No newline at end of file
+export type SidePanelAction = UnionOf<typeof sidePanelActions>;
diff --git a/src/store/side-panel/side-panel-reducer.test.ts b/src/store/side-panel/side-panel-reducer.test.ts
index 5edd902..e517fc8 100644
--- a/src/store/side-panel/side-panel-reducer.test.ts
+++ b/src/store/side-panel/side-panel-reducer.test.ts
@@ -2,8 +2,8 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import sidePanelReducer from "./side-panel-reducer";
-import actions from "./side-panel-action";
+import { sidePanelReducer } from "./side-panel-reducer";
+import { sidePanelActions } from "./side-panel-action";
import { ProjectsIcon } from "../../components/icon/icon";
describe('side-panel-reducer', () => {
@@ -28,7 +28,7 @@ describe('side-panel-reducer', () => {
}
];
- const state = sidePanelReducer(initialState, actions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(initialState[0].id));
+ const state = sidePanelReducer(initialState, sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(initialState[0].id));
expect(state).toEqual(project);
});
@@ -52,7 +52,7 @@ describe('side-panel-reducer', () => {
}
];
- const state = sidePanelReducer(initialState, actions.TOGGLE_SIDE_PANEL_ITEM_OPEN(initialState[0].id));
+ const state = sidePanelReducer(initialState, sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(initialState[0].id));
expect(state).toEqual(project);
});
@@ -76,7 +76,7 @@ describe('side-panel-reducer', () => {
}
];
- const state = sidePanelReducer(initialState, actions.RESET_SIDE_PANEL_ACTIVITY(initialState[0].id));
+ const state = sidePanelReducer(initialState, sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(initialState[0].id));
expect(state).toEqual(project);
});
-});
\ No newline at end of file
+});
diff --git a/src/store/side-panel/side-panel-reducer.ts b/src/store/side-panel/side-panel-reducer.ts
index a0da7db..2bbd6a1 100644
--- a/src/store/side-panel/side-panel-reducer.ts
+++ b/src/store/side-panel/side-panel-reducer.ts
@@ -3,17 +3,17 @@
// SPDX-License-Identifier: AGPL-3.0
import * as _ from "lodash";
-import actions, { SidePanelAction } from './side-panel-action';
+import { sidePanelActions, SidePanelAction } from './side-panel-action';
import { SidePanelItem } from '../../components/side-panel/side-panel';
import { ProjectsIcon, ShareMeIcon, WorkflowIcon, RecentIcon, FavoriteIcon, TrashIcon } from "../../components/icon/icon";
export type SidePanelState = SidePanelItem[];
-const sidePanelReducer = (state: SidePanelState = sidePanelData, action: SidePanelAction) => {
+export const sidePanelReducer = (state: SidePanelState = sidePanelData, action: SidePanelAction) => {
if (state.length === 0) {
return sidePanelData;
} else {
- return actions.match(action, {
+ return sidePanelActions.match(action, {
TOGGLE_SIDE_PANEL_ITEM_OPEN: itemId => state.map(it => itemId === it.id && it.open === false ? {...it, open: true} : {...it, open: false}),
TOGGLE_SIDE_PANEL_ITEM_ACTIVE: itemId => {
const sidePanel = _.cloneDeep(state);
@@ -91,5 +91,3 @@ function resetSidePanelActivity(sidePanel: SidePanelItem[]) {
t.active = false;
}
}
-
-export default sidePanelReducer;
diff --git a/src/store/store.ts b/src/store/store.ts
index d87c803..adb7ddd 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -7,13 +7,13 @@ import { routerMiddleware, routerReducer, RouterState } from "react-router-redux
import thunkMiddleware from 'redux-thunk';
import { History } from "history";
-import projectsReducer, { ProjectState } from "./project/project-reducer";
-import sidePanelReducer, { SidePanelState } from './side-panel/side-panel-reducer';
-import authReducer, { AuthState } from "./auth/auth-reducer";
-import dataExplorerReducer, { DataExplorerState } from './data-explorer/data-explorer-reducer';
+import { projectsReducer, ProjectState } from "./project/project-reducer";
+import { sidePanelReducer, SidePanelState } from './side-panel/side-panel-reducer';
+import { authReducer, AuthState } from "./auth/auth-reducer";
+import { dataExplorerReducer, DataExplorerState } from './data-explorer/data-explorer-reducer';
import { projectPanelMiddleware } from './project-panel/project-panel-middleware';
-import detailsPanelReducer, { DetailsPanelState } from './details-panel/details-panel-reducer';
-import contextMenuReducer, { ContextMenuState } from './context-menu/context-menu-reducer';
+import { detailsPanelReducer, DetailsPanelState } from './details-panel/details-panel-reducer';
+import { contextMenuReducer, ContextMenuState } from './context-menu/context-menu-reducer';
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
@@ -41,7 +41,7 @@ const rootReducer = combineReducers({
});
-export default function configureStore(history: History) {
+export function configureStore(history: History) {
const middlewares: Middleware[] = [
routerMiddleware(history),
thunkMiddleware,
diff --git a/src/utils/dialog-validator.tsx b/src/utils/dialog-validator.tsx
index 1d1a921..9697a86 100644
--- a/src/utils/dialog-validator.tsx
+++ b/src/utils/dialog-validator.tsx
@@ -5,68 +5,70 @@
import * as React from 'react';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
+type CssRules = "formInputError";
+
+const styles: StyleRulesCallback<CssRules> = theme => ({
+ formInputError: {
+ color: "#ff0000",
+ marginLeft: "5px",
+ fontSize: "11px",
+ }
+});
+
type ValidatorProps = {
- value: string,
- onChange: (isValid: boolean | string) => void;
- render: (hasError: boolean) => React.ReactElement<any>;
- isRequired: boolean;
+ value: string,
+ onChange: (isValid: boolean | string) => void;
+ render: (hasError: boolean) => React.ReactElement<any>;
+ isRequired: boolean;
};
interface ValidatorState {
- isPatternValid: boolean;
- isLengthValid: boolean;
+ isPatternValid: boolean;
+ isLengthValid: boolean;
}
const nameRegEx = /^[a-zA-Z0-9-_ ]+$/;
const maxInputLength = 60;
-class Validator extends React.Component<ValidatorProps & WithStyles<CssRules>> {
- state: ValidatorState = {
- isPatternValid: true,
- isLengthValid: true
- };
+export const Validator = withStyles(styles)(
+ class extends React.Component<ValidatorProps & WithStyles<CssRules>> {
+ state: ValidatorState = {
+ isPatternValid: true,
+ isLengthValid: true
+ };
- componentWillReceiveProps(nextProps: ValidatorProps) {
- const { value } = nextProps;
+ componentWillReceiveProps(nextProps: ValidatorProps) {
+ const { value } = nextProps;
- if (this.props.value !== value) {
- this.setState({
- isPatternValid: value.match(nameRegEx),
- isLengthValid: value.length < maxInputLength
- }, () => this.onChange());
- }
- }
+ if (this.props.value !== value) {
+ this.setState({
+ isPatternValid: value.match(nameRegEx),
+ isLengthValid: value.length < maxInputLength
+ }, () => this.onChange());
+ }
+ }
- onChange() {
- const { value, onChange, isRequired } = this.props;
- const { isPatternValid, isLengthValid } = this.state;
- const isValid = value && isPatternValid && isLengthValid && (isRequired || (!isRequired && value.length > 0));
+ onChange() {
+ const { value, onChange, isRequired } = this.props;
+ const { isPatternValid, isLengthValid } = this.state;
+ const isValid = value && isPatternValid && isLengthValid && (isRequired || (!isRequired && value.length > 0));
- onChange(isValid);
- }
+ onChange(isValid);
+ }
- render() {
- const { classes, isRequired, value } = this.props;
- const { isPatternValid, isLengthValid } = this.state;
+ render() {
+ const { classes, isRequired, value } = this.props;
+ const { isPatternValid, isLengthValid } = this.state;
- return (
- <span>
- {this.props.render(!(isPatternValid && isLengthValid) && (isRequired || (!isRequired && value.length > 0)))}
- {!isPatternValid && (isRequired || (!isRequired && value.length > 0)) ? <span className={classes.formInputError}>This field allow only alphanumeric characters, dashes, spaces and underscores.<br /></span> : null}
- {!isLengthValid ? <span className={classes.formInputError}>This field should have max 60 characters.</span> : null}
- </span>
- );
- }
-}
-
-type CssRules = "formInputError";
-
-const styles: StyleRulesCallback<CssRules> = theme => ({
- formInputError: {
- color: "#ff0000",
- marginLeft: "5px",
- fontSize: "11px",
- }
-});
-
-export default withStyles(styles)(Validator);
\ No newline at end of file
+ return (
+ <span>
+ {this.props.render(!(isPatternValid && isLengthValid) && (isRequired || (!isRequired && value.length > 0)))}
+ {!isPatternValid && (isRequired || (!isRequired && value.length > 0)) ?
+ <span className={classes.formInputError}>This field allow only alphanumeric characters, dashes, spaces and underscores.<br/></span> : null}
+ {!isLengthValid ?
+ <span className={classes.formInputError}>This field should have max 60 characters.</span> : null}
+ </span>
+ );
+ }
+ }
+);
diff --git a/src/views-components/api-token/api-token.tsx b/src/views-components/api-token/api-token.tsx
index e4ba491..1d017cc 100644
--- a/src/views-components/api-token/api-token.tsx
+++ b/src/views-components/api-token/api-token.tsx
@@ -5,33 +5,27 @@
import { Redirect, RouteProps } from "react-router";
import * as React from "react";
import { connect, DispatchProp } from "react-redux";
-import authActions, { getUserDetails } from "../../store/auth/auth-action";
+import { authActions, getUserDetails } from "../../store/auth/auth-action";
import { authService } from "../../services/services";
import { getProjectList } from "../../store/project/project-action";
+import { getUrlParameter } from "../../common/url";
interface ApiTokenProps {
}
-class ApiToken extends React.Component<ApiTokenProps & RouteProps & DispatchProp<any>, {}> {
- static getUrlParameter(search: string, name: string) {
- const safeName = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
- const regex = new RegExp('[\\?&]' + safeName + '=([^&#]*)');
- const results = regex.exec(search);
- return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
+export const ApiToken = connect()(
+ class extends React.Component<ApiTokenProps & RouteProps & DispatchProp<any>, {}> {
+ componentDidMount() {
+ const search = this.props.location ? this.props.location.search : "";
+ const apiToken = getUrlParameter(search, 'api_token');
+ this.props.dispatch(authActions.SAVE_API_TOKEN(apiToken));
+ this.props.dispatch<any>(getUserDetails()).then(() => {
+ const rootUuid = authService.getRootUuid();
+ this.props.dispatch(getProjectList(rootUuid));
+ });
+ }
+ render() {
+ return <Redirect to="/"/>;
+ }
}
-
- componentDidMount() {
- const search = this.props.location ? this.props.location.search : "";
- const apiToken = ApiToken.getUrlParameter(search, 'api_token');
- this.props.dispatch(authActions.SAVE_API_TOKEN(apiToken));
- this.props.dispatch<any>(getUserDetails()).then(() => {
- const rootUuid = authService.getRootUuid();
- this.props.dispatch(getProjectList(rootUuid));
- });
- }
- render() {
- return <Redirect to="/"/>;
- }
-}
-
-export default connect()(ApiToken);
+);
diff --git a/src/views-components/context-menu/action-sets/project-action-set.ts b/src/views-components/context-menu/action-sets/project-action-set.ts
index 61ff9fe..66dbd4d 100644
--- a/src/views-components/context-menu/action-sets/project-action-set.ts
+++ b/src/views-components/context-menu/action-sets/project-action-set.ts
@@ -3,17 +3,17 @@
// SPDX-License-Identifier: AGPL-3.0
import { ContextMenuActionSet } from "../context-menu-action-set";
-import actions from "../../../store/project/project-action";
+import { projectActions } from "../../../store/project/project-action";
import { ShareIcon, NewProjectIcon } from "../../../components/icon/icon";
export const projectActionSet: ContextMenuActionSet = [[{
icon: NewProjectIcon,
name: "New project",
execute: (dispatch, resource) => {
- dispatch(actions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
+ dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
}
}, {
icon: ShareIcon,
name: "Share",
execute: () => { return; }
-}]];
\ No newline at end of file
+}]];
diff --git a/src/views-components/context-menu/action-sets/root-project-action-set.ts b/src/views-components/context-menu/action-sets/root-project-action-set.ts
index afb428a..139bd26 100644
--- a/src/views-components/context-menu/action-sets/root-project-action-set.ts
+++ b/src/views-components/context-menu/action-sets/root-project-action-set.ts
@@ -3,13 +3,13 @@
// SPDX-License-Identifier: AGPL-3.0
import { ContextMenuActionSet } from "../context-menu-action-set";
-import actions from "../../../store/project/project-action";
+import { projectActions } from "../../../store/project/project-action";
import { NewProjectIcon } from "../../../components/icon/icon";
export const rootProjectActionSet: ContextMenuActionSet = [[{
icon: NewProjectIcon,
name: "New project",
execute: (dispatch, resource) => {
- dispatch(actions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
+ dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
}
-}]];
\ No newline at end of file
+}]];
diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx
index 2f837d5..da62753 100644
--- a/src/views-components/context-menu/context-menu.tsx
+++ b/src/views-components/context-menu/context-menu.tsx
@@ -4,8 +4,8 @@
import { connect } from "react-redux";
import { RootState } from "../../store/store";
-import actions from "../../store/context-menu/context-menu-actions";
-import ContextMenu, { ContextMenuProps, ContextMenuItem } from "../../components/context-menu/context-menu";
+import { contextMenuActions } from "../../store/context-menu/context-menu-actions";
+import { ContextMenu, ContextMenuProps, ContextMenuItem } from "../../components/context-menu/context-menu";
import { createAnchorAt } from "../../components/popover/helpers";
import { ContextMenuResource } from "../../store/context-menu/context-menu-reducer";
import { ContextMenuActionSet, ContextMenuAction } from "./context-menu-action-set";
@@ -24,10 +24,10 @@ const mapStateToProps = (state: RootState): DataProps => {
type ActionProps = Pick<ContextMenuProps, "onClose"> & { onItemClick: (item: ContextMenuItem, resource?: ContextMenuResource) => void };
const mapDispatchToProps = (dispatch: Dispatch): ActionProps => ({
onClose: () => {
- dispatch(actions.CLOSE_CONTEXT_MENU());
+ dispatch(contextMenuActions.CLOSE_CONTEXT_MENU());
},
onItemClick: (action: ContextMenuAction, resource?: ContextMenuResource) => {
- dispatch(actions.CLOSE_CONTEXT_MENU());
+ dispatch(contextMenuActions.CLOSE_CONTEXT_MENU());
if (resource) {
action.execute(dispatch, resource);
}
@@ -54,3 +54,7 @@ const getMenuActionSet = (resource?: ContextMenuResource): ContextMenuActionSet
return resource ? menuActionSets.get(resource.kind) || [] : [];
};
+export enum ContextMenuKind {
+ RootProject = "RootProject",
+ Project = "Project"
+}
diff --git a/src/views-components/context-menu/index.ts b/src/views-components/context-menu/index.ts
deleted file mode 100644
index 6059e8f..0000000
--- a/src/views-components/context-menu/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { ContextMenuHOC, addMenuActionSet } from "./context-menu";
-import { projectActionSet } from "./action-sets/project-action-set";
-import { rootProjectActionSet } from "./action-sets/root-project-action-set";
-
-export default ContextMenuHOC;
-
-export enum ContextMenuKind {
- RootProject = "RootProject",
- Project = "Project"
-}
-
-addMenuActionSet(ContextMenuKind.RootProject, rootProjectActionSet);
-addMenuActionSet(ContextMenuKind.Project, projectActionSet);
\ No newline at end of file
diff --git a/src/views-components/create-project-dialog/create-project-dialog.tsx b/src/views-components/create-project-dialog/create-project-dialog.tsx
index 6b69b79..2f3e0b7 100644
--- a/src/views-components/create-project-dialog/create-project-dialog.tsx
+++ b/src/views-components/create-project-dialog/create-project-dialog.tsx
@@ -5,9 +5,9 @@
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { RootState } from "../../store/store";
-import DialogProjectCreate from "../dialog-create/dialog-project-create";
-import actions, { createProject, getProjectList } from "../../store/project/project-action";
-import dataExplorerActions from "../../store/data-explorer/data-explorer-action";
+import { DialogProjectCreate as DialogProjectCreateComponent } from "../dialog-create/dialog-project-create";
+import { projectActions, createProject, getProjectList } from "../../store/project/project-action";
+import { dataExplorerActions } from "../../store/data-explorer/data-explorer-action";
import { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel";
const mapStateToProps = (state: RootState) => ({
@@ -25,11 +25,11 @@ const submit = (data: { name: string, description: string }) =>
const mapDispatchToProps = (dispatch: Dispatch) => ({
handleClose: () => {
- dispatch(actions.CLOSE_PROJECT_CREATOR());
+ dispatch(projectActions.CLOSE_PROJECT_CREATOR());
},
onSubmit: (data: { name: string, description: string }) => {
dispatch<any>(submit(data));
}
});
-export default connect(mapStateToProps, mapDispatchToProps)(DialogProjectCreate);
+export const CreateProjectDialog = connect(mapStateToProps, mapDispatchToProps)(DialogProjectCreateComponent);
diff --git a/src/views-components/data-explorer/data-explorer.tsx b/src/views-components/data-explorer/data-explorer.tsx
index b0e189f..2645504 100644
--- a/src/views-components/data-explorer/data-explorer.tsx
+++ b/src/views-components/data-explorer/data-explorer.tsx
@@ -4,10 +4,10 @@
import { connect } from "react-redux";
import { RootState } from "../../store/store";
-import DataExplorer from "../../components/data-explorer/data-explorer";
+import { DataExplorer as DataExplorerComponent } from "../../components/data-explorer/data-explorer";
import { getDataExplorer } from "../../store/data-explorer/data-explorer-reducer";
import { Dispatch } from "redux";
-import actions from "../../store/data-explorer/data-explorer-action";
+import { dataExplorerActions } from "../../store/data-explorer/data-explorer-action";
import { DataColumn } from "../../components/data-table/data-column";
import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
@@ -24,35 +24,35 @@ const mapStateToProps = (state: RootState, { id }: Props) =>
const mapDispatchToProps = (dispatch: Dispatch, { id, onRowClick, onRowDoubleClick, onContextMenu }: Props) => ({
onSearch: (searchValue: string) => {
- dispatch(actions.SET_SEARCH_VALUE({ id, searchValue }));
+ dispatch(dataExplorerActions.SET_SEARCH_VALUE({ id, searchValue }));
},
onColumnToggle: (column: DataColumn<any>) => {
- dispatch(actions.TOGGLE_COLUMN({ id, columnName: column.name }));
+ dispatch(dataExplorerActions.TOGGLE_COLUMN({ id, columnName: column.name }));
},
onSortToggle: (column: DataColumn<any>) => {
- dispatch(actions.TOGGLE_SORT({ id, columnName: column.name }));
+ dispatch(dataExplorerActions.TOGGLE_SORT({ id, columnName: column.name }));
},
onFiltersChange: (filters: DataTableFilterItem[], column: DataColumn<any>) => {
- dispatch(actions.SET_FILTERS({ id, columnName: column.name, filters }));
+ dispatch(dataExplorerActions.SET_FILTERS({ id, columnName: column.name, filters }));
},
onChangePage: (page: number) => {
- dispatch(actions.SET_PAGE({ id, page }));
+ dispatch(dataExplorerActions.SET_PAGE({ id, page }));
},
onChangeRowsPerPage: (rowsPerPage: number) => {
- dispatch(actions.SET_ROWS_PER_PAGE({ id, rowsPerPage }));
+ dispatch(dataExplorerActions.SET_ROWS_PER_PAGE({ id, rowsPerPage }));
},
onRowClick,
onRowDoubleClick,
-
+
onContextMenu,
});
-export default connect(mapStateToProps, mapDispatchToProps)(DataExplorer);
+export const DataExplorer = connect(mapStateToProps, mapDispatchToProps)(DataExplorerComponent);
diff --git a/src/views-components/details-panel/details-panel.tsx b/src/views-components/details-panel/details-panel.tsx
index 148a048..b61a1e4 100644
--- a/src/views-components/details-panel/details-panel.tsx
+++ b/src/views-components/details-panel/details-panel.tsx
@@ -9,80 +9,16 @@ import { ArvadosTheme } from '../../common/custom-theme';
import * as classnames from "classnames";
import { connect } from 'react-redux';
import { RootState } from '../../store/store';
-import actions from "../../store/details-panel/details-panel-action";
+import { detailsPanelActions } from "../../store/details-panel/details-panel-action";
import { ProjectResource } from '../../models/project';
import { CollectionResource } from '../../models/collection';
import { CloseIcon } from '../../components/icon/icon';
import { ProcessResource } from '../../models/process';
-import DetailsPanelFactory from '../../components/details-panel-factory/details-panel-factory';
-import AbstractItem from '../../components/details-panel-factory/items/abstract-item';
+import { DetailsPanelFactory } from '../../components/details-panel-factory/details-panel-factory';
+import { AbstractItem } from '../../components/details-panel-factory/items/abstract-item';
import { EmptyResource } from '../../models/empty';
import { Dispatch } from "redux";
-export interface DetailsPanelDataProps {
- onCloseDrawer: () => void;
- isOpened: boolean;
- item: AbstractItem;
-}
-
-type DetailsPanelProps = DetailsPanelDataProps & WithStyles<CssRules>;
-
-class DetailsPanel extends React.Component<DetailsPanelProps, {}> {
- state = {
- tabsValue: 0
- };
-
- handleChange = (event: any, value: boolean) => {
- this.setState({ tabsValue: value });
- }
-
- renderTabContainer = (children: React.ReactElement<any>) =>
- <Typography className={this.props.classes.tabContainer} component="div">
- {children}
- </Typography>
-
- render() {
- const { classes, onCloseDrawer, isOpened, item } = this.props;
- const { tabsValue } = this.state;
- return (
- <Typography component="div" className={classnames([classes.container, { [classes.opened]: isOpened }])}>
- <Drawer variant="permanent" anchor="right" classes={{ paper: classes.drawerPaper }}>
- <Typography component="div" className={classes.headerContainer}>
- <Grid container alignItems='center' justify='space-around'>
- <Grid item xs={2}>
- {item.getIcon(classes.headerIcon)}
- </Grid>
- <Grid item xs={8}>
- <Typography variant="title">
- {item.getTitle()}
- </Typography>
- </Grid>
- <Grid item>
- <IconButton color="inherit" onClick={onCloseDrawer}>
- {<CloseIcon />}
- </IconButton>
- </Grid>
- </Grid>
- </Typography>
- <Tabs value={tabsValue} onChange={this.handleChange}>
- <Tab disableRipple label="Details" />
- <Tab disableRipple label="Activity" disabled />
- </Tabs>
- {tabsValue === 0 && this.renderTabContainer(
- <Grid container direction="column">
- {item.buildDetails()}
- </Grid>
- )}
- {tabsValue === 1 && this.renderTabContainer(
- <Grid container direction="column" />
- )}
- </Drawer>
- </Typography>
- );
- }
-
-}
-
type CssRules = 'drawerPaper' | 'container' | 'opened' | 'headerContainer' | 'headerIcon' | 'tabContainer';
const drawerWidth = 320;
@@ -135,10 +71,74 @@ const mapStateToProps = ({ detailsPanel }: RootState) => {
const mapDispatchToProps = (dispatch: Dispatch) => ({
onCloseDrawer: () => {
- dispatch(actions.TOGGLE_DETAILS_PANEL());
+ dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
}
});
-const DetailsPanelContainer = connect(mapStateToProps, mapDispatchToProps)(DetailsPanel);
+export interface DetailsPanelDataProps {
+ onCloseDrawer: () => void;
+ isOpened: boolean;
+ item: AbstractItem;
+}
+
+type DetailsPanelProps = DetailsPanelDataProps & WithStyles<CssRules>;
-export default withStyles(styles)(DetailsPanelContainer);
+export const DetailsPanel = withStyles(styles)(
+ connect(mapStateToProps, mapDispatchToProps)(
+ class extends React.Component<DetailsPanelProps, {}> {
+ state = {
+ tabsValue: 0
+ };
+
+ handleChange = (event: any, value: boolean) => {
+ this.setState({ tabsValue: value });
+ }
+
+ renderTabContainer = (children: React.ReactElement<any>) =>
+ <Typography className={this.props.classes.tabContainer} component="div">
+ {children}
+ </Typography>
+
+ render() {
+ const { classes, onCloseDrawer, isOpened, item } = this.props;
+ const { tabsValue } = this.state;
+ return (
+ <Typography component="div"
+ className={classnames([classes.container, { [classes.opened]: isOpened }])}>
+ <Drawer variant="permanent" anchor="right" classes={{ paper: classes.drawerPaper }}>
+ <Typography component="div" className={classes.headerContainer}>
+ <Grid container alignItems='center' justify='space-around'>
+ <Grid item xs={2}>
+ {item.getIcon(classes.headerIcon)}
+ </Grid>
+ <Grid item xs={8}>
+ <Typography variant="title">
+ {item.getTitle()}
+ </Typography>
+ </Grid>
+ <Grid item>
+ <IconButton color="inherit" onClick={onCloseDrawer}>
+ {<CloseIcon/>}
+ </IconButton>
+ </Grid>
+ </Grid>
+ </Typography>
+ <Tabs value={tabsValue} onChange={this.handleChange}>
+ <Tab disableRipple label="Details"/>
+ <Tab disableRipple label="Activity" disabled/>
+ </Tabs>
+ {tabsValue === 0 && this.renderTabContainer(
+ <Grid container direction="column">
+ {item.buildDetails()}
+ </Grid>
+ )}
+ {tabsValue === 1 && this.renderTabContainer(
+ <Grid container direction="column"/>
+ )}
+ </Drawer>
+ </Typography>
+ );
+ }
+ }
+ )
+);
diff --git a/src/views-components/dialog-create/dialog-project-create.tsx b/src/views-components/dialog-create/dialog-project-create.tsx
index 89deea6..aefb815 100644
--- a/src/views-components/dialog-create/dialog-project-create.tsx
+++ b/src/views-components/dialog-create/dialog-project-create.tsx
@@ -10,131 +10,133 @@ import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
-import Validator from '../../utils/dialog-validator';
+import { Validator } from '../../utils/dialog-validator';
+
+type CssRules = "button" | "lastButton" | "dialogContent" | "textField" | "dialog" | "dialogTitle";
+
+const styles: StyleRulesCallback<CssRules> = theme => ({
+ button: {
+ marginLeft: theme.spacing.unit
+ },
+ lastButton: {
+ marginLeft: theme.spacing.unit,
+ marginRight: "20px",
+ },
+ dialogContent: {
+ marginTop: "20px",
+ },
+ dialogTitle: {
+ paddingBottom: "0"
+ },
+ textField: {
+ marginTop: "32px",
+ },
+ dialog: {
+ minWidth: "600px",
+ minHeight: "320px"
+ }
+});
interface ProjectCreateProps {
- open: boolean;
- handleClose: () => void;
- onSubmit: (data: { name: string, description: string }) => void;
+ open: boolean;
+ handleClose: () => void;
+ onSubmit: (data: { name: string, description: string }) => void;
}
interface DialogState {
- name: string;
- description: string;
- isNameValid: boolean;
- isDescriptionValid: boolean;
+ name: string;
+ description: string;
+ isNameValid: boolean;
+ isDescriptionValid: boolean;
}
-class DialogProjectCreate extends React.Component<ProjectCreateProps & WithStyles<CssRules>> {
- state: DialogState = {
- name: '',
- description: '',
- isNameValid: false,
- isDescriptionValid: true
- };
-
- render() {
- const { name, description } = this.state;
- const { classes, open, handleClose } = this.props;
+export const DialogProjectCreate = withStyles(styles)(
+ class extends React.Component<ProjectCreateProps & WithStyles<CssRules>> {
+ state: DialogState = {
+ name: '',
+ description: '',
+ isNameValid: false,
+ isDescriptionValid: true
+ };
- return (
- <Dialog
- open={open}
- onClose={handleClose}>
- <div className={classes.dialog}>
- <DialogTitle id="form-dialog-title" className={classes.dialogTitle}>Create a project</DialogTitle>
- <DialogContent className={classes.dialogContent}>
- <Validator
- value={name}
- onChange={e => this.isNameValid(e)}
- isRequired={true}
- render={hasError =>
- <TextField
- margin="dense"
- className={classes.textField}
- id="name"
- onChange={e => this.handleProjectName(e)}
- label="Project name"
- error={hasError}
- fullWidth />} />
- <Validator
- value={description}
- onChange={e => this.isDescriptionValid(e)}
- isRequired={false}
- render={hasError =>
- <TextField
- margin="dense"
- className={classes.textField}
- id="description"
- onChange={e => this.handleDescriptionValue(e)}
- label="Description - optional"
- error={hasError}
- fullWidth />} />
- </DialogContent>
- <DialogActions>
- <Button onClick={handleClose} className={classes.button} color="primary">CANCEL</Button>
- <Button onClick={this.handleSubmit} className={classes.lastButton} color="primary" disabled={!this.state.isNameValid || (!this.state.isDescriptionValid && description.length > 0)} variant="raised">CREATE A PROJECT</Button>
- </DialogActions>
- </div>
- </Dialog>
- );
- }
+ render() {
+ const { name, description } = this.state;
+ const { classes, open, handleClose } = this.props;
- handleSubmit = () => {
- this.props.onSubmit({
- name: this.state.name,
- description: this.state.description
- });
- }
+ return (
+ <Dialog
+ open={open}
+ onClose={handleClose}>
+ <div className={classes.dialog}>
+ <DialogTitle id="form-dialog-title" className={classes.dialogTitle}>Create a project</DialogTitle>
+ <DialogContent className={classes.dialogContent}>
+ <Validator
+ value={name}
+ onChange={e => this.isNameValid(e)}
+ isRequired={true}
+ render={hasError =>
+ <TextField
+ margin="dense"
+ className={classes.textField}
+ id="name"
+ onChange={e => this.handleProjectName(e)}
+ label="Project name"
+ error={hasError}
+ fullWidth/>}/>
+ <Validator
+ value={description}
+ onChange={e => this.isDescriptionValid(e)}
+ isRequired={false}
+ render={hasError =>
+ <TextField
+ margin="dense"
+ className={classes.textField}
+ id="description"
+ onChange={e => this.handleDescriptionValue(e)}
+ label="Description - optional"
+ error={hasError}
+ fullWidth/>}/>
+ </DialogContent>
+ <DialogActions>
+ <Button onClick={handleClose} className={classes.button} color="primary">CANCEL</Button>
+ <Button onClick={this.handleSubmit} className={classes.lastButton} color="primary"
+ disabled={!this.state.isNameValid || (!this.state.isDescriptionValid && description.length > 0)}
+ variant="raised">CREATE A PROJECT</Button>
+ </DialogActions>
+ </div>
+ </Dialog>
+ );
+ }
- handleProjectName(e: React.ChangeEvent<HTMLInputElement>) {
- this.setState({
- name: e.target.value,
- });
- }
+ handleSubmit = () => {
+ this.props.onSubmit({
+ name: this.state.name,
+ description: this.state.description
+ });
+ }
- handleDescriptionValue(e: React.ChangeEvent<HTMLInputElement>) {
- this.setState({
- description: e.target.value,
- });
- }
+ handleProjectName(e: React.ChangeEvent<HTMLInputElement>) {
+ this.setState({
+ name: e.target.value,
+ });
+ }
- isNameValid(value: boolean | string) {
- this.setState({
- isNameValid: value,
- });
- }
+ handleDescriptionValue(e: React.ChangeEvent<HTMLInputElement>) {
+ this.setState({
+ description: e.target.value,
+ });
+ }
- isDescriptionValid(value: boolean | string) {
- this.setState({
- isDescriptionValid: value,
- });
- }
-}
-
-type CssRules = "button" | "lastButton" | "dialogContent" | "textField" | "dialog" | "dialogTitle";
-
-const styles: StyleRulesCallback<CssRules> = theme => ({
- button: {
- marginLeft: theme.spacing.unit
- },
- lastButton: {
- marginLeft: theme.spacing.unit,
- marginRight: "20px",
- },
- dialogContent: {
- marginTop: "20px",
- },
- dialogTitle: {
- paddingBottom: "0"
- },
- textField: {
- marginTop: "32px",
- },
- dialog: {
- minWidth: "600px",
- minHeight: "320px"
- }
-});
+ isNameValid(value: boolean | string) {
+ this.setState({
+ isNameValid: value,
+ });
+ }
-export default withStyles(styles)(DialogProjectCreate);
\ No newline at end of file
+ isDescriptionValid(value: boolean | string) {
+ this.setState({
+ isDescriptionValid: value,
+ });
+ }
+ }
+);
diff --git a/src/views-components/main-app-bar/main-app-bar.test.tsx b/src/views-components/main-app-bar/main-app-bar.test.tsx
index ac744b9..6d5c9de 100644
--- a/src/views-components/main-app-bar/main-app-bar.test.tsx
+++ b/src/views-components/main-app-bar/main-app-bar.test.tsx
@@ -5,10 +5,10 @@
import * as React from "react";
import { mount, configure, ReactWrapper } from "enzyme";
import * as Adapter from "enzyme-adapter-react-16";
-import MainAppBar from "./main-app-bar";
-import SearchBar from "../../components/search-bar/search-bar";
-import Breadcrumbs from "../../components/breadcrumbs/breadcrumbs";
-import DropdownMenu from "../../components/dropdown-menu/dropdown-menu";
+import { MainAppBar } from "./main-app-bar";
+import { SearchBar } from "../../components/search-bar/search-bar";
+import { Breadcrumbs } from "../../components/breadcrumbs/breadcrumbs";
+import { DropdownMenu } from "../../components/dropdown-menu/dropdown-menu";
import { Button, MenuItem, IconButton } from "@material-ui/core";
import { User } from "../../models/user";
diff --git a/src/views-components/main-app-bar/main-app-bar.tsx b/src/views-components/main-app-bar/main-app-bar.tsx
index c44448a..9c03108 100644
--- a/src/views-components/main-app-bar/main-app-bar.tsx
+++ b/src/views-components/main-app-bar/main-app-bar.tsx
@@ -5,9 +5,9 @@
import * as React from "react";
import { AppBar, Toolbar, Typography, Grid, IconButton, Badge, Button, MenuItem } from "@material-ui/core";
import { User, getUserFullname } from "../../models/user";
-import SearchBar from "../../components/search-bar/search-bar";
-import Breadcrumbs, { Breadcrumb } from "../../components/breadcrumbs/breadcrumbs";
-import DropdownMenu from "../../components/dropdown-menu/dropdown-menu";
+import { SearchBar } from "../../components/search-bar/search-bar";
+import { Breadcrumbs, Breadcrumb } from "../../components/breadcrumbs/breadcrumbs";
+import { DropdownMenu } from "../../components/dropdown-menu/dropdown-menu";
import { DetailsIcon, NotificationIcon, UserPanelIcon, HelpIcon } from "../../components/icon/icon";
export interface MainAppBarMenuItem {
@@ -81,7 +81,6 @@ export const MainAppBar: React.SFC<MainAppBarProps> = (props) => {
</AppBar>;
};
-
const renderMenuForUser = ({ user, menuItems, onMenuItemClick }: MainAppBarProps) => {
return (
<>
@@ -118,5 +117,3 @@ const renderMenuItems = (menuItems: MainAppBarMenuItem[], onMenuItemClick: (menu
</MenuItem>
));
};
-
-export default MainAppBar;
diff --git a/src/views-components/project-tree/project-tree.test.tsx b/src/views-components/project-tree/project-tree.test.tsx
index d53f8a9..56566da 100644
--- a/src/views-components/project-tree/project-tree.test.tsx
+++ b/src/views-components/project-tree/project-tree.test.tsx
@@ -10,7 +10,7 @@ import ListItemIcon from '@material-ui/core/ListItemIcon';
import { Collapse } from '@material-ui/core';
import CircularProgress from '@material-ui/core/CircularProgress';
-import ProjectTree from './project-tree';
+import { ProjectTree } from './project-tree';
import { TreeItem } from '../../components/tree/tree';
import { ProjectResource } from '../../models/project';
import { mockProjectResource } from '../../models/test-utils';
@@ -81,9 +81,9 @@ describe("ProjectTree component", () => {
]
}
];
- const wrapper = mount(<ProjectTree
- projects={project}
- toggleOpen={jest.fn()}
+ const wrapper = mount(<ProjectTree
+ projects={project}
+ toggleOpen={jest.fn()}
toggleActive={jest.fn()}
onContextMenu={jest.fn()} />);
@@ -98,9 +98,9 @@ describe("ProjectTree component", () => {
active: true,
status: 1
};
- const wrapper = mount(<ProjectTree
- projects={[project]}
- toggleOpen={jest.fn()}
+ const wrapper = mount(<ProjectTree
+ projects={[project]}
+ toggleOpen={jest.fn()}
toggleActive={jest.fn()}
onContextMenu={jest.fn()} />);
diff --git a/src/views-components/project-tree/project-tree.tsx b/src/views-components/project-tree/project-tree.tsx
index 3a84471..c9d4c3e 100644
--- a/src/views-components/project-tree/project-tree.tsx
+++ b/src/views-components/project-tree/project-tree.tsx
@@ -5,36 +5,11 @@
import * as React from 'react';
import { ReactElement } from 'react';
import { StyleRulesCallback, Theme, WithStyles, withStyles } from '@material-ui/core/styles';
-import Tree, { TreeItem, TreeItemStatus } from '../../components/tree/tree';
+import { Tree, TreeItem, TreeItemStatus } from '../../components/tree/tree';
import { ProjectResource } from '../../models/project';
import { ProjectIcon } from '../../components/icon/icon';
import { ArvadosTheme } from '../../common/custom-theme';
-import ListItemTextIcon from '../../components/list-item-text-icon/list-item-text-icon';
-
-export interface ProjectTreeProps {
- projects: Array<TreeItem<ProjectResource>>;
- toggleOpen: (id: string, status: TreeItemStatus) => void;
- toggleActive: (id: string, status: TreeItemStatus) => void;
- onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<ProjectResource>) => void;
-}
-
-class ProjectTree<T> extends React.Component<ProjectTreeProps & WithStyles<CssRules>> {
- render(): ReactElement<any> {
- const { classes, projects, toggleOpen, toggleActive, onContextMenu } = this.props;
- return (
- <div className={classes.root}>
- <Tree items={projects}
- onContextMenu={onContextMenu}
- toggleItemOpen={toggleOpen}
- toggleItemActive={toggleActive}
- render={
- (project: TreeItem<ProjectResource>) =>
- <ListItemTextIcon icon={ProjectIcon} name={project.data.name} isActive={project.active} hasMargin={true} />
- }/>
- </div>
- );
- }
-}
+import { ListItemTextIcon } from '../../components/list-item-text-icon/list-item-text-icon';
type CssRules = 'root';
@@ -44,4 +19,33 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
}
});
-export default withStyles(styles)(ProjectTree);
+export interface ProjectTreeProps {
+ projects: Array<TreeItem<ProjectResource>>;
+ toggleOpen: (id: string, status: TreeItemStatus) => void;
+ toggleActive: (id: string, status: TreeItemStatus) => void;
+ onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<ProjectResource>) => void;
+}
+
+export const ProjectTree = withStyles(styles)(
+ class ProjectTreeGeneric<T> extends React.Component<ProjectTreeProps & WithStyles<CssRules>> {
+ render(): ReactElement<any> {
+ const { classes, projects, toggleOpen, toggleActive, onContextMenu } = this.props;
+ return (
+ <div className={classes.root}>
+ <Tree items={projects}
+ onContextMenu={onContextMenu}
+ toggleItemOpen={toggleOpen}
+ toggleItemActive={toggleActive}
+ render={
+ (project: TreeItem<ProjectResource>) =>
+ <ListItemTextIcon
+ icon={ProjectIcon}
+ name={project.data.name}
+ isActive={project.active}
+ hasMargin={true}/>
+ }/>
+ </div>
+ );
+ }
+ }
+);
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 2fdb715..312a248 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -6,7 +6,7 @@ import * as React from 'react';
import { ProjectPanelItem } from './project-panel-item';
import { Grid, Typography, Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
import { formatDate, formatFileSize } from '../../common/formatters';
-import DataExplorer from "../../views-components/data-explorer/data-explorer";
+import { DataExplorer } from "../../views-components/data-explorer/data-explorer";
import { DispatchProp, connect } from 'react-redux';
import { DataColumns } from '../../components/data-table/data-table';
import { RouteComponentProps } from 'react-router';
@@ -17,58 +17,6 @@ import { SortDirection } from '../../components/data-table/data-column';
import { ResourceKind } from '../../models/resource';
import { resourceLabel } from '../../common/labels';
-export const PROJECT_PANEL_ID = "projectPanel";
-
-export interface ProjectPanelFilter extends DataTableFilterItem {
- type: ResourceKind | ContainerRequestState;
-}
-
-type ProjectPanelProps = {
- currentItemId: string,
- onItemClick: (item: ProjectPanelItem) => void,
- onContextMenu: (event: React.MouseEvent<HTMLElement>, item: ProjectPanelItem) => void;
- onDialogOpen: (ownerUuid: string) => void;
- onItemDoubleClick: (item: ProjectPanelItem) => void,
- onItemRouteChange: (itemId: string) => void
-}
- & DispatchProp
- & WithStyles<CssRules>
- & RouteComponentProps<{ id: string }>;
-
-class ProjectPanel extends React.Component<ProjectPanelProps> {
- render() {
- const { classes, currentItemId, onItemClick, onItemDoubleClick, onContextMenu, onDialogOpen } = this.props;
- return <div>
- <div className={classes.toolbar}>
- <Button color="primary" variant="raised" className={classes.button}>
- Create a collection
- </Button>
- <Button color="primary" variant="raised" className={classes.button}>
- Run a process
- </Button>
- <Button color="primary" onClick={this.handleNewProjectClick} variant="raised" className={classes.button}>
- New project
- </Button>
- </div>
- <DataExplorer
- id={PROJECT_PANEL_ID}
- onRowClick={this.props.onItemClick}
- onRowDoubleClick={this.props.onItemDoubleClick}
- onContextMenu={this.props.onContextMenu}
- extractKey={(item: ProjectPanelItem) => item.uuid} />
- </div>;
- }
-
- handleNewProjectClick = () => {
- this.props.onDialogOpen(this.props.currentItemId);
- }
- componentWillReceiveProps({ match, currentItemId, onItemRouteChange }: ProjectPanelProps) {
- if (match.params.id !== currentItemId) {
- onItemRouteChange(match.params.id);
- }
- }
-}
-
type CssRules = "toolbar" | "button";
const styles: StyleRulesCallback<CssRules> = theme => ({
@@ -144,7 +92,10 @@ export enum ProjectPanelColumnNames {
Owner = "Owner",
FileSize = "File size",
LastModified = "Last modified"
+}
+export interface ProjectPanelFilter extends DataTableFilterItem {
+ type: ResourceKind | ContainerRequestState;
}
export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [{
@@ -207,7 +158,54 @@ export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [{
width: "150px"
}];
+export const PROJECT_PANEL_ID = "projectPanel";
+
+type ProjectPanelProps = {
+ currentItemId: string,
+ onItemClick: (item: ProjectPanelItem) => void,
+ onContextMenu: (event: React.MouseEvent<HTMLElement>, item: ProjectPanelItem) => void;
+ onDialogOpen: (ownerUuid: string) => void;
+ onItemDoubleClick: (item: ProjectPanelItem) => void,
+ onItemRouteChange: (itemId: string) => void
+}
+ & DispatchProp
+ & WithStyles<CssRules>
+ & RouteComponentProps<{ id: string }>;
-export default withStyles(styles)(
+export const ProjectPanel = withStyles(styles)(
connect((state: RootState) => ({ currentItemId: state.projects.currentItemId }))(
- ProjectPanel));
+ class extends React.Component<ProjectPanelProps> {
+ render() {
+ const { classes } = this.props;
+ return <div>
+ <div className={classes.toolbar}>
+ <Button color="primary" variant="raised" className={classes.button}>
+ Create a collection
+ </Button>
+ <Button color="primary" variant="raised" className={classes.button}>
+ Run a process
+ </Button>
+ <Button color="primary" onClick={this.handleNewProjectClick} variant="raised" className={classes.button}>
+ New project
+ </Button>
+ </div>
+ <DataExplorer
+ id={PROJECT_PANEL_ID}
+ onRowClick={this.props.onItemClick}
+ onRowDoubleClick={this.props.onItemDoubleClick}
+ onContextMenu={this.props.onContextMenu}
+ extractKey={(item: ProjectPanelItem) => item.uuid} />
+ </div>;
+ }
+
+ handleNewProjectClick = () => {
+ this.props.onDialogOpen(this.props.currentItemId);
+ }
+ componentWillReceiveProps({ match, currentItemId, onItemRouteChange }: ProjectPanelProps) {
+ if (match.params.id !== currentItemId) {
+ onItemRouteChange(match.params.id);
+ }
+ }
+ }
+ )
+);
diff --git a/src/views/workbench/workbench.test.tsx b/src/views/workbench/workbench.test.tsx
index 79a98ad..538b8e7 100644
--- a/src/views/workbench/workbench.test.tsx
+++ b/src/views/workbench/workbench.test.tsx
@@ -4,9 +4,9 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-import Workbench from '../../views/workbench/workbench';
+import { Workbench } from '../../views/workbench/workbench';
import { Provider } from "react-redux";
-import configureStore from "../../store/store";
+import { configureStore } from "../../store/store";
import createBrowserHistory from "history/createBrowserHistory";
import { ConnectedRouter } from "react-router-redux";
import { MuiThemeProvider } from '@material-ui/core/styles';
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index eaf3a2e..934fe53 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -7,211 +7,31 @@ import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/st
import Drawer from '@material-ui/core/Drawer';
import { connect, DispatchProp } from "react-redux";
import { Route, Switch, RouteComponentProps } from "react-router";
-import authActions from "../../store/auth/auth-action";
+import { authActions } from "../../store/auth/auth-action";
import { User } from "../../models/user";
import { RootState } from "../../store/store";
-import MainAppBar, {
- MainAppBarActionProps,
- MainAppBarMenuItem
-} from '../../views-components/main-app-bar/main-app-bar';
+import { MainAppBar, MainAppBarActionProps, MainAppBarMenuItem } from '../../views-components/main-app-bar/main-app-bar';
import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs';
import { push } from 'react-router-redux';
-import ProjectTree from '../../views-components/project-tree/project-tree';
+import { ProjectTree } from '../../views-components/project-tree/project-tree';
import { TreeItem } from "../../components/tree/tree";
import { getTreePath } from '../../store/project/project-reducer';
-import sidePanelActions from '../../store/side-panel/side-panel-action';
-import SidePanel, { SidePanelItem } from '../../components/side-panel/side-panel';
+import { sidePanelActions } from '../../store/side-panel/side-panel-action';
+import { SidePanel, SidePanelItem } from '../../components/side-panel/side-panel';
import { ItemMode, setProjectItem } from "../../store/navigation/navigation-action";
-import projectActions from "../../store/project/project-action";
-import ProjectPanel from "../project-panel/project-panel";
-import DetailsPanel from '../../views-components/details-panel/details-panel';
+import { projectActions } from "../../store/project/project-action";
+import { ProjectPanel } from "../project-panel/project-panel";
+import { DetailsPanel } from '../../views-components/details-panel/details-panel';
import { ArvadosTheme } from '../../common/custom-theme';
-import ContextMenu, { ContextMenuKind } from "../../views-components/context-menu";
-import CreateProjectDialog from "../../views-components/create-project-dialog/create-project-dialog";
+import { CreateProjectDialog } from "../../views-components/create-project-dialog/create-project-dialog";
import { authService } from '../../services/services';
-import detailsPanelActions, { loadDetails } from "../../store/details-panel/details-panel-action";
-import contextMenuActions from "../../store/context-menu/context-menu-actions";
+import { detailsPanelActions, loadDetails } from "../../store/details-panel/details-panel-action";
+import { contextMenuActions } from "../../store/context-menu/context-menu-actions";
import { SidePanelIdentifiers } from '../../store/side-panel/side-panel-reducer';
import { ProjectResource } from '../../models/project';
import { ResourceKind } from '../../models/resource';
-
-interface WorkbenchDataProps {
- projects: Array<TreeItem<ProjectResource>>;
- currentProjectId: string;
- user?: User;
- sidePanelItems: SidePanelItem[];
-}
-
-interface WorkbenchActionProps {
-}
-
-type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & DispatchProp & WithStyles<CssRules>;
-
-interface NavBreadcrumb extends Breadcrumb {
- itemId: string;
-}
-
-interface NavMenuItem extends MainAppBarMenuItem {
- action: () => void;
-}
-
-interface WorkbenchState {
- anchorEl: any;
- searchText: string;
- menuItems: {
- accountMenu: NavMenuItem[],
- helpMenu: NavMenuItem[],
- anonymousMenu: NavMenuItem[]
- };
-}
-
-
-class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
- state = {
- isCreationDialogOpen: false,
- anchorEl: null,
- searchText: "",
- breadcrumbs: [],
- menuItems: {
- accountMenu: [
- {
- label: "Logout",
- action: () => this.props.dispatch(authActions.LOGOUT())
- },
- {
- label: "My account",
- action: () => this.props.dispatch(push("/my-account"))
- }
- ],
- helpMenu: [
- {
- label: "Help",
- action: () => this.props.dispatch(push("/help"))
- }
- ],
- anonymousMenu: [
- {
- label: "Sign in",
- action: () => this.props.dispatch(authActions.LOGIN())
- }
- ]
- }
- };
-
- render() {
- const path = getTreePath(this.props.projects, this.props.currentProjectId);
- const breadcrumbs = path.map(item => ({
- label: item.data.name,
- itemId: item.data.uuid,
- status: item.status
- }));
-
- const { classes, user } = this.props;
- return (
- <div className={classes.root}>
- <div className={classes.appBar}>
- <MainAppBar
- breadcrumbs={breadcrumbs}
- searchText={this.state.searchText}
- user={this.props.user}
- menuItems={this.state.menuItems}
- {...this.mainAppBarActions} />
- </div>
- {user &&
- <Drawer
- variant="permanent"
- classes={{
- paper: classes.drawerPaper,
- }}>
- <div className={classes.toolbar} />
- <SidePanel
- toggleOpen={this.toggleSidePanelOpen}
- toggleActive={this.toggleSidePanelActive}
- sidePanelItems={this.props.sidePanelItems}
- onContextMenu={(event) => this.openContextMenu(event, authService.getUuid() || "", ContextMenuKind.RootProject)}>
- <ProjectTree
- projects={this.props.projects}
- toggleOpen={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.OPEN))}
- onContextMenu={(event, item) => this.openContextMenu(event, item.data.uuid, ContextMenuKind.Project)}
- toggleActive={itemId => {
- this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE));
- this.props.dispatch<any>(loadDetails(itemId, ResourceKind.Project));
- this.props.dispatch<any>(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.Projects));
- }} />
- </SidePanel>
- </Drawer>}
- <main className={classes.contentWrapper}>
- <div className={classes.content}>
- <Switch>
- <Route path="/projects/:id" render={this.renderProjectPanel} />
- </Switch>
- </div>
- { user && <DetailsPanel /> }
- </main>
- <ContextMenu />
- <CreateProjectDialog />
- </div>
- );
- }
-
- renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => <ProjectPanel
- onItemRouteChange={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE))}
- onContextMenu={(event, item) => this.openContextMenu(event, item.uuid, ContextMenuKind.Project)}
- onDialogOpen={this.handleCreationDialogOpen}
- onItemClick={item => {
- this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
- }}
- onItemDoubleClick={item => {
- this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE));
- this.props.dispatch<any>(loadDetails(item.uuid, ResourceKind.Project));
- }}
- {...props} />
-
- mainAppBarActions: MainAppBarActionProps = {
- onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
- this.props.dispatch<any>(setProjectItem(itemId, ItemMode.BOTH));
- this.props.dispatch<any>(loadDetails(itemId, ResourceKind.Project));
- },
- onSearch: searchText => {
- this.setState({ searchText });
- this.props.dispatch(push(`/search?q=${searchText}`));
- },
- onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action(),
- onDetailsPanelToggle: () => {
- this.props.dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
- },
- onContextMenu: (event: React.MouseEvent<HTMLElement>, breadcrumb: NavBreadcrumb) => {
- this.openContextMenu(event, breadcrumb.itemId, ContextMenuKind.Project);
- }
- };
-
- toggleSidePanelOpen = (itemId: string) => {
- this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId));
- }
-
- toggleSidePanelActive = (itemId: string) => {
- this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId));
- this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId));
- this.props.dispatch(push("/"));
- }
-
- handleCreationDialogOpen = (itemUuid: string) => {
- this.props.dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: itemUuid }));
- }
-
- openContextMenu = (event: React.MouseEvent<HTMLElement>, itemUuid: string, kind: ContextMenuKind) => {
- event.preventDefault();
- this.props.dispatch(
- contextMenuActions.OPEN_CONTEXT_MENU({
- position: { x: event.clientX, y: event.clientY },
- resource: { uuid: itemUuid, kind }
- })
- );
- }
-
-
-}
+import { ContextMenuHOC, ContextMenuKind } from "../../views-components/context-menu/context-menu";
const drawerWidth = 240;
const appBarHeight = 100;
@@ -254,13 +74,187 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
toolbar: theme.mixins.toolbar
});
-export default connect<WorkbenchDataProps>(
- (state: RootState) => ({
- projects: state.projects.items,
- currentProjectId: state.projects.currentItemId,
- user: state.auth.user,
- sidePanelItems: state.sidePanel
- })
-)(
- withStyles(styles)(Workbench)
+interface WorkbenchDataProps {
+ projects: Array<TreeItem<ProjectResource>>;
+ currentProjectId: string;
+ user?: User;
+ sidePanelItems: SidePanelItem[];
+}
+
+interface WorkbenchActionProps {
+}
+
+type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & DispatchProp & WithStyles<CssRules>;
+
+interface NavBreadcrumb extends Breadcrumb {
+ itemId: string;
+}
+
+interface NavMenuItem extends MainAppBarMenuItem {
+ action: () => void;
+}
+
+interface WorkbenchState {
+ anchorEl: any;
+ searchText: string;
+ menuItems: {
+ accountMenu: NavMenuItem[],
+ helpMenu: NavMenuItem[],
+ anonymousMenu: NavMenuItem[]
+ };
+}
+
+export const Workbench = withStyles(styles)(
+ connect<WorkbenchDataProps>(
+ (state: RootState) => ({
+ projects: state.projects.items,
+ currentProjectId: state.projects.currentItemId,
+ user: state.auth.user,
+ sidePanelItems: state.sidePanel
+ })
+ )(
+ class extends React.Component<WorkbenchProps, WorkbenchState> {
+ state = {
+ isCreationDialogOpen: false,
+ anchorEl: null,
+ searchText: "",
+ breadcrumbs: [],
+ menuItems: {
+ accountMenu: [
+ {
+ label: "Logout",
+ action: () => this.props.dispatch(authActions.LOGOUT())
+ },
+ {
+ label: "My account",
+ action: () => this.props.dispatch(push("/my-account"))
+ }
+ ],
+ helpMenu: [
+ {
+ label: "Help",
+ action: () => this.props.dispatch(push("/help"))
+ }
+ ],
+ anonymousMenu: [
+ {
+ label: "Sign in",
+ action: () => this.props.dispatch(authActions.LOGIN())
+ }
+ ]
+ }
+ };
+
+ render() {
+ const path = getTreePath(this.props.projects, this.props.currentProjectId);
+ const breadcrumbs = path.map(item => ({
+ label: item.data.name,
+ itemId: item.data.uuid,
+ status: item.status
+ }));
+
+ const { classes, user } = this.props;
+ return (
+ <div className={classes.root}>
+ <div className={classes.appBar}>
+ <MainAppBar
+ breadcrumbs={breadcrumbs}
+ searchText={this.state.searchText}
+ user={this.props.user}
+ menuItems={this.state.menuItems}
+ {...this.mainAppBarActions} />
+ </div>
+ {user &&
+ <Drawer
+ variant="permanent"
+ classes={{
+ paper: classes.drawerPaper,
+ }}>
+ <div className={classes.toolbar} />
+ <SidePanel
+ toggleOpen={this.toggleSidePanelOpen}
+ toggleActive={this.toggleSidePanelActive}
+ sidePanelItems={this.props.sidePanelItems}
+ onContextMenu={(event) => this.openContextMenu(event, authService.getUuid() || "", ContextMenuKind.RootProject)}>
+ <ProjectTree
+ projects={this.props.projects}
+ toggleOpen={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.OPEN))}
+ onContextMenu={(event, item) => this.openContextMenu(event, item.data.uuid, ContextMenuKind.Project)}
+ toggleActive={itemId => {
+ this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE));
+ this.props.dispatch<any>(loadDetails(itemId, ResourceKind.Project));
+ this.props.dispatch<any>(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.Projects));
+ }} />
+ </SidePanel>
+ </Drawer>}
+ <main className={classes.contentWrapper}>
+ <div className={classes.content}>
+ <Switch>
+ <Route path="/projects/:id" render={this.renderProjectPanel} />
+ </Switch>
+ </div>
+ { user && <DetailsPanel /> }
+ </main>
+ <ContextMenuHOC />
+ <CreateProjectDialog />
+ </div>
+ );
+ }
+
+ renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => <ProjectPanel
+ onItemRouteChange={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE))}
+ onContextMenu={(event, item) => this.openContextMenu(event, item.uuid, ContextMenuKind.Project)}
+ onDialogOpen={this.handleCreationDialogOpen}
+ onItemClick={item => {
+ this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
+ }}
+ onItemDoubleClick={item => {
+ this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE));
+ this.props.dispatch<any>(loadDetails(item.uuid, ResourceKind.Project));
+ }}
+ {...props} />
+
+ mainAppBarActions: MainAppBarActionProps = {
+ onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
+ this.props.dispatch<any>(setProjectItem(itemId, ItemMode.BOTH));
+ this.props.dispatch<any>(loadDetails(itemId, ResourceKind.Project));
+ },
+ onSearch: searchText => {
+ this.setState({ searchText });
+ this.props.dispatch(push(`/search?q=${searchText}`));
+ },
+ onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action(),
+ onDetailsPanelToggle: () => {
+ this.props.dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+ },
+ onContextMenu: (event: React.MouseEvent<HTMLElement>, breadcrumb: NavBreadcrumb) => {
+ this.openContextMenu(event, breadcrumb.itemId, ContextMenuKind.Project);
+ }
+ };
+
+ toggleSidePanelOpen = (itemId: string) => {
+ this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId));
+ }
+
+ toggleSidePanelActive = (itemId: string) => {
+ this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId));
+ this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId));
+ this.props.dispatch(push("/"));
+ }
+
+ handleCreationDialogOpen = (itemUuid: string) => {
+ this.props.dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: itemUuid }));
+ }
+
+ openContextMenu = (event: React.MouseEvent<HTMLElement>, itemUuid: string, kind: ContextMenuKind) => {
+ event.preventDefault();
+ this.props.dispatch(
+ contextMenuActions.OPEN_CONTEXT_MENU({
+ position: { x: event.clientX, y: event.clientY },
+ resource: { uuid: itemUuid, kind }
+ })
+ );
+ }
+ }
+ )
);
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list