Merge branch '18692-frozen-projects-workbench-support' into main
[arvados-workbench2.git] / 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 { Input, Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@material-ui/core';
16 import { GenericInputProps, GenericInput } from './generic-input';
17 import { ProjectsTreePicker } from 'views-components/projects-tree-picker/projects-tree-picker';
18 import { connect, DispatchProp } from 'react-redux';
19 import { initProjectsTreePicker } from 'store/tree-picker/tree-picker-actions';
20 import { TreeItem } from 'components/tree/tree';
21 import { ProjectsTreePickerItem } from 'views-components/projects-tree-picker/generic-projects-tree-picker';
22 import { CollectionFile, CollectionFileType } from 'models/collection-file';
23
24 export interface FileInputProps {
25     input: FileCommandInputParameter;
26     options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
27 }
28 export const FileInput = ({ input, options }: FileInputProps) =>
29     <Field
30         name={input.id}
31         commandInput={input}
32         component={FileInputComponent as any}
33         format={format}
34         parse={parse}
35         {...{
36             options
37         }}
38         validate={getValidation(input)} />;
39
40 const format = (value?: File) => value ? value.basename : '';
41
42 const parse = (file: CollectionFile): File => ({
43     class: CWLType.FILE,
44     location: `keep:${file.id}`,
45     basename: file.name,
46 });
47
48 const getValidation = memoize(
49     (input: FileCommandInputParameter) => ([
50         isRequiredInput(input)
51             ? (file?: File) => file ? undefined : ERROR_MESSAGE
52             : () => undefined,
53     ]));
54
55 interface FileInputComponentState {
56     open: boolean;
57     file?: CollectionFile;
58 }
59
60 const FileInputComponent = connect()(
61     class FileInputComponent extends React.Component<GenericInputProps & DispatchProp & {
62         options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
63     }, FileInputComponentState> {
64         state: FileInputComponentState = {
65             open: false,
66         };
67
68         componentDidMount() {
69             this.props.dispatch<any>(
70                 initProjectsTreePicker(this.props.commandInput.id));
71         }
72
73         render() {
74             return <>
75                 {this.renderInput()}
76                 {this.renderDialog()}
77             </>;
78         }
79
80         openDialog = () => {
81             this.componentDidMount();
82             this.setState({ open: true });
83         }
84
85         closeDialog = () => {
86             this.setState({ open: false });
87         }
88
89         submit = () => {
90             this.closeDialog();
91             this.props.input.onChange(this.state.file);
92         }
93
94         setFile = (_: {}, { data }: TreeItem<ProjectsTreePickerItem>) => {
95             if ('type' in data && data.type === CollectionFileType.FILE) {
96                 this.setState({ file: data });
97             } else {
98                 this.setState({ file: undefined });
99             }
100         }
101
102         renderInput() {
103             return <GenericInput
104                 component={props =>
105                     <Input
106                         readOnly
107                         fullWidth
108                         disabled={props.commandInput.disabled}
109                         value={props.input.value}
110                         error={props.meta.touched && !!props.meta.error}
111                         onClick={!props.commandInput.disabled ? this.openDialog : undefined}
112                         onKeyPress={!props.commandInput.disabled ? this.openDialog : undefined} />}
113                 {...this.props} />;
114         }
115
116         renderDialog() {
117             return <Dialog
118                 open={this.state.open}
119                 onClose={this.closeDialog}
120                 fullWidth
121                 data-cy="choose-a-file-dialog"
122                 maxWidth='md'>
123                 <DialogTitle>Choose a file</DialogTitle>
124                 <DialogContent>
125                     <ProjectsTreePicker
126                         pickerId={this.props.commandInput.id}
127                         includeCollections
128                         includeFiles
129                         options={this.props.options}
130                         toggleItemActive={this.setFile} />
131                 </DialogContent>
132                 <DialogActions>
133                     <Button onClick={this.closeDialog}>Cancel</Button>
134                     <Button
135                         disabled={!this.state.file}
136                         variant='contained'
137                         color='primary'
138                         onClick={this.submit}>Ok</Button>
139                 </DialogActions>
140             </Dialog>;
141         }
142
143     });
144
145