-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathFileUploadForm.tsx
More file actions
110 lines (96 loc) · 3.72 KB
/
FileUploadForm.tsx
File metadata and controls
110 lines (96 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import { useCallback, useContext, useState } from 'react';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { ApolloError } from '@apollo/client';
import { ErrorDisplay } from '@neinteractiveliterature/litform';
import useAsyncFunction from '../useAsyncFunction';
import FileInputWithPreview from '../CmsAdmin/CmsFilesAdmin/FileInputWithPreview';
import { DirectUpload, DirectUploadDelegate, Blob } from '@rails/activestorage';
import RailsDirectUploadsContext from '../RailsDirectUploadsContext';
import classNames from 'classnames';
import AuthenticityTokensManager from '../AuthenticityTokensContext';
function uploadFile(file: File, directUploadURL: string, onProgress?: (event: ProgressEvent<XMLHttpRequest>) => void) {
return new Promise<Blob | undefined>((resolve, reject) => {
const delegate: DirectUploadDelegate = {
directUploadWillStoreFileWithXHR: (xhr) => {
if (onProgress) {
xhr.upload.addEventListener('progress', onProgress);
}
},
};
const upload = new DirectUpload(file, directUploadURL, delegate);
upload.create((error, blob) => {
if (error) {
reject(error);
} else {
resolve(blob);
}
});
});
}
export type FileUploadFormProps = {
onUpload?: (blob: Blob, file: File) => unknown;
};
function FileUploadForm({ onUpload }: FileUploadFormProps): React.JSX.Element {
const { t } = useTranslation();
const [file, setFile] = useState<File | null | undefined>();
const [uploadAsync, error, uploading] = useAsyncFunction(uploadFile);
const [progressPercent, setProgressPercent] = useState<number>(0);
const [progressIndeterminate, setProgressIndeterminate] = useState(false);
const { railsDirectUploadsUrl } = useContext(RailsDirectUploadsContext);
const { railsDirectUploads: directUploadsAuthenticityToken } = AuthenticityTokensManager.instance.tokens;
const onProgress = useCallback((event: ProgressEvent<XMLHttpRequest>) => {
setProgressIndeterminate(!event.lengthComputable);
if (event.lengthComputable) {
setProgressPercent((event.loaded / event.total) * 100);
}
}, []);
const uploadFormSubmitted = async (event: React.FormEvent) => {
event.preventDefault();
event.stopPropagation();
if (!file) {
return;
}
const blob = await uploadAsync(file, railsDirectUploadsUrl, onProgress);
if (blob && onUpload) {
await onUpload(blob, file);
}
setFile(null);
};
return (
<div className="card">
<div className="card-header">{t('cms.fileUploadForm.title')}</div>
<div className="card-body">
{/* ActiveStorage JS requires us to put the csrf token in the head */}
<meta name="csrf-token" content={directUploadsAuthenticityToken} />
<FileInputWithPreview file={file} onChange={setFile} disabled={uploading} />
<ErrorDisplay graphQLError={error as ApolloError} />
{uploading && (
<div className="progress">
<div
className={classNames('progress-bar progress-bar-striped', {
'progress-bar-animated': progressIndeterminate,
})}
role="progressbar"
aria-valuenow={progressIndeterminate ? progressPercent : 100}
aria-valuemin={0}
aria-valuemax={100}
aria-label="Upload progress"
/>
</div>
)}
<div className="mt-2">
<button
type="submit"
onClick={uploadFormSubmitted}
className="btn btn-primary me-4"
disabled={!file || uploading}
>
{t('cms.fileUploadForm.uploadFileButton')}
</button>
</div>
</div>
</div>
);
}
export default FileUploadForm;