Skip to content

Commit df10092

Browse files
Merge pull request #15386 from logonoff/OCPBUGS-45297-yaml
OCPBUGS-45297: Refactor DroppableEditYAML
2 parents 2917cba + fa4a7b3 commit df10092

5 files changed

Lines changed: 258 additions & 251 deletions

File tree

frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ export type CodeEditorRef = {
677677
};
678678

679679
export type ResourceYAMLEditorProps = {
680-
initialResource: string | { [key: string]: any };
680+
initialResource: K8sResourceKind;
681681
header?: string;
682682
onSave?: (content: string) => void;
683683
readOnly?: boolean;

frontend/public/components/droppable-edit-yaml.tsx

Lines changed: 160 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,177 @@
1-
/* eslint-disable @typescript-eslint/no-use-before-define */
2-
import * as React from 'react';
3-
import * as _ from 'lodash-es';
4-
import { NativeTypes } from 'react-dnd-html5-backend';
5-
import { DropTarget } from 'react-dnd';
1+
import { useState, useCallback } from 'react';
62
import { ResourceYAMLEditorProps } from '@console/dynamic-plugin-sdk';
3+
import { isText } from 'istextorbinary';
74

85
import { EditYAML } from './edit-yaml';
9-
import withDragDropContext from './utils/drag-drop-context';
10-
import { DropTargetMonitor } from 'react-dnd/lib/interfaces';
11-
import * as ITOB from 'istextorbinary';
6+
import {
7+
DropEvent,
8+
DropzoneErrorCode,
9+
MultipleFileUpload,
10+
MultipleFileUploadProps,
11+
} from '@patternfly/react-core';
12+
import { useTranslation } from 'react-i18next';
13+
import { units } from './utils';
1214

1315
// Maximal file size, in bytes, that user can upload
1416
const maxFileUploadSize = 4000000;
15-
const fileSizeErrorMsg = 'Maximum file size exceeded. File limit is 4MB.';
16-
const fileTypeErrorMsg = 'Binary file detected. Edit text based YAML files only.';
17-
18-
const boxTarget = {
19-
drop(props, monitor) {
20-
if (props.onDrop && monitor.isOver()) {
21-
props.onDrop(props, monitor);
22-
}
23-
},
24-
};
2517

26-
const EditYAMLComponent = DropTarget(NativeTypes.FILE, boxTarget, (connectObj, monitor) => ({
27-
connectDropTarget: connectObj.dropTarget(),
28-
isOver: monitor.isOver(),
29-
canDrop: monitor.canDrop(),
30-
}))(EditYAML as React.FC<EditYAMLProps>);
18+
export type DroppedFile = {
19+
error?: string;
20+
id: string;
21+
name: string;
22+
size: number;
23+
};
3124

3225
type DroppableEditYAMLProps = ResourceYAMLEditorProps & {
3326
allowMultiple?: boolean;
3427
isCodeImportRedirect?: boolean;
3528
};
3629

30+
const useDropErrorMessage = (): ((errorCode: DropzoneErrorCode, fileName: string) => string) => {
31+
const { t } = useTranslation('public');
32+
33+
return useCallback(
34+
(errorCode, fileName) => {
35+
switch (errorCode) {
36+
case 'file-too-large':
37+
return t(
38+
'Ignoring {{ fileName }}: Maximum file size exceeded. File limit is {{ size }}.',
39+
{
40+
fileName,
41+
size: units.humanize(maxFileUploadSize, 'decimalBytes', true).string,
42+
},
43+
);
44+
case 'too-many-files':
45+
return t('Ignoring {{ fileName }}: Too many files. Maximum one file can be uploaded.', {
46+
fileName,
47+
});
48+
case 'file-invalid-type':
49+
return t(
50+
'Ignoring {{ fileName }}: Invalid file type. Only text based YAML files are supported.',
51+
{
52+
fileName,
53+
},
54+
);
55+
default:
56+
return t('An error occurred while uploading {{ fileName }}.', {
57+
fileName,
58+
});
59+
}
60+
},
61+
[t],
62+
);
63+
};
64+
65+
export const DroppableEditYAML: React.FCC<DroppableEditYAMLProps> = ({
66+
allowMultiple,
67+
initialResource,
68+
create = false,
69+
onChange = () => null,
70+
hideHeader = false,
71+
isCodeImportRedirect = false,
72+
...props
73+
}) => {
74+
const { t } = useTranslation('public');
75+
const getDropErrorMessage = useDropErrorMessage();
76+
77+
const [fileUpload, setFileUpload] = useState('');
78+
const [errors, setErrors] = useState<string[]>([]);
79+
80+
const clearFileUpload = useCallback(() => {
81+
setFileUpload('');
82+
setErrors([]);
83+
}, [setFileUpload, setErrors]);
84+
85+
const onFileDrop = useCallback(
86+
(event: DropEvent, files: File[]) => {
87+
event.preventDefault();
88+
clearFileUpload();
89+
const fileAcc = [];
90+
91+
files.forEach((yamlFile: File, i: number) => {
92+
if (!yamlFile) {
93+
return;
94+
}
95+
const lastFile = i === files.length - 1;
96+
const reader = new FileReader();
97+
reader.onload = (ev) => {
98+
const arrayBuffer = ev.target.result as ArrayBuffer;
99+
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
100+
101+
if (isText(null, buffer)) {
102+
fileAcc.push(buffer.toString().trim());
103+
104+
if (!lastFile) {
105+
fileAcc.push('---');
106+
} else {
107+
setFileUpload(fileAcc.join('\n'));
108+
}
109+
}
110+
};
111+
reader.readAsArrayBuffer(yamlFile);
112+
});
113+
},
114+
[clearFileUpload],
115+
);
116+
117+
const onDropRejected: MultipleFileUploadProps['dropzoneProps']['onDropRejected'] = useCallback(
118+
(rejections) => {
119+
setErrors(
120+
rejections.map((rejection) => {
121+
return getDropErrorMessage(
122+
rejection.errors[0].code as DropzoneErrorCode,
123+
rejection.file.name,
124+
);
125+
}),
126+
);
127+
},
128+
[getDropErrorMessage, maxFileUploadSize],
129+
);
130+
131+
return (
132+
<MultipleFileUpload
133+
dropzoneProps={{
134+
maxFiles: allowMultiple ? undefined : 1,
135+
maxSize: maxFileUploadSize,
136+
onDrop: (acceptedFiles, fileRejections, event) => {
137+
clearFileUpload();
138+
139+
if (fileRejections.length > 0) {
140+
onDropRejected(fileRejections, event);
141+
}
142+
if (acceptedFiles.length > 0) {
143+
onFileDrop(event, acceptedFiles as File[]);
144+
}
145+
},
146+
accept: {
147+
'application/yaml': ['.yaml', '.yml'],
148+
},
149+
}}
150+
style={{ display: 'contents' }}
151+
>
152+
<div className="co-file-dropzone co-file-dropzone__flex">
153+
<div className="co-file-dropzone-container">
154+
<p className="co-file-dropzone__drop-text">{t('Drop file here')}</p>
155+
</div>
156+
<EditYAML
157+
{...props}
158+
allowMultiple={allowMultiple}
159+
obj={initialResource}
160+
fileUpload={fileUpload}
161+
error={errors.join('\n')}
162+
clearFileUpload={clearFileUpload}
163+
create={create}
164+
onChange={onChange}
165+
hideHeader={hideHeader}
166+
isCodeImportRedirect={isCodeImportRedirect}
167+
/>
168+
</div>
169+
</MultipleFileUpload>
170+
);
171+
};
172+
37173
// Prevents SDK users from passing additional props
38-
export const ResourceYAMLEditor: React.FC<ResourceYAMLEditorProps> = ({
174+
export const ResourceYAMLEditor: React.FCC<ResourceYAMLEditorProps> = ({
39175
initialResource,
40176
header,
41177
onSave,
@@ -54,127 +190,3 @@ export const ResourceYAMLEditor: React.FC<ResourceYAMLEditorProps> = ({
54190
hideHeader={hideHeader}
55191
/>
56192
);
57-
58-
export const DroppableEditYAML = withDragDropContext<DroppableEditYAMLProps>(
59-
class DroppableEditYAML extends React.Component<DroppableEditYAMLProps, DroppableEditYAMLState> {
60-
private fileUploadContents: string = '';
61-
constructor(props) {
62-
super(props);
63-
this.state = {
64-
fileUpload: '',
65-
errors: [],
66-
};
67-
this.handleFileDrop = this.handleFileDrop.bind(this);
68-
this.clearFileUpload = this.clearFileUpload.bind(this);
69-
}
70-
71-
addDocument(newFileContent: string) {
72-
this.fileUploadContents = _.isEmpty(this.fileUploadContents)
73-
? newFileContent
74-
: `${this.fileUploadContents}\n---\n${newFileContent}`;
75-
}
76-
77-
readFileContents(file, lastFile) {
78-
// If unsupported file type is dropped into drop zone, file will be undefined
79-
if (!file) {
80-
return;
81-
}
82-
// limit size size uploading to 1 mb
83-
if (file.size <= maxFileUploadSize) {
84-
const reader = new FileReader();
85-
reader.onload = () => {
86-
// The type is actually an ArrayBuffer (as we use readAsArrayBuffer), but TS doesn't know that
87-
const buffer = Buffer.from(reader.result as ArrayBuffer);
88-
if (ITOB.isBinary(null, buffer)) {
89-
this.setState((previousState) => ({
90-
errors: [...previousState.errors, `Ignoring ${file.name}: ${fileTypeErrorMsg}`],
91-
}));
92-
} else {
93-
this.addDocument(buffer.toString().trim());
94-
if (lastFile) {
95-
this.setState({ fileUpload: this.fileUploadContents });
96-
}
97-
}
98-
};
99-
reader.readAsArrayBuffer(file);
100-
} else {
101-
this.setState((previousState) => ({
102-
errors: [...previousState.errors, `Ignoring ${file.name}: ${fileSizeErrorMsg}`],
103-
}));
104-
}
105-
}
106-
107-
handleFileDrop(item, monitor) {
108-
const { allowMultiple } = this.props;
109-
if (!monitor) {
110-
return;
111-
}
112-
this.clearFileUpload();
113-
if (allowMultiple) {
114-
monitor.getItem().files.forEach((yamlFile, i) => {
115-
this.readFileContents(yamlFile, i === monitor.getItem().files.length - 1);
116-
});
117-
} else {
118-
const [file] = monitor.getItem().files;
119-
this.readFileContents(file, true);
120-
}
121-
}
122-
123-
clearFileUpload() {
124-
this.setState({ fileUpload: '', errors: [] });
125-
this.fileUploadContents = '';
126-
}
127-
128-
render() {
129-
const {
130-
allowMultiple,
131-
initialResource,
132-
create = false,
133-
onChange = () => null,
134-
hideHeader = false,
135-
isCodeImportRedirect = false,
136-
} = this.props;
137-
const { errors, fileUpload } = this.state;
138-
return (
139-
<EditYAMLComponent
140-
{...this.props}
141-
allowMultiple={allowMultiple}
142-
obj={initialResource}
143-
fileUpload={fileUpload}
144-
error={errors.join('\n')}
145-
onDrop={this.handleFileDrop}
146-
clearFileUpload={this.clearFileUpload}
147-
create={create}
148-
onChange={onChange}
149-
hideHeader={hideHeader}
150-
isCodeImportRedirect={isCodeImportRedirect}
151-
/>
152-
);
153-
}
154-
},
155-
);
156-
157-
type EditYAMLProps = {
158-
allowMultiple?: boolean;
159-
obj: ResourceYAMLEditorProps['initialResource'];
160-
fileUpload: string;
161-
error: string;
162-
onDrop: (item: any, monitor: DropTargetMonitor) => void;
163-
clearFileUpload: () => void;
164-
create?: boolean;
165-
onChange?: (content: string) => void;
166-
hideHeader?: boolean;
167-
isCodeImportRedirect?: boolean;
168-
};
169-
170-
export type DroppedFile = {
171-
error?: string;
172-
id: string;
173-
name: string;
174-
size: number;
175-
};
176-
177-
export type DroppableEditYAMLState = {
178-
errors: string[];
179-
fileUpload: string;
180-
};

0 commit comments

Comments
 (0)