17436: fixed workflow cration favorites list
[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 * as 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}
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.setState({ open: true });
82         }
83
84         closeDialog = () => {
85             this.setState({ open: false });
86         }
87
88         submit = () => {
89             this.closeDialog();
90             this.props.input.onChange(this.state.file);
91         }
92
93         setFile = (_: {}, { data }: TreeItem<ProjectsTreePickerItem>) => {
94             if ('type' in data && data.type === CollectionFileType.FILE) {
95                 this.setState({ file: data });
96             } else {
97                 this.setState({ file: undefined });
98             }
99         }
100
101         renderInput() {
102             return <GenericInput
103                 component={props =>
104                     <Input
105                         readOnly
106                         fullWidth
107                         disabled={props.commandInput.disabled}
108                         value={props.input.value}
109                         error={props.meta.touched && !!props.meta.error}
110                         onClick={!props.commandInput.disabled ? this.openDialog : undefined}
111                         onKeyPress={!props.commandInput.disabled ? this.openDialog : undefined} />}
112                 {...this.props} />;
113         }
114
115         renderDialog() {
116             return <Dialog
117                 open={this.state.open}
118                 onClose={this.closeDialog}
119                 fullWidth
120                 data-cy="choose-a-file-dialog"
121                 maxWidth='md'>
122                 <DialogTitle>Choose a file</DialogTitle>
123                 <DialogContent>
124                     <ProjectsTreePicker
125                         pickerId={this.props.commandInput.id}
126                         includeCollections
127                         includeFiles
128                         options={this.props.options}
129                         toggleItemActive={this.setFile} />
130                 </DialogContent>
131                 <DialogActions>
132                     <Button onClick={this.closeDialog}>Cancel</Button>
133                     <Button
134                         disabled={!this.state.file}
135                         variant='contained'
136                         color='primary'
137                         onClick={this.submit}>Ok</Button>
138                 </DialogActions>
139             </Dialog>;
140         }
141
142     });
143
144