diff --git a/css/master.css b/css/master.css index ae4d0da..97f6225 100644 --- a/css/master.css +++ b/css/master.css @@ -139,6 +139,7 @@ body { background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); border-radius: 12px; border: 2px solid #667eea; + flex-wrap: wrap; } .nav-button { @@ -171,6 +172,32 @@ body { text-align: center; } +.page-jump { + display: flex; + align-items: center; + gap: 8px; + margin-left: 10px; + padding-left: 15px; + border-left: 2px solid rgba(102, 126, 234, 0.3); +} + +#jumpToPageInput { + width: 60px; + padding: 8px; + border: 2px solid #667eea; + border-radius: 6px; + text-align: center; + font-size: 1em; + font-weight: 500; + color: #667eea; +} + +#jumpToPageInput:focus { + outline: none; + border-color: #764ba2; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2); +} + .page-selector label { display: inline-flex; align-items: center; @@ -222,6 +249,46 @@ body { font-size: 0.9em; } +.signature-placeholder { + background: white; + border: 2px dashed #667eea; + border-radius: 8px; + padding: 12px; + margin-bottom: 15px; + text-align: center; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + max-width: 400px; + margin-left: auto; + margin-right: auto; +} + +.placeholder-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; +} + +.placeholder-icon { + font-size: 1.8em; + opacity: 0.7; +} + +.placeholder-text { + font-size: 0.9em; + color: #667eea; + font-weight: 500; +} + +#placeholderSignatureImage { + max-width: 200px; + max-height: 80px; + width: auto; + height: auto; + margin-top: 8px; +} + .signature-overlay { position: absolute; border: 3px dashed #667eea; diff --git a/html/master.html b/html/master.html index 5ddeb5c..f1ab17c 100644 --- a/html/master.html +++ b/html/master.html @@ -44,6 +44,18 @@ Seite 1 von 1 +
+ + +
+ + +
diff --git a/js/master.js b/js/master.js index bbe2585..e70bae6 100644 --- a/js/master.js +++ b/js/master.js @@ -114,6 +114,27 @@ document.getElementById('pdfInput').addEventListener('change', async (e) => { document.getElementById('uploadSection').style.display = 'none'; document.getElementById('statusSection').style.display = 'block'; + // Show signature placeholder + document.getElementById('signaturePlaceholder').style.display = 'block'; + + // If signature already exists, make sure overlay will be visible + if (signatureDataUrl) { + const overlay = document.getElementById('signatureOverlay'); + if (overlay) { + // Ensure overlay is ready to be shown + overlay.style.display = 'none'; // Reset first + overlay.classList.remove('show'); // Reset class + // Reset position to default when loading new PDF + signaturePos.x = 0.5; + signaturePos.y = 0.1; + console.log('Overlay zurückgesetzt für neues PDF-Laden, Position auf Standard zurückgesetzt'); + } + } else { + // Reset position to default if no signature + signaturePos.x = 0.5; + signaturePos.y = 0.1; + } + // Render PDF preview using the copy await renderPdfPreview(renderCopy); @@ -174,6 +195,111 @@ async function renderPdfPreview(pdfBytes, pageNum = 1) { // Update page navigation updatePageNavigation(); + // If signature was already received, show it again after PDF loads + setTimeout(() => { + const overlay = document.getElementById('signatureOverlay'); + const placeholder = document.getElementById('signaturePlaceholder'); + + if (signatureDataUrl && overlay) { + // Signature was already received, restore it + console.log('=== Unterschrift war bereits vorhanden, zeige sie wieder an ==='); + const img = document.getElementById('signatureImage'); + const placeholderImg = document.getElementById('placeholderSignatureImage'); + const placeholderContent = document.getElementById('placeholderContent'); + + // Show in placeholder + placeholder.style.display = 'block'; + placeholderImg.src = signatureDataUrl; + placeholderImg.style.display = 'block'; + placeholderContent.style.display = 'none'; + + // Show in overlay - IMPORTANT: Set image src FIRST, then show overlay + img.src = signatureDataUrl; + + // Ensure overlay is visible + overlay.classList.add('show'); + overlay.style.display = 'block'; // Force display + + console.log('Overlay show-Klasse hinzugefügt:', overlay.classList.contains('show')); + + // Position overlay (only if single page view, not all pages) + const pdfPreview = document.getElementById('pdfPreview'); + if (!pdfPreview.classList.contains('scrollable')) { + // Single page view - position overlay + const canvas = document.getElementById('pdfCanvas'); + if (canvas && canvas.clientWidth > 0 && canvas.clientHeight > 0) { + const overlayWidth = 250; + // Calculate position and ensure it's within bounds + let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; + let topPos = canvas.clientHeight * signaturePos.y; + + // Clamp position to visible area + leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); + topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); // 100px min height for overlay + + overlay.style.left = leftPos + 'px'; + overlay.style.top = topPos + 'px'; + overlay.style.position = 'absolute'; + overlay.style.zIndex = '10'; + + // Update signaturePos with clamped values + signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; + signaturePos.y = topPos / canvas.clientHeight; + + console.log('Overlay positioniert nach PDF-Laden:', overlay.style.left, overlay.style.top); + console.log('Canvas Größe:', canvas.clientWidth, 'x', canvas.clientHeight); + console.log('Overlay sichtbar:', overlay.classList.contains('show'), 'display:', overlay.style.display); + } else { + // Retry positioning - canvas might not be ready yet + console.log('Canvas noch nicht bereit, versuche erneut...'); + setTimeout(() => { + const canvas = document.getElementById('pdfCanvas'); + if (canvas && canvas.clientWidth > 0) { + const overlayWidth = 250; + // Calculate position and ensure it's within bounds + let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; + let topPos = canvas.clientHeight * signaturePos.y; + + // Clamp position to visible area + leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); + topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); + + overlay.style.left = leftPos + 'px'; + overlay.style.top = topPos + 'px'; + overlay.style.position = 'absolute'; + overlay.style.zIndex = '10'; + overlay.classList.add('show'); + overlay.style.display = 'block'; + + // Update signaturePos with clamped values + signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; + signaturePos.y = topPos / canvas.clientHeight; + + console.log('Overlay positioniert (2. Versuch nach PDF-Laden):', overlay.style.left, overlay.style.top); + console.log('Overlay sichtbar:', overlay.classList.contains('show')); + } else { + console.error('Canvas auch nach Retry nicht gefunden!'); + } + }, 500); + } + } else { + // All pages view - hide overlay + overlay.classList.remove('show'); + overlay.style.display = 'none'; + } + + // Show controls + document.getElementById('signatureControls').classList.add('show'); + } else { + // No signature yet, show placeholder + if (!overlay.classList.contains('show')) { + placeholder.style.display = 'block'; + } else { + placeholder.style.display = 'none'; + } + } + }, 300); // Increased delay to ensure canvas is ready + console.log('PDF Seite', pageNum, 'erfolgreich gerendert'); } catch (error) { console.error('Fehler beim Rendern des PDFs:', error); @@ -207,6 +333,14 @@ async function renderAllPages(pdfBytes) { const pdfPreview = document.getElementById('pdfPreview'); pdfPreview.classList.add('scrollable'); + // Hide signature overlay when showing all pages + const overlay = document.getElementById('signatureOverlay'); + if (overlay) { + overlay.classList.remove('show'); + overlay.style.display = 'none'; + console.log('Overlay ausgeblendet beim Rendern aller Seiten'); + } + // Render all pages for (let pageNum = 1; pageNum <= numPages; pageNum++) { @@ -253,6 +387,7 @@ function updatePageNavigation() { const pageInfo = document.getElementById('pageInfo'); const prevBtn = document.getElementById('prevPageBtn'); const nextBtn = document.getElementById('nextPageBtn'); + const jumpInput = document.getElementById('jumpToPageInput'); const pageSelector = document.getElementById('pageSelector'); const currentPageNumSpan = document.getElementById('currentPageNum'); @@ -262,6 +397,12 @@ function updatePageNavigation() { prevBtn.disabled = currentPageNum <= 1; nextBtn.disabled = currentPageNum >= totalPages; + // Update jump input max value and placeholder + if (jumpInput) { + jumpInput.max = totalPages; + jumpInput.placeholder = `1-${totalPages}`; + } + // Show page selector if signature is visible if (document.getElementById('signatureOverlay').classList.contains('show')) { pageSelector.style.display = 'block'; @@ -273,12 +414,98 @@ function updatePageNavigation() { } } +async function jumpToPage() { + const jumpInput = document.getElementById('jumpToPageInput'); + const pageNum = parseInt(jumpInput.value); + + if (isNaN(pageNum) || pageNum < 1 || pageNum > totalPages) { + alert(`Bitte geben Sie eine gültige Seitennummer zwischen 1 und ${totalPages} ein.`); + jumpInput.value = ''; + return; + } + + if (pageNum === currentPageNum) { + jumpInput.value = ''; + return; // Already on this page + } + + await renderPdfPreview(originalPdfBytes, pageNum); + + // Reposition overlay if signature is visible + setTimeout(() => { + const overlay = document.getElementById('signatureOverlay'); + if (overlay && overlay.classList.contains('show')) { + const canvas = document.getElementById('pdfCanvas'); + if (canvas && canvas.clientWidth > 0 && canvas.clientHeight > 0) { + const overlayWidth = 250; + // Calculate position and ensure it's within bounds + let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; + let topPos = canvas.clientHeight * signaturePos.y; + + // Clamp position to visible area + leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); + topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); + + overlay.style.left = leftPos + 'px'; + overlay.style.top = topPos + 'px'; + overlay.style.position = 'absolute'; + + // Update signaturePos with clamped values + signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; + signaturePos.y = topPos / canvas.clientHeight; + + console.log('Overlay nach Seitenwechsel positioniert:', overlay.style.left, overlay.style.top); + } + } + }, 100); + + // Send page change to signature station + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ + type: 'page_change', + pageNum: pageNum, + totalPages: totalPages + })); + } + + // Clear input + jumpInput.value = ''; +} + async function changePage(direction) { const newPage = currentPageNum + direction; if (newPage < 1 || newPage > totalPages) return; await renderPdfPreview(originalPdfBytes, newPage); + // Reposition overlay if signature is visible + setTimeout(() => { + const overlay = document.getElementById('signatureOverlay'); + if (overlay && overlay.classList.contains('show')) { + const canvas = document.getElementById('pdfCanvas'); + if (canvas && canvas.clientWidth > 0 && canvas.clientHeight > 0) { + const overlayWidth = 250; + // Calculate position and ensure it's within bounds + let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; + let topPos = canvas.clientHeight * signaturePos.y; + + // Clamp position to visible area + leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); + topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); + + overlay.style.left = leftPos + 'px'; + overlay.style.top = topPos + 'px'; + overlay.style.position = 'absolute'; + + // Update signaturePos with clamped values + signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; + signaturePos.y = topPos / canvas.clientHeight; + + console.log('Overlay nach Seitenwechsel positioniert:', overlay.style.left, overlay.style.top); + } + } + }, 100); + // Send page change to signature station if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ @@ -308,12 +535,37 @@ function updatePageSelection() { } } +// Placeholder handling is now simpler - just show/hide + async function showSignatureOverlay(signatureDataUrl) { + console.log('=== showSignatureOverlay aufgerufen ==='); + console.log('signatureDataUrl Länge:', signatureDataUrl ? signatureDataUrl.length : 'NULL'); + const overlay = document.getElementById('signatureOverlay'); const img = document.getElementById('signatureImage'); + const placeholder = document.getElementById('signaturePlaceholder'); + const placeholderImg = document.getElementById('placeholderSignatureImage'); + const placeholderContent = document.getElementById('placeholderContent'); + if (!overlay || !img || !placeholder) { + console.error('FEHLER: Overlay, img oder placeholder nicht gefunden!'); + return; + } + + // Show signature in placeholder first (always visible) + placeholder.style.display = 'block'; + placeholderImg.src = signatureDataUrl; + placeholderImg.style.display = 'block'; + placeholderContent.style.display = 'none'; + + console.log('Unterschrift im Platzhalter angezeigt'); + + // Also show in overlay for dragging on PDF img.src = signatureDataUrl; overlay.classList.add('show'); + overlay.style.display = 'block'; // Force display + overlay.style.position = 'absolute'; // Ensure positioning works + overlay.style.zIndex = '10'; // Ensure it's on top document.getElementById('signatureControls').classList.add('show'); @@ -324,90 +576,159 @@ async function showSignatureOverlay(signatureDataUrl) { } document.getElementById('statusMessage').className = 'status signed'; - document.getElementById('statusMessage').textContent = '✅ Unterschrift erhalten! Wähle die Seite(n) und positioniere sie.'; + document.getElementById('statusMessage').textContent = '✅ Unterschrift erhalten! Unterschrift oben sichtbar. Ziehe sie auf das PDF.'; document.getElementById('downloadButton').disabled = true; document.getElementById('downloadHint').style.display = 'none'; - // Position signature initially (use clientWidth for displayed size) - const canvas = document.getElementById('pdfCanvas'); - overlay.style.left = (canvas.clientWidth * signaturePos.x) + 'px'; - overlay.style.top = (canvas.clientHeight * signaturePos.y) + 'px'; + // Initialize drag and resize handlers (only once) + initializeDragAndResize(); - // Make signature draggable - makeDraggable(overlay); - makeResizable(overlay); + // Wait for canvas to be ready, then position signature overlay + setTimeout(() => { + const canvas = document.getElementById('pdfCanvas'); + if (canvas && canvas.clientWidth > 0 && canvas.clientHeight > 0) { + // Position signature overlay on PDF (centered horizontally, top area) + const overlayWidth = 250; // Initial width + // Calculate position and ensure it's within bounds + let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; + let topPos = canvas.clientHeight * signaturePos.y; + + // Clamp position to visible area + leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); + topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); // 100px min height for overlay + + overlay.style.left = leftPos + 'px'; + overlay.style.top = topPos + 'px'; + overlay.style.position = 'absolute'; + + // Update signaturePos with clamped values + signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; + signaturePos.y = topPos / canvas.clientHeight; + + console.log('Overlay positioniert bei:', overlay.style.left, overlay.style.top); + console.log('Canvas Größe:', canvas.clientWidth, 'x', canvas.clientHeight); + console.log('Overlay sichtbar:', overlay.classList.contains('show')); + } else { + console.error('Canvas nicht gefunden oder noch nicht geladen'); + // Retry after a bit longer delay + setTimeout(() => { + const canvas = document.getElementById('pdfCanvas'); + if (canvas && canvas.clientWidth > 0) { + const overlayWidth = 250; + // Calculate position and ensure it's within bounds + let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; + let topPos = canvas.clientHeight * signaturePos.y; + + // Clamp position to visible area + leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); + topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); + + overlay.style.left = leftPos + 'px'; + overlay.style.top = topPos + 'px'; + overlay.style.position = 'absolute'; + + // Update signaturePos with clamped values + signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; + signaturePos.y = topPos / canvas.clientHeight; + + console.log('Overlay positioniert (2. Versuch) bei:', overlay.style.left, overlay.style.top); + } + }, 500); + } + }, 200); } -function makeDraggable(element) { - let isDragging = false; - let startX, startY, initialX, initialY; +// Dragging state (shared across calls) +let isDragging = false; +let isResizing = false; +let dragStartX, dragStartY, dragInitialX, dragInitialY; +let resizeStartX, resizeStartWidth; - element.addEventListener('mousedown', (e) => { +// Initialize dragging/resizing once +let dragInitialized = false; + +function initializeDragAndResize() { + if (dragInitialized) return; // Only initialize once + + const overlay = document.getElementById('signatureOverlay'); + const resizeHandle = document.getElementById('resizeHandle'); + + if (!overlay || !resizeHandle) return; + + // Make draggable + overlay.addEventListener('mousedown', (e) => { if (e.target.classList.contains('resize-handle') || e.target.classList.contains('handle')) { return; } isDragging = true; - startX = e.clientX; - startY = e.clientY; - initialX = element.offsetLeft; - initialY = element.offsetTop; - element.style.cursor = 'grabbing'; + dragStartX = e.clientX; + dragStartY = e.clientY; + dragInitialX = overlay.offsetLeft; + dragInitialY = overlay.offsetTop; + overlay.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', (e) => { - if (!isDragging) return; + if (isDragging && overlay.classList.contains('show')) { + const dx = e.clientX - dragStartX; + const dy = e.clientY - dragStartY; + overlay.style.left = (dragInitialX + dx) + 'px'; + overlay.style.top = (dragInitialY + dy) + 'px'; + } - const dx = e.clientX - startX; - const dy = e.clientY - startY; - - element.style.left = (initialX + dx) + 'px'; - element.style.top = (initialY + dy) + 'px'; + if (isResizing && overlay.classList.contains('show')) { + const dx = e.clientX - resizeStartX; + const newWidth = Math.max(100, resizeStartWidth + dx); + overlay.querySelector('img').style.maxWidth = newWidth + 'px'; + } }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; - element.style.cursor = 'move'; + overlay.style.cursor = 'move'; updateSignaturePosition(); } - }); -} - -function makeResizable(element) { - const resizeHandle = document.getElementById('resizeHandle'); - let isResizing = false; - let startX, startWidth; - - resizeHandle.addEventListener('mousedown', (e) => { - e.stopPropagation(); - isResizing = true; - startX = e.clientX; - startWidth = element.querySelector('img').width; - }); - - document.addEventListener('mousemove', (e) => { - if (!isResizing) return; - - const dx = e.clientX - startX; - const newWidth = Math.max(100, startWidth + dx); - element.querySelector('img').style.maxWidth = newWidth + 'px'; - }); - - document.addEventListener('mouseup', () => { if (isResizing) { isResizing = false; updateSignatureScale(); } }); + + // Make resizable + resizeHandle.addEventListener('mousedown', (e) => { + e.stopPropagation(); + isResizing = true; + resizeStartX = e.clientX; + resizeStartWidth = overlay.querySelector('img').width; + }); + + dragInitialized = true; +} + +function makeDraggable(element) { + // Function kept for compatibility, but actual initialization is in initializeDragAndResize +} + +function makeResizable(element) { + // Function kept for compatibility, but actual initialization is in initializeDragAndResize } function updateSignaturePosition() { const overlay = document.getElementById('signatureOverlay'); const canvas = document.getElementById('pdfCanvas'); - signaturePos.x = overlay.offsetLeft / canvas.width; - signaturePos.y = overlay.offsetTop / canvas.height; - console.log('Neue Position:', signaturePos); + // Use clientWidth/clientHeight instead of width/height (display size vs internal size) + const canvasDisplayWidth = canvas.clientWidth || canvas.width; + const canvasDisplayHeight = canvas.clientHeight || canvas.height; + + if (canvasDisplayWidth > 0 && canvasDisplayHeight > 0) { + signaturePos.x = overlay.offsetLeft / canvasDisplayWidth; + signaturePos.y = overlay.offsetTop / canvasDisplayHeight; + console.log('Neue Position:', signaturePos, 'Canvas Display:', canvasDisplayWidth, 'x', canvasDisplayHeight); + } else { + console.error('Canvas Display Größe ist 0!'); + } } function updateSignatureScale() { @@ -527,16 +848,39 @@ async function addSignatureToPdf() { function removeSignatureOverlay() { // Only remove the overlay, keep signedPdfBytes intact - document.getElementById('signatureOverlay').classList.remove('show'); + const overlay = document.getElementById('signatureOverlay'); + overlay.classList.remove('show'); + overlay.style.display = 'none'; // Explicitly hide overlay document.getElementById('signatureControls').classList.remove('show'); + + // Reset placeholder to default state + const placeholder = document.getElementById('signaturePlaceholder'); + const placeholderImg = document.getElementById('placeholderSignatureImage'); + const placeholderContent = document.getElementById('placeholderContent'); + placeholderImg.style.display = 'none'; + placeholderContent.style.display = 'flex'; + placeholder.style.display = 'block'; + signatureDataUrl = null; console.log('removeSignatureOverlay() aufgerufen - signedPdfBytes:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL'); } function removeSignature() { // Complete reset including signedPdfBytes - document.getElementById('signatureOverlay').classList.remove('show'); + const overlay = document.getElementById('signatureOverlay'); + overlay.classList.remove('show'); + overlay.style.display = 'none'; // Explicitly hide overlay document.getElementById('signatureControls').classList.remove('show'); + + // Reset placeholder to default state + const placeholder = document.getElementById('signaturePlaceholder'); + const placeholderImg = document.getElementById('placeholderSignatureImage'); + const placeholderContent = document.getElementById('placeholderContent'); + placeholder.style.display = 'block'; + placeholderImg.style.display = 'none'; + placeholderImg.src = ''; + placeholderContent.style.display = 'flex'; + document.getElementById('statusMessage').className = 'status waiting'; document.getElementById('statusMessage').textContent = 'Warte auf Unterschrift von der Signatur-Station...'; document.getElementById('downloadButton').disabled = true; @@ -753,5 +1097,20 @@ document.getElementById('discardButton').addEventListener('click', () => { } }); +// Initialize drag and resize on load +initializeDragAndResize(); + +// Add Enter key handler for jump to page input +document.addEventListener('DOMContentLoaded', () => { + const jumpInput = document.getElementById('jumpToPageInput'); + if (jumpInput) { + jumpInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + jumpToPage(); + } + }); + } +}); + // Connect WebSocket on load connectWebSocket();