Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions locales/de-global.json5
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
label: 'Anwendungsmenü',
login: 'Anmelden',
logout: 'Abmelden',
upload: 'Daten Hochladen',
imprint: 'Impressum',
'privacy-policy': 'Datenschutzerklärung',
accessibility: 'Barrierefreiheit',
Expand Down Expand Up @@ -134,4 +135,10 @@
'loki-logo': 'LOKI-Logo',
okay: 'Okay',
yAxisLabel: 'Wert',
upload: {
header: 'Falldaten Hochladen',
dragNotice: 'Ziehen sie Ihre Datei(en) in diesen Bereich oder nutzen Sie den Knopf unten, um Ihre Falldaten an ESID zu senden.',
button: 'Daten hochladen',
dropNotice: 'Hier ablegen um die Datei(en) hochzuladen.',
},
}
7 changes: 7 additions & 0 deletions locales/en-global.json5
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
label: 'Application menu',
login: 'Login',
logout: 'Logout',
upload: 'Upload Data',
imprint: 'Imprint',
'privacy-policy': 'Privacy Policy',
accessibility: 'Accessibility',
Expand Down Expand Up @@ -149,4 +150,10 @@
WIP: 'This functionality is still work in progress.',
okay: 'Okay',
yAxisLabel: 'Value',
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.',
},
}
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"i18next-browser-languagedetector": "^7.2.0",
"i18next-http-backend": "^2.4.2",
"json5": "^2.2.3",
"ldrs": "^1.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^13.5.0",
Expand Down
7 changes: 5 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {selectDistrict} from 'store/DataSelectionSlice';
import {I18nextProvider, useTranslation} from 'react-i18next';
import i18n from './util/i18n';
import {MUILocalization} from 'components/shared/MUILocalization';

import LoadingOverlay from './components/shared/LoadingOverlay';
import AuthProvider from './components/AuthProvider';
import BaseDataContext from 'context/BaseDataContext';
/**
Expand All @@ -29,7 +29,10 @@ import BaseDataContext from 'context/BaseDataContext';
*/
export default function App(): JSX.Element {
return (
<Suspense fallback='loading'>
<Suspense
// Use Loading Overlay with default background and primary color (theme isn't loaded at this point)
fallback={<LoadingOverlay show={true} overlayColor={'#F0F0F2'} throbberColor={'#543CF0'}></LoadingOverlay>}
>
<Provider store={Store}>
<AuthProvider>
<ThemeProvider theme={Theme}>
Expand Down
16 changes: 16 additions & 0 deletions src/components/TopBar/ApplicationMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {useAppSelector} from 'store/hooks';
import {AuthContext, IAuthContext} from 'react-oauth2-code-pkce';
import CircularProgress from '@mui/material/CircularProgress';

// 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'));
Expand Down Expand Up @@ -50,6 +52,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 keycloakLogout = () => {
window.location.assign(
Expand Down Expand Up @@ -80,6 +83,12 @@ export default function ApplicationMenu(): JSX.Element {
keycloakLogout();
};

/** 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();
Expand Down Expand Up @@ -135,13 +144,20 @@ export default function ApplicationMenu(): JSX.Element {
</MenuItem>
)}
<Divider />
<MenuItem onClick={uploadClicked} disabled={!isAuthenticated}>
{t('topBar.menu.upload')}
</MenuItem>
<MenuItem onClick={imprintClicked}>{t('topBar.menu.imprint')}</MenuItem>
<MenuItem onClick={privacyPolicyClicked}>{t('topBar.menu.privacy-policy')}</MenuItem>
<MenuItem onClick={accessibilityClicked}>{t('topBar.menu.accessibility')}</MenuItem>
<MenuItem onClick={attributionClicked}>{t('topBar.menu.attribution')}</MenuItem>
<MenuItem onClick={changelogClicked}>{t('topBar.menu.changelog')}</MenuItem>
</Menu>

<Dialog maxWidth='lg' fullWidth={true} open={uploadOpen} onClose={() => setUploadOpen(false)}>
<DataUploadDialog />
</Dialog>

<Dialog maxWidth='lg' fullWidth={true} open={imprintOpen} onClose={() => setImprintOpen(false)}>
<Suspense
fallback={
Expand Down
182 changes: 182 additions & 0 deletions src/components/TopBar/PopUps/DataUploadDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// SPDX-FileCopyrightText: 2024 German Aerospace Center (DLR)
// SPDX-License-Identifier: Apache-2.0

import React, {useCallback, useEffect} 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, List, ListItem, ListItemText} from '@mui/material';
import {Clear, CloudUpload, Done} from '@mui/icons-material';
Comment thread
NXXR marked this conversation as resolved.
Outdated
import {helix} from 'ldrs';
import {useSendCasedataFileQuery} from 'store/services/utilsApi';

/**
* 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);

// Register throbber for later use.
useEffect(() => {
helix.register();
}, []);

const [uploadList, setUploadList] = React.useState<{fileinfo: string; file: File}[]>([]);

const fileTypes: string[] = [];

// Function to handle data upload.
const handleFiles = useCallback((filelist: FileList) => {
Comment thread
NXXR marked this conversation as resolved.
Outdated
// Function to increase readability of file size appended behind filename.
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`;
}
};
// Update file display with new files.
const displaylist: {fileinfo: string; file: File}[] = [];
for (let i = 0; i < filelist.length; i++) {
const file = filelist[i];

displaylist.push({
fileinfo: `${file.name} (${fileSizeToString(file.size)})`,
file: file,
});
}
setUploadList(displaylist);
}, []);
Comment thread
NXXR marked this conversation as resolved.
Outdated

// 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);
}
},
[handleFiles]
);

// Callback for files selected through dialog.
const handleClick = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
if (e.target.files && e.target.files[0]) {
handleFiles(e.target.files);
}
},
[handleFiles]
);

return (
<form id='upload-form' onDragEnter={handleDrag} onDragLeave={handleDrag} onSubmit={(e) => e.preventDefault()}>
<input type='file' id='upload-input' multiple={true} accept={fileTypes.join(',')} onChange={handleClick} hidden />
<Box
sx={{
margin: theme.spacing(4),
padding: theme.spacing(4),
minHeight: '30vw',
background: theme.palette.background.paper,
border: `${theme.palette.divider} ${dragActive ? 'solid' : 'dashed'} 2px`,
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-around',
alignItems: 'center',
}}
>
<Typography variant='h1'>{t('upload.header')}</Typography>
<div>{t('upload.dragNotice')}</div>
{uploadList.length > 0 && (
<List>
{uploadList.map((item) => (
// Create a list item for each file.
<FileItem key={item.fileinfo} fileinfo={item.fileinfo} file={item.file} />
))}
</List>
)}
<label htmlFor='upload-input'>
<Button variant='contained' startIcon={<CloudUpload />} component='span'>
{t('upload.button')}
</Button>
</label>
</Box>
{dragActive && (
// Add an overlay on top of the popup to display a notice and make handling the drag events smoother.
<div
id='upload-drop-notice'
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={handleDrop}
style={{
position: 'absolute',
width: '100%',
height: '100%',
top: 0,
left: 0,
bottom: 0,
right: 0,
background: 'rgba(255, 255, 255, 0.6)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Typography
variant='h1'
sx={{
background: 'white',
border: `solid ${theme.palette.divider} 1px`,
borderRadius: '1em',
padding: '1em',
}}
>
{t('upload.dropNotice')}
</Typography>
</div>
)}
</form>
);
}

function FileItem({fileinfo, file}: {fileinfo: string; file: File}): JSX.Element {
Comment thread
NXXR marked this conversation as resolved.
Outdated
const theme = useTheme();
const {isSuccess, isError} = useSendCasedataFileQuery(file);

return (
<ListItem
disableGutters
secondaryAction={
isSuccess ? (
<Done sx={{color: theme.palette.primary.main, fontSize: 45}} />
) : isError ? (
<Clear sx={{color: theme.palette.error.main, fontSize: 45}} />
) : (
<l-helix size={45} speed={2.5} color={theme.palette.divider}></l-helix>
)
}
>
<ListItemText primary={fileinfo} />
Comment thread
NXXR marked this conversation as resolved.
Outdated
</ListItem>
);
}
12 changes: 11 additions & 1 deletion src/components/shared/LoadingContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ import React from 'react';
import Box from '@mui/material/Box';
import LoadingOverlay from './LoadingOverlay';
import {SxProps} from '@mui/system';
import {useTheme} from '@mui/material/styles';

/**
* This is a wrapper component for a container that can have a loading indicator overlayed.
*/
export default function LoadingContainer(props: LoadingContainerProps): JSX.Element {
const theme = useTheme();

return (
<Box sx={{...props.sx, position: 'relative'}}>
{props.children}
<LoadingOverlay show={props.show} overlayColor={props.overlayColor} />
<LoadingOverlay
show={props.show}
overlayColor={props.overlayColor}
throbberColor={props.throbberColor ? props.throbberColor : theme.palette.primary.main}
/>
</Box>
);
}
Expand All @@ -28,6 +35,9 @@ interface LoadingContainerProps {
/** The color of the overlay. */
overlayColor: string;

/** The color of the throbber. Theme primary color by default. */
throbberColor?: string;

/** React prop to allow nesting components. Do not set manually. */
children: React.ReactNode;
}
Loading