- Replace @material-ui/core -> @mui/material - Replace @material-ui/icons -> @mui/icons-material - Switch makeStyles imports to @mui/styles (compat) - Add @mui/material, @mui/icons-material, @mui/styles, @emotion/react, @emotion/styled to package.json Notes: Kept makeStyles via @mui/styles for incremental migration; next: replace makeStyles usage with sx/styled where needed.
314 lines
8.2 KiB
JavaScript
314 lines
8.2 KiB
JavaScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { makeStyles } from '@mui/styles';
|
|
import {
|
|
Typography,
|
|
Box,
|
|
CircularProgress,
|
|
IconButton
|
|
} from '@mui/material';
|
|
import {
|
|
Home as HomeIcon,
|
|
ExitToApp as ExitIcon
|
|
} from '@mui/icons-material';
|
|
|
|
// Utils
|
|
import { fetchAllGroups } from '../../Utils/batchUpload';
|
|
|
|
const useStyles = makeStyles({
|
|
fullscreenContainer: {
|
|
position: 'fixed',
|
|
top: 0,
|
|
left: 0,
|
|
width: '100%',
|
|
height: '100%',
|
|
backgroundColor: '#000',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
zIndex: 9999,
|
|
overflow: 'hidden'
|
|
},
|
|
exitButton: {
|
|
position: 'absolute',
|
|
top: '20px',
|
|
right: '20px',
|
|
color: 'white',
|
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
'&:hover': {
|
|
backgroundColor: 'rgba(0,0,0,0.8)'
|
|
}
|
|
},
|
|
homeButton: {
|
|
position: 'absolute',
|
|
top: '20px',
|
|
left: '20px',
|
|
color: 'white',
|
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
'&:hover': {
|
|
backgroundColor: 'rgba(0,0,0,0.8)'
|
|
}
|
|
},
|
|
slideshowImage: {
|
|
maxWidth: '100%',
|
|
maxHeight: '100%',
|
|
objectFit: 'contain',
|
|
transition: 'opacity 0.5s ease-in-out'
|
|
},
|
|
descriptionContainer: {
|
|
position: 'fixed',
|
|
left: 40,
|
|
bottom: 40,
|
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
|
padding: '25px 35px',
|
|
borderRadius: '12px',
|
|
maxWidth: '35vw',
|
|
minWidth: '260px',
|
|
textAlign: 'left',
|
|
backdropFilter: 'blur(5px)',
|
|
zIndex: 10001,
|
|
boxShadow: '0 4px 24px rgba(0,0,0,0.4)'
|
|
},
|
|
titleText: {
|
|
color: 'white',
|
|
fontSize: '28px',
|
|
fontWeight: '500',
|
|
margin: '0 0 8px 0',
|
|
fontFamily: 'roboto'
|
|
},
|
|
yearAuthorText: {
|
|
color: '#FFD700',
|
|
fontSize: '18px',
|
|
fontWeight: '400',
|
|
margin: '0 0 12px 0',
|
|
fontFamily: 'roboto'
|
|
},
|
|
descriptionText: {
|
|
color: '#E0E0E0',
|
|
fontSize: '16px',
|
|
fontWeight: '300',
|
|
margin: '0 0 12px 0',
|
|
fontFamily: 'roboto',
|
|
lineHeight: '1.4'
|
|
},
|
|
metaText: {
|
|
color: '#999',
|
|
fontSize: '12px',
|
|
marginTop: '8px',
|
|
fontFamily: 'roboto'
|
|
},
|
|
loadingContainer: {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
color: 'white'
|
|
}
|
|
});
|
|
|
|
function SlideshowPage() {
|
|
const classes = useStyles();
|
|
const navigate = useNavigate();
|
|
|
|
const [allGroups, setAllGroups] = useState([]);
|
|
const [currentGroupIndex, setCurrentGroupIndex] = useState(0);
|
|
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState(null);
|
|
const [fadeOut, setFadeOut] = useState(false);
|
|
|
|
// Slideshow-Timing Konstanten
|
|
const IMAGE_DISPLAY_TIME = 4000; // 4 Sekunden pro Bild
|
|
const TRANSITION_TIME = 500; // 0.5 Sekunden für Fade-Effekt
|
|
|
|
// Gruppen laden
|
|
useEffect(() => {
|
|
const loadAllGroups = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const groupsData = await fetchAllGroups();
|
|
|
|
if (groupsData.groups && groupsData.groups.length > 0) {
|
|
// Mische die Gruppen zufällig
|
|
const shuffledGroups = [...groupsData.groups].sort(() => Math.random() - 0.5);
|
|
setAllGroups(shuffledGroups);
|
|
setCurrentGroupIndex(0);
|
|
setCurrentImageIndex(0);
|
|
} else {
|
|
setError('Keine Slideshows gefunden');
|
|
}
|
|
} catch (err) {
|
|
console.error('Fehler beim Laden der Gruppen:', err);
|
|
setError('Fehler beim Laden der Slideshows');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
loadAllGroups();
|
|
}, []);
|
|
|
|
// Automatischer Slideshow-Wechsel
|
|
const nextImage = useCallback(() => {
|
|
if (allGroups.length === 0) return;
|
|
|
|
const currentGroup = allGroups[currentGroupIndex];
|
|
if (!currentGroup || !currentGroup.images) return;
|
|
|
|
setFadeOut(true);
|
|
|
|
setTimeout(() => {
|
|
if (currentImageIndex + 1 < currentGroup.images.length) {
|
|
// Nächstes Bild in der aktuellen Gruppe
|
|
setCurrentImageIndex(prev => prev + 1);
|
|
} else {
|
|
// Zur nächsten Gruppe wechseln (zufällig)
|
|
const nextGroupIndex = Math.floor(Math.random() * allGroups.length);
|
|
setCurrentGroupIndex(nextGroupIndex);
|
|
setCurrentImageIndex(0);
|
|
}
|
|
setFadeOut(false);
|
|
}, TRANSITION_TIME);
|
|
}, [allGroups, currentGroupIndex, currentImageIndex]);
|
|
|
|
// Timer für automatischen Wechsel
|
|
useEffect(() => {
|
|
if (loading || error || allGroups.length === 0) return;
|
|
|
|
const timer = setInterval(nextImage, IMAGE_DISPLAY_TIME);
|
|
return () => clearInterval(timer);
|
|
}, [loading, error, allGroups, nextImage]);
|
|
|
|
// Keyboard-Navigation
|
|
useEffect(() => {
|
|
const handleKeyPress = (event) => {
|
|
switch (event.key) {
|
|
case 'Escape':
|
|
navigate('/');
|
|
break;
|
|
case ' ':
|
|
case 'ArrowRight':
|
|
nextImage();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', handleKeyPress);
|
|
return () => document.removeEventListener('keydown', handleKeyPress);
|
|
}, [nextImage, navigate]);
|
|
|
|
// Aktuelle Gruppe und Bild
|
|
const currentGroup = allGroups[currentGroupIndex];
|
|
const currentImage = currentGroup?.images?.[currentImageIndex];
|
|
|
|
if (loading) {
|
|
return (
|
|
<Box className={classes.fullscreenContainer}>
|
|
<Box className={classes.loadingContainer}>
|
|
<CircularProgress style={{ color: 'white', marginBottom: '20px' }} />
|
|
<Typography style={{ color: 'white' }}>Slideshow wird geladen...</Typography>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<Box className={classes.fullscreenContainer}>
|
|
<Box className={classes.loadingContainer}>
|
|
<Typography style={{ color: 'white', fontSize: '24px' }}>{error}</Typography>
|
|
<IconButton
|
|
className={classes.homeButton}
|
|
onClick={() => navigate('/')}
|
|
title="Zur Startseite"
|
|
>
|
|
<HomeIcon />
|
|
</IconButton>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (!currentGroup || !currentImage) {
|
|
return (
|
|
<Box className={classes.fullscreenContainer}>
|
|
<Box className={classes.loadingContainer}>
|
|
<Typography style={{ color: 'white', fontSize: '24px' }}>
|
|
Keine Bilder verfügbar
|
|
</Typography>
|
|
<IconButton
|
|
className={classes.homeButton}
|
|
onClick={() => navigate('/')}
|
|
title="Zur Startseite"
|
|
>
|
|
<HomeIcon />
|
|
</IconButton>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box className={classes.fullscreenContainer}>
|
|
{/* Navigation Buttons */}
|
|
<IconButton
|
|
className={classes.homeButton}
|
|
onClick={() => navigate('/')}
|
|
title="Zur Startseite"
|
|
>
|
|
<HomeIcon />
|
|
</IconButton>
|
|
|
|
<IconButton
|
|
className={classes.exitButton}
|
|
onClick={() => navigate('/')}
|
|
title="Slideshow beenden"
|
|
>
|
|
<ExitIcon />
|
|
</IconButton>
|
|
|
|
{/* Hauptbild */}
|
|
<img
|
|
src={`/api${currentImage.filePath}`}
|
|
alt={currentImage.originalName}
|
|
className={classes.slideshowImage}
|
|
style={{
|
|
opacity: fadeOut ? 0 : 1,
|
|
transition: `opacity ${TRANSITION_TIME}ms ease-in-out`
|
|
}}
|
|
/>
|
|
|
|
{/* Beschreibung */}
|
|
<Box className={classes.descriptionContainer}>
|
|
{/* Titel */}
|
|
<Typography className={classes.titleText}>
|
|
{currentGroup.title || 'Unbenanntes Projekt'}
|
|
</Typography>
|
|
|
|
{/* Jahr und Name */}
|
|
<Typography className={classes.yearAuthorText}>
|
|
{currentGroup.year}
|
|
{currentGroup.name && ` • ${currentGroup.name}`}
|
|
</Typography>
|
|
|
|
{/* Beschreibung (wenn vorhanden) */}
|
|
{currentGroup.description && (
|
|
<Typography className={classes.descriptionText}>
|
|
{currentGroup.description}
|
|
</Typography>
|
|
)}
|
|
|
|
{/* Meta-Informationen */}
|
|
<Typography className={classes.metaText}>
|
|
Bild {currentImageIndex + 1} von {currentGroup.images.length} •
|
|
Slideshow {currentGroupIndex + 1} von {allGroups.length}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
export default SlideshowPage;
|