diff --git a/.gitignore b/.gitignore index 5d381cc..1594a2d 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,27 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +# PDF Web App specific - Ignore upload and output contents but keep directories +uploads/* +!uploads/.gitkeep +output/* +!output/.gitkeep + +# IDEs +.vscode/ +.idea/ + +# OS specific +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Temporary files +*.tmp +*.temp +*.bak + diff --git a/QUICKSTART.md b/QUICKSTART.md index 077d7d7..2eb4816 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -48,6 +48,10 @@ Die App läuft jetzt lokal auf Ihrem Computer. Sie können: - ✅ PDFs zusammenführen - ✅ PDFs zu Bildern konvertieren - ✅ Drag & Drop verwenden +- ✅ **NEU: Bildvorschau mit Rotation** - Bilder anzeigen und drehen +- ✅ **NEU: Bildorientierung ändern** - 90°, 180°, 270° Rotation +- ✅ **NEU: Verschiedene Vorschaugrößen** - Klein, Mittel, Groß +- ✅ **NEU: Bildmodal** - Bilder in voller Größe anzeigen - ✅ Vollständig offline arbeiten ## Bei Problemen: diff --git a/app.py b/app.py index 17b4958..58a3146 100644 --- a/app.py +++ b/app.py @@ -112,9 +112,9 @@ def convert_images_to_pdf(): """Konvertiert hochgeladene Bilder zu PDF""" try: data = request.get_json() - filenames = data.get('filenames', []) + files_data = data.get('files', []) - if not filenames: + if not files_data: return jsonify({'error': 'Keine Dateien ausgewählt'}), 400 # PDF erstellen @@ -125,12 +125,19 @@ def convert_images_to_pdf(): c = canvas.Canvas(output_path, pagesize=A4) page_width, page_height = A4 - for filename in filenames: + for file_data in files_data: + filename = file_data.get('filename') + rotation = file_data.get('rotation', 0) + file_path = os.path.join(UPLOAD_FOLDER, filename) if os.path.exists(file_path): - # Bild öffnen und Größe anpassen + # Bild öffnen und rotieren with Image.open(file_path) as img: + # Bild rotieren falls nötig + if rotation != 0: + img = img.rotate(-rotation, expand=True) # Negative Rotation für korrekte Richtung + img_width, img_height = img.size # Seitenverhältnis berechnen @@ -151,8 +158,16 @@ def convert_images_to_pdf(): x = (page_width - new_width) / 2 y = (page_height - new_height) / 2 - # Bild zur PDF hinzufügen - c.drawImage(file_path, x, y, width=new_width, height=new_height) + # Temporäres rotiertes Bild speichern + if rotation != 0: + temp_path = os.path.join(UPLOAD_FOLDER, f"temp_rotated_{filename}") + img.save(temp_path) + c.drawImage(temp_path, x, y, width=new_width, height=new_height) + # Temporäre Datei löschen + os.remove(temp_path) + else: + c.drawImage(file_path, x, y, width=new_width, height=new_height) + c.showPage() c.save() @@ -278,6 +293,20 @@ def merge_pdfs(): except Exception as e: return jsonify({'error': f'Fehler beim Zusammenführen: {str(e)}'}), 500 +@app.route('/uploads/') +def serve_uploaded_file(filename): + """Serviert hochgeladene Dateien für die Vorschau""" + try: + file_path = os.path.join(UPLOAD_FOLDER, filename) + + if not os.path.exists(file_path): + return "Datei nicht gefunden", 404 + + return send_file(file_path) + + except Exception as e: + return f"Fehler beim Laden der Datei: {str(e)}", 500 + @app.route('/download/') def download_file(filename): """Download einer generierten Datei""" diff --git a/output/.gitkeep b/output/.gitkeep new file mode 100644 index 0000000..39b028c --- /dev/null +++ b/output/.gitkeep @@ -0,0 +1,2 @@ +# Diese Datei sorgt dafür, dass der output Ordner in Git erhalten bleibt +# Der Ordnerinhalt wird durch .gitignore ignoriert \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index 7e5f2ad..ee24e72 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -411,4 +411,101 @@ footer { /* File Type Icons */ .file-type-pdf { color: #dc3545; } .file-type-image { color: #28a745; } -.file-type-zip { color: #ffc107; } \ No newline at end of file +.file-type-zip { color: #ffc107; } + +/* Image Preview */ +.image-preview { + max-width: 120px; + max-height: 120px; + object-fit: cover; + border-radius: var(--border-radius); + border: 2px solid #e9ecef; + transition: all 0.3s ease; + cursor: pointer; +} + +.image-preview:hover { + border-color: var(--primary-color); + transform: scale(1.05); +} + +.image-preview.size-small { + max-width: 80px; + max-height: 80px; +} + +.image-preview.size-medium { + max-width: 120px; + max-height: 120px; +} + +.image-preview.size-large { + max-width: 160px; + max-height: 160px; +} + +.image-preview-large { + max-width: 200px; + max-height: 200px; + cursor: pointer; +} + +/* Rotation controls */ +.rotation-controls { + display: flex; + gap: 0.25rem; + margin-top: 0.5rem; +} + +.rotation-btn { + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.75rem; +} + +/* Image rotation classes */ +.rotate-0 { transform: rotate(0deg); } +.rotate-90 { transform: rotate(90deg); } +.rotate-180 { transform: rotate(180deg); } +.rotate-270 { transform: rotate(270deg); } + +/* Image modal */ +.image-modal .modal-body { + padding: 0; + text-align: center; +} + +.image-modal .modal-body img { + max-width: 100%; + max-height: 80vh; + object-fit: contain; +} + +/* File item enhanced */ +.file-item-enhanced { + padding: 1rem; + border-radius: var(--border-radius); + transition: all 0.3s ease; +} + +.file-item-enhanced:hover { + background-color: rgba(0, 123, 255, 0.05); + transform: translateY(-2px); + box-shadow: var(--box-shadow-lg); +} + +.image-info { + flex: 1; + min-width: 0; +} + +.image-controls { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: flex-end; +} \ No newline at end of file diff --git a/static/js/images-to-pdf.js b/static/js/images-to-pdf.js index 2739dba..fc27bd1 100644 --- a/static/js/images-to-pdf.js +++ b/static/js/images-to-pdf.js @@ -122,27 +122,50 @@ function displayFiles() { function createFileItem(file, index) { const div = document.createElement('div'); - div.className = 'list-group-item file-item'; + div.className = 'list-group-item file-item-enhanced'; div.dataset.index = index; + // Initialize rotation if not set + if (!file.rotation) { + file.rotation = 0; + } + div.innerHTML = ` -
- -
-
-
${file.original_name}
-
${formatFileSize(file.size)}
-
-
- - - +
+
+ ${file.original_name} +
+
+
${file.original_name}
+
${formatFileSize(file.size)}
+
+ + + ${file.rotation}° +
+
+
+
+ + +
+ +
`; @@ -200,11 +223,16 @@ async function convertToPdf() { setLoadingState(convertBtn, true); try { - const filenames = imagesToPdfFiles.map(file => file.filename); + // Include rotation data for each file + const filesWithRotation = imagesToPdfFiles.map(file => ({ + filename: file.filename, + rotation: file.rotation || 0, + original_name: file.original_name + })); const response = await makeRequest('/api/convert-images-to-pdf', { method: 'POST', - body: JSON.stringify({ filenames }) + body: JSON.stringify({ files: filesWithRotation }) }); if (response.success) { @@ -264,7 +292,97 @@ function hideResults() { errorArea.style.display = 'none'; } +function rotateImage(index, degrees) { + if (index >= 0 && index < imagesToPdfFiles.length) { + imagesToPdfFiles[index].rotation = (imagesToPdfFiles[index].rotation + degrees) % 360; + if (imagesToPdfFiles[index].rotation < 0) { + imagesToPdfFiles[index].rotation += 360; + } + displayFiles(); + showNotification(`Bild um ${degrees}° gedreht.`, 'info'); + } +} + +function showImageModal(filename, originalName, index) { + // Create modal if it doesn't exist + let modal = document.getElementById('image-modal'); + if (!modal) { + modal = document.createElement('div'); + modal.className = 'modal fade image-modal'; + modal.id = 'image-modal'; + modal.setAttribute('tabindex', '-1'); + modal.setAttribute('aria-hidden', 'true'); + + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + } else { + // Update existing modal + document.getElementById('image-modal-title').textContent = originalName; + const modalImage = document.getElementById('modal-image'); + modalImage.src = `/uploads/${filename}`; + modalImage.alt = originalName; + modalImage.className = `img-fluid rotate-${imagesToPdfFiles[index].rotation}`; + + // Update rotation buttons + const rotateButtons = modal.querySelectorAll('.btn-outline-primary'); + rotateButtons[0].onclick = () => rotateImageInModal(index, -90); + rotateButtons[1].onclick = () => rotateImageInModal(index, 90); + } + + // Show modal + const bootstrapModal = new bootstrap.Modal(modal); + bootstrapModal.show(); +} + +function rotateImageInModal(index, degrees) { + rotateImage(index, degrees); + + // Update modal image + const modalImage = document.getElementById('modal-image'); + modalImage.className = `img-fluid rotate-${imagesToPdfFiles[index].rotation}`; +} + +function updatePreviewSize() { + const previewSize = document.getElementById('preview-size').value; + const previews = document.querySelectorAll('.image-preview'); + + previews.forEach(preview => { + // Remove existing size classes + preview.classList.remove('size-small', 'size-medium', 'size-large'); + // Add new size class + preview.classList.add('size-' + previewSize); + }); +} + // Global functions for button actions window.moveFileUp = moveFileUp; window.moveFileDown = moveFileDown; -window.removeFile = removeFile; \ No newline at end of file +window.removeFile = removeFile; +window.rotateImage = rotateImage; +window.showImageModal = showImageModal; +window.rotateImageInModal = rotateImageInModal; +window.updatePreviewSize = updatePreviewSize; \ No newline at end of file diff --git a/templates/images_to_pdf.html b/templates/images_to_pdf.html index 07d2d3a..97f9536 100644 --- a/templates/images_to_pdf.html +++ b/templates/images_to_pdf.html @@ -131,6 +131,15 @@ Seitenverhältnis beibehalten
+ +
+ + +
diff --git a/uploads/.gitkeep b/uploads/.gitkeep new file mode 100644 index 0000000..d842cca --- /dev/null +++ b/uploads/.gitkeep @@ -0,0 +1,2 @@ +# Diese Datei sorgt dafür, dass der uploads Ordner in Git erhalten bleibt +# Der Ordnerinhalt wird durch .gitignore ignoriert \ No newline at end of file