Textfeld für Unterschriftoverlay und gitignore
This commit is contained in:
126
.gitignore
vendored
Normal file
126
.gitignore
vendored
Normal file
@@ -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
|
||||||
@@ -312,6 +312,21 @@ body {
|
|||||||
pointer-events: none;
|
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 {
|
.signature-overlay .handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10px;
|
top: -10px;
|
||||||
@@ -339,6 +354,14 @@ body {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-input-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f8f9ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
.signature-controls {
|
.signature-controls {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- pdf-signature-network
|
- pdf-signature-network
|
||||||
healthcheck:
|
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
|
interval: 30s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
@@ -39,6 +39,14 @@
|
|||||||
Warte auf Unterschrift von der Signatur-Station...
|
Warte auf Unterschrift von der Signatur-Station...
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="text-input-section" id="textInputSection" style="display: none;">
|
||||||
|
<label for="textInput" style="display: block; margin-bottom: 10px; font-weight: 500; color: #667eea;">
|
||||||
|
📝 Text für Unterschrifts-Overlay (wird oben links angezeigt):
|
||||||
|
</label>
|
||||||
|
<input type="text" id="textInput"
|
||||||
|
style="width: 100%; padding: 12px; border: 2px solid #667eea; border-radius: 8px; font-size: 1em;">
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Page Navigation -->
|
<!-- Page Navigation -->
|
||||||
<div class="page-navigation" id="pageNavigation" style="display: none;">
|
<div class="page-navigation" id="pageNavigation" style="display: none;">
|
||||||
<button class="nav-button" id="prevPageBtn" onclick="changePage(-1)">◀ Zurück</button>
|
<button class="nav-button" id="prevPageBtn" onclick="changePage(-1)">◀ Zurück</button>
|
||||||
@@ -64,6 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="signature-overlay" id="signatureOverlay">
|
<div class="signature-overlay" id="signatureOverlay">
|
||||||
<div class="handle" onclick="removeSignatureOverlay()" title="Unterschrift entfernen">×</div>
|
<div class="handle" onclick="removeSignatureOverlay()" title="Unterschrift entfernen">×</div>
|
||||||
|
<div class="text-overlay-content" id="textOverlayContent" style="display: none;"></div>
|
||||||
<img id="signatureImage" src="" alt="Unterschrift">
|
<img id="signatureImage" src="" alt="Unterschrift">
|
||||||
<div class="resize-handle" id="resizeHandle"></div>
|
<div class="resize-handle" id="resizeHandle"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,7 +100,7 @@
|
|||||||
🗑️ Unterschrift entfernen
|
🗑️ Unterschrift entfernen
|
||||||
</button>
|
</button>
|
||||||
<button class="upload-button" id="placeSignatureButton" style="flex: 2; background: linear-gradient(135deg, #28a745 0%, #20c997 100%);">
|
<button class="upload-button" id="placeSignatureButton" style="flex: 2; background: linear-gradient(135deg, #28a745 0%, #20c997 100%);">
|
||||||
✓ Unterschrift platzieren & PDF erstellen
|
✓ Unterschrift & Text platzieren & PDF erstellen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
104
js/master.js
104
js/master.js
@@ -16,6 +16,10 @@ let signatureDataUrl = null;
|
|||||||
let signaturePos = { x: 0.5, y: 0.1 }; // Relative position (0-1)
|
let signaturePos = { x: 0.5, y: 0.1 }; // Relative position (0-1)
|
||||||
let signatureScale = 0.3;
|
let signatureScale = 0.3;
|
||||||
|
|
||||||
|
// Text overlay state (now part of signature overlay)
|
||||||
|
let textOverlayText = '';
|
||||||
|
let textFontSize = 14;
|
||||||
|
|
||||||
// WebSocket connection
|
// WebSocket connection
|
||||||
let ws = null;
|
let ws = null;
|
||||||
|
|
||||||
@@ -114,6 +118,10 @@ document.getElementById('pdfInput').addEventListener('change', async (e) => {
|
|||||||
document.getElementById('uploadSection').style.display = 'none';
|
document.getElementById('uploadSection').style.display = 'none';
|
||||||
document.getElementById('statusSection').style.display = 'block';
|
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
|
// Show signature placeholder
|
||||||
document.getElementById('signaturePlaceholder').style.display = 'block';
|
document.getElementById('signaturePlaceholder').style.display = 'block';
|
||||||
|
|
||||||
@@ -298,6 +306,9 @@ async function renderPdfPreview(pdfBytes, pageNum = 1) {
|
|||||||
placeholder.style.display = 'none';
|
placeholder.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update text in overlay if it exists
|
||||||
|
updateTextInOverlay();
|
||||||
}, 300); // Increased delay to ensure canvas is ready
|
}, 300); // Increased delay to ensure canvas is ready
|
||||||
|
|
||||||
console.log('PDF Seite', pageNum, 'erfolgreich gerendert');
|
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);
|
console.log('Overlay nach Seitenwechsel positioniert:', overlay.style.left, overlay.style.top);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
// Send page change to signature station
|
// Send page change to signature station
|
||||||
@@ -567,6 +579,9 @@ async function showSignatureOverlay(signatureDataUrl) {
|
|||||||
overlay.style.position = 'absolute'; // Ensure positioning works
|
overlay.style.position = 'absolute'; // Ensure positioning works
|
||||||
overlay.style.zIndex = '10'; // Ensure it's on top
|
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');
|
document.getElementById('signatureControls').classList.add('show');
|
||||||
|
|
||||||
// Show page selector if multi-page
|
// Show page selector if multi-page
|
||||||
@@ -779,7 +794,7 @@ async function addSignatureToPdf() {
|
|||||||
// Embed signature image once
|
// Embed signature image once
|
||||||
const signatureImage = await pdfLibDoc.embedPng(signatureDataUrl);
|
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) {
|
for (const pageNum of pagesToSign) {
|
||||||
const page = pages[pageNum - 1]; // 0-indexed
|
const page = pages[pageNum - 1]; // 0-indexed
|
||||||
const { width, height } = page.getSize();
|
const { width, height } = page.getSize();
|
||||||
@@ -818,6 +833,28 @@ async function addSignatureToPdf() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log('✓ Unterschrift zu Seite', pageNum, 'hinzugefügt');
|
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
|
// 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() {
|
function removeSignatureOverlay() {
|
||||||
// Only remove the overlay, keep signedPdfBytes intact
|
// Only remove the overlay, keep signedPdfBytes intact
|
||||||
const overlay = document.getElementById('signatureOverlay');
|
const overlay = document.getElementById('signatureOverlay');
|
||||||
@@ -853,6 +922,12 @@ function removeSignatureOverlay() {
|
|||||||
overlay.style.display = 'none'; // Explicitly hide overlay
|
overlay.style.display = 'none'; // Explicitly hide overlay
|
||||||
document.getElementById('signatureControls').classList.remove('show');
|
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
|
// Reset placeholder to default state
|
||||||
const placeholder = document.getElementById('signaturePlaceholder');
|
const placeholder = document.getElementById('signaturePlaceholder');
|
||||||
const placeholderImg = document.getElementById('placeholderSignatureImage');
|
const placeholderImg = document.getElementById('placeholderSignatureImage');
|
||||||
@@ -1003,9 +1078,15 @@ document.getElementById('downloadButton').addEventListener('click', async () =>
|
|||||||
// Reset for next PDF
|
// Reset for next PDF
|
||||||
document.getElementById('uploadSection').style.display = 'block';
|
document.getElementById('uploadSection').style.display = 'block';
|
||||||
document.getElementById('statusSection').style.display = 'none';
|
document.getElementById('statusSection').style.display = 'none';
|
||||||
|
|
||||||
|
// Hide text input section
|
||||||
|
document.getElementById('textInputSection').style.display = 'none';
|
||||||
|
|
||||||
// Reset file input
|
// Reset file input
|
||||||
document.getElementById('pdfInput').value = '';
|
document.getElementById('pdfInput').value = '';
|
||||||
|
|
||||||
|
// Reset text input to default with current date
|
||||||
|
updateTextInputWithDate();
|
||||||
|
|
||||||
// Reset place signature button
|
// Reset place signature button
|
||||||
const placeBtn = document.getElementById('placeSignatureButton');
|
const placeBtn = document.getElementById('placeSignatureButton');
|
||||||
@@ -1029,6 +1110,7 @@ document.getElementById('downloadButton').addEventListener('click', async () =>
|
|||||||
pdfFileName = '';
|
pdfFileName = '';
|
||||||
pdfDoc = null;
|
pdfDoc = null;
|
||||||
currentPage = null;
|
currentPage = null;
|
||||||
|
textOverlayText = '';
|
||||||
|
|
||||||
console.log('Alles zurückgesetzt für nächste PDF');
|
console.log('Alles zurückgesetzt für nächste PDF');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1043,9 +1125,15 @@ function resetToStart() {
|
|||||||
// Show upload section, hide status section
|
// Show upload section, hide status section
|
||||||
document.getElementById('uploadSection').style.display = 'block';
|
document.getElementById('uploadSection').style.display = 'block';
|
||||||
document.getElementById('statusSection').style.display = 'none';
|
document.getElementById('statusSection').style.display = 'none';
|
||||||
|
|
||||||
|
// Hide text input section
|
||||||
|
document.getElementById('textInputSection').style.display = 'none';
|
||||||
|
|
||||||
// Reset file input
|
// Reset file input
|
||||||
document.getElementById('pdfInput').value = '';
|
document.getElementById('pdfInput').value = '';
|
||||||
|
|
||||||
|
// Reset text input to default with current date
|
||||||
|
updateTextInputWithDate();
|
||||||
|
|
||||||
// Reset place signature button
|
// Reset place signature button
|
||||||
const placeBtn = document.getElementById('placeSignatureButton');
|
const placeBtn = document.getElementById('placeSignatureButton');
|
||||||
@@ -1079,6 +1167,10 @@ function resetToStart() {
|
|||||||
pdfDoc = null;
|
pdfDoc = null;
|
||||||
currentPage = null;
|
currentPage = null;
|
||||||
signatureDataUrl = null;
|
signatureDataUrl = null;
|
||||||
|
textOverlayText = '';
|
||||||
|
|
||||||
|
// Reset text input to default with current date
|
||||||
|
updateTextInputWithDate();
|
||||||
|
|
||||||
console.log('Zurück zum Anfang - bereit für neue PDF');
|
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
|
// Connect WebSocket on load
|
||||||
connectWebSocket();
|
connectWebSocket();
|
||||||
|
|||||||
Reference in New Issue
Block a user