modified: .gitignore

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
This commit is contained in:
SimolZimol
2025-10-12 22:39:04 +02:00
parent 97fd0762d7
commit a01a5c1274
8 changed files with 313 additions and 28 deletions

View File

@@ -411,4 +411,101 @@ footer {
/* File Type Icons */
.file-type-pdf { color: #dc3545; }
.file-type-image { color: #28a745; }
.file-type-zip { color: #ffc107; }
.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;
}

View File

@@ -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 = `
<div class="file-icon">
<i class="${getFileIcon(file.original_name)}"></i>
</div>
<div class="file-info">
<div class="file-name">${file.original_name}</div>
<div class="file-details">${formatFileSize(file.size)}</div>
</div>
<div class="file-actions">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="moveFileUp(${index})" ${index === 0 ? 'disabled' : ''}>
<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' : ''}>
<i class="fas fa-arrow-down"></i>
</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeFile(${index})">
<i class="fas fa-trash"></i>
</button>
<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>
`;
@@ -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 = `
<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.removeFile = removeFile;
window.rotateImage = rotateImage;
window.showImageModal = showImageModal;
window.rotateImageInModal = rotateImageInModal;
window.updatePreviewSize = updatePreviewSize;