@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
323 lines (322 loc) • 12.6 kB
JavaScript
"use client";
import _extends from "@babel/runtime-corejs3/helpers/esm/extends";
import _pushInstanceProperty from "core-js-pure/stable/instance/push.js";
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import classnames from 'classnames';
import FieldBlock from "../../FieldBlock/index.js";
import { useFieldProps, usePath, useTranslation as useFormsTranslation } from "../../hooks/index.js";
import Upload from "../../../../components/Upload.js";
import useUpload, { isFileEqual } from "../../../../components/upload/useUpload.js";
import { pickSpacingProps } from "../../../../components/flex/utils.js";
import HelpButtonInline, { HelpButtonInlineContent } from "../../../../components/help-button/HelpButtonInline.js";
import { useTranslation as useSharedTranslation } from "../../../../shared/index.js";
import { FormError } from "../../utils/index.js";
import { useIterateItemNo } from "../../Iterate/ItemNo/useIterateItemNo.js";
function UploadComponent(props) {
const sharedTr = useSharedTranslation().Upload;
const formsTr = useFormsTranslation().Upload;
const errorMessages = useMemo(() => ({
'Field.errorRequired': formsTr.errorRequired
}), [formsTr.errorRequired]);
const validateRequired = useCallback((value, {
required,
isChanged,
error
}) => {
const hasError = value === null || value === void 0 ? void 0 : value.some(file => file.errorMessage);
if (hasError) {
return new FormError('Upload.errorInvalidFiles');
}
const hasFiles = (value === null || value === void 0 ? void 0 : value.length) > 0;
if (required && (!isChanged && !hasFiles || !hasFiles)) {
return error;
}
return undefined;
}, []);
const fromInput = useCallback(value => {
value === null || value === void 0 || value.forEach((item, index) => {
var _item$file;
if (!item) {
return;
}
value[index] = item;
value[index]['name'] = item['name'] || ((_item$file = item.file) === null || _item$file === void 0 ? void 0 : _item$file.name);
});
return value;
}, []);
const preparedProps = {
errorMessages,
validateRequired,
fromInput,
toInput: transformFiles,
...props
};
const {
id,
className,
width: widthProp = 'stretch',
value,
label,
labelDescription,
help,
htmlAttributes,
disabled,
handleChange,
handleFocus,
handleBlur,
fileHandler,
onValidationError,
dataContext,
...rest
} = useFieldProps(preparedProps, {
executeOnChangeRegardlessOfError: true
});
const {
identifier
} = usePath({
id,
path: props.path,
itemPath: props.itemPath
});
const {
setFieldState,
setFieldInternals
} = dataContext || {};
const {
title = sharedTr.title,
text = sharedTr.text,
variant = 'normal',
acceptedFileTypes = ['pdf', 'png', 'jpg', 'jpeg'],
filesAmountLimit = 100,
fileMaxSize = 5,
skeleton,
onFileDelete,
onFileClick,
download,
allowDuplicates,
disableDragAndDrop,
buttonProps
} = rest;
const {
files,
setFiles,
clearFiles
} = useUpload(id);
const labelWithItemNo = useIterateItemNo({
label: label !== null && label !== void 0 ? label : title,
labelSuffix: props.labelSuffix,
required: props.required
});
const filesRef = useRef();
useMemo(() => {
filesRef.current = files;
}, [files]);
const isSameUploadFile = useCallback((fileA, fileB) => {
if (!fileA || !fileB) {
return false;
}
if (fileA.id && fileB.id && fileA.id === fileB.id) {
return true;
}
return isFileEqual(fileA.file, fileB.file);
}, []);
const isPendingOrErrorFile = useCallback(file => {
return Boolean((file === null || file === void 0 ? void 0 : file.isLoading) || (file === null || file === void 0 ? void 0 : file.errorMessage));
}, []);
useEffect(() => {
return () => {
clearFiles();
};
}, [clearFiles]);
useEffect(() => {
var _filesRef$current;
const externalFiles = value !== null && value !== void 0 ? value : [];
const localFiles = (_filesRef$current = filesRef.current) !== null && _filesRef$current !== void 0 ? _filesRef$current : [];
const mergedExternalFiles = externalFiles.map(externalFile => {
if (!(externalFile !== null && externalFile !== void 0 && externalFile.isLoading)) {
return externalFile;
}
const localResolvedFile = localFiles.find(localFile => isSameUploadFile(localFile, externalFile) && !(localFile !== null && localFile !== void 0 && localFile.isLoading));
return localResolvedFile || externalFile;
});
const filesToPreserve = localFiles.filter(localFile => {
if (!isPendingOrErrorFile(localFile)) {
return false;
}
return !mergedExternalFiles.some(externalFile => isSameUploadFile(externalFile, localFile));
});
setFiles([...mergedExternalFiles, ...filesToPreserve]);
}, [isPendingOrErrorFile, isSameUploadFile, setFiles, value]);
const handleChangeAsync = useCallback(async files => {
var _filesRef$current2;
const filesArray = files || [];
const existingFileIds = ((_filesRef$current2 = filesRef.current) === null || _filesRef$current2 === void 0 ? void 0 : _filesRef$current2.map(file => file.id)) || [];
const existingFiles = filesArray.filter(file => existingFileIds.includes(file.id));
const newFiles = filesArray.filter(file => !existingFileIds.includes(file.id));
const newValidFiles = newFiles.filter(file => !file.errorMessage);
if (newValidFiles.length > 0) {
const fieldIdentifier = identifier;
setFieldState === null || setFieldState === void 0 || setFieldState(fieldIdentifier, 'pending');
setFieldInternals === null || setFieldInternals === void 0 || setFieldInternals(fieldIdentifier, {
enableAsyncMode: true
});
try {
const newFilesLoading = newFiles.map(file => ({
...file,
isLoading: !file.errorMessage
}));
setFiles([...filesRef.current, ...newFilesLoading]);
const incomingFiles = await fileHandler(newValidFiles);
if (!incomingFiles) {
setFiles(existingFiles);
handleChange(existingFiles);
} else {
var _newFilesLoading$filt;
const updatedByResponse = new Set();
incomingFiles.forEach(file => {
const incomingFileObj = {
...file,
isLoading: false
};
const foundIndex = newFilesLoading.findIndex(newFile => newFile.isLoading);
if (foundIndex >= 0) {
newFilesLoading[foundIndex] = incomingFileObj;
updatedByResponse.add(foundIndex);
} else {
_pushInstanceProperty(newFilesLoading).call(newFilesLoading, incomingFileObj);
}
});
newFilesLoading.forEach((file, index) => {
var _filesRef$current3;
if (updatedByResponse.has(index)) {
return;
}
const currentFile = (_filesRef$current3 = filesRef.current) === null || _filesRef$current3 === void 0 ? void 0 : _filesRef$current3.find(f => f.id && f.id === file.id || f.file && f.file === file.file);
if (currentFile !== null && currentFile !== void 0 && currentFile.isLoading) {
newFilesLoading[index] = {
...file,
isLoading: true
};
}
});
const indexOfFirstNewFile = filesRef.current.findIndex(({
id
}) => id === newFiles[0].id);
const updatedFiles = [...filesRef.current.slice(0, indexOfFirstNewFile), ...((_newFilesLoading$filt = newFilesLoading === null || newFilesLoading === void 0 ? void 0 : newFilesLoading.filter(file => file != null)) !== null && _newFilesLoading$filt !== void 0 ? _newFilesLoading$filt : []), ...filesRef.current.slice(indexOfFirstNewFile + newFilesLoading.length)];
setFiles(updatedFiles);
handleChange(updatedFiles);
}
} finally {
setFieldState === null || setFieldState === void 0 || setFieldState(fieldIdentifier, undefined);
}
} else {
handleChange(files);
}
}, [identifier, fileHandler, onValidationError, handleChange, setFieldInternals, setFieldState, setFiles]);
const processValidationErrors = useCallback((files, existingFiles) => {
var _existingFiles$map;
if (!files || !onValidationError) {
return files;
}
const existingFileIds = (_existingFiles$map = existingFiles === null || existingFiles === void 0 ? void 0 : existingFiles.map(file => file.id)) !== null && _existingFiles$map !== void 0 ? _existingFiles$map : [];
const newFiles = files.filter(file => !existingFileIds.includes(file.id));
const newInvalidFiles = newFiles.filter(file => file.errorMessage);
if (newInvalidFiles.length === 0) {
return files;
}
const processedInvalidFiles = onValidationError(newInvalidFiles) || newInvalidFiles;
return files.map(file => {
const processedFile = processedInvalidFiles.find(processed => processed.id === file.id || processed.file && processed.file === file.file);
return processedFile || file;
});
}, [onValidationError]);
const changeHandler = useCallback(({
files
}) => {
let changeValue = (files === null || files === void 0 ? void 0 : files.length) === 0 ? undefined : files;
handleBlur();
if (changeValue) {
handleFocus();
}
changeValue = processValidationErrors(changeValue, filesRef.current);
if (fileHandler) {
handleChangeAsync(changeValue);
} else {
handleChange(changeValue);
}
}, [handleBlur, handleFocus, fileHandler, processValidationErrors, handleChangeAsync, handleChange]);
const width = widthProp;
const fieldBlockProps = {
id,
forId: `${id}-input`,
labelSrOnly: true,
className: classnames('dnb-forms-field-upload', className),
width,
help: undefined,
...pickSpacingProps(props)
};
const usedLabelDescription = labelDescription !== null && labelDescription !== void 0 ? labelDescription : text;
return React.createElement(FieldBlock, fieldBlockProps, React.createElement(Upload, _extends({
id: id,
variant: variant,
acceptedFileTypes: acceptedFileTypes,
filesAmountLimit: filesAmountLimit,
download: download,
allowDuplicates: allowDuplicates,
disableDragAndDrop: disableDragAndDrop,
buttonProps: buttonProps,
disabled: disabled,
fileMaxSize: fileMaxSize,
skeleton: skeleton,
onChange: changeHandler,
onFileDelete: onFileDelete,
onFileClick: onFileClick,
title: help && labelDescription === false ? React.createElement(LabelWithHelpButton, {
label: labelWithItemNo,
id: id,
help: help
}) : labelWithItemNo,
text: help && (labelDescription !== null && labelDescription !== void 0 ? labelDescription : text) ? React.createElement(LabelWithHelpButton, {
label: usedLabelDescription,
id: id,
help: help
}) : usedLabelDescription
}, htmlAttributes), help && React.createElement(HelpButtonInlineContent, {
contentId: `${id}-help`,
help: help,
roundedCorner: variant === 'compact'
}), props.children));
}
function LabelWithHelpButton(props) {
const {
label,
id,
help
} = props;
return React.createElement(React.Fragment, null, label, React.createElement(HelpButtonInline, {
contentId: `${id}-help`,
left: label ? 'x-small' : false,
help: help
}));
}
export default UploadComponent;
UploadComponent._supportsSpacingProps = true;
export function transformFiles(value) {
if (Array.isArray(value)) {
if (value.length === 0) {
return undefined;
}
value.map(item => {
if (item !== null && item !== void 0 && item.file && !(item.file instanceof File)) {
var _lastModified, _item$file2, _type, _item$file3;
item['file'] = new File([], item['name'] || (item === null || item === void 0 ? void 0 : item.file['name']), {
lastModified: (_lastModified = (_item$file2 = item.file) === null || _item$file2 === void 0 ? void 0 : _item$file2.lastModified) !== null && _lastModified !== void 0 ? _lastModified : 0,
type: (_type = (_item$file3 = item.file) === null || _item$file3 === void 0 ? void 0 : _item$file3.type) !== null && _type !== void 0 ? _type : ''
});
}
return item;
});
}
return value;
}
//# sourceMappingURL=Upload.js.map