Skip to content

Commit c98fbd6

Browse files
TASK: adjust handling of selected, uploaded and rejected files in upload dialogs, improve UX
1 parent 277cc45 commit c98fbd6

File tree

8 files changed

+97
-23
lines changed

8 files changed

+97
-23
lines changed

Resources/Private/JavaScript/asset-upload-screen/src/NewAssetUpload.tsx

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Button } from '@neos-project/react-ui-components';
55
import { createUseMediaUiStyles, MediaUiTheme, useIntl, useNotify } from '@media-ui/core/src';
66
import { useUploadDialogState, useUploadFiles } from '@media-ui/feature-asset-upload/src/hooks';
77
import { useCallback } from 'react';
8-
import { UploadedFile } from '@media-ui/feature-asset-upload/src/interfaces';
8+
import { FilesUploadState, UploadedFile } from '@media-ui/feature-asset-upload/src/interfaces';
99
import { PreviewSection, UploadSection } from '@media-ui/feature-asset-upload/src/components';
1010

1111
const useStyles = createUseMediaUiStyles((theme: MediaUiTheme) => ({
@@ -30,32 +30,71 @@ const NewAssetUpload = (props: { onComplete: (result: { object: { __identity: st
3030
const handleUpload = useCallback(() => {
3131
uploadFiles(dialogState.files.selected)
3232
.then(({ data: { uploadFiles } }) => {
33+
setFiles((prev) => {
34+
return {
35+
selected: [],
36+
finished: [
37+
...prev.finished,
38+
...prev.selected.filter((file) =>
39+
uploadFiles.find((result) => {
40+
return result.success && result.filename === file.name
41+
? (file.uploadStateResult = result.result)
42+
: false;
43+
})
44+
),
45+
],
46+
rejected: [
47+
...prev.rejected,
48+
...prev.selected.filter((file) =>
49+
uploadFiles.find((result) => {
50+
return !result.success && result.filename === file.name
51+
? (file.uploadStateResult = result.result)
52+
: false;
53+
})
54+
),
55+
],
56+
} as FilesUploadState;
57+
});
3358
if (!uploadFiles[0].success) {
3459
Notify.warning(
60+
translate('uploadDialog.uploadFinishedWithErrors', 'Some files could not be uploaded'),
3561
translate('uploadDialog.uploadFinishedWithErrors', 'Some files could not be uploaded')
3662
);
3763
} else {
3864
Notify.ok(translate('uploadDialog.uploadFinished', 'Upload finished'));
3965
onComplete({ object: { __identity: uploadFiles[0].assetId } });
4066
}
67+
setUploadPossible(false);
4168
})
4269
.catch((error) => {
4370
Notify.error(translate('fileUpload.error', 'Upload failed'), error);
4471
});
45-
}, [uploadFiles, dialogState.files.selected, Notify, translate, onComplete]);
72+
}, [uploadFiles, dialogState.files.selected, setFiles, setUploadPossible, Notify, translate, onComplete]);
4673

4774
const handleSetFiles = useCallback(
4875
(files: UploadedFile[]) => {
4976
setFiles((prev) => {
50-
return { ...prev, selected: files };
77+
const fileNames = new Set();
78+
for (const file of prev.finished.concat(prev.rejected)) {
79+
fileNames.add(file.name);
80+
}
81+
const newSelectedFiles = files.filter((file) => {
82+
return fileNames.has(file.name) ? false : fileNames.add(file.name);
83+
});
84+
return { ...prev, selected: newSelectedFiles };
5185
});
5286
},
5387
[setFiles]
5488
);
5589

5690
return (
5791
<section className={classes.uploadArea}>
58-
<UploadSection files={dialogState.files.selected} loading={loading} onSetFiles={handleSetFiles} />
92+
<UploadSection
93+
files={dialogState.files.selected}
94+
loading={loading}
95+
onSetFiles={handleSetFiles}
96+
maxFiles={1}
97+
/>
5998
<PreviewSection
6099
files={dialogState.files}
61100
loading={loading}

Resources/Private/JavaScript/asset-upload/src/components/Dialogs/NewAssetDialog.tsx

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ const NewAssetDialog: React.FC = () => {
2727
const classes = useStyles();
2828

2929
const handleUpload = useCallback(() => {
30-
uploadFiles(dialogState.files.selected)
30+
const filesToUpload = dialogState.files.selected.filter((file) => !dialogState.files.finished.includes(file));
31+
uploadFiles(filesToUpload)
3132
.then(({ data: { uploadFiles } }) => {
3233
// FIXME: Mapping the uploadState to the files name is not the best solution as the same filename might be used multiple times
3334
// Move uploaded or failed files into separate lists
@@ -37,13 +38,21 @@ const NewAssetDialog: React.FC = () => {
3738
finished: [
3839
...prev.finished,
3940
...prev.selected.filter((file) =>
40-
uploadFiles.find((result) => result.success && result.filename === file.name)
41+
uploadFiles.find((result) => {
42+
return result.success && result.filename === file.name
43+
? (file.uploadStateResult = result.result)
44+
: false;
45+
})
4146
),
4247
],
4348
rejected: [
4449
...prev.rejected,
4550
...prev.selected.filter((file) =>
46-
uploadFiles.find((result) => !result.success && result.filename === file.name)
51+
uploadFiles.find((result) => {
52+
return !result.success && result.filename === file.name
53+
? (file.uploadStateResult = result.result)
54+
: false;
55+
})
4756
),
4857
],
4958
} as FilesUploadState;
@@ -60,16 +69,33 @@ const NewAssetDialog: React.FC = () => {
6069
if (uploadFiles.some((result) => result.success)) {
6170
void refetchAssets();
6271
}
72+
setUploadPossible(false);
6373
})
6474
.catch((error) => {
6575
Notify.error(translate('fileUpload.error', 'Upload failed'), error);
6676
});
67-
}, [uploadFiles, dialogState.files.selected, setFiles, Notify, translate, refetchAssets]);
77+
}, [
78+
setUploadPossible,
79+
dialogState.files.selected,
80+
dialogState.files.finished,
81+
uploadFiles,
82+
setFiles,
83+
Notify,
84+
translate,
85+
refetchAssets,
86+
]);
6887

6988
const handleSetFiles = useCallback(
7089
(files: UploadedFile[]) => {
7190
setFiles((prev) => {
72-
return { ...prev, selected: files };
91+
const fileNames = new Set();
92+
for (const file of prev.finished.concat(prev.rejected)) {
93+
fileNames.add(file.name);
94+
}
95+
const newSelectedFiles = files.filter((file) => {
96+
return fileNames.has(file.name) ? false : fileNames.add(file.name);
97+
});
98+
return { ...prev, selected: newSelectedFiles };
7399
});
74100
},
75101
[setFiles]

Resources/Private/JavaScript/asset-upload/src/components/FilePreview.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,13 @@ const FilePreview: React.FC<FilePreviewProps> = ({
131131
setUploadPossible,
132132
}: FilePreviewProps) => {
133133
const classes = useStyles();
134-
const success = fileState?.success;
135-
const error = fileState && !success;
134+
const success = fileState?.success || dialogState.files.finished.includes(file);
135+
const disabled = success || fileState?.result === 'EXISTS' || dialogState.files.rejected.includes(file);
136+
const error = (fileState && !success) || dialogState.files.rejected.includes(file);
137+
const result =
138+
fileState?.result ||
139+
dialogState.files.rejected[dialogState.files.rejected.indexOf(file)]?.uploadStateResult ||
140+
dialogState.files.finished[dialogState.files.finished.indexOf(file)]?.uploadStateResult;
136141
const { translate } = useIntl();
137142
const { config } = useConfigQuery();
138143
const [copyrightNoticeNotNeededChecked, setCopyrightNoticeNotNeededChecked] = useState(false);
@@ -153,7 +158,10 @@ const FilePreview: React.FC<FilePreviewProps> = ({
153158

154159
const setUploadProperty = (propertyName: string, propertyValue: UploadProperty) => {
155160
const files: UploadedFile[] = [...dialogState.files.selected];
156-
if (files.length === 0) {
161+
const newFile =
162+
files.length === 0 &&
163+
!(dialogState.files.finished.includes(file) || dialogState.files.rejected.includes(file));
164+
if (newFile) {
157165
file[propertyName] = propertyValue;
158166
files.push(file);
159167
} else {
@@ -234,15 +242,15 @@ const FilePreview: React.FC<FilePreviewProps> = ({
234242
{loading && <Icon icon="spinner" spin={true} />}
235243
{success && <Icon icon="check" />}
236244
{error && <Icon icon="exclamation-circle" />}
237-
{fileState?.result && <span>{fileState.result}</span>}
245+
{result && <span>{result}</span>}
238246
</div>
239247
</div>
240248
<div className={classes.properties}>
241249
{uploadPropertiesConfig['title'].show ? (
242250
<Property label={translate('inspector.title', 'Title')}>
243251
<TextInput
244252
className={classes.textInput}
245-
disabled={false}
253+
disabled={disabled}
246254
value={file.title || ''}
247255
onChange={setTitle}
248256
/>
@@ -254,7 +262,7 @@ const FilePreview: React.FC<FilePreviewProps> = ({
254262
<Property label={translate('inspector.caption', 'Caption')}>
255263
<TextArea
256264
className={classes.textArea}
257-
disabled={false}
265+
disabled={disabled}
258266
minRows={2}
259267
expandedRows={4}
260268
value={file.caption || ''}
@@ -269,7 +277,7 @@ const FilePreview: React.FC<FilePreviewProps> = ({
269277
<Property label={translate('inspector.copyrightNotice', 'Copyright notice')}>
270278
<TextArea
271279
className={classes.textArea}
272-
disabled={false}
280+
disabled={disabled}
273281
minRows={2}
274282
expandedRows={4}
275283
value={file.copyrightNotice || ''}
@@ -285,7 +293,7 @@ const FilePreview: React.FC<FilePreviewProps> = ({
285293
>
286294
<CheckBox
287295
onChange={setCopyrightNoticeNotNeeded}
288-
disabled={false}
296+
disabled={disabled}
289297
isChecked={copyrightNoticeNotNeededChecked}
290298
/>
291299
</Property>

Resources/Private/JavaScript/asset-upload/src/interfaces/UploadedFile.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export default interface UploadedFile extends File {
1111
copyrightNoticeNotNeeded?: boolean;
1212
title?: string;
1313
caption?: string;
14+
uploadStateResult?: string;
1415
}

Resources/Public/AssetEditor/Plugin.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Resources/Public/AssetEditor/Plugin.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)