|
| 1 | +// SPDX-FileCopyrightText: 2024 German Aerospace Center (DLR) |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +import React, {useCallback, useEffect} from 'react'; |
| 5 | +import {useTheme} from '@mui/material/styles'; |
| 6 | +import Box from '@mui/material/Box'; |
| 7 | +import Typography from '@mui/material/Typography'; |
| 8 | +import {useTranslation} from 'react-i18next'; |
| 9 | +import Button from '@mui/material/Button'; |
| 10 | +import List from '@mui/material'; |
| 11 | +import ListItem from '@mui/material'; |
| 12 | +import ListItemText from '@mui/material'; |
| 13 | +import Clear from '@mui/icons-material/Clear'; |
| 14 | +import CloudUpload from '@mui/icons-material/CloudUpload'; |
| 15 | +import Done from '@mui/icons-material/Done'; |
| 16 | +import {helix} from 'ldrs'; |
| 17 | +import {useSendCasedataFileQuery} from 'store/services/utilsApi'; |
| 18 | + |
| 19 | +/** |
| 20 | + * This component displays the accessibility legal text. |
| 21 | + */ |
| 22 | +export default function DataUploadDialog(): JSX.Element { |
| 23 | + const {t} = useTranslation(); |
| 24 | + const theme = useTheme(); |
| 25 | + const [dragActive, setDragActive] = React.useState(false); |
| 26 | + |
| 27 | + // Register throbber for later use. |
| 28 | + useEffect(() => { |
| 29 | + helix.register(); |
| 30 | + }, []); |
| 31 | + |
| 32 | + const [uploadList, setUploadList] = React.useState<{fileinfo: string; file: File}[]>([]); |
| 33 | + |
| 34 | + const fileTypes: string[] = []; |
| 35 | + |
| 36 | + // Function to handle data upload. |
| 37 | + const handleFiles = useCallback((fileList: FileList) => { |
| 38 | + // Function to increase readability of file size appended behind filename. |
| 39 | + const fileSizeToString = (size: number) => { |
| 40 | + if (size < 1024) { |
| 41 | + return `${size} B`; |
| 42 | + } else if (size >= 1024 && size < 1048576) { |
| 43 | + return `${(size / 1024).toFixed(1)} KB`; |
| 44 | + } else { |
| 45 | + return `${(size / 1048576).toFixed(1)} MB`; |
| 46 | + } |
| 47 | + }; |
| 48 | + // Update file display with new files. |
| 49 | + const displayList: {fileInfo: string; file: File}[] = []; |
| 50 | + for (let i = 0; i < fileList.length; i++) { |
| 51 | + const file = fileList[i]; |
| 52 | + |
| 53 | + displayList.push({ |
| 54 | + fileInfo: `${file.name} (${fileSizeToString(file.size)})`, |
| 55 | + file: file, |
| 56 | + }); |
| 57 | + } |
| 58 | + setUploadList(displayList); |
| 59 | + }, []); |
| 60 | + |
| 61 | + // Callback for drag event (to modify styling). |
| 62 | + const handleDrag = useCallback((e: React.DragEvent) => { |
| 63 | + e.preventDefault(); |
| 64 | + e.stopPropagation(); |
| 65 | + if (e.type === 'dragenter' || e.type === 'dragover') { |
| 66 | + setDragActive(true); |
| 67 | + } else if (e.type === 'dragleave') { |
| 68 | + setDragActive(false); |
| 69 | + } |
| 70 | + }, []); |
| 71 | + |
| 72 | + // Callback for files selected through drag & drop. |
| 73 | + const handleDrop = useCallback( |
| 74 | + (e: React.DragEvent) => { |
| 75 | + e.preventDefault(); |
| 76 | + e.stopPropagation(); |
| 77 | + setDragActive(false); |
| 78 | + if (e.dataTransfer.files && e.dataTransfer.files[0]) { |
| 79 | + handleFiles(e.dataTransfer.files); |
| 80 | + } |
| 81 | + }, |
| 82 | + [handleFiles] |
| 83 | + ); |
| 84 | + |
| 85 | + // Callback for files selected through dialog. |
| 86 | + const handleClick = useCallback( |
| 87 | + (e: React.ChangeEvent<HTMLInputElement>) => { |
| 88 | + e.preventDefault(); |
| 89 | + if (e.target.files && e.target.files[0]) { |
| 90 | + handleFiles(e.target.files); |
| 91 | + } |
| 92 | + }, |
| 93 | + [handleFiles] |
| 94 | + ); |
| 95 | + |
| 96 | + return ( |
| 97 | + <form id='upload-form' onDragEnter={handleDrag} onDragLeave={handleDrag} onSubmit={(e) => e.preventDefault()}> |
| 98 | + <input type='file' id='upload-input' multiple={true} accept={fileTypes.join(',')} onChange={handleClick} hidden /> |
| 99 | + <Box |
| 100 | + sx={{ |
| 101 | + margin: theme.spacing(4), |
| 102 | + padding: theme.spacing(4), |
| 103 | + minHeight: '30vw', |
| 104 | + background: theme.palette.background.paper, |
| 105 | + border: `${theme.palette.divider} ${dragActive ? 'solid' : 'dashed'} 2px`, |
| 106 | + display: 'flex', |
| 107 | + flexDirection: 'column', |
| 108 | + justifyContent: 'space-around', |
| 109 | + alignItems: 'center', |
| 110 | + }} |
| 111 | + > |
| 112 | + <Typography variant='h1'>{t('upload.header')}</Typography> |
| 113 | + <div>{t('upload.dragNotice')}</div> |
| 114 | + {uploadList.length > 0 && ( |
| 115 | + <List> |
| 116 | + {uploadList.map((item) => ( |
| 117 | + // Create a list item for each file. |
| 118 | + <FileItem key={item.fileinfo} fileinfo={item.fileinfo} file={item.file} /> |
| 119 | + ))} |
| 120 | + </List> |
| 121 | + )} |
| 122 | + <label htmlFor='upload-input'> |
| 123 | + <Button variant='contained' startIcon={<CloudUpload />} component='span'> |
| 124 | + {t('upload.button')} |
| 125 | + </Button> |
| 126 | + </label> |
| 127 | + </Box> |
| 128 | + {dragActive && ( |
| 129 | + // Add an overlay on top of the popup to display a notice and make handling the drag events smoother. |
| 130 | + <div |
| 131 | + id='upload-drop-notice' |
| 132 | + onDragEnter={handleDrag} |
| 133 | + onDragLeave={handleDrag} |
| 134 | + onDragOver={handleDrag} |
| 135 | + onDrop={handleDrop} |
| 136 | + style={{ |
| 137 | + position: 'absolute', |
| 138 | + width: '100%', |
| 139 | + height: '100%', |
| 140 | + top: 0, |
| 141 | + left: 0, |
| 142 | + bottom: 0, |
| 143 | + right: 0, |
| 144 | + background: 'rgba(255, 255, 255, 0.6)', |
| 145 | + display: 'flex', |
| 146 | + justifyContent: 'center', |
| 147 | + alignItems: 'center', |
| 148 | + }} |
| 149 | + > |
| 150 | + <Typography |
| 151 | + variant='h1' |
| 152 | + sx={{ |
| 153 | + background: 'white', |
| 154 | + border: `solid ${theme.palette.divider} 1px`, |
| 155 | + borderRadius: '1em', |
| 156 | + padding: '1em', |
| 157 | + }} |
| 158 | + > |
| 159 | + {t('upload.dropNotice')} |
| 160 | + </Typography> |
| 161 | + </div> |
| 162 | + )} |
| 163 | + </form> |
| 164 | + ); |
| 165 | +} |
| 166 | + |
| 167 | +function FileItem({fileInfo, file}: {fileInfo: string; file: File}): JSX.Element { |
| 168 | + const theme = useTheme(); |
| 169 | + const {isSuccess, isError} = useSendCasedataFileQuery(file); |
| 170 | + |
| 171 | + return ( |
| 172 | + <ListItem |
| 173 | + disableGutters |
| 174 | + secondaryAction={ |
| 175 | + isSuccess ? ( |
| 176 | + <Done sx={{color: theme.palette.primary.main, fontSize: 45}} /> |
| 177 | + ) : isError ? ( |
| 178 | + <Clear sx={{color: theme.palette.error.main, fontSize: 45}} /> |
| 179 | + ) : ( |
| 180 | + <l-helix size={45} speed={2.5} color={theme.palette.divider}></l-helix> |
| 181 | + ) |
| 182 | + } |
| 183 | + > |
| 184 | + <ListItemText primary={fileInfo} /> |
| 185 | + </ListItem> |
| 186 | + ); |
| 187 | +} |
0 commit comments