}
export interface ArvadosTheme extends Theme {
- customs: any;
+ customs: {
+ colors: Colors
+ };
+}
+
+interface Colors {
+ green700: string;
+ yellow700: string;
}
const red900 = red["900"];
-const yellow700 = yellow["700"];
const purple800 = purple["800"];
const grey200 = grey["200"];
const grey300 = grey["300"];
const themeOptions: ArvadosThemeOptions = {
customs: {
colors: {
- green700: green["700"]
+ green700: green["700"],
+ yellow700: yellow["700"]
}
},
overrides: {
root: {
fontSize: '1.25rem'
}
+ },
+ MuiCardHeader: {
+ avatar: {
+ display: 'flex',
+ alignItems: 'center'
+ },
+ title: {
+ color: grey700,
+ fontSize: '1.25rem'
+ }
+ },
+ MuiMenuItem: {
+ root: {
+ padding: '8px 16px'
+ }
}
},
mixins: {
import ContentCopy from '@material-ui/icons/ContentCopy';
import CreateNewFolder from '@material-ui/icons/CreateNewFolder';
import Delete from '@material-ui/icons/Delete';
+import DeviceHub from '@material-ui/icons/DeviceHub';
import Edit from '@material-ui/icons/Edit';
import Folder from '@material-ui/icons/Folder';
import GetApp from '@material-ui/icons/GetApp';
import Inbox from '@material-ui/icons/Inbox';
import Info from '@material-ui/icons/Info';
import Input from '@material-ui/icons/Input';
+import LibraryBooks from '@material-ui/icons/LibraryBooks';
import Menu from '@material-ui/icons/Menu';
import MoreVert from '@material-ui/icons/MoreVert';
import Notifications from '@material-ui/icons/Notifications';
import PlayArrow from '@material-ui/icons/PlayArrow';
import RateReview from '@material-ui/icons/RateReview';
import Search from '@material-ui/icons/Search';
+import SettingsApplications from '@material-ui/icons/SettingsApplications';
import Star from '@material-ui/icons/Star';
import StarBorder from '@material-ui/icons/StarBorder';
export type IconType = React.SFC<{ className?: string }>;
export const AddFavoriteIcon: IconType = (props) => <StarBorder {...props} />;
-export const AdvancedIcon: IconType = (props) => <Folder {...props} />;
+export const AdvancedIcon: IconType = (props) => <SettingsApplications {...props} />;
export const CustomizeTableIcon: IconType = (props) => <Menu {...props} />;
export const CopyIcon: IconType = (props) => <ContentCopy {...props} />;
-export const CollectionIcon: IconType = (props) => <Folder {...props} />;
+export const CollectionIcon: IconType = (props) => <LibraryBooks {...props} />;
export const CloseIcon: IconType = (props) => <Close {...props} />;
export const DefaultIcon: IconType = (props) => <RateReview {...props} />;
export const DetailsIcon: IconType = (props) => <Info {...props} />;
export const ProcessIcon: IconType = (props) => <BubbleChart {...props} />;
export const ProjectIcon: IconType = (props) => <Folder {...props} />;
export const ProjectsIcon: IconType = (props) => <Inbox {...props} />;
-export const ProvenanceGraphIcon: IconType = (props) => <Folder {...props} />;
+export const ProvenanceGraphIcon: IconType = (props) => <DeviceHub {...props} />;
export const RecentIcon: IconType = (props) => <AccessTime {...props} />;
export const RemoveIcon: IconType = (props) => <Delete {...props} />;
export const RemoveFavoriteIcon: IconType = (props) => <Star {...props} />;
import { projectActionSet } from "./views-components/context-menu/action-sets/project-action-set";
import { resourceActionSet } from './views-components/context-menu/action-sets/resource-action-set';
import { favoriteActionSet } from "./views-components/context-menu/action-sets/favorite-action-set";
+import { collectionActionSet } from './views-components/context-menu/action-sets/collection-action-set';
addMenuActionSet(ContextMenuKind.ROOT_PROJECT, rootProjectActionSet);
addMenuActionSet(ContextMenuKind.PROJECT, projectActionSet);
addMenuActionSet(ContextMenuKind.RESOURCE, resourceActionSet);
addMenuActionSet(ContextMenuKind.FAVORITE, favoriteActionSet);
+addMenuActionSet(ContextMenuKind.COLLECTION, collectionActionSet);
fetchConfig()
.then(config => {
deleteAt: string;
isTrashed: boolean;
}
+
+export const getCollectionUrl = (uuid: string) => {
+ return `/collections/${uuid}`;
+};
\ No newline at end of file
export interface ProjectResource extends GroupResource {
groupClass: GroupClass.PROJECT;
}
+
+export const getProjectUrl = (uuid: string) => {
+ return `/projects/${uuid}`;
+};
export const groupsService = new GroupsService(apiClient);
export const projectService = new ProjectService(apiClient);
export const linkService = new LinkService(apiClient);
-export const favoriteService = new FavoriteService(linkService, groupsService);
+export const favoriteService = new FavoriteService(linkService, groupsService);
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { unionize, ofType, UnionOf } from "unionize";
+import { CommonResourceService } from "../../common/api/common-resource-service";
+import { apiClient } from "../../common/api/server-api";
+import { Dispatch } from "redux";
+import { ResourceKind } from "../../models/resource";
+import { CollectionResource } from "../../models/collection";
+
+export const collectionPanelActions = unionize({
+ LOAD_COLLECTION: ofType<{ uuid: string, kind: ResourceKind }>(),
+ LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>(),
+}, { tag: 'type', value: 'payload' });
+
+export type CollectionPanelAction = UnionOf<typeof collectionPanelActions>;
+
+export const loadCollection = (uuid: string, kind: ResourceKind) =>
+ (dispatch: Dispatch) => {
+ dispatch(collectionPanelActions.LOAD_COLLECTION({ uuid, kind }));
+ return new CommonResourceService(apiClient, "collections")
+ .get(uuid)
+ .then(item => {
+ dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: item as CollectionResource }));
+ });
+ };
+
+
+
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { collectionPanelActions, CollectionPanelAction } from "./collection-panel-action";
+import { CollectionResource } from "../../models/collection";
+
+export interface CollectionPanelState {
+ item: CollectionResource | null;
+}
+
+const initialState = {
+ item: null
+};
+
+export const collectionPanelReducer = (state: CollectionPanelState = initialState, action: CollectionPanelAction) =>
+ collectionPanelActions.match(action, {
+ default: () => state,
+ LOAD_COLLECTION: () => state,
+ LOAD_COLLECTION_SUCCESS: ({ item }) => ({ ...state, item }),
+ });
//
// SPDX-License-Identifier: AGPL-3.0
-import { ResourceKind } from "../../models/resource";
import { contextMenuActions, ContextMenuAction } from "./context-menu-actions";
export interface ContextMenuState {
import { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel";
import { RootState } from "../store";
import { Resource, ResourceKind } from "../../models/resource";
+import { getCollectionUrl } from "../../models/collection";
+import { getProjectUrl } from "../../models/project";
export const getResourceUrl = <T extends Resource>(resource: T): string => {
switch (resource.kind) {
- case ResourceKind.PROJECT: return `/projects/${resource.uuid}`;
- case ResourceKind.COLLECTION: return `/collections/${resource.uuid}`;
+ case ResourceKind.PROJECT: return getProjectUrl(resource.uuid);
+ case ResourceKind.COLLECTION: return getCollectionUrl(resource.uuid);
default: return resource.href;
}
};
import { reducer as formReducer } from 'redux-form';
import { FavoritesState, favoritesReducer } from './favorites/favorites-reducer';
import { snackbarReducer, SnackbarState } from './snackbar/snackbar-reducer';
+import { CollectionPanelState, collectionPanelReducer } from './collection-panel/collection-panel-reducer';
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
router: RouterState;
dataExplorer: DataExplorerState;
sidePanel: SidePanelState;
+ collectionPanel: CollectionPanelState;
detailsPanel: DetailsPanelState;
contextMenu: ContextMenuState;
favorites: FavoritesState;
router: routerReducer,
dataExplorer: dataExplorerReducer,
sidePanel: sidePanelReducer,
+ collectionPanel: collectionPanelReducer,
detailsPanel: detailsPanelReducer,
contextMenu: contextMenuReducer,
form: formReducer,
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+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 { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, ProvenanceGraphIcon, AdvancedIcon, RemoveIcon } from "../../../components/icon/icon";
+
+export const collectionActionSet: ContextMenuActionSet = [[
+ {
+ icon: RenameIcon,
+ name: "Edit collection",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ },
+ {
+ icon: ShareIcon,
+ name: "Share",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ },
+ {
+ icon: MoveToIcon,
+ name: "Move to",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ },
+ {
+ component: ToggleFavoriteAction,
+ execute: (dispatch, resource) => {
+ dispatch<any>(toggleFavorite(resource)).then(() => {
+ dispatch<any>(dataExplorerActions.REQUEST_ITEMS({ id: FAVORITE_PANEL_ID }));
+ });
+ }
+ },
+ {
+ icon: CopyIcon,
+ name: "Copy to project",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ },
+ {
+ icon: DetailsIcon,
+ name: "View details",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ },
+ {
+ icon: ProvenanceGraphIcon,
+ name: "Provenance graph",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ },
+ {
+ icon: AdvancedIcon,
+ name: "Advanced",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ },
+ {
+ icon: RemoveIcon,
+ name: "Remove",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ }
+]];
// SPDX-License-Identifier: AGPL-3.0
import { ContextMenuActionSet } from "../context-menu-action-set";
-import { ToggleFavoriteAction } from "./favorite-action";
+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 { ContextMenuActionSet } from "../context-menu-action-set";
import { projectActions } from "../../../store/project/project-action";
import { NewProjectIcon } from "../../../components/icon/icon";
-import { ToggleFavoriteAction } from "./favorite-action";
+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";
// SPDX-License-Identifier: AGPL-3.0
import { ContextMenuActionSet } from "../context-menu-action-set";
-import { ToggleFavoriteAction } from "./favorite-action";
+import { ToggleFavoriteAction } from "../actions/favorite-action";
import { toggleFavorite } from "../../../store/favorites/favorites-actions";
export const resourceActionSet: ContextMenuActionSet = [[{
import * as React from "react";
import { ListItemIcon, ListItemText } from "@material-ui/core";
-import { FavoriteIcon, AddFavoriteIcon, RemoveFavoriteIcon } from "../../../components/icon/icon";
+import { AddFavoriteIcon, RemoveFavoriteIcon } from "../../../components/icon/icon";
import { connect } from "react-redux";
import { RootState } from "../../../store/store";
ROOT_PROJECT = "RootProject",
PROJECT = "Project",
RESOURCE = "Resource",
- FAVORITE = "Favorite"
+ FAVORITE = "Favorite",
+ COLLECTION = 'Collection'
}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import {
+ StyleRulesCallback, WithStyles, withStyles, Card,
+ CardHeader, IconButton, CardContent, Grid
+} from '@material-ui/core';
+import { connect } from 'react-redux';
+import { RouteComponentProps } from 'react-router';
+import { ArvadosTheme } from '../../common/custom-theme';
+import { RootState } from '../../store/store';
+import { MoreOptionsIcon, CollectionIcon } from '../../components/icon/icon';
+import { DetailsAttribute } from '../../components/details-attribute/details-attribute';
+import { CollectionResource } from '../../models/collection';
+
+type CssRules = 'card' | 'iconHeader';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ card: {
+ marginBottom: '20px'
+ },
+ iconHeader: {
+ fontSize: '1.875rem',
+ color: theme.customs.colors.yellow700
+ }
+});
+
+interface CollectionPanelDataProps {
+ item: CollectionResource;
+}
+
+interface CollectionPanelActionProps {
+ onItemRouteChange: (collectionId: string) => void;
+ onContextMenu: (event: React.MouseEvent<HTMLElement>, item: CollectionResource) => void;
+}
+
+type CollectionPanelProps = CollectionPanelDataProps & CollectionPanelActionProps
+ & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
+
+export const CollectionPanel = withStyles(styles)(
+ connect((state: RootState) => ({ item: state.collectionPanel.item }))(
+ class extends React.Component<CollectionPanelProps> {
+
+ render() {
+ const { classes, item, onContextMenu } = this.props;
+ return <div>
+ <Card className={classes.card}>
+ <CardHeader
+ avatar={ <CollectionIcon className={classes.iconHeader} /> }
+ action={
+ <IconButton
+ aria-label="More options"
+ onClick={event => onContextMenu(event, item)}>
+ <MoreOptionsIcon />
+ </IconButton>
+ }
+ title={item && item.name } />
+ <CardContent>
+ <Grid container direction="column">
+ <Grid item xs={6}>
+ <DetailsAttribute label='Collection UUID' value={item && item.uuid} />
+ <DetailsAttribute label='Content size' value='54 MB' />
+ <DetailsAttribute label='Owner' value={item && item.ownerUuid} />
+ </Grid>
+ </Grid>
+ </CardContent>
+ </Card>
+
+ <Card className={classes.card}>
+ <CardHeader title="Tags" />
+ <CardContent>
+ <Grid container direction="column">
+ <Grid item xs={4}>
+ Tags
+ </Grid>
+ </Grid>
+ </CardContent>
+ </Card>
+
+ <Card className={classes.card}>
+ <CardHeader title="Files" />
+ <CardContent>
+ <Grid container direction="column">
+ <Grid item xs={4}>
+ Tags
+ </Grid>
+ </Grid>
+ </CardContent>
+ </Card>
+ </div>;
+ }
+
+ componentWillReceiveProps({ match, item, onItemRouteChange }: CollectionPanelProps) {
+ if (!item || match.params.id !== item.uuid) {
+ onItemRouteChange(match.params.id);
+ }
+ }
+
+ }
+ )
+);
\ No newline at end of file
{renderIcon(item)}
</Grid>
<Grid item>
- <Typography color="primary">
+ <Typography color="default">
{item.name}
</Typography>
</Grid>
import { detailsPanelActions, loadDetails } from "../../store/details-panel/details-panel-action";
import { contextMenuActions } from "../../store/context-menu/context-menu-actions";
-import { sidePanelData, SidePanelIdentifiers } from '../../store/side-panel/side-panel-reducer';
+import { SidePanelIdentifiers } from '../../store/side-panel/side-panel-reducer';
import { ProjectResource } from '../../models/project';
import { ResourceKind } from '../../models/resource';
import { ContextMenu, ContextMenuKind } from "../../views-components/context-menu/context-menu";
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 { CollectionPanel } from '../collection-panel/collection-panel';
+import { loadCollection } from '../../store/collection-panel/collection-panel-action';
+import { getCollectionUrl } from '../../models/collection';
const drawerWidth = 240;
const appBarHeight = 100;
<Switch>
<Route path="/projects/:id" render={this.renderProjectPanel} />
<Route path="/favorites" render={this.renderFavoritePanel} />
+ <Route path="/collections/:id" render={this.renderCollectionPanel} />
</Switch>
</div>
{user && <DetailsPanel />}
);
}
+ renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel
+ onItemRouteChange={(collectionId) => this.props.dispatch<any>(loadCollection(collectionId, ResourceKind.COLLECTION))}
+ onContextMenu={(event, item) => {
+ this.openContextMenu(event, {
+ uuid: item.uuid,
+ name: item.name,
+ kind: ContextMenuKind.COLLECTION
+ });
+ }}
+ {...props} />
+
renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => <ProjectPanel
onItemRouteChange={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE))}
onContextMenu={(event, item) => {
+
const kind = item.kind === ResourceKind.PROJECT ? ContextMenuKind.PROJECT : ContextMenuKind.RESOURCE;
this.openContextMenu(event, {
uuid: item.uuid,
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));
+ switch (item.kind) {
+ case ResourceKind.COLLECTION:
+ this.props.dispatch<any>(loadCollection(item.uuid, item.kind as ResourceKind));
+ this.props.dispatch(push(getCollectionUrl(item.uuid)));
+ default:
+ this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE));
+ this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
+ }
+
}}
{...props} />
this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
}}
onItemDoubleClick={item => {
- this.props.dispatch<any>(loadDetails(item.uuid, ResourceKind.PROJECT));
- this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE));
- this.props.dispatch<any>(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.PROJECTS));
+ switch (item.kind) {
+ case ResourceKind.COLLECTION:
+ this.props.dispatch<any>(loadCollection(item.uuid, item.kind as ResourceKind));
+ this.props.dispatch(push(getCollectionUrl(item.uuid)));
+ default:
+ this.props.dispatch<any>(loadDetails(item.uuid, ResourceKind.PROJECT));
+ this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE));
+ this.props.dispatch<any>(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.PROJECTS));
+ }
+
}}
{...props} />