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