{
name: "Column 1",
render: () => <span />,
- selected: true
+ selected: true,
+ configurable: true
},
{
name: "Column 2",
{
name: "Column 1",
render: () => <span />,
- selected: true
+ selected: true,
+ configurable: true
},
{
name: "Column 2",
render: () => <span />,
- selected: false
+ selected: false,
+ configurable: true
},
{
name: "Column 3",
render: () => <span />,
- selected: true
+ selected: true,
+ configurable: true
}
];
const columnsConfigurator = mount(<ColumnSelector columns={columns} onColumnToggle={jest.fn()} />);
{
name: "Column 1",
render: () => <span />,
- selected: true
+ selected: true,
+ configurable: true
}
];
const onColumnToggle = jest.fn();
import * as React from 'react';
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 { DataColumn } from '../data-table/data-column';
import { Popover } from "../popover/popover";
import { IconButtonProps } from '@material-ui/core/IconButton';
import { DataColumns } from '../data-table/data-table';
<Paper>
<List dense>
{columns
- .filter(isColumnConfigurable)
- .map((column, index) => (
+ .filter(column => column.configurable)
+ .map((column, index) =>
<ListItem
button
key={index}
{column.name}
</ListItemText>
</ListItem>
- ))}
+ )}
</List>
</Paper>
</Popover>
columns: DataColumns<T>;
searchValue: string;
rowsPerPage: number;
- rowsPerPageOptions?: number[];
+ rowsPerPageOptions: number[];
page: number;
onSearch: (value: string) => void;
onRowClick: (item: T) => void;
contextMenuColumn = {
name: "Actions",
selected: true,
+ configurable: false,
key: "context-actions",
- renderHeader: () => null,
render: this.renderContextMenuTrigger,
width: "auto"
};
export interface DataColumn<T, F extends DataTableFilterItem = DataTableFilterItem> {
name: string;
selected: boolean;
- configurable?: boolean;
+ configurable: boolean;
key?: React.Key;
sortDirection?: SortDirection;
filters?: F[];
- render: (item: T) => React.ReactElement<void>;
- renderHeader?: () => React.ReactElement<void> | null;
+ render: (item: T) => React.ReactElement<any>;
+ renderHeader?: () => React.ReactElement<any>;
width?: string;
}
NONE = "none"
}
-export const isColumnConfigurable = <T>(column: DataColumn<T>) => {
- return column.configurable === undefined || column.configurable;
-};
-
export const toggleSortDirection = <T>(column: DataColumn<T>): DataColumn<T> => {
return column.sortDirection
? column.sortDirection === SortDirection.ASC
}, { tag: "type", value: "payload" });
export type DataExplorerAction = UnionOf<typeof dataExplorerActions>;
+
+export const bindDataExplorerActions = (id: string) => ({
+ RESET_PAGINATION: () =>
+ dataExplorerActions.RESET_PAGINATION({ id }),
+ REQUEST_ITEMS: () =>
+ dataExplorerActions.REQUEST_ITEMS({ id }),
+ SET_COLUMNS: (payload: { columns: DataColumns<any> }) =>
+ dataExplorerActions.SET_COLUMNS({ ...payload, id }),
+ SET_FILTERS: (payload: { columnName: string, filters: DataTableFilterItem[] }) =>
+ dataExplorerActions.SET_FILTERS({ ...payload, id }),
+ SET_ITEMS: (payload: { items: any[], page: number, rowsPerPage: number, itemsAvailable: number }) =>
+ dataExplorerActions.SET_ITEMS({ ...payload, id }),
+ SET_PAGE: (payload: { page: number }) =>
+ dataExplorerActions.SET_PAGE({ ...payload, id }),
+ SET_ROWS_PER_PAGE: (payload: { rowsPerPage: number }) =>
+ dataExplorerActions.SET_ROWS_PER_PAGE({ ...payload, id }),
+ TOGGLE_COLUMN: (payload: { columnName: string }) =>
+ dataExplorerActions.TOGGLE_COLUMN({ ...payload, id }),
+ TOGGLE_SORT: (payload: { columnName: string }) =>
+ dataExplorerActions.TOGGLE_SORT({ ...payload, id }),
+ SET_SEARCH_VALUE: (payload: { searchValue: string }) =>
+ dataExplorerActions.SET_SEARCH_VALUE({ ...payload, id }),
+});
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch, MiddlewareAPI } from "redux";
+import { RootState } from "../store";
+
+export abstract class DataExplorerMiddlewareService {
+ protected readonly id: string;
+
+ protected constructor(id: string) {
+ this.id = id;
+ }
+
+ public getId() {
+ return this.id;
+ }
+
+ abstract requestItems(api: MiddlewareAPI<Dispatch, RootState>): void;
+}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { DataExplorerMiddlewareService } from "./data-explorer-middleware-service";
+import { dataExplorerMiddleware } from "./data-explorer-middleware";
+import { MiddlewareAPI } from "redux";
+import { DataColumns } from "../../components/data-table/data-table";
+import { dataExplorerActions } from "./data-explorer-action";
+
+
+describe("DataExplorerMiddleware", () => {
+
+ it("handles only actions that are identified by service id", () => {
+ const config = {
+ id: "ServiceId",
+ columns: [{
+ name: "Column",
+ selected: true,
+ configurable: false,
+ render: jest.fn()
+ }],
+ requestItems: jest.fn(),
+ setApi: jest.fn()
+ };
+ const service = new ServiceMock(config);
+ const api = {
+ getState: jest.fn(),
+ dispatch: jest.fn()
+ };
+ const next = jest.fn();
+ const middleware = dataExplorerMiddleware(service)(api)(next);
+ middleware(dataExplorerActions.SET_PAGE({ id: "OtherId", page: 0 }));
+ middleware(dataExplorerActions.SET_PAGE({ id: "ServiceId", page: 0 }));
+ middleware(dataExplorerActions.SET_PAGE({ id: "OtherId", page: 0 }));
+ expect(api.dispatch).toHaveBeenCalledWith(dataExplorerActions.REQUEST_ITEMS({ id: "ServiceId" }));
+ expect(api.dispatch).toHaveBeenCalledTimes(1);
+ });
+
+ it("handles REQUEST_ITEMS action", () => {
+ const config = {
+ id: "ServiceId",
+ columns: [{
+ name: "Column",
+ selected: true,
+ configurable: false,
+ render: jest.fn()
+ }],
+ requestItems: jest.fn(),
+ setApi: jest.fn()
+ };
+ const service = new ServiceMock(config);
+ const api = {
+ getState: jest.fn(),
+ dispatch: jest.fn()
+ };
+ const next = jest.fn();
+ const middleware = dataExplorerMiddleware(service)(api)(next);
+ middleware(dataExplorerActions.REQUEST_ITEMS({ id: "ServiceId" }));
+ expect(config.requestItems).toHaveBeenCalled();
+ });
+
+ it("handles SET_PAGE action", () => {
+ const config = {
+ id: "ServiceId",
+ columns: [],
+ requestItems: jest.fn(),
+ setApi: jest.fn()
+ };
+ const service = new ServiceMock(config);
+ const api = {
+ getState: jest.fn(),
+ dispatch: jest.fn()
+ };
+ const next = jest.fn();
+ const middleware = dataExplorerMiddleware(service)(api)(next);
+ middleware(dataExplorerActions.SET_PAGE({ id: service.getId(), page: 0 }));
+ expect(api.dispatch).toHaveBeenCalledTimes(1);
+ });
+
+ it("handles SET_ROWS_PER_PAGE action", () => {
+ const config = {
+ id: "ServiceId",
+ columns: [],
+ requestItems: jest.fn(),
+ setApi: jest.fn()
+ };
+ const service = new ServiceMock(config);
+ const api = {
+ getState: jest.fn(),
+ dispatch: jest.fn()
+ };
+ const next = jest.fn();
+ const middleware = dataExplorerMiddleware(service)(api)(next);
+ middleware(dataExplorerActions.SET_ROWS_PER_PAGE({ id: service.getId(), rowsPerPage: 0 }));
+ expect(api.dispatch).toHaveBeenCalledTimes(1);
+ });
+
+ it("handles SET_FILTERS action", () => {
+ const config = {
+ id: "ServiceId",
+ columns: [],
+ requestItems: jest.fn(),
+ setApi: jest.fn()
+ };
+ const service = new ServiceMock(config);
+ const api = {
+ getState: jest.fn(),
+ dispatch: jest.fn()
+ };
+ const next = jest.fn();
+ const middleware = dataExplorerMiddleware(service)(api)(next);
+ middleware(dataExplorerActions.SET_FILTERS({ id: service.getId(), columnName: "", filters: [] }));
+ expect(api.dispatch).toHaveBeenCalledTimes(2);
+ });
+
+ it("handles SET_ROWS_PER_PAGE action", () => {
+ const config = {
+ id: "ServiceId",
+ columns: [],
+ requestItems: jest.fn(),
+ setApi: jest.fn()
+ };
+ const service = new ServiceMock(config);
+ const api = {
+ getState: jest.fn(),
+ dispatch: jest.fn()
+ };
+ const next = jest.fn();
+ const middleware = dataExplorerMiddleware(service)(api)(next);
+ middleware(dataExplorerActions.SET_ROWS_PER_PAGE({ id: service.getId(), rowsPerPage: 0 }));
+ expect(api.dispatch).toHaveBeenCalledTimes(1);
+ });
+
+ it("handles TOGGLE_SORT action", () => {
+ const config = {
+ id: "ServiceId",
+ columns: [],
+ requestItems: jest.fn(),
+ setApi: jest.fn()
+ };
+ const service = new ServiceMock(config);
+ const api = {
+ getState: jest.fn(),
+ dispatch: jest.fn()
+ };
+ const next = jest.fn();
+ const middleware = dataExplorerMiddleware(service)(api)(next);
+ middleware(dataExplorerActions.TOGGLE_SORT({ id: service.getId(), columnName: "" }));
+ expect(api.dispatch).toHaveBeenCalledTimes(1);
+ });
+
+ it("handles SET_SEARCH_VALUE action", () => {
+ const config = {
+ id: "ServiceId",
+ columns: [],
+ requestItems: jest.fn(),
+ setApi: jest.fn()
+ };
+ const service = new ServiceMock(config);
+ const api = {
+ getState: jest.fn(),
+ dispatch: jest.fn()
+ };
+ const next = jest.fn();
+ const middleware = dataExplorerMiddleware(service)(api)(next);
+ middleware(dataExplorerActions.SET_SEARCH_VALUE({ id: service.getId(), searchValue: "" }));
+ expect(api.dispatch).toHaveBeenCalledTimes(2);
+ });
+
+ it("forwards other actions", () => {
+ const config = {
+ id: "ServiceId",
+ columns: [],
+ requestItems: jest.fn(),
+ setApi: jest.fn()
+ };
+ const service = new ServiceMock(config);
+ const api = {
+ getState: jest.fn(),
+ dispatch: jest.fn()
+ };
+ const next = jest.fn();
+ const middleware = dataExplorerMiddleware(service)(api)(next);
+ middleware(dataExplorerActions.SET_COLUMNS({ id: service.getId(), columns: [] }));
+ middleware(dataExplorerActions.SET_ITEMS({ id: service.getId(), items: [], rowsPerPage: 0, itemsAvailable: 0, page: 0 }));
+ middleware(dataExplorerActions.TOGGLE_COLUMN({ id: service.getId(), columnName: "" }));
+ expect(api.dispatch).toHaveBeenCalledTimes(0);
+ expect(next).toHaveBeenCalledTimes(3);
+ });
+
+});
+
+class ServiceMock extends DataExplorerMiddlewareService {
+ constructor(private config: {
+ id: string,
+ columns: DataColumns<any>,
+ requestItems: (api: MiddlewareAPI) => void
+ }) {
+ super(config.id);
+ }
+
+ getColumns() {
+ return this.config.columns;
+ }
+
+ requestItems(api: MiddlewareAPI) {
+ this.config.requestItems(api);
+ }
+}
--- /dev/null
+
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Middleware } from "redux";
+import { dataExplorerActions, bindDataExplorerActions } from "./data-explorer-action";
+import { DataExplorerMiddlewareService } from "./data-explorer-middleware-service";
+
+export const dataExplorerMiddleware = (service: DataExplorerMiddlewareService): Middleware => api => next => {
+ const handleAction = <T extends { id: string }>(handler: (data: T) => void) =>
+ (data: T) => {
+ if (data.id === service.getId()) {
+ handler(data);
+ }
+ };
+ const actions = bindDataExplorerActions(service.getId());
+
+ return action => {
+ dataExplorerActions.match(action, {
+ SET_PAGE: handleAction(() => {
+ api.dispatch(actions.REQUEST_ITEMS());
+ }),
+ SET_ROWS_PER_PAGE: handleAction(() => {
+ api.dispatch(actions.REQUEST_ITEMS());
+ }),
+ SET_FILTERS: handleAction(() => {
+ api.dispatch(actions.RESET_PAGINATION());
+ api.dispatch(actions.REQUEST_ITEMS());
+ }),
+ TOGGLE_SORT: handleAction(() => {
+ api.dispatch(actions.REQUEST_ITEMS());
+ }),
+ SET_SEARCH_VALUE: handleAction(() => {
+ api.dispatch(actions.RESET_PAGINATION());
+ api.dispatch(actions.REQUEST_ITEMS());
+ }),
+ REQUEST_ITEMS: handleAction(() => {
+ service.requestItems(api);
+ }),
+ default: () => next(action)
+ });
+ };
+};
import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
import { DataColumns } from "../../components/data-table/data-table";
-interface DataExplorer {
+export interface DataExplorer {
columns: DataColumns<any>;
items: any[];
itemsAvailable: number;
page: number;
rowsPerPage: number;
- rowsPerPageOptions?: number[];
+ rowsPerPageOptions: number[];
searchValue: string;
}
searchValue: ""
};
-export type DataExplorerState = Record<string, DataExplorer | undefined>;
+export type DataExplorerState = Record<string, DataExplorer>;
export const dataExplorerReducer = (state: DataExplorerState = {}, action: DataExplorerAction) =>
dataExplorerActions.match(action, {
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { bindDataExplorerActions } from "../data-explorer/data-explorer-action";
+
+export const FAVORITE_PANEL_ID = "favoritePanel";
+export const favoritePanelActions = bindDataExplorerActions(FAVORITE_PANEL_ID);
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { DataExplorerMiddlewareService } from "../data-explorer/data-explorer-middleware-service";
+import { FavoritePanelFilter, FavoritePanelColumnNames } from "../../views/favorite-panel/favorite-panel";
+import { RootState } from "../store";
+import { DataColumns } from "../../components/data-table/data-table";
+import { FavoritePanelItem, resourceToDataItem } from "../../views/favorite-panel/favorite-panel-item";
+import { FavoriteOrderBuilder } from "../../services/favorite-service/favorite-order-builder";
+import { favoriteService, authService } from "../../services/services";
+import { SortDirection } from "../../components/data-table/data-column";
+import { FilterBuilder } from "../../common/api/filter-builder";
+import { LinkResource } from "../../models/link";
+import { checkPresenceInFavorites } from "../favorites/favorites-actions";
+import { favoritePanelActions } from "./favorite-panel-action";
+import { Dispatch, MiddlewareAPI } from "redux";
+
+export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareService {
+ constructor(id: string) {
+ super(id);
+ }
+
+ requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+ const dataExplorer = api.getState().dataExplorer[this.getId()];
+ const columns = dataExplorer.columns as DataColumns<FavoritePanelItem, FavoritePanelFilter>;
+ const sortColumn = dataExplorer.columns.find(
+ ({ sortDirection }) => sortDirection !== undefined && sortDirection !== "none"
+ );
+ const typeFilters = getColumnFilters(columns, FavoritePanelColumnNames.TYPE);
+ const order = FavoriteOrderBuilder.create();
+ if (typeFilters.length > 0) {
+ favoriteService
+ .list(authService.getUuid()!, {
+ limit: dataExplorer.rowsPerPage,
+ offset: dataExplorer.page * dataExplorer.rowsPerPage,
+ order: sortColumn!.name === FavoritePanelColumnNames.NAME
+ ? sortColumn!.sortDirection === SortDirection.ASC
+ ? order.addDesc("name")
+ : order.addAsc("name")
+ : order,
+ filters: FilterBuilder
+ .create<LinkResource>()
+ .addIsA("headUuid", typeFilters.map(filter => filter.type))
+ .addILike("name", dataExplorer.searchValue)
+ })
+ .then(response => {
+ api.dispatch(favoritePanelActions.SET_ITEMS({
+ items: response.items.map(resourceToDataItem),
+ itemsAvailable: response.itemsAvailable,
+ page: Math.floor(response.offset / response.limit),
+ rowsPerPage: response.limit
+ }));
+ api.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
+ });
+ } else {
+ api.dispatch(favoritePanelActions.SET_ITEMS({
+ items: [],
+ itemsAvailable: 0,
+ page: 0,
+ rowsPerPage: dataExplorer.rowsPerPage
+ }));
+ }
+ }
+}
+
+const getColumnFilters = (columns: DataColumns<FavoritePanelItem, FavoritePanelFilter>, columnName: string) => {
+ const column = columns.find(c => c.name === columnName);
+ return column && column.filters ? column.filters.filter(f => f.selected) : [];
+};
+++ /dev/null
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { Middleware } from "redux";
-import { dataExplorerActions } from "../data-explorer/data-explorer-action";
-import { favoriteService } from "../../services/services";
-import { RootState } from "../store";
-import { getDataExplorer } from "../data-explorer/data-explorer-reducer";
-import { FilterBuilder } from "../../common/api/filter-builder";
-import { DataColumns } from "../../components/data-table/data-table";
-import {
- columns,
- FAVORITE_PANEL_ID,
- FavoritePanelColumnNames,
- FavoritePanelFilter
-} from "../../views/favorite-panel/favorite-panel";
-import { FavoritePanelItem, resourceToDataItem } from "../../views/favorite-panel/favorite-panel-item";
-import { LinkResource } from "../../models/link";
-import { checkPresenceInFavorites } from "../favorites/favorites-actions";
-import { OrderBuilder } from "../../common/api/order-builder";
-import { SortDirection } from "../../components/data-table/data-column";
-import { GroupContentsResource, GroupContentsResourcePrefix } from "../../services/groups-service/groups-service";
-import { FavoriteOrderBuilder } from "../../services/favorite-service/favorite-order-builder";
-
-export const favoritePanelMiddleware: Middleware = store => next => {
- next(dataExplorerActions.SET_COLUMNS({ id: FAVORITE_PANEL_ID, columns }));
-
- return action => {
-
- const handlePanelAction = <T extends { id: string }>(handler: (data: T) => void) =>
- (data: T) => {
- next(action);
- if (data.id === FAVORITE_PANEL_ID) {
- handler(data);
- }
- };
-
- dataExplorerActions.match(action, {
- SET_PAGE: handlePanelAction(() => {
- store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: FAVORITE_PANEL_ID }));
- }),
- SET_ROWS_PER_PAGE: handlePanelAction(() => {
- store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: FAVORITE_PANEL_ID }));
- }),
- SET_FILTERS: handlePanelAction(() => {
- store.dispatch(dataExplorerActions.RESET_PAGINATION({ id: FAVORITE_PANEL_ID }));
- store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: FAVORITE_PANEL_ID }));
- }),
- TOGGLE_SORT: handlePanelAction(() => {
- store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: FAVORITE_PANEL_ID }));
- }),
- SET_SEARCH_VALUE: handlePanelAction(() => {
- store.dispatch(dataExplorerActions.RESET_PAGINATION({ id: FAVORITE_PANEL_ID }));
- store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: FAVORITE_PANEL_ID }));
- }),
- REQUEST_ITEMS: handlePanelAction(() => {
- const state = store.getState() as RootState;
- const dataExplorer = getDataExplorer(state.dataExplorer, FAVORITE_PANEL_ID);
- const columns = dataExplorer.columns as DataColumns<FavoritePanelItem, FavoritePanelFilter>;
- const sortColumn = dataExplorer.columns.find(({ sortDirection }) => Boolean(sortDirection && sortDirection !== "none"));
- const typeFilters = getColumnFilters(columns, FavoritePanelColumnNames.TYPE);
- const order = FavoriteOrderBuilder.create();
- if (typeFilters.length > 0) {
- favoriteService
- .list(state.projects.currentItemId, {
- limit: dataExplorer.rowsPerPage,
- offset: dataExplorer.page * dataExplorer.rowsPerPage,
- order: sortColumn!.name === FavoritePanelColumnNames.NAME
- ? sortColumn!.sortDirection === SortDirection.ASC
- ? order.addDesc("name")
- : order.addAsc("name")
- : order,
- filters: FilterBuilder
- .create<LinkResource>()
- .addIsA("headUuid", typeFilters.map(filter => filter.type))
- .addILike("name", dataExplorer.searchValue)
- })
- .then(response => {
- store.dispatch(dataExplorerActions.SET_ITEMS({
- id: FAVORITE_PANEL_ID,
- items: response.items.map(resourceToDataItem),
- itemsAvailable: response.itemsAvailable,
- page: Math.floor(response.offset / response.limit),
- rowsPerPage: response.limit
- }));
- store.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
- });
- } else {
- store.dispatch(dataExplorerActions.SET_ITEMS({
- id: FAVORITE_PANEL_ID,
- items: [],
- itemsAvailable: 0,
- page: 0,
- rowsPerPage: dataExplorer.rowsPerPage
- }));
- }
- }),
- default: () => next(action)
- });
- };
-};
-
-const getOrder = (direction: SortDirection) => {
- const order = OrderBuilder.create<LinkResource>();
- const addRule = (builder: OrderBuilder<GroupContentsResource | LinkResource>, direction: SortDirection) =>
- direction === SortDirection.ASC
- ? builder.addAsc("name")
- : builder.addDesc("name");
-
- return [
- OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.COLLECTION),
- OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROCESS),
- OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROJECT)
- ].reduce((acc, b) =>
- acc.concat(addRule(b, direction)), addRule(OrderBuilder.create(), direction));
-};
-
-const getColumnFilters = (columns: DataColumns<FavoritePanelItem, FavoritePanelFilter>, columnName: string) => {
- const column = columns.find(c => c.name === columnName);
- return column && column.filters ? column.filters.filter(f => f.selected) : [];
-};
-
-
-
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 { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel";
import { RootState } from "../store";
import { Resource, ResourceKind } from "../../models/resource";
+import { projectPanelActions } from "../project-panel/project-panel-action";
import { getCollectionUrl } from "../../models/collection";
import { getProjectUrl } from "../../models/project";
if (itemMode === ItemMode.OPEN || itemMode === ItemMode.BOTH) {
dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(treeItem.data.uuid));
}
- dispatch(dataExplorerActions.RESET_PAGINATION({id: PROJECT_PANEL_ID}));
- dispatch(dataExplorerActions.REQUEST_ITEMS({id: PROJECT_PANEL_ID}));
+ dispatch(projectPanelActions.RESET_PAGINATION());
+ dispatch(projectPanelActions.REQUEST_ITEMS());
}));
}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { bindDataExplorerActions } from "../data-explorer/data-explorer-action";
+
+export const PROJECT_PANEL_ID = "projectPanel";
+export const projectPanelActions = bindDataExplorerActions(PROJECT_PANEL_ID);
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { DataExplorerMiddlewareService } from "../data-explorer/data-explorer-middleware-service";
+import { ProjectPanelColumnNames, ProjectPanelFilter } from "../../views/project-panel/project-panel";
+import { RootState } from "../store";
+import { DataColumns } from "../../components/data-table/data-table";
+import { groupsService } from "../../services/services";
+import { ProjectPanelItem, resourceToDataItem } from "../../views/project-panel/project-panel-item";
+import { SortDirection } from "../../components/data-table/data-column";
+import { OrderBuilder } from "../../common/api/order-builder";
+import { FilterBuilder } from "../../common/api/filter-builder";
+import { ProcessResource } from "../../models/process";
+import { GroupContentsResourcePrefix, GroupContentsResource } from "../../services/groups-service/groups-service";
+import { checkPresenceInFavorites } from "../favorites/favorites-actions";
+import { projectPanelActions } from "./project-panel-action";
+import { Dispatch, MiddlewareAPI } from "redux";
+
+export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService {
+ constructor(id: string) {
+ super(id);
+ }
+
+ requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+ const state = api.getState();
+ const dataExplorer = state.dataExplorer[this.getId()];
+ const columns = dataExplorer.columns as DataColumns<ProjectPanelItem, ProjectPanelFilter>;
+ const typeFilters = getColumnFilters(columns, ProjectPanelColumnNames.TYPE);
+ const statusFilters = getColumnFilters(columns, ProjectPanelColumnNames.STATUS);
+ const sortColumn = dataExplorer.columns.find(({ sortDirection }) => Boolean(sortDirection && sortDirection !== "none"));
+ const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC ? SortDirection.ASC : SortDirection.DESC;
+ if (typeFilters.length > 0) {
+ groupsService
+ .contents(state.projects.currentItemId, {
+ limit: dataExplorer.rowsPerPage,
+ offset: dataExplorer.page * dataExplorer.rowsPerPage,
+ order: sortColumn
+ ? sortColumn.name === ProjectPanelColumnNames.NAME
+ ? getOrder("name", sortDirection)
+ : getOrder("createdAt", sortDirection)
+ : OrderBuilder.create(),
+ filters: FilterBuilder
+ .create()
+ .concat(FilterBuilder
+ .create()
+ .addIsA("uuid", typeFilters.map(f => f.type)))
+ .concat(FilterBuilder
+ .create<ProcessResource>(GroupContentsResourcePrefix.PROCESS)
+ .addIn("state", statusFilters.map(f => f.type)))
+ .concat(getSearchFilter(dataExplorer.searchValue))
+ })
+ .then(response => {
+ api.dispatch(projectPanelActions.SET_ITEMS({
+ items: response.items.map(resourceToDataItem),
+ itemsAvailable: response.itemsAvailable,
+ page: Math.floor(response.offset / response.limit),
+ rowsPerPage: response.limit
+ }));
+ api.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
+ });
+ } else {
+ api.dispatch(projectPanelActions.SET_ITEMS({
+ items: [],
+ itemsAvailable: 0,
+ page: 0,
+ rowsPerPage: dataExplorer.rowsPerPage
+ }));
+ }
+ }
+}
+
+const getColumnFilters = (columns: DataColumns<ProjectPanelItem, ProjectPanelFilter>, columnName: string) => {
+ const column = columns.find(c => c.name === columnName);
+ return column && column.filters ? column.filters.filter(f => f.selected) : [];
+};
+
+const getOrder = (attribute: "name" | "createdAt", direction: SortDirection) =>
+ [
+ OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.COLLECTION),
+ OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROCESS),
+ OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROJECT)
+ ].reduce((acc, b) =>
+ acc.concat(direction === SortDirection.ASC
+ ? b.addAsc(attribute)
+ : b.addDesc(attribute)), OrderBuilder.create());
+
+const getSearchFilter = (searchValue: string) =>
+ searchValue
+ ? [
+ FilterBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.COLLECTION),
+ FilterBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROCESS),
+ FilterBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROJECT)]
+ .reduce((acc, b) =>
+ acc.concat(b.addILike("name", searchValue)), FilterBuilder.create())
+ : FilterBuilder.create();
+++ /dev/null
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { Middleware } from "redux";
-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 } from "../data-explorer/data-explorer-reducer";
-import { resourceToDataItem, ProjectPanelItem } from "../../views/project-panel/project-panel-item";
-import { FilterBuilder } from "../../common/api/filter-builder";
-import { DataColumns } from "../../components/data-table/data-table";
-import { ProcessResource } from "../../models/process";
-import { OrderBuilder } from "../../common/api/order-builder";
-import { GroupContentsResource, GroupContentsResourcePrefix } from "../../services/groups-service/groups-service";
-import { SortDirection } from "../../components/data-table/data-column";
-import { checkPresenceInFavorites } from "../favorites/favorites-actions";
-
-export const projectPanelMiddleware: Middleware = store => next => {
- next(dataExplorerActions.SET_COLUMNS({ id: PROJECT_PANEL_ID, columns }));
-
- return action => {
-
- const handleProjectPanelAction = <T extends { id: string }>(handler: (data: T) => void) =>
- (data: T) => {
- next(action);
- if (data.id === PROJECT_PANEL_ID) {
- handler(data);
- }
- };
-
- dataExplorerActions.match(action, {
- SET_PAGE: handleProjectPanelAction(() => {
- store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
- }),
- SET_ROWS_PER_PAGE: handleProjectPanelAction(() => {
- store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
- }),
- SET_FILTERS: handleProjectPanelAction(() => {
- store.dispatch(dataExplorerActions.RESET_PAGINATION({ id: PROJECT_PANEL_ID }));
- store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
- }),
- TOGGLE_SORT: handleProjectPanelAction(() => {
- store.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
- }),
- SET_SEARCH_VALUE: handleProjectPanelAction(() => {
- 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;
- const dataExplorer = getDataExplorer(state.dataExplorer, PROJECT_PANEL_ID);
- const columns = dataExplorer.columns as DataColumns<ProjectPanelItem, ProjectPanelFilter>;
- const typeFilters = getColumnFilters(columns, ProjectPanelColumnNames.TYPE);
- const statusFilters = getColumnFilters(columns, ProjectPanelColumnNames.STATUS);
- const sortColumn = dataExplorer.columns.find(({ sortDirection }) => Boolean(sortDirection && sortDirection !== "none"));
- const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC ? SortDirection.ASC : SortDirection.DESC;
- if (typeFilters.length > 0) {
- groupsService
- .contents(state.projects.currentItemId, {
- limit: dataExplorer.rowsPerPage,
- offset: dataExplorer.page * dataExplorer.rowsPerPage,
- order: sortColumn
- ? sortColumn.name === ProjectPanelColumnNames.NAME
- ? getOrder("name", sortDirection)
- : getOrder("createdAt", sortDirection)
- : OrderBuilder.create(),
- filters: FilterBuilder
- .create()
- .concat(FilterBuilder
- .create()
- .addIsA("uuid", typeFilters.map(f => f.type)))
- .concat(FilterBuilder
- .create<ProcessResource>(GroupContentsResourcePrefix.PROCESS)
- .addIn("state", statusFilters.map(f => f.type)))
- .concat(getSearchFilter(dataExplorer.searchValue))
- })
- .then(response => {
- store.dispatch(dataExplorerActions.SET_ITEMS({
- id: PROJECT_PANEL_ID,
- items: response.items.map(resourceToDataItem),
- itemsAvailable: response.itemsAvailable,
- page: Math.floor(response.offset / response.limit),
- rowsPerPage: response.limit
- }));
- store.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
- });
- } else {
- store.dispatch(dataExplorerActions.SET_ITEMS({
- id: PROJECT_PANEL_ID,
- items: [],
- itemsAvailable: 0,
- page: 0,
- rowsPerPage: dataExplorer.rowsPerPage
- }));
- }
- }),
- default: () => next(action)
- });
- };
-};
-
-const getColumnFilters = (columns: DataColumns<ProjectPanelItem, ProjectPanelFilter>, columnName: string) => {
- const column = columns.find(c => c.name === columnName);
- return column && column.filters ? column.filters.filter(f => f.selected) : [];
-};
-
-const getOrder = (attribute: "name" | "createdAt", direction: SortDirection) =>
- [
- OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.COLLECTION),
- OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROCESS),
- OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROJECT)
- ].reduce((acc, b) =>
- acc.concat(direction === SortDirection.ASC
- ? b.addAsc(attribute)
- : b.addDesc(attribute)), OrderBuilder.create());
-
-const getSearchFilter = (searchValue: string) =>
- searchValue
- ? [
- FilterBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.COLLECTION),
- FilterBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROCESS),
- FilterBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROJECT)]
- .reduce((acc, b) =>
- acc.concat(b.addILike("name", searchValue)), FilterBuilder.create())
- : FilterBuilder.create();
-
-
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";
-import { dataExplorerActions } from "../data-explorer/data-explorer-action";
import { Dispatch } from "redux";
-import { FAVORITE_PANEL_ID } from "../../views/favorite-panel/favorite-panel";
import { push } from "react-router-redux";
+import { favoritePanelActions } from "../favorite-panel/favorite-panel-action";
export type SidePanelState = SidePanelItem[];
active: false,
activeAction: (dispatch: Dispatch) => {
dispatch(push("/favorites"));
- dispatch(dataExplorerActions.RESET_PAGINATION({id: FAVORITE_PANEL_ID}));
- dispatch(dataExplorerActions.REQUEST_ITEMS({id: FAVORITE_PANEL_ID}));
+ dispatch(favoritePanelActions.RESET_PAGINATION());
+ dispatch(favoritePanelActions.REQUEST_ITEMS());
}
},
{
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 { favoritePanelMiddleware } from "./favorite-panel/favorite-panel-middleware";
import { reducer as formReducer } from 'redux-form';
import { FavoritesState, favoritesReducer } from './favorites/favorites-reducer';
import { snackbarReducer, SnackbarState } from './snackbar/snackbar-reducer';
import { CollectionPanelFilesState } from './collection-panel/collection-panel-files/collection-panel-files-state';
import { collectionPanelFilesReducer } from './collection-panel/collection-panel-files/collections-panel-files-reducer';
+import { dataExplorerMiddleware } from "./data-explorer/data-explorer-middleware";
+import { FAVORITE_PANEL_ID } from "./favorite-panel/favorite-panel-action";
+import { PROJECT_PANEL_ID } from "./project-panel/project-panel-action";
+import { ProjectPanelMiddlewareService } from "./project-panel/project-panel-middleware-service";
+import { FavoritePanelMiddlewareService } from "./favorite-panel/favorite-panel-middleware-service";
import { CollectionCreatorState, collectionCreationReducer } from './collections/creator/collection-creator-reducer';
import { CollectionPanelState, collectionPanelReducer } from './collection-panel/collection-panel-reducer';
import { DialogState, dialogReducer } from './dialog/dialog-reducer';
dialog: dialogReducer
});
-
export function configureStore(history: History) {
+ const projectPanelMiddleware = dataExplorerMiddleware(
+ new ProjectPanelMiddlewareService(PROJECT_PANEL_ID)
+ );
+ const favoritePanelMiddleware = dataExplorerMiddleware(
+ new FavoritePanelMiddlewareService(FAVORITE_PANEL_ID)
+ );
+
const middlewares: Middleware[] = [
routerMiddleware(history),
thunkMiddleware,
import { ToggleFavoriteAction } from "../actions/favorite-action";
import { toggleFavorite } from "../../../store/favorites/favorites-actions";
import { dataExplorerActions } from "../../../store/data-explorer/data-explorer-action";
-import { FAVORITE_PANEL_ID } from "../../../views/favorite-panel/favorite-panel";
import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, ProvenanceGraphIcon, AdvancedIcon, RemoveIcon } from "../../../components/icon/icon";
+import { favoritePanelActions } from "../../../store/favorite-panel/favorite-panel-action";
export const collectionActionSet: ContextMenuActionSet = [[
{
component: ToggleFavoriteAction,
execute: (dispatch, resource) => {
dispatch<any>(toggleFavorite(resource)).then(() => {
- dispatch<any>(dataExplorerActions.REQUEST_ITEMS({ id: FAVORITE_PANEL_ID }));
+ dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
});
}
},
import { ContextMenuActionSet } from "../context-menu-action-set";
import { ToggleFavoriteAction } from "../actions/favorite-action";
import { toggleFavorite } from "../../../store/favorites/favorites-actions";
-import { dataExplorerActions } from "../../../store/data-explorer/data-explorer-action";
-import { FAVORITE_PANEL_ID } from "../../../views/favorite-panel/favorite-panel";
+import { favoritePanelActions } from "../../../store/favorite-panel/favorite-panel-action";
export const favoriteActionSet: ContextMenuActionSet = [[{
component: ToggleFavoriteAction,
execute: (dispatch, resource) => {
dispatch<any>(toggleFavorite(resource)).then(() => {
- dispatch<any>(dataExplorerActions.REQUEST_ITEMS({ id : FAVORITE_PANEL_ID }));
+ dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
});
}
}]];
import { NewProjectIcon } from "../../../components/icon/icon";
import { ToggleFavoriteAction } from "../actions/favorite-action";
import { toggleFavorite } from "../../../store/favorites/favorites-actions";
-import { dataExplorerActions } from "../../../store/data-explorer/data-explorer-action";
-import { FAVORITE_PANEL_ID } from "../../../views/favorite-panel/favorite-panel";
+import { favoritePanelActions } from "../../../store/favorite-panel/favorite-panel-action";
export const projectActionSet: ContextMenuActionSet = [[{
icon: NewProjectIcon,
component: ToggleFavoriteAction,
execute: (dispatch, resource) => {
dispatch<any>(toggleFavorite(resource)).then(() => {
- dispatch<any>(dataExplorerActions.REQUEST_ITEMS({ id : FAVORITE_PANEL_ID }));
+ dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
});
}
}]];
import { RootState } from "../../store/store";
import { DialogProjectCreate } 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";
+import { projectPanelActions } from "../../store/project-panel/project-panel-action";
import { snackbarActions } from "../../store/snackbar/snackbar-actions";
const mapStateToProps = (state: RootState) => ({
message: "Created a new project",
hideDuration: 2000
}));
- dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID }));
+ dispatch(projectPanelActions.REQUEST_ITEMS());
dispatch<any>(getProjectList(ownerUuid));
});
};
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";
+import { DataColumns } from "../../components/data-table/data-table";
interface Props {
id: string;
+ columns: DataColumns<any>;
onRowClick: (item: any) => void;
onContextMenu: (event: React.MouseEvent<HTMLElement>, item: any) => void;
onRowDoubleClick: (item: any) => void;
const mapStateToProps = (state: RootState, { id }: Props) =>
getDataExplorer(state.dataExplorer, id);
-const mapDispatchToProps = (dispatch: Dispatch, { id, onRowClick, onRowDoubleClick, onContextMenu }: Props) => ({
- onSearch: (searchValue: string) => {
- dispatch(dataExplorerActions.SET_SEARCH_VALUE({ id, searchValue }));
- },
+const mapDispatchToProps = (dispatch: Dispatch, { id, columns, onRowClick, onRowDoubleClick, onContextMenu }: Props) => {
+ dispatch(dataExplorerActions.SET_COLUMNS({ id, columns }));
+ return {
+ onSearch: (searchValue: string) => {
+ dispatch(dataExplorerActions.SET_SEARCH_VALUE({ id, searchValue }));
+ },
- onColumnToggle: (column: DataColumn<any>) => {
- dispatch(dataExplorerActions.TOGGLE_COLUMN({ id, columnName: column.name }));
- },
+ onColumnToggle: (column: DataColumn<any>) => {
+ dispatch(dataExplorerActions.TOGGLE_COLUMN({ id, columnName: column.name }));
+ },
- onSortToggle: (column: DataColumn<any>) => {
- dispatch(dataExplorerActions.TOGGLE_SORT({ id, columnName: column.name }));
- },
+ onSortToggle: (column: DataColumn<any>) => {
+ dispatch(dataExplorerActions.TOGGLE_SORT({ id, columnName: column.name }));
+ },
- onFiltersChange: (filters: DataTableFilterItem[], column: DataColumn<any>) => {
- dispatch(dataExplorerActions.SET_FILTERS({ id, columnName: column.name, filters }));
- },
+ onFiltersChange: (filters: DataTableFilterItem[], column: DataColumn<any>) => {
+ dispatch(dataExplorerActions.SET_FILTERS({ id, columnName: column.name, filters }));
+ },
- onChangePage: (page: number) => {
- dispatch(dataExplorerActions.SET_PAGE({ id, page }));
- },
+ onChangePage: (page: number) => {
+ dispatch(dataExplorerActions.SET_PAGE({ id, page }));
+ },
- onChangeRowsPerPage: (rowsPerPage: number) => {
- dispatch(dataExplorerActions.SET_ROWS_PER_PAGE({ id, rowsPerPage }));
- },
+ onChangeRowsPerPage: (rowsPerPage: number) => {
+ dispatch(dataExplorerActions.SET_ROWS_PER_PAGE({ id, rowsPerPage }));
+ },
- onRowClick,
+ onRowClick,
- onRowDoubleClick,
+ onRowDoubleClick,
- onContextMenu,
-});
+ onContextMenu,
+ };
+};
export const DataExplorer = connect(mapStateToProps, mapDispatchToProps)(DataExplorerComponent);
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { Grid, Typography } from '@material-ui/core';
+import { FavoriteStar } from '../favorite-star/favorite-star';
+import { ResourceKind } from '../../models/resource';
+import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon } from '../../components/icon/icon';
+import { formatDate, formatFileSize } from '../../common/formatters';
+import { resourceLabel } from '../../common/labels';
+
+
+export const renderName = (item: {name: string; uuid: string, kind: string}) =>
+ <Grid container alignItems="center" wrap="nowrap" spacing={16}>
+ <Grid item>
+ {renderIcon(item)}
+ </Grid>
+ <Grid item>
+ <Typography color="primary">
+ {item.name}
+ </Typography>
+ </Grid>
+ <Grid item>
+ <Typography variant="caption">
+ <FavoriteStar resourceUuid={item.uuid} />
+ </Typography>
+ </Grid>
+ </Grid>;
+
+
+export const renderIcon = (item: {kind: string}) => {
+ switch (item.kind) {
+ case ResourceKind.PROJECT:
+ return <ProjectIcon />;
+ case ResourceKind.COLLECTION:
+ return <CollectionIcon />;
+ case ResourceKind.PROCESS:
+ return <ProcessIcon />;
+ default:
+ return <DefaultIcon />;
+ }
+};
+
+export const renderDate = (date: string) => {
+ return <Typography noWrap>{formatDate(date)}</Typography>;
+};
+
+export const renderFileSize = (fileSize?: number) =>
+ <Typography noWrap>
+ {formatFileSize(fileSize)}
+ </Typography>;
+
+export const renderOwner = (owner: string) =>
+ <Typography noWrap color="primary" >
+ {owner}
+ </Typography>;
+
+export const renderType = (type: string) =>
+ <Typography noWrap>
+ {resourceLabel(type)}
+ </Typography>;
+
+export const renderStatus = (item: {status?: string}) =>
+ <Typography noWrap align="center" >
+ {item.status || "-"}
+ </Typography>;
\ No newline at end of file
import * as React from 'react';
import { FavoritePanelItem } from './favorite-panel-item';
-import { Grid, Typography, Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
-import { formatDate, formatFileSize } from '../../common/formatters';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
import { DataExplorer } from "../../views-components/data-explorer/data-explorer";
import { DispatchProp, connect } from 'react-redux';
import { DataColumns } from '../../components/data-table/data-table';
import { SortDirection } from '../../components/data-table/data-column';
import { ResourceKind } from '../../models/resource';
import { resourceLabel } from '../../common/labels';
-import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon } from '../../components/icon/icon';
import { ArvadosTheme } from '../../common/custom-theme';
-import { FavoriteStar } from "../../views-components/favorite-star/favorite-star";
+import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '../../views-components/data-explorer/renderers';
+import { FAVORITE_PANEL_ID } from "../../store/favorite-panel/favorite-panel-action";
type CssRules = "toolbar" | "button";
},
});
-const renderName = (item: FavoritePanelItem) =>
- <Grid container alignItems="center" wrap="nowrap" spacing={16}>
- <Grid item>
- {renderIcon(item)}
- </Grid>
- <Grid item>
- <Typography color="primary">
- {item.name}
- </Typography>
- </Grid>
- <Grid item>
- <Typography variant="caption">
- <FavoriteStar resourceUuid={item.uuid} />
- </Typography>
- </Grid>
- </Grid>;
-
-
-const renderIcon = (item: FavoritePanelItem) => {
- switch (item.kind) {
- case ResourceKind.PROJECT:
- return <ProjectIcon />;
- case ResourceKind.COLLECTION:
- return <CollectionIcon />;
- case ResourceKind.PROCESS:
- return <ProcessIcon />;
- default:
- return <DefaultIcon />;
- }
-};
-
-const renderDate = (date: string) => {
- return <Typography noWrap>{formatDate(date)}</Typography>;
-};
-
-const renderFileSize = (fileSize?: number) =>
- <Typography noWrap>
- {formatFileSize(fileSize)}
- </Typography>;
-
-const renderOwner = (owner: string) =>
- <Typography noWrap color="primary" >
- {owner}
- </Typography>;
-
-const renderType = (type: string) =>
- <Typography noWrap>
- {resourceLabel(type)}
- </Typography>;
-
-const renderStatus = (item: FavoritePanelItem) =>
- <Typography noWrap align="center" >
- {item.status || "-"}
- </Typography>;
-
export enum FavoritePanelColumnNames {
NAME = "Name",
STATUS = "Status",
{
name: FavoritePanelColumnNames.NAME,
selected: true,
+ configurable: true,
sortDirection: SortDirection.ASC,
render: renderName,
width: "450px"
{
name: "Status",
selected: true,
+ configurable: true,
filters: [
{
name: ContainerRequestState.COMMITTED,
{
name: FavoritePanelColumnNames.TYPE,
selected: true,
+ configurable: true,
filters: [
{
name: resourceLabel(ResourceKind.COLLECTION),
{
name: FavoritePanelColumnNames.OWNER,
selected: true,
+ configurable: true,
render: item => renderOwner(item.owner),
width: "200px"
},
{
name: FavoritePanelColumnNames.FILE_SIZE,
selected: true,
+ configurable: true,
render: item => renderFileSize(item.fileSize),
width: "50px"
},
{
name: FavoritePanelColumnNames.LAST_MODIFIED,
selected: true,
+ configurable: true,
sortDirection: SortDirection.NONE,
render: item => renderDate(item.lastModified),
width: "150px"
}
];
-export const FAVORITE_PANEL_ID = "favoritePanel";
-
interface FavoritePanelDataProps {
currentItemId: string;
}
render() {
return <DataExplorer
id={FAVORITE_PANEL_ID}
+ columns={columns}
onRowClick={this.props.onItemClick}
onRowDoubleClick={this.props.onItemDoubleClick}
onContextMenu={this.props.onContextMenu}
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 { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
import { DataExplorer } from "../../views-components/data-explorer/data-explorer";
import { DispatchProp, connect } from 'react-redux';
import { DataColumns } from '../../components/data-table/data-table';
import { SortDirection } from '../../components/data-table/data-column';
import { ResourceKind } from '../../models/resource';
import { resourceLabel } from '../../common/labels';
-import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon, FavoriteIcon } from '../../components/icon/icon';
import { ArvadosTheme } from '../../common/custom-theme';
-import { FavoriteStar } from '../../views-components/favorite-star/favorite-star';
+import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '../../views-components/data-explorer/renderers';
type CssRules = "toolbar" | "button";
},
});
-const renderName = (item: ProjectPanelItem) =>
- <Grid container alignItems="center" wrap="nowrap" spacing={16}>
- <Grid item>
- {renderIcon(item)}
- </Grid>
- <Grid item>
- <Typography color="default">
- {item.name}
- </Typography>
- </Grid>
- <Grid item>
- <Typography variant="caption">
- <FavoriteStar resourceUuid={item.uuid} />
- </Typography>
- </Grid>
- </Grid>;
-
-
-const renderIcon = (item: ProjectPanelItem) => {
- switch (item.kind) {
- case ResourceKind.PROJECT:
- return <ProjectIcon />;
- case ResourceKind.COLLECTION:
- return <CollectionIcon />;
- case ResourceKind.PROCESS:
- return <ProcessIcon />;
- default:
- return <DefaultIcon />;
- }
-};
-
-const renderDate = (date: string) => {
- return <Typography noWrap>{formatDate(date)}</Typography>;
-};
-
-const renderFileSize = (fileSize?: number) =>
- <Typography noWrap>
- {formatFileSize(fileSize)}
- </Typography>;
-
-const renderOwner = (owner: string) =>
- <Typography noWrap color="primary" >
- {owner}
- </Typography>;
-
-const renderType = (type: string) =>
- <Typography noWrap>
- {resourceLabel(type)}
- </Typography>;
-
-const renderStatus = (item: ProjectPanelItem) =>
- <Typography noWrap align="center" >
- {item.status || "-"}
- </Typography>;
-
export enum ProjectPanelColumnNames {
NAME = "Name",
STATUS = "Status",
{
name: ProjectPanelColumnNames.NAME,
selected: true,
+ configurable: true,
sortDirection: SortDirection.ASC,
render: renderName,
width: "450px"
{
name: "Status",
selected: true,
+ configurable: true,
filters: [
{
name: ContainerRequestState.COMMITTED,
{
name: ProjectPanelColumnNames.TYPE,
selected: true,
+ configurable: true,
filters: [
{
name: resourceLabel(ResourceKind.COLLECTION),
{
name: ProjectPanelColumnNames.OWNER,
selected: true,
+ configurable: true,
render: item => renderOwner(item.owner),
width: "200px"
},
{
name: ProjectPanelColumnNames.FILE_SIZE,
selected: true,
+ configurable: true,
render: item => renderFileSize(item.fileSize),
width: "50px"
},
{
name: ProjectPanelColumnNames.LAST_MODIFIED,
selected: true,
+ configurable: true,
sortDirection: SortDirection.NONE,
render: item => renderDate(item.lastModified),
width: "150px"
</div>
<DataExplorer
id={PROJECT_PANEL_ID}
+ columns={columns}
onRowClick={this.props.onItemClick}
onRowDoubleClick={this.props.onItemDoubleClick}
onContextMenu={this.props.onContextMenu}
}
}
)
-);
\ No newline at end of file
+);
import { ProjectResource } from '../../models/project';
import { ResourceKind } from '../../models/resource';
import { ContextMenu, ContextMenuKind } from "../../views-components/context-menu/context-menu";
-import { FavoritePanel, FAVORITE_PANEL_ID } from "../favorite-panel/favorite-panel";
+import { FavoritePanel } from "../favorite-panel/favorite-panel";
import { CurrentTokenDialog } from '../../views-components/current-token-dialog/current-token-dialog';
import { dataExplorerActions } from '../../store/data-explorer/data-explorer-action';
import { Snackbar } from '../../views-components/snackbar/snackbar';
+import { favoritePanelActions } from '../../store/favorite-panel/favorite-panel-action';
import { CreateCollectionDialog } from '../../views-components/create-collection-dialog/create-collection-dialog';
import { CollectionPanel } from '../collection-panel/collection-panel';
import { loadCollection } from '../../store/collection-panel/collection-panel-action';
{...props} />
renderFavoritePanel = (props: RouteComponentProps<{ id: string }>) => <FavoritePanel
- onItemRouteChange={() => this.props.dispatch<any>(dataExplorerActions.REQUEST_ITEMS({ id: FAVORITE_PANEL_ID }))}
+ onItemRouteChange={() => this.props.dispatch<any>(favoritePanelActions.REQUEST_ITEMS())}
onContextMenu={(event, item) => {
const kind = item.kind === ResourceKind.PROJECT ? ContextMenuKind.PROJECT : ContextMenuKind.RESOURCE;
this.openContextMenu(event, {