Merge branch '21720-material-ui-upgrade'
[arvados.git] / services / workbench2 / src / views-components / details-panel / collection-details.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { CollectionIcon, RenameIcon } from 'components/icon/icon';
7 import { CollectionResource } from 'models/collection';
8 import { DetailsData } from "./details-data";
9 import { CollectionDetailsAttributes } from 'views/collection-panel/collection-panel';
10 import { RootState } from 'store/store';
11 import { filterResources, getResource, ResourcesState } from 'store/resources/resources';
12 import { connect } from 'react-redux';
13 import { CustomStyleRulesCallback } from 'common/custom-theme';
14 import { Button, Grid, ListItem, Typography } from '@mui/material';
15 import { WithStyles } from '@mui/styles';
16 import withStyles from '@mui/styles/withStyles';
17 import { formatDate, formatFileSize } from 'common/formatters';
18 import { UserNameFromID } from '../data-explorer/renderers';
19 import { Dispatch } from 'redux';
20 import { navigateTo } from 'store/navigation/navigation-action';
21 import { openContextMenu, resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
22 import { openCollectionUpdateDialog } from 'store/collections/collection-update-actions';
23 import { resourceIsFrozen } from 'common/frozen-resources';
24
25 export type CssRules = 'versionBrowserHeader'
26     | 'versionBrowserItem'
27     | 'versionBrowserField'
28     | 'editButton'
29     | 'editIcon'
30     | 'tag';
31
32 const styles: CustomStyleRulesCallback<CssRules> = theme => ({
33     versionBrowserHeader: {
34         textAlign: 'center',
35         fontWeight: 'bold',
36     },
37     versionBrowserItem: {
38         flexWrap: 'wrap',
39     },
40     versionBrowserField: {
41         textAlign: 'center',
42     },
43     editIcon: {
44         paddingRight: theme.spacing(0.5),
45         fontSize: '1.125rem',
46     },
47     editButton: {
48         boxShadow: 'none',
49         padding: '2px 10px 2px 5px',
50         fontSize: '0.75rem'
51     },
52     tag: {
53         marginRight: theme.spacing(0.5),
54         marginBottom: theme.spacing(0.5)
55     },
56 });
57
58 export class CollectionDetails extends DetailsData<CollectionResource> {
59
60     getIcon(className?: string) {
61         return <CollectionIcon className={className} />;
62     }
63
64     getTabLabels() {
65         return ['Details', 'Versions'];
66     }
67
68     getDetails({tabNr}) {
69         switch (tabNr) {
70             case 0:
71                 return this.getCollectionInfo();
72             case 1:
73                 return this.getVersionBrowser();
74             default:
75                 return <div />;
76         }
77     }
78
79     private getCollectionInfo() {
80         return <CollectionInfo />;
81     }
82
83     private getVersionBrowser() {
84         return <CollectionVersionBrowser />;
85     }
86 }
87
88 interface CollectionInfoDataProps {
89     resources: ResourcesState;
90     currentCollection: CollectionResource | undefined;
91 }
92
93 interface CollectionInfoDispatchProps {
94     editCollection: (collection: CollectionResource | undefined) => void;
95 }
96
97 const ciMapStateToProps = (state: RootState): CollectionInfoDataProps => {
98     return {
99         resources: state.resources,
100         currentCollection: getResource<CollectionResource>(state.detailsPanel.resourceUuid)(state.resources),
101     };
102 };
103
104 const ciMapDispatchToProps = (dispatch: Dispatch): CollectionInfoDispatchProps => ({
105     editCollection: (collection: CollectionResource) =>
106         dispatch<any>(openCollectionUpdateDialog({
107             uuid: collection.uuid,
108             name: collection.name,
109             description: collection.description,
110             properties: collection.properties,
111             storageClassesDesired: collection.storageClassesDesired,
112         })),
113 });
114
115 type CollectionInfoProps = CollectionInfoDataProps & CollectionInfoDispatchProps & WithStyles<CssRules>;
116
117 const CollectionInfo = withStyles(styles)(
118     connect(ciMapStateToProps, ciMapDispatchToProps)(
119         ({ currentCollection, resources, editCollection, classes }: CollectionInfoProps) =>
120             currentCollection !== undefined
121                 ? <div>
122                     <Button
123                         disabled={resourceIsFrozen(currentCollection, resources)}
124                         className={classes.editButton} variant='contained'
125                         data-cy='details-panel-edit-btn' color='primary' size='small'
126                         onClick={() => editCollection(currentCollection)}>
127                         <RenameIcon className={classes.editIcon} /> Edit
128                     </Button>
129                     <CollectionDetailsAttributes classes={classes} twoCol={false} item={currentCollection} />
130                 </div>
131                 : <div />
132     )
133 );
134
135 interface CollectionVersionBrowserProps {
136     currentCollection: CollectionResource | undefined;
137     versions: CollectionResource[];
138 }
139
140 interface CollectionVersionBrowserDispatchProps {
141     showVersion: (c: CollectionResource) => void;
142     handleContextMenu: (event: React.MouseEvent<HTMLElement>, collection: CollectionResource) => void;
143 }
144
145 const vbMapStateToProps = (state: RootState): CollectionVersionBrowserProps => {
146     const currentCollection = getResource<CollectionResource>(state.detailsPanel.resourceUuid)(state.resources);
147     const versions = (currentCollection
148         && filterResources(rsc =>
149             (rsc as CollectionResource).currentVersionUuid === currentCollection.currentVersionUuid)(state.resources)
150                 .sort((a: CollectionResource, b: CollectionResource) => b.version - a.version) as CollectionResource[])
151         || [];
152     return { currentCollection, versions };
153 };
154
155 const vbMapDispatchToProps = () =>
156     (dispatch: Dispatch): CollectionVersionBrowserDispatchProps => ({
157         showVersion: (collection) => dispatch<any>(navigateTo(collection.uuid)),
158         handleContextMenu: (event: React.MouseEvent<HTMLElement>, collection: CollectionResource) => {
159             const menuKind = dispatch<any>(resourceUuidToContextMenuKind(collection.uuid));
160             if (collection && menuKind) {
161                 dispatch<any>(openContextMenu(event, {
162                     name: collection.name,
163                     uuid: collection.uuid,
164                     description: collection.description,
165                     storageClassesDesired: collection.storageClassesDesired,
166                     ownerUuid: collection.ownerUuid,
167                     isTrashed: collection.isTrashed,
168                     kind: collection.kind,
169                     menuKind
170                 }));
171             }
172         },
173     });
174
175 const CollectionVersionBrowser = withStyles(styles)(
176     connect(vbMapStateToProps, vbMapDispatchToProps)(
177         ({ currentCollection, versions, showVersion, handleContextMenu, classes }: CollectionVersionBrowserProps & CollectionVersionBrowserDispatchProps & WithStyles<CssRules>) => {
178             return <div data-cy="collection-version-browser">
179                 <Grid container>
180                     <Grid item xs={2}>
181                         <Typography variant="caption" className={classes.versionBrowserHeader}>
182                             Nr
183                         </Typography>
184                     </Grid>
185                     <Grid item xs={4}>
186                         <Typography variant="caption" className={classes.versionBrowserHeader}>
187                             Size
188                         </Typography>
189                     </Grid>
190                     <Grid item xs={6}>
191                         <Typography variant="caption" className={classes.versionBrowserHeader}>
192                             Date
193                         </Typography>
194                     </Grid>
195                 { versions.map(item => {
196                     const isSelectedVersion = !!(currentCollection && currentCollection.uuid === item.uuid);
197                     return (
198                         <ListItem button style={{padding: '4px'}}
199                             data-cy={`collection-version-browser-select-${item.version}`}
200                             key={item.version}
201                             onClick={e => showVersion(item)}
202                             onContextMenu={event => handleContextMenu(event, item)}
203                             selected={isSelectedVersion}
204                             className={classes.versionBrowserItem}>
205                             <Grid item xs={2}>
206                                 <Typography variant="caption" className={classes.versionBrowserField}>
207                                     {item.version}
208                                 </Typography>
209                             </Grid>
210                             <Grid item xs={4}>
211                                 <Typography variant="caption" className={classes.versionBrowserField}>
212                                     {formatFileSize(item.fileSizeTotal)}
213                                 </Typography>
214                             </Grid>
215                             <Grid item xs={6}>
216                                 <Typography variant="caption" className={classes.versionBrowserField}>
217                                     {formatDate(item.modifiedAt)}
218                                 </Typography>
219                             </Grid>
220                             <Grid item xs={12}>
221                                 <Typography variant="caption" className={classes.versionBrowserField}>
222                                     Modified by: <UserNameFromID uuid={item.modifiedByUserUuid} />
223                                 </Typography>
224                             </Grid>
225                         </ListItem>
226                     );
227                 })}
228                 </Grid>
229             </div>;
230         }));