modified: QUICKSTART.md modified: app.py new file: output/.gitkeep modified: static/css/style.css modified: static/js/images-to-pdf.js modified: templates/images_to_pdf.html new file: uploads/.gitkeep
388 lines
13 KiB
JavaScript
388 lines
13 KiB
JavaScript
// Images to PDF - JavaScript Functionality
|
|
|
|
let imagesToPdfFiles = [];
|
|
let imagesToPdfSortable = null;
|
|
|
|
// DOM elements
|
|
let uploadArea, fileInput, fileList, filesContainer, convertBtn, clearBtn;
|
|
let progressBar, progressText, uploadProgress, resultArea, errorArea;
|
|
let downloadLink;
|
|
|
|
// Initialize when page loads
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Get DOM elements
|
|
uploadArea = document.getElementById('upload-area');
|
|
fileInput = document.getElementById('file-input');
|
|
fileList = document.getElementById('file-list');
|
|
filesContainer = document.getElementById('files-container');
|
|
convertBtn = document.getElementById('convert-btn');
|
|
clearBtn = document.getElementById('clear-btn');
|
|
progressBar = document.getElementById('progress-bar');
|
|
progressText = document.getElementById('progress-text');
|
|
uploadProgress = document.getElementById('upload-progress');
|
|
resultArea = document.getElementById('result-area');
|
|
errorArea = document.getElementById('error-area');
|
|
downloadLink = document.getElementById('download-link');
|
|
|
|
// Setup event listeners
|
|
setupEventListeners();
|
|
|
|
// Setup drag and drop
|
|
setupDragAndDrop(uploadArea, handleFiles);
|
|
});
|
|
|
|
function setupEventListeners() {
|
|
// File input change
|
|
fileInput.addEventListener('change', function(e) {
|
|
handleFiles(Array.from(e.target.files));
|
|
});
|
|
|
|
// Convert button
|
|
convertBtn.addEventListener('click', convertToPdf);
|
|
|
|
// Clear button
|
|
clearBtn.addEventListener('click', clearFiles);
|
|
|
|
// Upload area click
|
|
uploadArea.addEventListener('click', function() {
|
|
fileInput.click();
|
|
});
|
|
}
|
|
|
|
function handleFiles(files) {
|
|
// Hide previous results/errors
|
|
hideResults();
|
|
|
|
if (!files || files.length === 0) {
|
|
showError('Keine Dateien ausgewählt.');
|
|
return;
|
|
}
|
|
|
|
// Filter valid image files
|
|
const validFiles = files.filter(file => {
|
|
const isValid = file.type.startsWith('image/');
|
|
if (!isValid) {
|
|
showNotification(`${file.name} ist keine gültige Bilddatei.`, 'warning');
|
|
}
|
|
return isValid;
|
|
});
|
|
|
|
if (validFiles.length === 0) {
|
|
showError('Keine gültigen Bilddateien gefunden.');
|
|
return;
|
|
}
|
|
|
|
// Show upload progress
|
|
showUploadProgress();
|
|
|
|
// Upload files
|
|
uploadImages(validFiles);
|
|
}
|
|
|
|
async function uploadImages(files) {
|
|
try {
|
|
const response = await uploadFiles(files, '/api/upload-images', updateProgress);
|
|
|
|
if (response.success) {
|
|
imagesToPdfFiles = response.files;
|
|
displayFiles();
|
|
showNotification(response.message, 'success');
|
|
} else {
|
|
throw new Error(response.error || 'Upload fehlgeschlagen');
|
|
}
|
|
} catch (error) {
|
|
showError(`Upload-Fehler: ${error.message}`);
|
|
} finally {
|
|
hideUploadProgress();
|
|
}
|
|
}
|
|
|
|
function updateProgress(percent) {
|
|
progressBar.style.width = percent + '%';
|
|
progressText.textContent = Math.round(percent) + '%';
|
|
}
|
|
|
|
function displayFiles() {
|
|
filesContainer.innerHTML = '';
|
|
|
|
imagesToPdfFiles.forEach((file, index) => {
|
|
const fileItem = createFileItem(file, index);
|
|
filesContainer.appendChild(fileItem);
|
|
});
|
|
|
|
// Show file list
|
|
fileList.style.display = 'block';
|
|
|
|
// Enable convert button
|
|
convertBtn.disabled = false;
|
|
|
|
// Setup sortable
|
|
setupSortableList();
|
|
}
|
|
|
|
function createFileItem(file, index) {
|
|
const div = document.createElement('div');
|
|
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 = `
|
|
<div class="d-flex align-items-center">
|
|
<div class="image-preview-container me-3">
|
|
<img src="/uploads/${file.filename}" class="image-preview size-medium rotate-${file.rotation}"
|
|
alt="${file.original_name}" onclick="showImageModal('${file.filename}', '${file.original_name}', ${index})">
|
|
</div>
|
|
<div class="image-info">
|
|
<div class="file-name fw-bold">${file.original_name}</div>
|
|
<div class="file-details text-muted">${formatFileSize(file.size)}</div>
|
|
<div class="rotation-controls">
|
|
<button type="button" class="btn btn-sm btn-outline-primary rotation-btn"
|
|
onclick="rotateImage(${index}, -90)" title="Links drehen">
|
|
<i class="fas fa-undo"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-outline-primary rotation-btn"
|
|
onclick="rotateImage(${index}, 90)" title="Rechts drehen">
|
|
<i class="fas fa-redo"></i>
|
|
</button>
|
|
<small class="text-muted ms-2">${file.rotation}°</small>
|
|
</div>
|
|
</div>
|
|
<div class="image-controls">
|
|
<div class="d-flex gap-1 mb-2">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="moveFileUp(${index})"
|
|
${index === 0 ? 'disabled' : ''} title="Nach oben">
|
|
<i class="fas fa-arrow-up"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="moveFileDown(${index})"
|
|
${index === imagesToPdfFiles.length - 1 ? 'disabled' : ''} title="Nach unten">
|
|
<i class="fas fa-arrow-down"></i>
|
|
</button>
|
|
</div>
|
|
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeFile(${index})" title="Entfernen">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
return div;
|
|
}
|
|
|
|
function setupSortableList() {
|
|
if (imagesToPdfSortable) {
|
|
imagesToPdfSortable.destroy();
|
|
}
|
|
|
|
imagesToPdfSortable = setupSortable(filesContainer, function(evt) {
|
|
// Update array order
|
|
const item = imagesToPdfFiles.splice(evt.oldIndex, 1)[0];
|
|
imagesToPdfFiles.splice(evt.newIndex, 0, item);
|
|
|
|
// Refresh display
|
|
displayFiles();
|
|
});
|
|
}
|
|
|
|
function moveFileUp(index) {
|
|
if (index > 0) {
|
|
[imagesToPdfFiles[index], imagesToPdfFiles[index - 1]] = [imagesToPdfFiles[index - 1], imagesToPdfFiles[index]];
|
|
displayFiles();
|
|
}
|
|
}
|
|
|
|
function moveFileDown(index) {
|
|
if (index < imagesToPdfFiles.length - 1) {
|
|
[imagesToPdfFiles[index], imagesToPdfFiles[index + 1]] = [imagesToPdfFiles[index + 1], imagesToPdfFiles[index]];
|
|
displayFiles();
|
|
}
|
|
}
|
|
|
|
function removeFile(index) {
|
|
imagesToPdfFiles.splice(index, 1);
|
|
|
|
if (imagesToPdfFiles.length === 0) {
|
|
clearFiles();
|
|
} else {
|
|
displayFiles();
|
|
}
|
|
|
|
showNotification('Datei entfernt.', 'info');
|
|
}
|
|
|
|
async function convertToPdf() {
|
|
if (imagesToPdfFiles.length === 0) {
|
|
showError('Keine Dateien zum Konvertieren vorhanden.');
|
|
return;
|
|
}
|
|
|
|
hideResults();
|
|
setLoadingState(convertBtn, true);
|
|
|
|
try {
|
|
// 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({ files: filesWithRotation })
|
|
});
|
|
|
|
if (response.success) {
|
|
showResult(response.filename, response.message);
|
|
} else {
|
|
throw new Error(response.error || 'Konvertierung fehlgeschlagen');
|
|
}
|
|
} catch (error) {
|
|
showError(`Konvertierungs-Fehler: ${error.message}`);
|
|
} finally {
|
|
setLoadingState(convertBtn, false);
|
|
}
|
|
}
|
|
|
|
function clearFiles() {
|
|
imagesToPdfFiles = [];
|
|
filesContainer.innerHTML = '';
|
|
fileList.style.display = 'none';
|
|
convertBtn.disabled = true;
|
|
fileInput.value = '';
|
|
hideResults();
|
|
|
|
if (imagesToPdfSortable) {
|
|
imagesToPdfSortable.destroy();
|
|
imagesToPdfSortable = null;
|
|
}
|
|
}
|
|
|
|
function showUploadProgress() {
|
|
uploadProgress.style.display = 'block';
|
|
progressBar.style.width = '0%';
|
|
progressText.textContent = '0%';
|
|
}
|
|
|
|
function hideUploadProgress() {
|
|
uploadProgress.style.display = 'none';
|
|
}
|
|
|
|
function showResult(filename, message) {
|
|
resultArea.style.display = 'block';
|
|
downloadLink.href = `/download/${filename}`;
|
|
downloadLink.querySelector('.me-2').nextSibling.textContent = 'PDF herunterladen';
|
|
|
|
// Update message if provided
|
|
if (message) {
|
|
resultArea.querySelector('p').textContent = message;
|
|
}
|
|
}
|
|
|
|
function showError(message) {
|
|
errorArea.style.display = 'block';
|
|
document.getElementById('error-message').textContent = message;
|
|
}
|
|
|
|
function hideResults() {
|
|
resultArea.style.display = 'none';
|
|
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 = `
|
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="image-modal-title">${originalName}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<img id="modal-image" src="/uploads/${filename}" class="img-fluid rotate-${imagesToPdfFiles[index].rotation}" alt="${originalName}">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<div class="btn-group me-auto">
|
|
<button type="button" class="btn btn-outline-primary" onclick="rotateImageInModal(${index}, -90)">
|
|
<i class="fas fa-undo me-1"></i>Links drehen
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="rotateImageInModal(${index}, 90)">
|
|
<i class="fas fa-redo me-1"></i>Rechts drehen
|
|
</button>
|
|
</div>
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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;
|
|
window.rotateImage = rotateImage;
|
|
window.showImageModal = showImageModal;
|
|
window.rotateImageInModal = rotateImageInModal;
|
|
window.updatePreviewSize = updatePreviewSize; |