From 48348cc6a831464e0792fc7cef6f45f1d1766aa6 Mon Sep 17 00:00:00 2001 From: Moritz Zeumer Date: Thu, 4 Apr 2024 14:10:07 +0200 Subject: [PATCH 01/17] add drag & drop function --- frontend/locales/de-global.json5 | 7 + frontend/locales/en-global.json5 | 7 + .../src/components/TopBar/ApplicationMenu.tsx | 13 ++ .../TopBar/PopUps/DataUploadDialog.tsx | 127 ++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 frontend/src/components/TopBar/PopUps/DataUploadDialog.tsx diff --git a/frontend/locales/de-global.json5 b/frontend/locales/de-global.json5 index a87d3a28..736606b6 100644 --- a/frontend/locales/de-global.json5 +++ b/frontend/locales/de-global.json5 @@ -10,6 +10,7 @@ menu: { label: 'Anwendungsmenü', login: 'Anmelden', + upload: 'Daten Hochladen', imprint: 'Impressum', 'privacy-policy': 'Datenschutzerklärung', accessibility: 'Barrierefreiheit', @@ -73,4 +74,10 @@ 'no-data': 'Keine Daten', 'loki-logo': 'LOKI-Logo', okay: 'Okay', + upload: { + header: 'Falldaten hochladen', + dragNotice: 'Ziehen Sie ihre Datei(en) in dieses Feld oder nutzen sie den Knopf um Falldaten für ESID hochzuladen.', + button: 'Daten hochladen', + dropNotice: 'Hier loslassen um die Daten hochzuladen.' + } } diff --git a/frontend/locales/en-global.json5 b/frontend/locales/en-global.json5 index 9909ce97..52510a44 100644 --- a/frontend/locales/en-global.json5 +++ b/frontend/locales/en-global.json5 @@ -10,6 +10,7 @@ menu: { label: 'Application menu', login: 'Login', + upload: 'Upload Data', imprint: 'Imprint', 'privacy-policy': 'Privacy Policy', accessibility: 'Accessibility', @@ -88,4 +89,10 @@ sanctus est Lorem ipsum dolor sit amet.', WIP: 'This functionality is still work in progress.', okay: 'Okay', + upload: { + header: 'Upload Case Data', + dragNotice: 'Drag and drop your file(s) in here to or use the button below to uplad your case data to ESID.', + button: 'Upload Data', + dropNotice: 'Drop here to upload.' + } } diff --git a/frontend/src/components/TopBar/ApplicationMenu.tsx b/frontend/src/components/TopBar/ApplicationMenu.tsx index e5a29af6..66d9b5d5 100644 --- a/frontend/src/components/TopBar/ApplicationMenu.tsx +++ b/frontend/src/components/TopBar/ApplicationMenu.tsx @@ -14,6 +14,7 @@ import Snackbar from '@mui/material/Snackbar'; import Box from '@mui/system/Box'; // Let's import pop-ups only once they are opened. +const DataUploadDialog = React.lazy(() => import('./PopUps/DataUploadDialog')); const ChangelogDialog = React.lazy(() => import('./PopUps/ChangelogDialog')); const ImprintDialog = React.lazy(() => import('./PopUps/ImprintDialog')); const PrivacyPolicyDialog = React.lazy(() => import('./PopUps/PrivacyPolicyDialog')); @@ -33,6 +34,7 @@ export default function ApplicationMenu(): JSX.Element { const [accessibilityOpen, setAccessibilityOpen] = React.useState(false); const [attributionsOpen, setAttributionsOpen] = React.useState(false); const [changelogOpen, setChangelogOpen] = React.useState(false); + const [uploadOpen, setUploadOpen] = React.useState(false); const [snackbarOpen, setSnackbarOpen] = React.useState(false); /** Calling this method opens the application menu. */ @@ -51,6 +53,12 @@ export default function ApplicationMenu(): JSX.Element { setSnackbarOpen(true); }; + /** This method gets called, when the login menu entry was clicked. */ + const uploadClicked = () => { + closeMenu(); + setUploadOpen(true); + }; + /** This method gets called, when the imprint menu entry was clicked. It opens a dialog showing the legal text. */ const imprintClicked = () => { closeMenu(); @@ -94,6 +102,7 @@ export default function ApplicationMenu(): JSX.Element { {t('topBar.menu.login')} + {t('topBar.menu.upload')} {t('topBar.menu.imprint')} {t('topBar.menu.privacy-policy')} @@ -102,6 +111,10 @@ export default function ApplicationMenu(): JSX.Element { {t('topBar.menu.changelog')} + setUploadOpen(false)}> + + + setImprintOpen(false)}> diff --git a/frontend/src/components/TopBar/PopUps/DataUploadDialog.tsx b/frontend/src/components/TopBar/PopUps/DataUploadDialog.tsx new file mode 100644 index 00000000..ef455e68 --- /dev/null +++ b/frontend/src/components/TopBar/PopUps/DataUploadDialog.tsx @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: 2024 German Aerospace Center (DLR) +// SPDX-License-Identifier: Apache-2.0 + +import React, {useCallback} from 'react'; +import {useTheme} from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import {useTranslation} from 'react-i18next'; +import {Button} from '@mui/material'; +import {CloudUpload} from '@mui/icons-material'; + +/** + * This component displays the accessibility legal text. + */ +export default function DataUploadDialog(): JSX.Element { + const {t} = useTranslation(); + const theme = useTheme(); + const [dragActive, setDragActive] = React.useState(false); + const [uploadStat, setUploadStat] = React.useState<{filename: string, status: boolean}[]>([]); + + const fileTypes: string[] = []; + + // Function to handle data upload. + const handleFiles = (filelist: FileList) => { + console.log(filelist); + // update file display + const displaylist: {filename: string, status: boolean}[] = []; + for (let i = 0; i < filelist.length; i++) { + const file = filelist[i]; + + displaylist.push({ + filename: file.name, + status: false + }); + } + }; + + // Callback for drag event (to modify styling) + const handleDrag = useCallback((e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.type === 'dragenter' || e.type === 'dragover') { + setDragActive(true); + } else if (e.type === 'dragleave') { + setDragActive(false); + } + }, []); + + // Callback for files selected through drag & drop + const handleDrop = useCallback((e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + if (e.dataTransfer.files && e.dataTransfer.files[0]) { + handleFiles(e.dataTransfer.files); + } + }, []); + + // Callback for files selected through dialog + const handleClick = useCallback((e: React.ChangeEvent) => { + e.preventDefault(); + if (e.target.files && e.target.files[0]) { + handleFiles(e.target.files); + } + }, []); + + return ( +
e.preventDefault()}> + + + {t('upload.header')} +
{t('upload.dragNotice')}
+ +
+ {dragActive && ( +
+ + {t('upload.dropNotice')} + +
+ )} +
+ ); +} From d89c6517c9da68bb59811ddcd2b1f4348d3e33fd Mon Sep 17 00:00:00 2001 From: Moritz Zeumer Date: Thu, 4 Apr 2024 17:24:17 +0200 Subject: [PATCH 02/17] add file list --- .../TopBar/PopUps/DataUploadDialog.tsx | 109 +++++++++++++----- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/frontend/src/components/TopBar/PopUps/DataUploadDialog.tsx b/frontend/src/components/TopBar/PopUps/DataUploadDialog.tsx index ef455e68..624bb92e 100644 --- a/frontend/src/components/TopBar/PopUps/DataUploadDialog.tsx +++ b/frontend/src/components/TopBar/PopUps/DataUploadDialog.tsx @@ -6,8 +6,8 @@ import {useTheme} from '@mui/material/styles'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import {useTranslation} from 'react-i18next'; -import {Button} from '@mui/material'; -import {CloudUpload} from '@mui/icons-material'; +import {Button, List, ListItem, ListItemText} from '@mui/material'; +import {Clear, CloudUpload, Done, MoreHoriz} from '@mui/icons-material'; /** * This component displays the accessibility legal text. @@ -16,24 +16,46 @@ export default function DataUploadDialog(): JSX.Element { const {t} = useTranslation(); const theme = useTheme(); const [dragActive, setDragActive] = React.useState(false); - const [uploadStat, setUploadStat] = React.useState<{filename: string, status: boolean}[]>([]); + + enum UploadStatus { + Started, + Error, + Done, + } + + const [uploadStat, setUploadStat] = React.useState<{filename: string; status: UploadStatus}[]>([]); const fileTypes: string[] = []; // Function to handle data upload. - const handleFiles = (filelist: FileList) => { - console.log(filelist); - // update file display - const displaylist: {filename: string, status: boolean}[] = []; - for (let i = 0; i < filelist.length; i++) { - const file = filelist[i]; - - displaylist.push({ - filename: file.name, - status: false - }); - } - }; + const handleFiles = useCallback( + (filelist: FileList) => { + const fileSizeToString = (size: number) => { + if (size < 1024) { + return `${size} B`; + } else if (size >= 1024 && size < 1048576) { + return `${(size / 1024).toFixed(1)} KB`; + } else { + return `${(size / 1048576).toFixed(1)} MB`; + } + }; + console.log(filelist); + // update file display + const displaylist: {filename: string; status: UploadStatus}[] = []; + for (let i = 0; i < filelist.length; i++) { + const file = filelist[i]; + + displaylist.push({ + filename: `${file.name} (${fileSizeToString(file.size)})`, + status: UploadStatus.Started, + }); + } + setUploadStat(displaylist); + + // TODO: init file upload, adjust UploadStat as needed + }, + [UploadStatus] + ); // Callback for drag event (to modify styling) const handleDrag = useCallback((e: React.DragEvent) => { @@ -47,22 +69,28 @@ export default function DataUploadDialog(): JSX.Element { }, []); // Callback for files selected through drag & drop - const handleDrop = useCallback((e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setDragActive(false); - if (e.dataTransfer.files && e.dataTransfer.files[0]) { - handleFiles(e.dataTransfer.files); - } - }, []); + const handleDrop = useCallback( + (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + if (e.dataTransfer.files && e.dataTransfer.files[0]) { + handleFiles(e.dataTransfer.files); + } + }, + [handleFiles] + ); // Callback for files selected through dialog - const handleClick = useCallback((e: React.ChangeEvent) => { - e.preventDefault(); - if (e.target.files && e.target.files[0]) { - handleFiles(e.target.files); - } - }, []); + const handleClick = useCallback( + (e: React.ChangeEvent) => { + e.preventDefault(); + if (e.target.files && e.target.files[0]) { + handleFiles(e.target.files); + } + }, + [handleFiles] + ); return (
e.preventDefault()}> @@ -82,6 +110,27 @@ export default function DataUploadDialog(): JSX.Element { > {t('upload.header')}
{t('upload.dragNotice')}
+ {uploadStat.length > 0 && ( + + {uploadStat.map((file) => ( + + ) : file.status === UploadStatus.Error ? ( + + ) : ( + // TODO: Throbber + ) + } + > + + + ))} + + )}