From ba767a374af901ca77b423a89a24b0fb06bf6283 Mon Sep 17 00:00:00 2001 From: Carsten Graf Date: Fri, 16 Jan 2026 14:14:38 +0000 Subject: [PATCH] =?UTF-8?q?Textfeld=20f=C3=BCr=20Unterschriftoverlay=20und?= =?UTF-8?q?=20gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 126 +++++++++++++++++++++++++++++++++++++++++++++ css/master.css | 23 +++++++++ docker-compose.yml | 8 ++- html/master.html | 11 +++- js/master.js | 104 ++++++++++++++++++++++++++++++++++++- 5 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9a06ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,126 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +yarn.lock + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +jspm_packages/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# OS files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +*~ + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo +*.swn +.project +.classpath +.settings/ + +# Logs +logs +*.log + +# Temporary files +tmp/ +temp/ +*.tmp diff --git a/css/master.css b/css/master.css index 97f6225..e4a35f3 100644 --- a/css/master.css +++ b/css/master.css @@ -312,6 +312,21 @@ body { pointer-events: none; } +.signature-overlay .text-overlay-content { + position: absolute; + top: 5px; + left: 5px; + font-size: 14px; + color: #333; + background: rgba(255, 255, 255, 0.9); + padding: 5px 10px; + border-radius: 4px; + pointer-events: none; + max-width: calc(100% - 20px); + word-wrap: break-word; + z-index: 11; +} + .signature-overlay .handle { position: absolute; top: -10px; @@ -339,6 +354,14 @@ body { border-radius: 3px; } +.text-input-section { + margin-bottom: 30px; + padding: 20px; + background: #f8f9ff; + border-radius: 12px; + border: 2px solid #667eea; +} + .signature-controls { margin-top: 20px; padding: 15px; diff --git a/docker-compose.yml b/docker-compose.yml index 72f695a..5a4517e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,13 @@ services: networks: - pdf-signature-network healthcheck: - test: ["CMD", "node", "-e", "require('http').get('http://localhost:8080', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] + test: + [ + "CMD", + "node", + "-e", + "require('http').get('http://pdf-signature:8080', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))" + ] interval: 30s timeout: 3s retries: 3 diff --git a/html/master.html b/html/master.html index f1ab17c..3902591 100644 --- a/html/master.html +++ b/html/master.html @@ -39,6 +39,14 @@ Warte auf Unterschrift von der Signatur-Station... + +
Ă—
+ Unterschrift
@@ -91,7 +100,7 @@ 🗑️ Unterschrift entfernen diff --git a/js/master.js b/js/master.js index e70bae6..af57fc6 100644 --- a/js/master.js +++ b/js/master.js @@ -16,6 +16,10 @@ let signatureDataUrl = null; let signaturePos = { x: 0.5, y: 0.1 }; // Relative position (0-1) let signatureScale = 0.3; +// Text overlay state (now part of signature overlay) +let textOverlayText = ''; +let textFontSize = 14; + // WebSocket connection let ws = null; @@ -114,6 +118,10 @@ document.getElementById('pdfInput').addEventListener('change', async (e) => { document.getElementById('uploadSection').style.display = 'none'; document.getElementById('statusSection').style.display = 'block'; + // Show text input section and update with current date + document.getElementById('textInputSection').style.display = 'block'; + updateTextInputWithDate(); + // Show signature placeholder document.getElementById('signaturePlaceholder').style.display = 'block'; @@ -298,6 +306,9 @@ async function renderPdfPreview(pdfBytes, pageNum = 1) { placeholder.style.display = 'none'; } } + + // Update text in overlay if it exists + updateTextInOverlay(); }, 300); // Increased delay to ensure canvas is ready console.log('PDF Seite', pageNum, 'erfolgreich gerendert'); @@ -504,6 +515,7 @@ async function changePage(direction) { console.log('Overlay nach Seitenwechsel positioniert:', overlay.style.left, overlay.style.top); } } + }, 100); // Send page change to signature station @@ -567,6 +579,9 @@ async function showSignatureOverlay(signatureDataUrl) { overlay.style.position = 'absolute'; // Ensure positioning works overlay.style.zIndex = '10'; // Ensure it's on top + // Update text in overlay if text input has value + updateTextInOverlay(); + document.getElementById('signatureControls').classList.add('show'); // Show page selector if multi-page @@ -779,7 +794,7 @@ async function addSignatureToPdf() { // Embed signature image once const signatureImage = await pdfLibDoc.embedPng(signatureDataUrl); - // Add signature to each selected page + // Add signature and text to each selected page for (const pageNum of pagesToSign) { const page = pages[pageNum - 1]; // 0-indexed const { width, height } = page.getSize(); @@ -818,6 +833,28 @@ async function addSignatureToPdf() { }); console.log('✓ Unterschrift zu Seite', pageNum, 'hinzugefügt'); + + // Add text overlay if it exists (text is part of signature overlay, positioned top-left) + if (textOverlayText) { + // Text is positioned relative to signature overlay (top-left) + // Calculate text position: overlay left position + small offset, overlay top position - small offset + const textOffsetX = 5 * scaleX; // 5px offset from left + const textOffsetY = -5 * scaleY; // 5px offset from top (negative because PDF y is bottom-up) + const textX = x + textOffsetX; + // overlayTop is from top of canvas, convert to PDF coordinates (bottom-up) + const overlayTopInPdf = height - (overlayTop * scaleY); + const textY = overlayTopInPdf + textOffsetY; // Top of overlay - small offset + + // Add text to page + page.drawText(textOverlayText, { + x: textX, + y: textY, + size: textFontSize * scaleY, + color: rgb(0, 0, 0), + }); + + console.log('✓ Text zu Seite', pageNum, 'hinzugefügt'); + } } // Save PDF @@ -846,6 +883,38 @@ async function addSignatureToPdf() { } } +function getCurrentDateString() { + const today = new Date(); + const day = String(today.getDate()).padStart(2, '0'); + const month = String(today.getMonth() + 1).padStart(2, '0'); + const year = today.getFullYear(); + return `${day}.${month}.${year}`; +} + +function updateTextInputWithDate() { + const textInput = document.getElementById('textInput'); + if (textInput) { + const currentDate = getCurrentDateString(); + textInput.value = `Abgeholt am ${currentDate} durch:`; + } +} + +function updateTextInOverlay() { + const textInput = document.getElementById('textInput'); + const text = textInput ? textInput.value : ''; + textOverlayText = text; + + const textOverlayContent = document.getElementById('textOverlayContent'); + if (textOverlayContent) { + if (text && text.trim()) { + textOverlayContent.textContent = text; + textOverlayContent.style.display = 'block'; + } else { + textOverlayContent.style.display = 'none'; + } + } +} + function removeSignatureOverlay() { // Only remove the overlay, keep signedPdfBytes intact const overlay = document.getElementById('signatureOverlay'); @@ -853,6 +922,12 @@ function removeSignatureOverlay() { overlay.style.display = 'none'; // Explicitly hide overlay document.getElementById('signatureControls').classList.remove('show'); + // Hide text in overlay + const textOverlayContent = document.getElementById('textOverlayContent'); + if (textOverlayContent) { + textOverlayContent.style.display = 'none'; + } + // Reset placeholder to default state const placeholder = document.getElementById('signaturePlaceholder'); const placeholderImg = document.getElementById('placeholderSignatureImage'); @@ -1003,9 +1078,15 @@ document.getElementById('downloadButton').addEventListener('click', async () => // Reset for next PDF document.getElementById('uploadSection').style.display = 'block'; document.getElementById('statusSection').style.display = 'none'; + + // Hide text input section + document.getElementById('textInputSection').style.display = 'none'; // Reset file input document.getElementById('pdfInput').value = ''; + + // Reset text input to default with current date + updateTextInputWithDate(); // Reset place signature button const placeBtn = document.getElementById('placeSignatureButton'); @@ -1029,6 +1110,7 @@ document.getElementById('downloadButton').addEventListener('click', async () => pdfFileName = ''; pdfDoc = null; currentPage = null; + textOverlayText = ''; console.log('Alles zurückgesetzt für nächste PDF'); } catch (error) { @@ -1043,9 +1125,15 @@ function resetToStart() { // Show upload section, hide status section document.getElementById('uploadSection').style.display = 'block'; document.getElementById('statusSection').style.display = 'none'; + + // Hide text input section + document.getElementById('textInputSection').style.display = 'none'; // Reset file input document.getElementById('pdfInput').value = ''; + + // Reset text input to default with current date + updateTextInputWithDate(); // Reset place signature button const placeBtn = document.getElementById('placeSignatureButton'); @@ -1079,6 +1167,10 @@ function resetToStart() { pdfDoc = null; currentPage = null; signatureDataUrl = null; + textOverlayText = ''; + + // Reset text input to default with current date + updateTextInputWithDate(); console.log('Zurück zum Anfang - bereit für neue PDF'); } @@ -1112,5 +1204,15 @@ document.addEventListener('DOMContentLoaded', () => { } }); +// Update text in overlay when text input changes +document.addEventListener('DOMContentLoaded', () => { + const textInput = document.getElementById('textInput'); + if (textInput) { + textInput.addEventListener('input', () => { + updateTextInOverlay(); + }); + } +}); + // Connect WebSocket on load connectWebSocket();