@rjsf/core
Version:
A simple React component capable of building HTML forms out of a JSON schema.
127 lines (126 loc) • 5.42 kB
JavaScript
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useMemo } from 'react';
import { dataURItoBlob, getTemplate, TranslatableString, } from '@rjsf/utils';
import Markdown from 'markdown-to-jsx';
function addNameToDataURL(dataURL, name) {
if (dataURL === null) {
return null;
}
return dataURL.replace(';base64', `;name=${encodeURIComponent(name)};base64`);
}
function processFile(file) {
const { name, size, type } = file;
return new Promise((resolve, reject) => {
const reader = new window.FileReader();
reader.onerror = reject;
reader.onload = (event) => {
if (typeof event.target?.result === 'string') {
resolve({
dataURL: addNameToDataURL(event.target.result, name),
name,
size,
type,
});
}
else {
resolve({
dataURL: null,
name,
size,
type,
});
}
};
reader.readAsDataURL(file);
});
}
function processFiles(files) {
return Promise.all(Array.from(files).map(processFile));
}
function FileInfoPreview({ fileInfo, registry, }) {
const { translateString } = registry;
const { dataURL, type, name } = fileInfo;
if (!dataURL) {
return null;
}
// If type is JPEG or PNG then show image preview.
// Originally, any type of image was supported, but this was changed into a whitelist
// since SVGs and animated GIFs are also images, which are generally considered a security risk.
if (['image/jpeg', 'image/png'].includes(type)) {
return _jsx("img", { src: dataURL, style: { maxWidth: '100%' }, className: 'file-preview' });
}
// otherwise, let users download file
return (_jsxs(_Fragment, { children: [' ', _jsx("a", { download: `preview-${name}`, href: dataURL, className: 'file-download', children: translateString(TranslatableString.PreviewLabel) })] }));
}
function FilesInfo({ filesInfo, registry, preview, onRemove, options, }) {
if (filesInfo.length === 0) {
return null;
}
const { translateString } = registry;
const { RemoveButton } = getTemplate('ButtonTemplates', registry, options);
return (_jsx("ul", { className: 'file-info', children: filesInfo.map((fileInfo, key) => {
const { name, size, type } = fileInfo;
const handleRemove = () => onRemove(key);
return (_jsxs("li", { children: [_jsx(Markdown, { children: translateString(TranslatableString.FilesInfo, [name, type, String(size)]) }), preview && _jsx(FileInfoPreview, { fileInfo: fileInfo, registry: registry }), _jsx(RemoveButton, { onClick: handleRemove, registry: registry })] }, key));
}) }));
}
function extractFileInfo(dataURLs) {
return dataURLs.reduce((acc, dataURL) => {
if (!dataURL) {
return acc;
}
try {
const { blob, name } = dataURItoBlob(dataURL);
return [
...acc,
{
dataURL,
name: name,
size: blob.size,
type: blob.type,
},
];
}
catch (e) {
// Invalid dataURI, so just ignore it.
return acc;
}
}, []);
}
/**
* The `FileWidget` is a widget for rendering file upload fields.
* It is typically used with a string property with data-url format.
*/
function FileWidget(props) {
const { disabled, readonly, required, multiple, onChange, value, options, registry } = props;
const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
const handleChange = useCallback((event) => {
if (!event.target.files) {
return;
}
// Due to variances in themes, dealing with multiple files for the array case now happens one file at a time.
// This is because we don't pass `multiple` into the `BaseInputTemplate` anymore. Instead, we deal with the single
// file in each event and concatenate them together ourselves
processFiles(event.target.files).then((filesInfoEvent) => {
const newValue = filesInfoEvent.map((fileInfo) => fileInfo.dataURL);
if (multiple) {
onChange(value.concat(newValue));
}
else {
onChange(newValue[0]);
}
});
}, [multiple, value, onChange]);
const filesInfo = useMemo(() => extractFileInfo(Array.isArray(value) ? value : [value]), [value]);
const rmFile = useCallback((index) => {
if (multiple) {
const newValue = value.filter((_, i) => i !== index);
onChange(newValue);
}
else {
onChange(undefined);
}
}, [multiple, value, onChange]);
return (_jsxs("div", { children: [_jsx(BaseInputTemplate, { ...props, disabled: disabled || readonly, type: 'file', required: value ? false : required, onChangeOverride: handleChange, value: '', accept: options.accept ? String(options.accept) : undefined }), _jsx(FilesInfo, { filesInfo: filesInfo, onRemove: rmFile, registry: registry, preview: options.filePreview, options: options })] }));
}
export default FileWidget;