469 lines
13 KiB
Markdown
469 lines
13 KiB
Markdown
# Image-Uploader Erweiterung: Multi-Image Upload mit Beschreibung
|
||
|
||
## 🎯 Ziel
|
||
Erweiterung der bestehenden Single-Image-Upload-Funktionalität zu einem Multi-Image-Upload mit Beschreibungstext für spätere Slideshow-Nutzung.
|
||
|
||
## 📊 Aufwandsschätzung
|
||
**Geschätzter Aufwand: 8-12 Stunden** (verteilt auf 2-3 Arbeitstage)
|
||
|
||
### Komplexitätsbewertung: ⭐⭐⭐☆☆ (Mittel)
|
||
|
||
## 🔄 Änderungsübersicht
|
||
|
||
### Frontend Änderungen (5-7 Stunden)
|
||
- **Neue Multi-Upload-Komponente**
|
||
- **UI für Beschreibungstext**
|
||
- **Vorschau-Galerie**
|
||
- **Upload-Progress für mehrere Dateien**
|
||
|
||
### Backend Änderungen (2-3 Stunden)
|
||
- **Neue API-Endpoints**
|
||
- **Datenbank/JSON-Struktur für Upload-Gruppen**
|
||
- **Batch-Upload-Verarbeitung**
|
||
|
||
### Integration & Testing (1-2 Stunden)
|
||
- **Frontend-Backend-Integration**
|
||
- **Error Handling**
|
||
- **UI/UX Tests**
|
||
|
||
---
|
||
|
||
## 🏗️ Technische Umsetzung
|
||
|
||
### 1. Backend-Erweiterungen
|
||
|
||
#### 1.1 Neue Datenstruktur
|
||
```javascript
|
||
// Neue Upload-Group Struktur
|
||
{
|
||
groupId: "unique-group-id",
|
||
description: "Benutzer-Beschreibung",
|
||
uploadDate: "2025-10-11T10:30:00Z",
|
||
images: [
|
||
{
|
||
fileName: "abc123.jpg",
|
||
originalName: "foto1.jpg",
|
||
filePath: "/upload/abc123.jpg",
|
||
uploadOrder: 1
|
||
},
|
||
{
|
||
fileName: "def456.png",
|
||
originalName: "foto2.png",
|
||
filePath: "/upload/def456.png",
|
||
uploadOrder: 2
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 1.2 Neue API-Endpoints
|
||
- `POST /api/upload/batch` - Multi-Image Upload
|
||
- `GET /api/groups/:groupId` - Upload-Gruppe abrufen
|
||
- `GET /api/groups` - Alle Upload-Gruppen auflisten
|
||
- `GET /api/slideshow/:groupId` - Slideshow-Daten
|
||
|
||
#### 1.3 Dateien zu erstellen/ändern:
|
||
```
|
||
backend/src/
|
||
├── routes/
|
||
│ ├── upload.js # ✏️ Erweitern
|
||
│ ├── batch-upload.js # 🆕 Neu
|
||
│ └── groups.js # 🆕 Neu
|
||
├── models/
|
||
│ └── uploadGroup.js # 🆕 Neu
|
||
├── utils/
|
||
│ └── groupStorage.js # 🆕 Neu (JSON-basiert)
|
||
└── constants.js # ✏️ Neue Endpoints hinzufügen
|
||
```
|
||
|
||
### 2. Frontend-Erweiterungen
|
||
|
||
#### 2.1 Neue Komponenten
|
||
```
|
||
frontend/src/Components/
|
||
├── ComponentUtils/
|
||
│ ├── MultiImageUpload.js # 🆕 Haupt-Upload-Komponente
|
||
│ ├── ImagePreviewGallery.js # 🆕 Vorschau der ausgewählten Bilder
|
||
│ ├── DescriptionInput.js # 🆕 Textfeld für Beschreibung
|
||
│ ├── UploadProgress.js # 🆕 Progress-Anzeige für alle Dateien
|
||
│ └── Css/
|
||
│ ├── MultiUpload.css # 🆕 Styling
|
||
│ └── ImageGallery.css # 🆕 Galerie-Styling
|
||
├── Pages/
|
||
│ ├── MultiUploadPage.js # 🆕 Neue Seite für Multi-Upload
|
||
│ ├── SlideshowPage.js # 🆕 Slideshow-Anzeige
|
||
│ └── GroupsOverviewPage.js # 🆕 Übersicht aller Upload-Gruppen
|
||
└── Utils/
|
||
└── batchUpload.js # 🆕 Batch-Upload-Logik
|
||
```
|
||
|
||
#### 2.2 Routing-Erweiterungen
|
||
```javascript
|
||
// Neue Routen in App.js
|
||
<Route path="/multi-upload" component={MultiUploadPage} />
|
||
<Route path="/slideshow/:groupId" component={SlideshowPage} />
|
||
<Route path="/groups" component={GroupsOverviewPage} />
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 Detaillierte Implementierungsschritte
|
||
|
||
### Phase 1: Backend-Grundlage (2-3h)
|
||
|
||
#### Schritt 1.1: Upload-Gruppen Datenmodell
|
||
```javascript
|
||
// backend/src/models/uploadGroup.js
|
||
class UploadGroup {
|
||
constructor(description) {
|
||
this.groupId = generateId();
|
||
this.description = description;
|
||
this.uploadDate = new Date().toISOString();
|
||
this.images = [];
|
||
}
|
||
|
||
addImage(fileName, originalName, uploadOrder) {
|
||
this.images.push({
|
||
fileName,
|
||
originalName,
|
||
filePath: `/upload/${fileName}`,
|
||
uploadOrder
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Schritt 1.2: JSON-basierte Speicherung
|
||
```javascript
|
||
// backend/src/utils/groupStorage.js
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const GROUPS_FILE = path.join(__dirname, '../data/upload-groups.json');
|
||
|
||
class GroupStorage {
|
||
static saveGroup(group) {
|
||
// JSON-Datei lesen, Gruppe hinzufügen, zurückschreiben
|
||
}
|
||
|
||
static getGroup(groupId) {
|
||
// Gruppe aus JSON-Datei laden
|
||
}
|
||
|
||
static getAllGroups() {
|
||
// Alle Gruppen laden
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Schritt 1.3: Batch-Upload API
|
||
```javascript
|
||
// backend/src/routes/batch-upload.js
|
||
router.post('/api/upload/batch', (req, res) => {
|
||
const { description } = req.body;
|
||
const files = req.files.images; // Array von Dateien
|
||
|
||
const group = new UploadGroup(description);
|
||
|
||
// Alle Dateien verarbeiten
|
||
files.forEach((file, index) => {
|
||
const fileName = generateId() + '.' + getFileExtension(file.name);
|
||
file.mv(`upload/${fileName}`);
|
||
group.addImage(fileName, file.name, index + 1);
|
||
});
|
||
|
||
GroupStorage.saveGroup(group);
|
||
res.json({ groupId: group.groupId, message: 'Upload successful' });
|
||
});
|
||
```
|
||
|
||
### Phase 2: Frontend Multi-Upload UI (3-4h)
|
||
|
||
#### Schritt 2.1: Multi-Image Dropzone
|
||
```javascript
|
||
// frontend/src/Components/ComponentUtils/MultiImageUpload.js
|
||
import { useDropzone } from 'react-dropzone';
|
||
|
||
function MultiImageUpload({ onImagesSelected }) {
|
||
const { getRootProps, getInputProps, acceptedFiles } = useDropzone({
|
||
accept: 'image/*',
|
||
multiple: true,
|
||
onDrop: (files) => {
|
||
onImagesSelected(files);
|
||
}
|
||
});
|
||
|
||
return (
|
||
<div {...getRootProps()} className="multi-dropzone">
|
||
<input {...getInputProps()} />
|
||
<p>Ziehe mehrere Bilder hierher oder klicke zum Auswählen</p>
|
||
<p>({acceptedFiles.length} Dateien ausgewählt)</p>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
#### Schritt 2.2: Bild-Vorschau Galerie
|
||
```javascript
|
||
// frontend/src/Components/ComponentUtils/ImagePreviewGallery.js
|
||
function ImagePreviewGallery({ images, onRemoveImage, onReorderImages }) {
|
||
return (
|
||
<div className="image-preview-gallery">
|
||
{images.map((image, index) => (
|
||
<div key={index} className="image-preview-item">
|
||
<img src={URL.createObjectURL(image)} alt={`Preview ${index + 1}`} />
|
||
<button onClick={() => onRemoveImage(index)}>✕</button>
|
||
<div className="image-order">{index + 1}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
#### Schritt 2.3: Beschreibungs-Input
|
||
```javascript
|
||
// frontend/src/Components/ComponentUtils/DescriptionInput.js
|
||
function DescriptionInput({ description, onDescriptionChange, maxLength = 500 }) {
|
||
return (
|
||
<div className="description-input">
|
||
<label>Beschreibung für diese Bildersammlung:</label>
|
||
<textarea
|
||
value={description}
|
||
onChange={(e) => onDescriptionChange(e.target.value)}
|
||
maxLength={maxLength}
|
||
placeholder="Beschreibe diese Bildersammlung für die spätere Slideshow..."
|
||
/>
|
||
<div className="character-count">{description.length}/{maxLength}</div>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### Phase 3: Upload-Logik & Progress (2-3h)
|
||
|
||
#### Schritt 3.1: Batch-Upload Funktion
|
||
```javascript
|
||
// frontend/src/Utils/batchUpload.js
|
||
async function uploadImageBatch(images, description, onProgress) {
|
||
const formData = new FormData();
|
||
|
||
images.forEach((image, index) => {
|
||
formData.append('images', image);
|
||
});
|
||
formData.append('description', description);
|
||
|
||
try {
|
||
const response = await fetch('/api/upload/batch', {
|
||
method: 'POST',
|
||
body: formData,
|
||
onUploadProgress: (progressEvent) => {
|
||
const progress = (progressEvent.loaded / progressEvent.total) * 100;
|
||
onProgress(progress);
|
||
}
|
||
});
|
||
|
||
return await response.json();
|
||
} catch (error) {
|
||
throw new Error(`Upload failed: ${error.message}`);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Schritt 3.2: Upload Progress Komponente
|
||
```javascript
|
||
// frontend/src/Components/ComponentUtils/UploadProgress.js
|
||
function UploadProgress({ progress, currentFile, totalFiles }) {
|
||
return (
|
||
<div className="upload-progress">
|
||
<div className="progress-bar">
|
||
<div className="progress-fill" style={{ width: `${progress}%` }}></div>
|
||
</div>
|
||
<div className="progress-text">
|
||
{currentFile && `Uploading: ${currentFile} (${Math.round(progress)}%)`}
|
||
{totalFiles && `${currentFile} von ${totalFiles} Dateien`}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### Phase 4: Hauptseite Integration (1-2h)
|
||
|
||
#### Schritt 4.1: Multi-Upload Seite
|
||
```javascript
|
||
// frontend/src/Components/Pages/MultiUploadPage.js
|
||
function MultiUploadPage() {
|
||
const [selectedImages, setSelectedImages] = useState([]);
|
||
const [description, setDescription] = useState('');
|
||
const [uploading, setUploading] = useState(false);
|
||
const [uploadProgress, setUploadProgress] = useState(0);
|
||
|
||
const handleUpload = async () => {
|
||
if (selectedImages.length === 0) return;
|
||
|
||
setUploading(true);
|
||
try {
|
||
const result = await uploadImageBatch(
|
||
selectedImages,
|
||
description,
|
||
setUploadProgress
|
||
);
|
||
|
||
// Redirect zur Slideshow oder Erfolgsseite
|
||
history.push(`/slideshow/${result.groupId}`);
|
||
} catch (error) {
|
||
// Error handling
|
||
} finally {
|
||
setUploading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="multi-upload-page">
|
||
<Navbar />
|
||
<div className="upload-container">
|
||
<MultiImageUpload onImagesSelected={setSelectedImages} />
|
||
|
||
{selectedImages.length > 0 && (
|
||
<>
|
||
<ImagePreviewGallery
|
||
images={selectedImages}
|
||
onRemoveImage={removeImageAtIndex}
|
||
onReorderImages={reorderImages}
|
||
/>
|
||
|
||
<DescriptionInput
|
||
description={description}
|
||
onDescriptionChange={setDescription}
|
||
/>
|
||
|
||
<button
|
||
onClick={handleUpload}
|
||
disabled={uploading || selectedImages.length === 0}
|
||
className="upload-button"
|
||
>
|
||
{uploading ? 'Uploading...' : `${selectedImages.length} Bilder hochladen`}
|
||
</button>
|
||
</>
|
||
)}
|
||
|
||
{uploading && (
|
||
<UploadProgress
|
||
progress={uploadProgress}
|
||
totalFiles={selectedImages.length}
|
||
/>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### Phase 5: Slideshow & Navigation (2h)
|
||
|
||
#### Schritt 5.1: Slideshow Komponente
|
||
```javascript
|
||
// frontend/src/Components/Pages/SlideshowPage.js
|
||
function SlideshowPage() {
|
||
const { groupId } = useParams();
|
||
const [group, setGroup] = useState(null);
|
||
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||
|
||
useEffect(() => {
|
||
fetch(`/api/groups/${groupId}`)
|
||
.then(res => res.json())
|
||
.then(setGroup);
|
||
}, [groupId]);
|
||
|
||
if (!group) return <div>Loading...</div>;
|
||
|
||
return (
|
||
<div className="slideshow-page">
|
||
<div className="slideshow-header">
|
||
<h1>{group.description}</h1>
|
||
<p>Hochgeladen am: {new Date(group.uploadDate).toLocaleDateString()}</p>
|
||
</div>
|
||
|
||
<div className="slideshow-container">
|
||
<img
|
||
src={group.images[currentImageIndex].filePath}
|
||
alt={`Bild ${currentImageIndex + 1}`}
|
||
className="slideshow-image"
|
||
/>
|
||
|
||
<div className="slideshow-controls">
|
||
<button onClick={() => setCurrentImageIndex(prev =>
|
||
prev > 0 ? prev - 1 : group.images.length - 1
|
||
)}>
|
||
‹ Vorheriges
|
||
</button>
|
||
|
||
<span>{currentImageIndex + 1} / {group.images.length}</span>
|
||
|
||
<button onClick={() => setCurrentImageIndex(prev =>
|
||
prev < group.images.length - 1 ? prev + 1 : 0
|
||
)}>
|
||
Nächstes ›
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="slideshow-thumbnails">
|
||
{group.images.map((image, index) => (
|
||
<img
|
||
key={index}
|
||
src={image.filePath}
|
||
alt={`Thumbnail ${index + 1}`}
|
||
className={`thumbnail ${index === currentImageIndex ? 'active' : ''}`}
|
||
onClick={() => setCurrentImageIndex(index)}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎨 UI/UX Verbesserungen
|
||
|
||
### Drag & Drop Features
|
||
- **Bilder-Reihenfolge ändern**: Drag & Drop in der Vorschau
|
||
- **Bilder entfernen**: X-Button auf jedem Vorschaubild
|
||
- **Bulk-Aktionen**: Alle entfernen, Reihenfolge umkehren
|
||
|
||
### Responsive Design
|
||
- **Mobile-optimiert**: Touch-friendly Upload und Slideshow
|
||
- **Tablet-Ansicht**: Optimierte Galerie-Darstellung
|
||
- **Desktop**: Erweiterte Features wie Keyboard-Navigation
|
||
|
||
### Benutzerfreundlichkeit
|
||
- **Progress-Feedback**: Echtzeitanzeige des Upload-Fortschritts
|
||
- **Error Handling**: Klare Fehlermeldungen bei Upload-Problemen
|
||
- **Auto-Save**: Beschreibung zwischenspeichern
|
||
- **Vorschau-Modus**: Slideshow vor Upload testen
|
||
|
||
---
|
||
|
||
## 🧪 Testing-Strategie
|
||
|
||
### Unit Tests
|
||
- Upload-Gruppe Datenmodell
|
||
- Batch-Upload API-Endpoints
|
||
- Frontend-Komponenten (Jest/React Testing Library)
|
||
|
||
### Integration Tests
|
||
- End-to-End Upload-Flow
|
||
- Slideshow-Navigation
|
||
- Error-Szenarien
|
||
|
||
### Performance Tests
|
||
- Multiple große Dateien (>10MB)
|
||
- Viele kleine Dateien (>50 Bilder)
|
||
- Speicher-Verbrauch bei großen Uploads
|
||
|
||
---
|
||
|