21720: replaced x8 spacing with indexed values
[arvados.git] / services / workbench2 / src / views / run-process-panel / inputs / file-input.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 { memoize } from 'lodash/fp';
7 import {
8     isRequiredInput,
9     FileCommandInputParameter,
10     File,
11     CWLType
12 } from 'models/workflow';
13 import { Field } from 'redux-form';
14 import { ERROR_MESSAGE } from 'validators/require';
15 import { CustomStyleRulesCallback } from 'common/custom-theme';
16 import { Input, Dialog, DialogTitle, DialogContent, DialogActions, Button, withStyles, WithStyles } from '@material-ui/core';
17 import { GenericInputProps, GenericInput } from './generic-input';
18 import { ProjectsTreePicker } from 'views-components/projects-tree-picker/projects-tree-picker';
19 import { connect, DispatchProp } from 'react-redux';
20 import { initProjectsTreePicker } from 'store/tree-picker/tree-picker-actions';
21 import { TreeItem } from 'components/tree/tree';
22 import { ProjectsTreePickerItem } from 'store/tree-picker/tree-picker-middleware';
23 import { CollectionFile, CollectionFileType } from 'models/collection-file';
24
25 export interface FileInputProps {
26     input: FileCommandInputParameter;
27     options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
28 }
29
30 type DialogContentCssRules = 'root' | 'pickerWrapper';
31
32 export const FileInput = ({ input, options }: FileInputProps) =>
33     <Field
34         name={input.id}
35         commandInput={input}
36         component={FileInputComponent as any}
37         format={format}
38         parse={parse}
39         {...{
40             options
41         }}
42         validate={getValidation(input)} />;
43
44 const format = (value?: File) => value ? value.basename : '';
45
46 const parse = (file: CollectionFile): File => ({
47     class: CWLType.FILE,
48     location: `keep:${file.id}`,
49     basename: file.name,
50 });
51
52 const getValidation = memoize(
53     (input: FileCommandInputParameter) => ([
54         isRequiredInput(input)
55             ? (file?: File) => file ? undefined : ERROR_MESSAGE
56             : () => undefined,
57     ]));
58
59 interface FileInputComponentState {
60     open: boolean;
61     file?: CollectionFile;
62 }
63
64 const FileInputComponent = connect()(
65     class FileInputComponent extends React.Component<GenericInputProps & DispatchProp & {
66         options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
67     }, FileInputComponentState> {
68         state: FileInputComponentState = {
69             open: false,
70         };
71
72         componentDidMount() {
73             this.props.dispatch<any>(
74                 initProjectsTreePicker(this.props.commandInput.id));
75         }
76
77         render() {
78             return <>
79                 {this.renderInput()}
80                 <this.dialog />
81             </>;
82         }
83
84         openDialog = () => {
85             this.componentDidMount();
86             this.setState({ open: true });
87         }
88
89         closeDialog = () => {
90             this.setState({ open: false });
91         }
92
93         submit = () => {
94             this.closeDialog();
95             this.props.input.onChange(this.state.file);
96         }
97
98         setFile = (_: {}, { data }: TreeItem<ProjectsTreePickerItem>) => {
99             if ('type' in data && data.type === CollectionFileType.FILE) {
100                 this.setState({ file: data });
101             } else {
102                 this.setState({ file: undefined });
103             }
104         }
105
106         renderInput() {
107             return <GenericInput
108                 component={props =>
109                     <Input
110                         readOnly
111                         fullWidth
112                         disabled={props.commandInput.disabled}
113                         value={props.input.value}
114                         error={props.meta.touched && !!props.meta.error}
115                         onClick={!props.commandInput.disabled ? this.openDialog : undefined}
116                         onKeyPress={!props.commandInput.disabled ? this.openDialog : undefined} />}
117                 {...this.props} />;
118         }
119
120         dialogContentStyles: CustomStyleRulesCallback<DialogContentCssRules> = ({ spacing }) => ({
121             root: {
122                 display: 'flex',
123                 flexDirection: 'column',
124             },
125             pickerWrapper: {
126                 flexBasis: `${spacing(8)}vh`,
127                 flexShrink: 1,
128                 minHeight: 0,
129                 display: 'flex',
130                 flexDirection: 'column',
131             },
132         });
133
134         dialog = withStyles(this.dialogContentStyles)(
135             ({ classes }: WithStyles<DialogContentCssRules>) =>
136                 <Dialog
137                     open={this.state.open}
138                     onClose={this.closeDialog}
139                     fullWidth
140                     data-cy="choose-a-file-dialog"
141                     maxWidth='md'>
142                     <DialogTitle>Choose a file</DialogTitle>
143                     <DialogContent className={classes.root}>
144                         <div className={classes.pickerWrapper}>
145                             <ProjectsTreePicker
146                                 pickerId={this.props.commandInput.id}
147                                 includeCollections
148                                 includeDirectories
149                                 includeFiles
150                                 cascadeSelection={false}
151                                 options={this.props.options}
152                                 toggleItemActive={this.setFile} />
153                         </div>
154                     </DialogContent>
155                     <DialogActions>
156                         <Button onClick={this.closeDialog}>Cancel</Button>
157                         <Button
158                             disabled={!this.state.file}
159                             variant='contained'
160                             color='primary'
161                             onClick={this.submit}>Ok</Button>
162                     </DialogActions>
163                 </Dialog >
164         );
165     });