import { LinkService } from "./link-service/link-service";
import { FavoriteService } from "./favorite-service/favorite-service";
import { AxiosInstance } from "axios";
- import { CommonResourceService } from "../common/api/common-resource-service";
- import { Resource } from "../models/resource";
import { CollectionService } from "./collection-service/collection-service";
+import { TagService } from "./tag-service/tag-service";
import Axios from "axios";
+ import { CollectionFilesService } from "./collection-files-service/collection-files-service";
export interface ServiceRepository {
apiClient: AxiosInstance;
projectService: ProjectService;
linkService: LinkService;
favoriteService: FavoriteService;
- collectionService: CommonResourceService<Resource>;
+ tagService: TagService;
+ collectionService: CollectionService;
+ collectionFilesService: CollectionFilesService;
}
export const createServices = (baseUrl: string): ServiceRepository => {
const linkService = new LinkService(apiClient);
const favoriteService = new FavoriteService(linkService, groupsService);
const collectionService = new CollectionService(apiClient);
-
+ const tagService = new TagService(linkService);
+ const collectionFilesService = new CollectionFilesService(collectionService);
+
return {
apiClient,
authService,
linkService,
favoriteService,
collectionService,
- tagService
++ tagService,
+ collectionFilesService
};
};
import { Dispatch } from "redux";
import { ResourceKind } from "../../models/resource";
import { CollectionResource } from "../../models/collection";
+ import { collectionPanelFilesAction } from "./collection-panel-files/collection-panel-files-actions";
+ import { createTree } from "../../models/tree";
import { RootState } from "../store";
import { ServiceRepository } from "../../services/services";
+import { TagResource, TagProperty } from "../../models/tag";
+import { snackbarActions } from "../snackbar/snackbar-actions";
export const collectionPanelActions = unionize({
LOAD_COLLECTION: ofType<{ uuid: string, kind: ResourceKind }>(),
- LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>()
+ LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>(),
+ LOAD_COLLECTION_TAGS: ofType<{ uuid: string }>(),
+ LOAD_COLLECTION_TAGS_SUCCESS: ofType<{ tags: TagResource[] }>(),
+ CREATE_COLLECTION_TAG: ofType<{ data: any }>(),
+ CREATE_COLLECTION_TAG_SUCCESS: ofType<{ tag: TagResource }>(),
+ DELETE_COLLECTION_TAG: ofType<{ uuid: string }>(),
+ DELETE_COLLECTION_TAG_SUCCESS: ofType<{ uuid: string }>()
}, { tag: 'type', value: 'payload' });
export type CollectionPanelAction = UnionOf<typeof collectionPanelActions>;
+export const COLLECTION_TAG_FORM_NAME = 'collectionTagForm';
+
export const loadCollection = (uuid: string, kind: ResourceKind) =>
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(collectionPanelActions.LOAD_COLLECTION({ uuid, kind }));
+ dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES({ files: createTree() }));
return services.collectionService
.get(uuid)
.then(item => {
- dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: item as CollectionResource }));
+ dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item }));
+ return services.collectionFilesService.getFiles(item.uuid);
+ })
+ .then(files => {
+ dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES(files));
});
};
+export const loadCollectionTags = (uuid: string) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(collectionPanelActions.LOAD_COLLECTION_TAGS({ uuid }));
+ return services.tagService
+ .list(uuid)
+ .then(tags => {
+ dispatch(collectionPanelActions.LOAD_COLLECTION_TAGS_SUCCESS({ tags }));
+ });
+ };
+
+export const createCollectionTag = (data: TagProperty) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(collectionPanelActions.CREATE_COLLECTION_TAG({ data }));
+ const item = getState().collectionPanel.item;
+ const uuid = item ? item.uuid : '';
+ return services.tagService
+ .create(uuid, data)
+ .then(tag => {
+ dispatch(collectionPanelActions.CREATE_COLLECTION_TAG_SUCCESS({ tag }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: "Tag has been successfully added.",
+ hideDuration: 2000
+ }));
+ });
+ };
+export const deleteCollectionTag = (uuid: string) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(collectionPanelActions.DELETE_COLLECTION_TAG({ uuid }));
+ return services.linkService
+ .delete(uuid)
+ .then(tag => {
+ dispatch(collectionPanelActions.DELETE_COLLECTION_TAG_SUCCESS({ uuid: tag.uuid }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: "Tag has been successfully deleted.",
+ hideDuration: 2000
+ }));
+ });
+ };
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
- import {
- StyleRulesCallback, WithStyles, withStyles, Card,
- CardHeader, IconButton, CardContent, Grid, Chip, TextField, Button
+ import {
+ StyleRulesCallback, WithStyles, withStyles, Card,
+ CardHeader, IconButton, CardContent, Grid, Chip
} from '@material-ui/core';
-import { connect } from 'react-redux';
+import { connect, DispatchProp } from "react-redux";
import { RouteComponentProps } from 'react-router';
import { ArvadosTheme } from '../../common/custom-theme';
import { RootState } from '../../store/store';
import { MoreOptionsIcon, CollectionIcon, CopyIcon } from '../../components/icon/icon';
import { DetailsAttribute } from '../../components/details-attribute/details-attribute';
import { CollectionResource } from '../../models/collection';
+ import { CollectionPanelFiles } from '../../views-components/collection-panel-files/collection-panel-files';
import * as CopyToClipboard from 'react-copy-to-clipboard';
+import { TagResource } from '../../models/tag';
+import { CollectionTagForm } from './collection-tag-form';
+import { deleteCollectionTag } from '../../store/collection-panel/collection-panel-action';
-type CssRules = 'card' | 'iconHeader' | 'tag' | 'copyIcon';
+type CssRules = 'card' | 'iconHeader' | 'tag' | 'copyIcon' | 'value';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
card: {
- marginBottom: '20px'
+ marginBottom: theme.spacing.unit * 2
},
iconHeader: {
fontSize: '1.875rem',
color: theme.customs.colors.yellow700
},
tag: {
- marginRight: theme.spacing.unit
+ marginRight: theme.spacing.unit,
+ marginBottom: theme.spacing.unit
},
copyIcon: {
marginLeft: theme.spacing.unit,
fontSize: '1.125rem',
+ color: theme.palette.grey["500"],
cursor: 'pointer'
+ },
+ value: {
+ textTransform: 'none'
}
});
interface CollectionPanelDataProps {
item: CollectionResource;
+ tags: TagResource[];
}
interface CollectionPanelActionProps {
onContextMenu: (event: React.MouseEvent<HTMLElement>, item: CollectionResource) => void;
}
-type CollectionPanelProps = CollectionPanelDataProps & CollectionPanelActionProps
+type CollectionPanelProps = CollectionPanelDataProps & CollectionPanelActionProps & DispatchProp
& WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
export const CollectionPanel = withStyles(styles)(
- connect((state: RootState) => ({ item: state.collectionPanel.item! }))(
- class extends React.Component<CollectionPanelProps> {
+ connect((state: RootState) => ({
+ item: state.collectionPanel.item,
+ tags: state.collectionPanel.tags
+ }))(
+ class extends React.Component<CollectionPanelProps> {
render() {
- const { classes, item, onContextMenu } = this.props;
+ const { classes, item, tags, onContextMenu } = this.props;
return <div>
<Card className={classes.card}>
- <CardHeader
+ <CardHeader
avatar={ <CollectionIcon className={classes.iconHeader} /> }
- action={
+ action={
<IconButton
aria-label="More options"
onClick={event => onContextMenu(event, item)}>
<MoreOptionsIcon />
- </IconButton>
+ </IconButton>
}
- title={item && item.name }
+ title={item && item.name }
subheader={item && item.description} />
<CardContent>
<Grid container direction="column">
<Grid item xs={6}>
- <DetailsAttribute label='Collection UUID' value={item && item.uuid}>
+ <DetailsAttribute classValue={classes.value}
+ label='Collection UUID'
+ value={item && item.uuid}>
<CopyToClipboard text={item && item.uuid}>
<CopyIcon className={classes.copyIcon} />
</CopyToClipboard>
</DetailsAttribute>
+ <DetailsAttribute label='Number of files' value='14' />
<DetailsAttribute label='Content size' value='54 MB' />
- <DetailsAttribute label='Owner' value={item && item.ownerUuid} />
+ <DetailsAttribute classValue={classes.value} label='Owner' value={item && item.ownerUuid} />
</Grid>
</Grid>
</CardContent>
<CardHeader title="Tags" />
<CardContent>
<Grid container direction="column">
- <Grid item xs={4}>
- <Chip label="Tag 1" className={classes.tag}/>
- <Chip label="Tag 2" className={classes.tag}/>
- <Chip label="Tag 3" className={classes.tag}/>
+ <Grid item xs={12}><CollectionTagForm /></Grid>
+ <Grid item xs={12}>
+ {
+ tags.map(tag => {
+ return <Chip key={tag.etag} className={classes.tag}
+ onDelete={this.handleDelete(tag.uuid)}
+ label={renderTagLabel(tag)} />;
+ })
+ }
</Grid>
</Grid>
</CardContent>
</Card>
-
- <Card className={classes.card}>
- <CardHeader title="Files" />
- <CardContent>
- <Grid container direction="column">
- <Grid item xs={4}>
- Files
- </Grid>
- </Grid>
- </CardContent>
- </Card>
+ <div className={classes.card}>
+ <CollectionPanelFiles/>
+ </div>
</div>;
}
+ handleDelete = (uuid: string) => () => {
+ this.props.dispatch<any>(deleteCollectionTag(uuid));
+ }
+
componentWillReceiveProps({ match, item, onItemRouteChange }: CollectionPanelProps) {
if (!item || match.params.id !== item.uuid) {
onItemRouteChange(match.params.id);
}
)
);
- };
+
+const renderTagLabel = (tag: TagResource) => {
+ const { properties } = tag;
+ return `${properties.key}: ${properties.value}`;
++};
import { ContextMenu, ContextMenuKind } from "../../views-components/context-menu/context-menu";
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';
+import { loadCollection, loadCollectionTags } from '../../store/collection-panel/collection-panel-action';
import { getCollectionUrl } from '../../models/collection';
+ import { RemoveDialog } from '../../views-components/remove-dialog/remove-dialog';
+ import { RenameDialog } from '../../views-components/rename-dialog/rename-dialog';
import { UpdateCollectionDialog } from '../../views-components/update-collection-dialog/update-collection-dialog.';
import { AuthService } from "../../services/auth-service/auth-service";
<Snackbar />
<CreateProjectDialog />
<CreateCollectionDialog />
+ <RemoveDialog />
+ <RenameDialog />
<UpdateCollectionDialog />
<CurrentTokenDialog
currentToken={this.props.currentToken}
);
}
- renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel
- onItemRouteChange={(collectionId) => this.props.dispatch<any>(loadCollection(collectionId, ResourceKind.COLLECTION))}
+ renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel
+ onItemRouteChange={(collectionId) => {
+ this.props.dispatch<any>(loadCollection(collectionId, ResourceKind.COLLECTION));
+ this.props.dispatch<any>(loadCollectionTags(collectionId));
+ }}
onContextMenu={(event, item) => {
this.openContextMenu(event, {
uuid: item.uuid,