20085: Fix quirky behavior when removing share with public/all users row
[arvados-workbench2.git] / src / views-components / sharing-dialog / sharing-dialog-component.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 {
7     Dialog,
8     DialogTitle,
9     Button,
10     Grid,
11     DialogContent,
12     CircularProgress,
13     Paper,
14     Tabs,
15     Tab,
16     Checkbox,
17     FormControlLabel,
18     Typography,
19     Tooltip,
20 } from '@material-ui/core';
21 import {
22     StyleRulesCallback,
23     WithStyles,
24     withStyles
25 } from '@material-ui/core/styles';
26 import { DialogActions } from 'components/dialog-actions/dialog-actions';
27 import { SharingURLsContent } from './sharing-urls';
28 import {
29     extractUuidObjectType,
30     ResourceObjectType
31 } from 'models/resource';
32 import { SharingInvitationForm } from './sharing-invitation-form';
33 import { SharingManagementForm } from './sharing-management-form';
34 import {
35     BasePicker,
36     Calendar,
37     MuiPickersUtilsProvider,
38     TimePickerView
39 } from 'material-ui-pickers';
40 import DateFnsUtils from "@date-io/date-fns";
41 import moment from 'moment';
42 import { SharingPublicAccessForm } from './sharing-public-access-form';
43 import { AddIcon } from 'components/icon/icon';
44
45 export interface SharingDialogDataProps {
46     open: boolean;
47     loading: boolean;
48     saveEnabled: boolean;
49     sharedResourceUuid: string;
50     sharingURLsNr: number;
51     privateAccess: boolean;
52     sharingURLsDisabled: boolean;
53     permissions: any[];
54 }
55 export interface SharingDialogActionProps {
56     onClose: () => void;
57     onSave: () => void;
58     onCreateSharingToken: (d: Date | undefined) => () => void;
59     refreshPermissions: () => void;
60 }
61 enum SharingDialogTab {
62     PERMISSIONS = 0,
63     URLS = 1,
64 }
65 export type SharingDialogComponentProps = SharingDialogDataProps & SharingDialogActionProps;
66
67 export default (props: SharingDialogComponentProps) => {
68     const { open, loading, saveEnabled, sharedResourceUuid,
69         sharingURLsNr, privateAccess, sharingURLsDisabled, permissions,
70         onClose, onSave, onCreateSharingToken, refreshPermissions } = props;
71     const showTabs = !sharingURLsDisabled && extractUuidObjectType(sharedResourceUuid) === ResourceObjectType.COLLECTION;
72     const [tabNr, setTabNr] = React.useState<number>(SharingDialogTab.PERMISSIONS);
73     const [expDate, setExpDate] = React.useState<Date>();
74     const [withExpiration, setWithExpiration] = React.useState<boolean>(false);
75
76     // Sets up the dialog depending on the resource type
77     if (!showTabs && tabNr !== SharingDialogTab.PERMISSIONS) {
78         setTabNr(SharingDialogTab.PERMISSIONS);
79     }
80
81     React.useEffect(() => {
82         if (!withExpiration) {
83             setExpDate(undefined);
84         } else {
85             setExpDate(moment().add(2, 'hour').minutes(0).seconds(0).toDate());
86         }
87     }, [withExpiration]);
88
89     return <Dialog
90         {...{ open, onClose }}
91         className="sharing-dialog"
92         fullWidth
93         maxWidth='md'
94         disableBackdropClick={saveEnabled}
95         disableEscapeKeyDown={saveEnabled}>
96         <DialogTitle>
97             Sharing settings
98         </DialogTitle>
99         {showTabs &&
100             <Tabs value={tabNr}
101                 onChange={(_, tb) => {
102                     if (tb === SharingDialogTab.PERMISSIONS) {
103                         refreshPermissions();
104                     }
105                     setTabNr(tb)
106                 }
107                 }>
108                 <Tab label="With users/groups" />
109                 <Tab label={`Sharing URLs ${sharingURLsNr > 0 ? '(' + sharingURLsNr + ')' : ''}`} disabled={saveEnabled} />
110             </Tabs>
111         }
112         <DialogContent>
113             {tabNr === SharingDialogTab.PERMISSIONS &&
114                 <Grid container direction='column' spacing={24}>
115                     <Grid item>
116                         <SharingPublicAccessForm onSave={onSave} />
117                     </Grid>
118                     <Grid item>
119                         <SharingManagementForm onSave={onSave} />
120                     </Grid>
121                 </Grid>
122             }
123             {tabNr === SharingDialogTab.URLS &&
124                 <SharingURLsContent uuid={sharedResourceUuid} />
125             }
126         </DialogContent>
127         <DialogActions>
128             <Grid container spacing={8}>
129                 {tabNr === SharingDialogTab.PERMISSIONS &&
130                     <Grid item md={12}>
131                         <SharingInvitationForm onSave={onSave} saveEnabled={saveEnabled} />
132                     </Grid>
133                 }
134                 {tabNr === SharingDialogTab.URLS && withExpiration && <>
135                     <Grid item container direction='row' md={12}>
136                         <MuiPickersUtilsProvider utils={DateFnsUtils}>
137                             <BasePicker autoOk value={expDate} onChange={setExpDate}>
138                                 {({ date, handleChange }) => (<>
139                                     <Grid item md={6}>
140                                         <Calendar date={date} minDate={new Date()} maxDate={undefined}
141                                             onChange={handleChange} />
142                                     </Grid>
143                                     <Grid item md={6}>
144                                         <TimePickerView type="hours" date={date} ampm={false}
145                                             onMinutesChange={() => { }}
146                                             onSecondsChange={() => { }}
147                                             onHourChange={handleChange}
148                                         />
149                                     </Grid>
150                                 </>)}
151                             </BasePicker>
152                         </MuiPickersUtilsProvider>
153                     </Grid>
154                     <Grid item md={12}>
155                         <Typography variant='caption' align='center'>
156                             Maximum expiration date may be limited by the cluster configuration.
157                         </Typography>
158                     </Grid>
159                 </>
160                 }
161                 {tabNr === SharingDialogTab.PERMISSIONS && !sharingURLsDisabled &&
162                     privateAccess && sharingURLsNr > 0 &&
163                     <Grid item md={12}>
164                         <Typography variant='caption' align='center' color='error'>
165                             Although there aren't specific permissions set, this is publicly accessible via Sharing URL(s).
166                         </Typography>
167                     </Grid>
168                 }
169                 <Grid item xs />
170                 {tabNr === SharingDialogTab.URLS && <>
171                     <Grid item><FormControlLabel
172                         control={<Checkbox color="primary" checked={withExpiration}
173                             onChange={(e) => setWithExpiration(e.target.checked)} />}
174                         label="With expiration" />
175                     </Grid>
176                     <Grid item>
177                         <Button variant="contained" color="primary"
178                             disabled={expDate !== undefined && expDate <= new Date()}
179                             onClick={onCreateSharingToken(expDate)}>
180                             Create sharing URL
181                         </Button>
182                     </Grid>
183                 </>
184                 }
185                 <Grid item>
186                     <Button onClick={() => {
187                         onClose();
188                         setWithExpiration(false);
189                     }}>
190                         Close
191                     </Button>
192                 </Grid>
193             </Grid>
194         </DialogActions>
195         {
196             loading && <LoadingIndicator />
197         }
198     </Dialog>;
199 };
200
201 const loadingIndicatorStyles: StyleRulesCallback<'root'> = theme => ({
202     root: {
203         position: 'absolute',
204         top: 0,
205         right: 0,
206         bottom: 0,
207         left: 0,
208         display: 'flex',
209         alignItems: 'center',
210         justifyContent: 'center',
211         backgroundColor: 'rgba(255, 255, 255, 0.8)',
212     },
213 });
214
215 const LoadingIndicator = withStyles(loadingIndicatorStyles)(
216     (props: WithStyles<'root'>) =>
217         <Paper classes={props.classes}>
218             <CircularProgress />
219         </Paper>
220 );