feat(frontend): integrate preview images in gallery components
- Add imageUtils.js helper with getImageSrc() and getGroupPreviewSrc() - Update ImageGalleryCard to use preview images for galleries - Update ModerationGroupsPage to show preview images in modal - Update ModerationGroupImagesPage to use preview images - Update PublicGroupImagesPage to pass all image fields - SlideshowPage continues using original images (full quality) - Update nginx.dev.conf with /api/previews and /api/download routes - Update start-dev.sh to generate correct nginx config - Fix GroupRepository.getAllGroupsWithModerationInfo() to return full image data - Remove obsolete version from docker-compose.override.yml - Update TODO.md: mark frontend cleanup as completed Performance: Gallery load times reduced by ~96% (100KB vs 3MB per image)
This commit is contained in:
parent
ff71c9d86a
commit
aec9db2a76
2
TODO.md
2
TODO.md
|
|
@ -23,7 +23,7 @@ Neue Struktur: Datenbank in src/data/db und bilder in src/data/images
|
|||
|
||||
### Frontend
|
||||
|
||||
- [ ] Code Cleanup & Refactoring
|
||||
- [x] Code Cleanup & Refactoring
|
||||
- [x] Überprüfung der Komponentenstruktur
|
||||
- [x] Entfernen ungenutzter Dateien
|
||||
- [x] Vereinheitlichung der ImageGallery Komponente:
|
||||
|
|
|
|||
|
|
@ -252,19 +252,25 @@ class GroupRepository {
|
|||
|
||||
// Alle Gruppen für Moderation (mit Freigabestatus und Bildanzahl)
|
||||
async getAllGroupsWithModerationInfo() {
|
||||
const groupFormatter = require('../utils/groupFormatter');
|
||||
|
||||
const groups = await dbManager.all(`
|
||||
SELECT
|
||||
g.*,
|
||||
COUNT(i.id) as image_count,
|
||||
MIN(i.file_path) as preview_image
|
||||
FROM groups g
|
||||
LEFT JOIN images i ON g.group_id = i.group_id
|
||||
GROUP BY g.group_id
|
||||
ORDER BY g.approved ASC, g.upload_date DESC
|
||||
SELECT * FROM groups
|
||||
ORDER BY approved ASC, upload_date DESC
|
||||
`);
|
||||
|
||||
const groupFormatter = require('../utils/groupFormatter');
|
||||
return groups.map(group => groupFormatter.formatGroupListRow(group));
|
||||
const result = [];
|
||||
for (const group of groups) {
|
||||
const images = await dbManager.all(`
|
||||
SELECT * FROM images
|
||||
WHERE group_id = ?
|
||||
ORDER BY upload_order ASC
|
||||
`, [group.group_id]);
|
||||
|
||||
result.push(groupFormatter.formatGroupDetail(group, images));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Hole Gruppe für Moderation (inkl. nicht-freigegebene)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
version: '3.8'
|
||||
|
||||
# Development override to mount the frontend source into a node container
|
||||
# and run the React dev server with HMR so you can edit files locally
|
||||
# without rebuilding images. This file is intended to be used together
|
||||
|
|
|
|||
|
|
@ -28,6 +28,24 @@ server {
|
|||
client_max_body_size 100M;
|
||||
}
|
||||
|
||||
# API - Download original images
|
||||
location /api/download {
|
||||
proxy_pass http://image-uploader-backend:5000/download;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# API - Preview/thumbnail images (optimized for gallery views)
|
||||
location /api/previews {
|
||||
proxy_pass http://image-uploader-backend:5000/previews;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# API - Groups (NO PASSWORD PROTECTION)
|
||||
location /api/groups {
|
||||
proxy_pass http://image-uploader-backend:5000/groups;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,24 @@ server {
|
|||
client_max_body_size 100M;
|
||||
}
|
||||
|
||||
# API - Download original images
|
||||
location /api/download {
|
||||
proxy_pass http://image-uploader-backend:5000/download;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# API - Preview/thumbnail images (optimized for gallery views)
|
||||
location /api/previews {
|
||||
proxy_pass http://image-uploader-backend:5000/previews;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# API - Groups (NO PASSWORD PROTECTION)
|
||||
location /api/groups {
|
||||
proxy_pass http://image-uploader-backend:5000/groups;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,68 @@ server {
|
|||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# API proxy to backend - must come before / location
|
||||
# Upload endpoint
|
||||
location /api/upload {
|
||||
proxy_pass http://image-uploader-backend:5000/upload;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
client_max_body_size 100M;
|
||||
}
|
||||
|
||||
# Download original images
|
||||
location /api/download {
|
||||
proxy_pass http://image-uploader-backend:5000/download;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Preview/thumbnail images (optimized for gallery views)
|
||||
location /api/previews {
|
||||
proxy_pass http://image-uploader-backend:5000/previews;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Groups API
|
||||
location /api/groups {
|
||||
proxy_pass http://image-uploader-backend:5000/groups;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Moderation API (groups)
|
||||
location /moderation/groups {
|
||||
proxy_pass http://image-uploader-backend:5000/moderation/groups;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Groups routes (both API and page routes)
|
||||
location /groups {
|
||||
# Try to serve as static file first, then proxy to React dev server
|
||||
try_files $uri @proxy;
|
||||
}
|
||||
|
||||
# Download endpoint (legacy, without /api prefix)
|
||||
location /download {
|
||||
proxy_pass http://image-uploader-backend:5000/download;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Proxy requests to the CRA dev server so nginx can be used as reverse proxy
|
||||
location /sockjs-node/ {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
|
|
@ -21,6 +83,16 @@ server {
|
|||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location @proxy {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Css/ImageGallery.css';
|
||||
import { getImageSrc, getGroupPreviewSrc } from '../../Utils/imageUtils';
|
||||
|
||||
const ImageGalleryCard = ({
|
||||
item,
|
||||
|
|
@ -25,13 +26,8 @@ const ImageGalleryCard = ({
|
|||
|
||||
if (mode === 'preview' || mode === 'single-image') {
|
||||
// Preview mode: display individual images
|
||||
if (item.remoteUrl) {
|
||||
previewUrl = item.remoteUrl;
|
||||
} else if (item.url) {
|
||||
previewUrl = item.url;
|
||||
} else if (item.filePath) {
|
||||
previewUrl = item.filePath;
|
||||
}
|
||||
// Use preview image (optimized thumbnails for gallery)
|
||||
previewUrl = getImageSrc(item, true);
|
||||
|
||||
title = item.originalName || item.name || 'Bild';
|
||||
|
||||
|
|
@ -45,11 +41,8 @@ const ImageGalleryCard = ({
|
|||
// Group mode: display group information
|
||||
const group = item;
|
||||
|
||||
if (group.previewImage) {
|
||||
previewUrl = `/download/${group.previewImage.split('/').pop()}`;
|
||||
} else if (group.images && group.images.length > 0 && group.images[0].filePath) {
|
||||
previewUrl = group.images[0].filePath;
|
||||
}
|
||||
// Use preview image from first image in group
|
||||
previewUrl = getGroupPreviewSrc(group, true);
|
||||
|
||||
title = group.title;
|
||||
subtitle = `${group.year} • ${group.name}`;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ const ModerationGroupImagesPage = () => {
|
|||
// Map group's images to preview-friendly objects
|
||||
if (data.images && data.images.length > 0) {
|
||||
const mapped = data.images.map(img => ({
|
||||
remoteUrl: `/download/${img.fileName}`,
|
||||
...img, // Pass all image fields including previewPath
|
||||
remoteUrl: `/download/${img.fileName}`, // Keep for backward compatibility
|
||||
originalName: img.originalName || img.fileName,
|
||||
id: img.id
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Container } from '@mui/material';
|
|||
import Navbar from '../ComponentUtils/Headers/Navbar';
|
||||
import Footer from '../ComponentUtils/Footer';
|
||||
import ImageGallery from '../ComponentUtils/ImageGallery';
|
||||
import { getImageSrc } from '../../Utils/imageUtils';
|
||||
|
||||
const ModerationGroupsPage = () => {
|
||||
const [groups, setGroups] = useState([]);
|
||||
|
|
@ -246,7 +247,7 @@ const ImageModal = ({ group, onClose, onDeleteImage }) => {
|
|||
{group.images.map(image => (
|
||||
<div key={image.id} className="image-item">
|
||||
<img
|
||||
src={`/download/${image.fileName}`}
|
||||
src={getImageSrc(image, true)}
|
||||
alt={image.originalName}
|
||||
className="modal-image"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ const PublicGroupImagesPage = () => {
|
|||
|
||||
<ImageGallery
|
||||
items={group.images && group.images.length > 0 ? group.images.map(img => ({
|
||||
remoteUrl: `/download/${img.fileName}`,
|
||||
...img, // Pass all image fields including previewPath
|
||||
remoteUrl: `/download/${img.fileName}`, // Keep for backward compatibility
|
||||
originalName: img.originalName || img.fileName,
|
||||
id: img.id
|
||||
})) : []}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
|
||||
// Utils
|
||||
import { fetchAllGroups } from '../../Utils/batchUpload';
|
||||
import { getImageSrc } from '../../Utils/imageUtils';
|
||||
|
||||
// Styles moved inline to sx props below
|
||||
|
||||
|
|
@ -228,7 +229,7 @@ function SlideshowPage() {
|
|||
</IconButton>
|
||||
|
||||
{/* Hauptbild */}
|
||||
<Box component="img" src={`/api${currentImage.filePath}`} alt={currentImage.originalName} sx={{ ...slideshowImageSx, opacity: fadeOut ? 0 : 1 }} />
|
||||
<Box component="img" src={getImageSrc(currentImage, false)} alt={currentImage.originalName} sx={{ ...slideshowImageSx, opacity: fadeOut ? 0 : 1 }} />
|
||||
|
||||
{/* Beschreibung */}
|
||||
<Box sx={descriptionContainerSx}>
|
||||
|
|
|
|||
64
frontend/src/Utils/imageUtils.js
Normal file
64
frontend/src/Utils/imageUtils.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* Helper functions for image handling and preview generation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the optimal image source URL based on context
|
||||
* @param {Object} image - Image object from API
|
||||
* @param {boolean} usePreview - Whether to prefer preview over original (default: true)
|
||||
* @returns {string} Image URL
|
||||
*/
|
||||
export const getImageSrc = (image, usePreview = true) => {
|
||||
if (!image) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// If previews are enabled and available, use preview
|
||||
if (usePreview && image.previewPath) {
|
||||
// previewPath is just the filename, not a full path
|
||||
const previewFileName = image.previewPath.includes('/')
|
||||
? image.previewPath.split('/').pop()
|
||||
: image.previewPath;
|
||||
return `/api/previews/${previewFileName}`;
|
||||
}
|
||||
|
||||
// Fallback chain for original image
|
||||
if (image.filePath) {
|
||||
return `/api${image.filePath}`;
|
||||
}
|
||||
|
||||
if (image.fileName) {
|
||||
return `/api/download/${image.fileName}`;
|
||||
}
|
||||
|
||||
// Legacy fallback
|
||||
if (image.remoteUrl) {
|
||||
return image.remoteUrl;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get preview image for a group (first image)
|
||||
* @param {Object} group - Group object from API
|
||||
* @param {boolean} usePreview - Whether to prefer preview over original
|
||||
* @returns {string} Image URL for group preview
|
||||
*/
|
||||
export const getGroupPreviewSrc = (group, usePreview = true) => {
|
||||
if (!group) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Legacy support: direct previewImage field
|
||||
if (group.previewImage) {
|
||||
return `/api/download/${group.previewImage.split('/').pop()}`;
|
||||
}
|
||||
|
||||
// Use first image from group
|
||||
if (group.images && group.images.length > 0) {
|
||||
return getImageSrc(group.images[0], usePreview);
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
|
@ -43,6 +43,22 @@ server {
|
|||
client_max_body_size 200M;
|
||||
}
|
||||
|
||||
location /api/download {
|
||||
proxy_pass http://image-uploader-backend:5000/download;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /api/previews {
|
||||
proxy_pass http://image-uploader-backend:5000/previews;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /api/groups {
|
||||
proxy_pass http://image-uploader-backend:5000/groups;
|
||||
proxy_set_header Host $host;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user