2021-11-04 05:41:10 +00:00
|
|
|
import React, { useEffect, useMemo } from 'react';
|
|
|
|
|
import { useDropzone } from 'react-dropzone';
|
|
|
|
|
import { resolveWidgetFieldValue } from '@/_helpers/utils';
|
2021-12-10 23:11:24 +00:00
|
|
|
import toast from 'react-hot-toast';
|
2021-11-04 05:41:10 +00:00
|
|
|
|
|
|
|
|
export const FilePicker = ({ width, height, component, currentState, onComponentOptionChanged, onEvent, darkMode }) => {
|
|
|
|
|
//* properties definitions
|
|
|
|
|
const enableDropzone = component.definition.properties.enableDropzone?.value ?? true;
|
|
|
|
|
const enablePicker = component.definition.properties?.enablePicker?.value ?? true;
|
|
|
|
|
const maxFileCount = component.definition.properties.maxFileCount?.value ?? 2;
|
|
|
|
|
const enableMultiple = component.definition.properties.enableMultiple?.value ?? false;
|
|
|
|
|
const fileType = component.definition.properties.fileType?.value ?? 'image/*';
|
|
|
|
|
const maxSize = component.definition.properties.maxSize?.value ?? 1048576;
|
|
|
|
|
const minSize = component.definition.properties.minSize?.value ?? 0;
|
|
|
|
|
|
|
|
|
|
const parsedEnableDropzone =
|
|
|
|
|
typeof enableDropzone !== 'boolean' ? resolveWidgetFieldValue(enableDropzone, currentState) : true;
|
|
|
|
|
const parsedEnablePicker =
|
|
|
|
|
typeof enablePicker !== 'boolean' ? resolveWidgetFieldValue(enablePicker, currentState) : true;
|
2021-11-22 03:49:27 +00:00
|
|
|
|
2021-11-04 05:41:10 +00:00
|
|
|
const parsedMaxFileCount =
|
|
|
|
|
typeof maxFileCount !== 'number' ? resolveWidgetFieldValue(maxFileCount, currentState) : maxFileCount;
|
|
|
|
|
const parsedEnableMultiple =
|
|
|
|
|
typeof enableMultiple !== 'boolean' ? resolveWidgetFieldValue(enableMultiple, currentState) : enableMultiple;
|
|
|
|
|
const parsedFileType = resolveWidgetFieldValue(fileType, currentState);
|
|
|
|
|
const parsedMinSize = typeof fileType !== 'number' ? resolveWidgetFieldValue(minSize, currentState) : minSize;
|
|
|
|
|
const parsedMaxSize = typeof fileType !== 'number' ? resolveWidgetFieldValue(maxSize, currentState) : maxSize;
|
|
|
|
|
|
|
|
|
|
//* styles definitions
|
|
|
|
|
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
|
|
|
|
|
const disabledState = component.definition.styles?.disabledState?.value ?? false;
|
|
|
|
|
|
|
|
|
|
const parsedDisabledState =
|
|
|
|
|
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
|
|
|
|
|
const parsedWidgetVisibility =
|
|
|
|
|
typeof widgetVisibility !== 'boolean' ? resolveWidgetFieldValue(widgetVisibility, currentState) : widgetVisibility;
|
|
|
|
|
|
|
|
|
|
const bgThemeColor = darkMode ? '#232E3C' : '#fff';
|
|
|
|
|
|
|
|
|
|
const baseStyle = {
|
|
|
|
|
flex: 1,
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
padding: '20px',
|
|
|
|
|
borderWidth: 1.5,
|
|
|
|
|
borderRadius: 2,
|
|
|
|
|
borderColor: '#42536A',
|
|
|
|
|
borderStyle: 'dashed',
|
|
|
|
|
color: '#bdbdbd',
|
|
|
|
|
outline: 'none',
|
|
|
|
|
transition: 'border .24s ease-in-out',
|
|
|
|
|
display: parsedWidgetVisibility ? 'flex' : 'none',
|
|
|
|
|
height,
|
|
|
|
|
backgroundColor: !parsedDisabledState && bgThemeColor,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const activeStyle = {
|
|
|
|
|
borderColor: '#2196f3',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const acceptStyle = {
|
|
|
|
|
borderColor: '#00e676',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const rejectStyle = {
|
|
|
|
|
borderColor: '#ff1744',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject, acceptedFiles, fileRejections } =
|
|
|
|
|
useDropzone({
|
|
|
|
|
accept: parsedFileType,
|
|
|
|
|
noClick: !parsedEnablePicker,
|
|
|
|
|
noDrag: !parsedEnableDropzone,
|
|
|
|
|
noKeyboard: true,
|
|
|
|
|
maxFiles: parsedMaxFileCount,
|
|
|
|
|
minSize: parsedMinSize,
|
|
|
|
|
maxSize: parsedMaxSize,
|
|
|
|
|
multiple: parsedEnableMultiple,
|
|
|
|
|
disabled: parsedDisabledState,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const style = useMemo(
|
|
|
|
|
() => ({
|
|
|
|
|
...baseStyle,
|
|
|
|
|
...(isDragActive && parsedEnableDropzone ? activeStyle : {}),
|
|
|
|
|
...(isDragAccept && parsedEnableDropzone ? acceptStyle : {}),
|
|
|
|
|
...(isDragReject && parsedEnableDropzone ? rejectStyle : {}),
|
|
|
|
|
}),
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
[baseStyle, isDragActive, isDragAccept, acceptStyle, isDragReject]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const [accepted, setAccepted] = React.useState(false);
|
|
|
|
|
const [showSelectdFiles, setShowSelectedFiles] = React.useState(false);
|
|
|
|
|
const [selectedFiles, setSelectedFiles] = React.useState([]);
|
|
|
|
|
|
2021-11-22 03:49:27 +00:00
|
|
|
/**
|
|
|
|
|
* *getFileData()
|
|
|
|
|
* @param {*} file
|
|
|
|
|
* @param {*} method: readAsDataURL, readAsText
|
|
|
|
|
*/
|
|
|
|
|
const getFileData = (file = {}, method = 'readAsText') => {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
reader.onload = (result) => {
|
|
|
|
|
resolve([result, reader]);
|
|
|
|
|
};
|
|
|
|
|
reader[method](file);
|
|
|
|
|
reader.onerror = (error) => {
|
|
|
|
|
reject(error);
|
|
|
|
|
if (error.name == 'NotReadableError') {
|
2021-12-10 23:11:24 +00:00
|
|
|
toast.error(error.message);
|
2021-11-22 03:49:27 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}).then((result) => {
|
|
|
|
|
if (method === 'readAsDataURL') {
|
|
|
|
|
return result[0].srcElement.result.split(',')[1];
|
|
|
|
|
}
|
|
|
|
|
return result[0].srcElement.result;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fileReader = async (file) => {
|
|
|
|
|
// * readAsText
|
|
|
|
|
const readFileAsText = await getFileData(file);
|
|
|
|
|
|
|
|
|
|
// * readAsDataURL
|
|
|
|
|
const readFileAsDataURL = await getFileData(file, 'readAsDataURL');
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
name: file.name,
|
|
|
|
|
type: file.type,
|
|
|
|
|
content: readFileAsText,
|
|
|
|
|
dataURL: readFileAsDataURL,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2021-11-04 05:41:10 +00:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (acceptedFiles.length === 0) {
|
|
|
|
|
onComponentOptionChanged(component, 'file', []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (acceptedFiles.length !== 0) {
|
|
|
|
|
const fileData = parsedEnableMultiple ? [...selectedFiles] : [];
|
|
|
|
|
acceptedFiles.map((acceptedFile) => {
|
2021-11-22 03:49:27 +00:00
|
|
|
const acceptedFileData = fileReader(acceptedFile);
|
|
|
|
|
acceptedFileData.then((data) => {
|
|
|
|
|
fileData.push(data);
|
2021-11-04 05:41:10 +00:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setSelectedFiles(fileData);
|
|
|
|
|
onComponentOptionChanged(component, 'file', fileData).then(() =>
|
|
|
|
|
onEvent('onFileSelected', { component }).then(() => {
|
|
|
|
|
setAccepted(true);
|
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
setShowSelectedFiles(true);
|
|
|
|
|
setAccepted(false);
|
|
|
|
|
resolve();
|
|
|
|
|
}, 600);
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fileRejections.length > 0) {
|
2021-12-10 23:11:24 +00:00
|
|
|
fileRejections.map((rejectedFile) => toast.error(rejectedFile.errors[0].message));
|
2021-11-04 05:41:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
setAccepted(false);
|
|
|
|
|
setShowSelectedFiles(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, [acceptedFiles.length, fileRejections.length]);
|
|
|
|
|
|
|
|
|
|
const clearSelectedFiles = (index) => {
|
|
|
|
|
setSelectedFiles((prevState) => {
|
|
|
|
|
const copy = JSON.parse(JSON.stringify(prevState));
|
|
|
|
|
copy.splice(index, 1);
|
|
|
|
|
return copy;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (selectedFiles.length === 0) {
|
|
|
|
|
setShowSelectedFiles(false);
|
|
|
|
|
}
|
|
|
|
|
onComponentOptionChanged(component, 'file', selectedFiles);
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, [selectedFiles]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<section>
|
|
|
|
|
{showSelectdFiles ? (
|
|
|
|
|
<FilePicker.AcceptedFiles
|
|
|
|
|
width={width}
|
|
|
|
|
height={height}
|
|
|
|
|
showFilezone={setShowSelectedFiles}
|
|
|
|
|
bgThemeColor={bgThemeColor}
|
|
|
|
|
>
|
|
|
|
|
{selectedFiles.map((acceptedFile, index) => (
|
|
|
|
|
<>
|
|
|
|
|
<div key={index} className="col-10">
|
|
|
|
|
<FilePicker.Signifiers
|
|
|
|
|
signifier={selectedFiles.length > 0}
|
|
|
|
|
feedback={acceptedFile.name}
|
|
|
|
|
cls="text-secondary d-flex justify-content-start file-list"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="col-2 mt-1">
|
|
|
|
|
<button
|
|
|
|
|
className="btn badge bg-azure-lt"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
clearSelectedFiles(index);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<img src="/assets/images/icons/trash.svg" width="12" height="12" className="mx-1" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
))}
|
|
|
|
|
</FilePicker.AcceptedFiles>
|
|
|
|
|
) : (
|
|
|
|
|
//* Dropzone
|
|
|
|
|
<div className="container" {...getRootProps({ style, className: 'dropzone' })}>
|
|
|
|
|
<input {...getInputProps()} />
|
|
|
|
|
<FilePicker.Signifiers signifier={accepted} feedback={null} cls="spinner-border text-azure p-0" />
|
|
|
|
|
<FilePicker.Signifiers
|
|
|
|
|
signifier={!isDragAccept && !accepted & !isDragReject}
|
|
|
|
|
feedback={'Drag & drop some files here, or click to select files'}
|
|
|
|
|
cls={`${darkMode ? 'text-secondary' : 'text-dark'} mt-3`}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<FilePicker.Signifiers
|
|
|
|
|
signifier={isDragAccept}
|
|
|
|
|
feedback={'All files will be accepted'}
|
|
|
|
|
cls="text-lime mt-3"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<FilePicker.Signifiers signifier={isDragReject} feedback={'Files will be rejected!'} cls="text-red mt-3" />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</section>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FilePicker.Signifiers = ({ signifier, feedback, cls }) => {
|
|
|
|
|
if (signifier) {
|
|
|
|
|
return <center>{feedback === null ? <div className={cls}></div> : <p className={cls}>{feedback}</p>}</center>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FilePicker.AcceptedFiles = ({ children, width, height, showFilezone, bgThemeColor }) => {
|
|
|
|
|
const styles = {
|
|
|
|
|
borderWidth: 1.5,
|
|
|
|
|
borderRadius: 2,
|
|
|
|
|
borderColor: '#42536A',
|
|
|
|
|
borderStyle: 'dashed',
|
|
|
|
|
color: '#bdbdbd',
|
|
|
|
|
outline: 'none',
|
|
|
|
|
padding: '5px',
|
|
|
|
|
overflowX: 'hidden',
|
|
|
|
|
overflowY: 'auto',
|
|
|
|
|
scrollbarWidth: 'none',
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
backgroundColor: bgThemeColor,
|
|
|
|
|
};
|
|
|
|
|
return (
|
|
|
|
|
<aside style={styles} onClick={() => showFilezone(false)}>
|
|
|
|
|
<span className="text-info">Files</span>
|
|
|
|
|
<div className="row accepted-files">{children}</div>
|
|
|
|
|
</aside>
|
|
|
|
|
);
|
|
|
|
|
};
|