V1.0
This commit is contained in:
757
js/master.js
Normal file
757
js/master.js
Normal file
@@ -0,0 +1,757 @@
|
||||
const { PDFDocument, rgb } = PDFLib;
|
||||
|
||||
// Configure pdf.js worker
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
||||
|
||||
let originalPdfBytes = null;
|
||||
let signedPdfBytes = null;
|
||||
let pdfFileName = '';
|
||||
let pdfDoc = null;
|
||||
let currentPage = null;
|
||||
let currentPageNum = 1;
|
||||
let totalPages = 1;
|
||||
let signatureDataUrl = null;
|
||||
|
||||
// Signature position and size (relative to PDF)
|
||||
let signaturePos = { x: 0.5, y: 0.1 }; // Relative position (0-1)
|
||||
let signatureScale = 0.3;
|
||||
|
||||
// WebSocket connection
|
||||
let ws = null;
|
||||
|
||||
function updateConnectionStatus(isConnected) {
|
||||
const statusIndicator = document.getElementById('statusIndicator');
|
||||
const connectionText = document.getElementById('connectionText');
|
||||
const pdfInput = document.getElementById('pdfInput');
|
||||
const pdfSelectButton = document.getElementById('pdfSelectButton');
|
||||
const pdfSelectHint = document.getElementById('pdfSelectHint');
|
||||
|
||||
if (isConnected) {
|
||||
statusIndicator.classList.remove('disconnected');
|
||||
statusIndicator.classList.add('connected');
|
||||
connectionText.textContent = '✓ Verbunden mit Signatur-Station';
|
||||
|
||||
// Enable PDF selection
|
||||
pdfInput.disabled = false;
|
||||
pdfSelectButton.disabled = false;
|
||||
pdfSelectHint.style.display = 'none';
|
||||
} else {
|
||||
statusIndicator.classList.remove('connected');
|
||||
statusIndicator.classList.add('disconnected');
|
||||
connectionText.textContent = '⏳ Warte auf Signatur-Station...';
|
||||
|
||||
// Disable PDF selection
|
||||
pdfInput.disabled = true;
|
||||
pdfSelectButton.disabled = true;
|
||||
pdfSelectHint.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function connectWebSocket() {
|
||||
// Use current host (works for localhost, IP addresses, and reverse proxy)
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsHost = window.location.hostname;
|
||||
// If using HTTPS (reverse proxy), use same port as page (usually 443, no port needed)
|
||||
// If using HTTP directly, use port 8080
|
||||
const wsPort = window.location.protocol === 'https:' ? '' : ':8080';
|
||||
const wsUrl = `${wsProtocol}//${wsHost}${wsPort}`;
|
||||
console.log('Verbinde WebSocket zu:', wsUrl);
|
||||
ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('WebSocket verbunden');
|
||||
// Show connection status section
|
||||
document.getElementById('connectionStatus').style.display = 'block';
|
||||
// Register as master
|
||||
ws.send(JSON.stringify({
|
||||
type: 'register_master'
|
||||
}));
|
||||
};
|
||||
|
||||
ws.onmessage = async (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === 'signature') {
|
||||
console.log('Unterschrift empfangen');
|
||||
signatureDataUrl = data.signature;
|
||||
await showSignatureOverlay(data.signature);
|
||||
} else if (data.type === 'signature_station_connected') {
|
||||
console.log('Signatur-Station verbunden');
|
||||
updateConnectionStatus(true);
|
||||
} else if (data.type === 'signature_station_disconnected') {
|
||||
console.log('Signatur-Station getrennt');
|
||||
updateConnectionStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket Fehler:', error);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('WebSocket getrennt');
|
||||
setTimeout(connectWebSocket, 1000);
|
||||
};
|
||||
}
|
||||
|
||||
document.getElementById('pdfInput').addEventListener('change', async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Store filename for download
|
||||
pdfFileName = file.name.replace('.pdf', '');
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
|
||||
// Store TWO copies - one for rendering, one for pdf-lib
|
||||
originalPdfBytes = new Uint8Array(arrayBuffer);
|
||||
const renderCopy = new Uint8Array(arrayBuffer.slice(0));
|
||||
|
||||
// Create array for sending BEFORE any async operations
|
||||
const pdfArray = Array.from(originalPdfBytes);
|
||||
|
||||
// Show status section
|
||||
document.getElementById('uploadSection').style.display = 'none';
|
||||
document.getElementById('statusSection').style.display = 'block';
|
||||
|
||||
// Render PDF preview using the copy
|
||||
await renderPdfPreview(renderCopy);
|
||||
|
||||
// Send PDF to server using the pre-created array
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'pdf',
|
||||
pdf: pdfArray
|
||||
}));
|
||||
console.log('PDF gesendet, Größe:', pdfArray.length);
|
||||
|
||||
// Send initial page info after a short delay (to ensure signature station received PDF)
|
||||
setTimeout(() => {
|
||||
if (ws && ws.readyState === WebSocket.OPEN && totalPages > 0) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'page_change',
|
||||
pageNum: 1,
|
||||
totalPages: totalPages
|
||||
}));
|
||||
console.log('Initiale Seiteninformation gesendet: Seite 1 von', totalPages);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
async function renderPdfPreview(pdfBytes, pageNum = 1) {
|
||||
try {
|
||||
// ALWAYS create a fresh copy to prevent detachment
|
||||
// pdf.js consumes/detaches the buffer during rendering
|
||||
let safeCopy;
|
||||
if (pdfBytes instanceof Uint8Array) {
|
||||
safeCopy = new Uint8Array(pdfBytes.buffer.slice(0));
|
||||
} else {
|
||||
safeCopy = new Uint8Array(pdfBytes);
|
||||
}
|
||||
|
||||
const loadingTask = pdfjsLib.getDocument({ data: safeCopy });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
totalPages = pdfDoc.numPages;
|
||||
currentPageNum = pageNum;
|
||||
|
||||
currentPage = await pdfDoc.getPage(pageNum);
|
||||
|
||||
const canvas = document.getElementById('pdfCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const viewport = currentPage.getViewport({ scale: 1.5 });
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
await currentPage.render(renderContext).promise;
|
||||
|
||||
// Update page navigation
|
||||
updatePageNavigation();
|
||||
|
||||
console.log('PDF Seite', pageNum, 'erfolgreich gerendert');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Rendern des PDFs:', error);
|
||||
alert('Fehler beim Anzeigen der PDF: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function renderAllPages(pdfBytes) {
|
||||
try {
|
||||
// Create a fresh copy
|
||||
let safeCopy;
|
||||
if (pdfBytes instanceof Uint8Array) {
|
||||
safeCopy = new Uint8Array(pdfBytes.buffer.slice(0));
|
||||
} else {
|
||||
safeCopy = new Uint8Array(pdfBytes);
|
||||
}
|
||||
|
||||
console.log('Rendere ALLE Seiten, Größe:', safeCopy.length, 'bytes');
|
||||
|
||||
const loadingTask = pdfjsLib.getDocument({ data: safeCopy });
|
||||
const doc = await loadingTask.promise;
|
||||
const numPages = doc.numPages;
|
||||
|
||||
console.log('PDF hat', numPages, 'Seiten');
|
||||
|
||||
// Get container and clear it
|
||||
const container = document.getElementById('pdfCanvasContainer');
|
||||
container.innerHTML = '';
|
||||
|
||||
// Make pdf-preview scrollable
|
||||
const pdfPreview = document.getElementById('pdfPreview');
|
||||
pdfPreview.classList.add('scrollable');
|
||||
|
||||
// Render all pages
|
||||
for (let pageNum = 1; pageNum <= numPages; pageNum++) {
|
||||
|
||||
// Add page separator
|
||||
const separator = document.createElement('div');
|
||||
separator.className = 'pdf-page-separator';
|
||||
separator.textContent = `Seite ${pageNum} von ${numPages}`;
|
||||
container.appendChild(separator);
|
||||
|
||||
// Create canvas for this page
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.className = 'pdf-page-canvas';
|
||||
canvas.id = `pdf-page-${pageNum}`;
|
||||
container.appendChild(canvas);
|
||||
|
||||
// Render page
|
||||
const page = await doc.getPage(pageNum);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const viewport = page.getViewport({ scale: 1.5 });
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
await page.render(renderContext).promise;
|
||||
}
|
||||
|
||||
// Hide page navigation after placing
|
||||
document.getElementById('pageNavigation').style.display = 'none';
|
||||
|
||||
console.log('Alle', numPages, 'Seiten erfolgreich gerendert');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Rendern aller Seiten:', error);
|
||||
alert('Fehler beim Anzeigen der PDF: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function updatePageNavigation() {
|
||||
const pageNav = document.getElementById('pageNavigation');
|
||||
const pageInfo = document.getElementById('pageInfo');
|
||||
const prevBtn = document.getElementById('prevPageBtn');
|
||||
const nextBtn = document.getElementById('nextPageBtn');
|
||||
const pageSelector = document.getElementById('pageSelector');
|
||||
const currentPageNumSpan = document.getElementById('currentPageNum');
|
||||
|
||||
if (totalPages > 1) {
|
||||
pageNav.style.display = 'flex';
|
||||
pageInfo.textContent = `Seite ${currentPageNum} von ${totalPages}`;
|
||||
prevBtn.disabled = currentPageNum <= 1;
|
||||
nextBtn.disabled = currentPageNum >= totalPages;
|
||||
|
||||
// Show page selector if signature is visible
|
||||
if (document.getElementById('signatureOverlay').classList.contains('show')) {
|
||||
pageSelector.style.display = 'block';
|
||||
currentPageNumSpan.textContent = currentPageNum;
|
||||
}
|
||||
} else {
|
||||
pageNav.style.display = 'none';
|
||||
pageSelector.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function changePage(direction) {
|
||||
const newPage = currentPageNum + direction;
|
||||
if (newPage < 1 || newPage > totalPages) return;
|
||||
|
||||
await renderPdfPreview(originalPdfBytes, newPage);
|
||||
|
||||
// Send page change to signature station
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'page_change',
|
||||
pageNum: newPage,
|
||||
totalPages: totalPages
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function updatePageSelection() {
|
||||
const currentOnly = document.getElementById('currentPageOnly');
|
||||
const allPages = document.getElementById('allPages');
|
||||
|
||||
// Toggle logic
|
||||
if (currentOnly.checked && allPages.checked) {
|
||||
if (event.target === currentOnly) {
|
||||
allPages.checked = false;
|
||||
} else {
|
||||
currentOnly.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure at least one is checked
|
||||
if (!currentOnly.checked && !allPages.checked) {
|
||||
currentOnly.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function showSignatureOverlay(signatureDataUrl) {
|
||||
const overlay = document.getElementById('signatureOverlay');
|
||||
const img = document.getElementById('signatureImage');
|
||||
|
||||
img.src = signatureDataUrl;
|
||||
overlay.classList.add('show');
|
||||
|
||||
document.getElementById('signatureControls').classList.add('show');
|
||||
|
||||
// Show page selector if multi-page
|
||||
if (totalPages > 1) {
|
||||
document.getElementById('pageSelector').style.display = 'block';
|
||||
document.getElementById('currentPageNum').textContent = currentPageNum;
|
||||
}
|
||||
|
||||
document.getElementById('statusMessage').className = 'status signed';
|
||||
document.getElementById('statusMessage').textContent = '✅ Unterschrift erhalten! Wähle die Seite(n) und positioniere sie.';
|
||||
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';
|
||||
|
||||
// Make signature draggable
|
||||
makeDraggable(overlay);
|
||||
makeResizable(overlay);
|
||||
}
|
||||
|
||||
function makeDraggable(element) {
|
||||
let isDragging = false;
|
||||
let startX, startY, initialX, initialY;
|
||||
|
||||
element.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';
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
|
||||
element.style.left = (initialX + dx) + 'px';
|
||||
element.style.top = (initialY + dy) + 'px';
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
element.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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function updateSignatureScale() {
|
||||
const img = document.getElementById('signatureImage');
|
||||
const canvas = document.getElementById('pdfCanvas');
|
||||
|
||||
signatureScale = img.width / canvas.width;
|
||||
console.log('Neue Skalierung:', signatureScale);
|
||||
}
|
||||
|
||||
async function addSignatureToPdf() {
|
||||
try {
|
||||
if (!originalPdfBytes || originalPdfBytes.length === 0) {
|
||||
console.error('originalPdfBytes ist nicht verfügbar');
|
||||
alert('Fehler: PDF-Daten nicht verfügbar. Bitte laden Sie die PDF erneut hoch.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine which pages to add signature to
|
||||
const currentOnly = document.getElementById('currentPageOnly').checked;
|
||||
const allPagesChecked = document.getElementById('allPages').checked;
|
||||
|
||||
let pagesToSign = [];
|
||||
if (allPagesChecked) {
|
||||
// All pages
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pagesToSign.push(i);
|
||||
}
|
||||
} else {
|
||||
// Current page only
|
||||
pagesToSign.push(currentPageNum);
|
||||
}
|
||||
|
||||
console.log('=== FÜGE UNTERSCHRIFTEN HINZU ===');
|
||||
console.log('Seiten zum Signieren:', pagesToSign.join(', '));
|
||||
|
||||
// Load PDF with pdf-lib
|
||||
// Use ignoreEncryption to handle encrypted PDFs
|
||||
const pdfLibDoc = await PDFDocument.load(originalPdfBytes, { ignoreEncryption: true });
|
||||
const pages = pdfLibDoc.getPages();
|
||||
|
||||
// Get signature position and size from overlay
|
||||
const overlay = document.getElementById('signatureOverlay');
|
||||
const overlayLeft = overlay.offsetLeft;
|
||||
const overlayTop = overlay.offsetTop;
|
||||
const overlayWidth = overlay.querySelector('img').width;
|
||||
|
||||
// Embed signature image once
|
||||
const signatureImage = await pdfLibDoc.embedPng(signatureDataUrl);
|
||||
|
||||
// Add signature to each selected page
|
||||
for (const pageNum of pagesToSign) {
|
||||
const page = pages[pageNum - 1]; // 0-indexed
|
||||
const { width, height } = page.getSize();
|
||||
|
||||
console.log('--- Bearbeite Seite', pageNum, '---');
|
||||
console.log('Seite Dimensionen:', width, 'x', height);
|
||||
|
||||
// Get canvas display size
|
||||
const canvas = document.getElementById('pdfCanvas');
|
||||
const canvasDisplayWidth = canvas.clientWidth;
|
||||
const canvasDisplayHeight = canvas.clientHeight;
|
||||
|
||||
const scaleX = width / canvasDisplayWidth;
|
||||
const scaleY = height / canvasDisplayHeight;
|
||||
|
||||
console.log('Scale Faktoren - X:', scaleX, 'Y:', scaleY);
|
||||
console.log('Overlay Position:', overlayLeft, overlayTop);
|
||||
console.log('Overlay Breite:', overlayWidth);
|
||||
|
||||
// Calculate signature dimensions in PDF coordinates
|
||||
const sigWidth = overlayWidth * scaleX;
|
||||
const sigHeight = sigWidth * (signatureImage.height / signatureImage.width);
|
||||
|
||||
// Convert position to PDF coordinates (bottom-left origin)
|
||||
const x = overlayLeft * scaleX;
|
||||
const y = height - (overlayTop * scaleY) - sigHeight;
|
||||
|
||||
console.log('PDF Position:', x, y, 'Größe:', sigWidth, 'x', sigHeight);
|
||||
|
||||
// Add signature to page
|
||||
page.drawImage(signatureImage, {
|
||||
x: x,
|
||||
y: y,
|
||||
width: sigWidth,
|
||||
height: sigHeight,
|
||||
});
|
||||
|
||||
console.log('✓ Unterschrift zu Seite', pageNum, 'hinzugefügt');
|
||||
}
|
||||
|
||||
// Save PDF
|
||||
console.log('Speichere PDF...');
|
||||
const pdfBytes = await pdfLibDoc.save();
|
||||
console.log('PDF gespeichert, Typ:', pdfBytes.constructor.name, 'Größe:', pdfBytes.length);
|
||||
|
||||
if (!pdfBytes || pdfBytes.length === 0) {
|
||||
console.error('FEHLER: pdfDoc.save() hat leeres Array zurückgegeben!');
|
||||
alert('Fehler beim Speichern der PDF. Bitte versuchen Sie es erneut.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store as Uint8Array
|
||||
signedPdfBytes = new Uint8Array(pdfBytes);
|
||||
console.log('signedPdfBytes gespeichert, Typ:', signedPdfBytes.constructor.name, 'Größe:', signedPdfBytes.length);
|
||||
console.log('=== UNTERSCHRIFTEN ERFOLGREICH HINZUGEFÜGT ===');
|
||||
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Hinzufügen der Unterschrift:', error);
|
||||
console.error('Stack:', error.stack);
|
||||
alert('Fehler beim Verarbeiten der Unterschrift: ' + error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function removeSignatureOverlay() {
|
||||
// Only remove the overlay, keep signedPdfBytes intact
|
||||
document.getElementById('signatureOverlay').classList.remove('show');
|
||||
document.getElementById('signatureControls').classList.remove('show');
|
||||
signatureDataUrl = null;
|
||||
console.log('removeSignatureOverlay() aufgerufen - signedPdfBytes:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL');
|
||||
}
|
||||
|
||||
function removeSignature() {
|
||||
// Complete reset including signedPdfBytes
|
||||
document.getElementById('signatureOverlay').classList.remove('show');
|
||||
document.getElementById('signatureControls').classList.remove('show');
|
||||
document.getElementById('statusMessage').className = 'status waiting';
|
||||
document.getElementById('statusMessage').textContent = 'Warte auf Unterschrift von der Signatur-Station...';
|
||||
document.getElementById('downloadButton').disabled = true;
|
||||
document.getElementById('downloadHint').style.display = 'none';
|
||||
signatureDataUrl = null;
|
||||
console.log('removeSignature() aufgerufen - SETZE signedPdfBytes auf NULL');
|
||||
signedPdfBytes = null;
|
||||
}
|
||||
|
||||
// Place signature button
|
||||
document.getElementById('placeSignatureButton').addEventListener('click', async () => {
|
||||
if (!signatureDataUrl) {
|
||||
console.error('Keine signatureDataUrl vorhanden');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('=== PLATZIERE UNTERSCHRIFT ===');
|
||||
console.log('signatureDataUrl vorhanden:', signatureDataUrl ? 'Ja' : 'Nein');
|
||||
console.log('originalPdfBytes vorhanden:', originalPdfBytes ? 'Ja (' + originalPdfBytes.length + ' bytes)' : 'Nein');
|
||||
|
||||
// Show loading state
|
||||
const btn = document.getElementById('placeSignatureButton');
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = '⏳ PDF wird erstellt...';
|
||||
btn.disabled = true;
|
||||
|
||||
// Add signature to PDF
|
||||
const success = await addSignatureToPdf();
|
||||
|
||||
console.log('addSignatureToPdf Ergebnis:', success);
|
||||
console.log('signedPdfBytes nach Bearbeitung:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL');
|
||||
|
||||
if (success && signedPdfBytes) {
|
||||
// Hide signature overlay (but keep signedPdfBytes!)
|
||||
removeSignatureOverlay();
|
||||
|
||||
// Update status
|
||||
document.getElementById('statusMessage').className = 'status ready';
|
||||
document.getElementById('statusMessage').textContent = '✅ PDF wurde erstellt! Bereit zum Download.';
|
||||
|
||||
// Enable download
|
||||
document.getElementById('downloadButton').disabled = false;
|
||||
document.getElementById('downloadHint').style.display = 'block';
|
||||
|
||||
console.log('Download-Button Status:', document.getElementById('downloadButton').disabled ? 'DEAKTIVIERT' : 'AKTIVIERT');
|
||||
console.log('signedPdfBytes für Download:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL');
|
||||
|
||||
// Send confirmation to signature station that signature was placed
|
||||
console.log('=== SENDE PLATZIERUNGS-BESTÄTIGUNG ===');
|
||||
console.log('WebSocket vorhanden:', ws ? 'Ja' : 'Nein');
|
||||
console.log('WebSocket readyState:', ws ? ws.readyState : 'N/A');
|
||||
console.log('WebSocket OPEN:', ws && ws.readyState === WebSocket.OPEN ? 'Ja' : 'Nein');
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
const message = JSON.stringify({
|
||||
type: 'signature_placed'
|
||||
});
|
||||
ws.send(message);
|
||||
console.log('✅ Platzierungs-Bestätigung an Server gesendet');
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler beim Senden der Bestätigung:', error);
|
||||
}
|
||||
} else {
|
||||
console.error('❌ WebSocket nicht verfügbar oder nicht geöffnet - Bestätigung kann nicht gesendet werden');
|
||||
}
|
||||
|
||||
// Render the FINAL PDF with signature - show ALL pages
|
||||
console.log('Rendere finale PDF mit Unterschrift - ALLE Seiten...');
|
||||
const renderCopy = new Uint8Array(signedPdfBytes);
|
||||
await renderAllPages(renderCopy);
|
||||
console.log('Finale PDF angezeigt - alle Seiten');
|
||||
console.log('signedPdfBytes nach Rendering:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL');
|
||||
console.log('=== PLATZIERUNG ERFOLGREICH ===');
|
||||
} else {
|
||||
// Reset button on error
|
||||
console.error('Platzierung fehlgeschlagen! success:', success, 'signedPdfBytes:', signedPdfBytes ? 'vorhanden' : 'NULL');
|
||||
btn.textContent = originalText;
|
||||
btn.disabled = false;
|
||||
alert('Fehler beim Platzieren der Unterschrift. Bitte versuchen Sie es erneut.');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('downloadButton').addEventListener('click', async () => {
|
||||
console.log('=== DOWNLOAD BUTTON GEKLICKT ===');
|
||||
console.log('signedPdfBytes:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL/UNDEFINED');
|
||||
console.log('signedPdfBytes type:', typeof signedPdfBytes);
|
||||
|
||||
if (!signedPdfBytes || signedPdfBytes.length === 0) {
|
||||
console.error('FEHLER: signedPdfBytes ist nicht verfügbar!');
|
||||
alert('Fehler: Kein signiertes PDF verfügbar. Bitte platzieren Sie die Unterschrift zuerst.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Starte Download, PDF Größe:', signedPdfBytes.length, 'bytes');
|
||||
|
||||
// Ensure signedPdfBytes is Uint8Array
|
||||
const pdfData = signedPdfBytes instanceof Uint8Array ? signedPdfBytes : new Uint8Array(signedPdfBytes);
|
||||
console.log('PDF Daten konvertiert, Typ:', pdfData.constructor.name, 'Größe:', pdfData.length);
|
||||
|
||||
// Download with _signed suffix
|
||||
const blob = new Blob([pdfData], { type: 'application/pdf' });
|
||||
console.log('Blob erstellt, Größe:', blob.size);
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
console.log('Blob URL erstellt:', url);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = pdfFileName + '_signed.pdf';
|
||||
console.log('Download Dateiname:', a.download);
|
||||
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
console.log('PDF heruntergeladen als:', pdfFileName + '_signed.pdf');
|
||||
console.log('=== DOWNLOAD ERFOLGREICH ===');
|
||||
|
||||
// Reset for next PDF
|
||||
document.getElementById('uploadSection').style.display = 'block';
|
||||
document.getElementById('statusSection').style.display = 'none';
|
||||
|
||||
// Reset file input
|
||||
document.getElementById('pdfInput').value = '';
|
||||
|
||||
// Reset place signature button
|
||||
const placeBtn = document.getElementById('placeSignatureButton');
|
||||
placeBtn.textContent = '✓ Unterschrift platzieren & PDF erstellen';
|
||||
placeBtn.disabled = false;
|
||||
|
||||
// Reset page navigation and scrollable state
|
||||
currentPageNum = 1;
|
||||
totalPages = 1;
|
||||
document.getElementById('pageNavigation').style.display = 'none';
|
||||
document.getElementById('pageSelector').style.display = 'none';
|
||||
document.getElementById('pdfPreview').classList.remove('scrollable');
|
||||
|
||||
// Reset canvas container to single canvas
|
||||
const container = document.getElementById('pdfCanvasContainer');
|
||||
container.innerHTML = '<canvas id="pdfCanvas"></canvas>';
|
||||
|
||||
removeSignature();
|
||||
originalPdfBytes = null;
|
||||
signedPdfBytes = null;
|
||||
pdfFileName = '';
|
||||
pdfDoc = null;
|
||||
currentPage = null;
|
||||
|
||||
console.log('Alles zurückgesetzt für nächste PDF');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Download:', error);
|
||||
alert('Fehler beim Herunterladen: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
function resetToStart() {
|
||||
console.log('=== ZURÜCK ZUM ANFANG ===');
|
||||
|
||||
// Show upload section, hide status section
|
||||
document.getElementById('uploadSection').style.display = 'block';
|
||||
document.getElementById('statusSection').style.display = 'none';
|
||||
|
||||
// Reset file input
|
||||
document.getElementById('pdfInput').value = '';
|
||||
|
||||
// Reset place signature button
|
||||
const placeBtn = document.getElementById('placeSignatureButton');
|
||||
placeBtn.textContent = '✓ Unterschrift platzieren & PDF erstellen';
|
||||
placeBtn.disabled = false;
|
||||
|
||||
// Reset download button
|
||||
document.getElementById('downloadButton').disabled = true;
|
||||
document.getElementById('downloadHint').style.display = 'none';
|
||||
|
||||
// Reset page navigation and scrollable state
|
||||
currentPageNum = 1;
|
||||
totalPages = 1;
|
||||
document.getElementById('pageNavigation').style.display = 'none';
|
||||
document.getElementById('pageSelector').style.display = 'none';
|
||||
document.getElementById('pdfPreview').classList.remove('scrollable');
|
||||
|
||||
// Reset canvas container to single canvas
|
||||
const container = document.getElementById('pdfCanvasContainer');
|
||||
container.innerHTML = '<canvas id="pdfCanvas"></canvas>';
|
||||
|
||||
// Reset status message
|
||||
document.getElementById('statusMessage').className = 'status waiting';
|
||||
document.getElementById('statusMessage').textContent = 'Warte auf Unterschrift von der Signatur-Station...';
|
||||
|
||||
// Clear all data
|
||||
removeSignature();
|
||||
originalPdfBytes = null;
|
||||
signedPdfBytes = null;
|
||||
pdfFileName = '';
|
||||
pdfDoc = null;
|
||||
currentPage = null;
|
||||
signatureDataUrl = null;
|
||||
|
||||
console.log('Zurück zum Anfang - bereit für neue PDF');
|
||||
}
|
||||
|
||||
// Discard button event listener
|
||||
document.getElementById('discardButton').addEventListener('click', () => {
|
||||
if (confirm('Möchten Sie wirklich verwerfen? Alle Änderungen gehen verloren.')) {
|
||||
// Notify signature station to reset
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'discard'
|
||||
}));
|
||||
console.log('Verwerfen-Nachricht an Signatur-Station gesendet');
|
||||
}
|
||||
resetToStart();
|
||||
}
|
||||
});
|
||||
|
||||
// Connect WebSocket on load
|
||||
connectWebSocket();
|
||||
300
js/signature.js
Normal file
300
js/signature.js
Normal file
@@ -0,0 +1,300 @@
|
||||
// Configure pdf.js worker
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
||||
|
||||
const canvas = document.getElementById('signatureCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
let isDrawing = false;
|
||||
let hasSignature = false;
|
||||
let ws = null;
|
||||
let currentPdf = null;
|
||||
let pdfDoc = null;
|
||||
let currentPageNum = 1;
|
||||
let totalPages = 1;
|
||||
|
||||
connectWebSocket();
|
||||
|
||||
function connectWebSocket() {
|
||||
// Use current host (works for localhost, IP addresses, and reverse proxy)
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsHost = window.location.hostname;
|
||||
// If using HTTPS (reverse proxy), use same port as page (usually 443, no port needed)
|
||||
// If using HTTP directly, use port 8080
|
||||
const wsPort = window.location.protocol === 'https:' ? '' : ':8080';
|
||||
const wsUrl = `${wsProtocol}//${wsHost}${wsPort}`;
|
||||
console.log('Verbinde WebSocket zu:', wsUrl);
|
||||
ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('WebSocket verbunden');
|
||||
document.getElementById('loadingSection').style.display = 'none';
|
||||
document.getElementById('waitingSection').style.display = 'block';
|
||||
|
||||
// Register as signature station
|
||||
ws.send(JSON.stringify({
|
||||
type: 'register_signature'
|
||||
}));
|
||||
};
|
||||
|
||||
ws.onmessage = async (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === 'pdf') {
|
||||
console.log('PDF empfangen');
|
||||
currentPdf = new Uint8Array(data.pdf);
|
||||
|
||||
// Show PDF notification
|
||||
document.getElementById('waitingSection').style.display = 'none';
|
||||
document.getElementById('signatureSection').style.display = 'block';
|
||||
document.getElementById('pdfDisplay').classList.add('show');
|
||||
|
||||
// Render ALL pages of PDF
|
||||
await renderPdfPreview(currentPdf);
|
||||
|
||||
// Clear previous signature if any
|
||||
clearSignature();
|
||||
} else if (data.type === 'page_change') {
|
||||
currentPageNum = data.pageNum;
|
||||
totalPages = data.totalPages;
|
||||
|
||||
// Scroll to the specific page
|
||||
const pageCanvas = document.getElementById(`page-canvas-${data.pageNum}`);
|
||||
if (pageCanvas) {
|
||||
pageCanvas.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
} else if (data.type === 'signature_placed') {
|
||||
console.log('✅ Unterschrift wurde platziert! Reset in 5 Sekunden...');
|
||||
console.log('Empfangene Daten:', JSON.stringify(data));
|
||||
|
||||
// Show immediate feedback
|
||||
const successMsg = document.getElementById('successMessage');
|
||||
successMsg.classList.add('show');
|
||||
|
||||
// Reset after 5 seconds
|
||||
setTimeout(() => {
|
||||
console.log('Auto-Reset wird durchgeführt...');
|
||||
|
||||
// Clear signature
|
||||
clearSignature();
|
||||
|
||||
// Hide success message
|
||||
document.getElementById('successMessage').classList.remove('show');
|
||||
|
||||
// Hide PDF and signature section, show waiting
|
||||
document.getElementById('signatureSection').style.display = 'none';
|
||||
document.getElementById('pdfDisplay').classList.remove('show');
|
||||
document.getElementById('waitingSection').style.display = 'block';
|
||||
|
||||
// Reset PDF data
|
||||
currentPdf = null;
|
||||
pdfDoc = null;
|
||||
currentPageNum = 1;
|
||||
totalPages = 1;
|
||||
|
||||
console.log('Bereit für nächste Unterschrift');
|
||||
}, 5000);
|
||||
} else if (data.type === 'discard') {
|
||||
console.log('🔄 Verwerfen-Nachricht empfangen, setze zurück...');
|
||||
resetSignatureStation();
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket Fehler:', error);
|
||||
document.getElementById('loadingSection').innerHTML =
|
||||
'<p style="color: #dc3545;">❌ Verbindung zum Server fehlgeschlagen.</p>';
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('WebSocket getrennt');
|
||||
setTimeout(connectWebSocket, 1000);
|
||||
};
|
||||
}
|
||||
|
||||
async function renderPdfPreview(pdfBytes, pageNum = null) {
|
||||
try {
|
||||
// Create fresh copy to prevent detachment
|
||||
const safeCopy = new Uint8Array(pdfBytes.buffer.slice(0));
|
||||
|
||||
console.log('renderPdfPreview aufgerufen mit', pdfBytes.length, 'bytes');
|
||||
const loadingTask = pdfjsLib.getDocument({ data: safeCopy });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
totalPages = pdfDoc.numPages;
|
||||
|
||||
// Update page info
|
||||
document.getElementById('pageInfoSignature').textContent = `${totalPages} Seite${totalPages > 1 ? 'n' : ''}`;
|
||||
|
||||
// Get container
|
||||
const container = document.getElementById('pdfPagesContainer');
|
||||
container.innerHTML = ''; // Clear existing content
|
||||
|
||||
// Render all pages
|
||||
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
|
||||
|
||||
// Add page separator if not first page
|
||||
if (pageNum > 1) {
|
||||
const separator = document.createElement('div');
|
||||
separator.className = 'page-separator';
|
||||
separator.textContent = `Seite ${pageNum}`;
|
||||
container.appendChild(separator);
|
||||
} else {
|
||||
// First page indicator
|
||||
const separator = document.createElement('div');
|
||||
separator.className = 'page-separator';
|
||||
separator.textContent = `Seite 1`;
|
||||
container.appendChild(separator);
|
||||
}
|
||||
|
||||
// Create canvas for this page
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.className = 'pdf-preview-canvas';
|
||||
canvas.id = `page-canvas-${pageNum}`;
|
||||
container.appendChild(canvas);
|
||||
|
||||
// Render page
|
||||
const page = await pdfDoc.getPage(pageNum);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const viewport = page.getViewport({ scale: 1.2 });
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
await page.render(renderContext).promise;
|
||||
}
|
||||
|
||||
console.log('Alle', totalPages, 'Seiten erfolgreich gerendert');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Rendern der PDF Vorschau:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Canvas setup
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
function startDrawing(e) {
|
||||
isDrawing = true;
|
||||
const pos = getPosition(e);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pos.x, pos.y);
|
||||
}
|
||||
|
||||
function draw(e) {
|
||||
if (!isDrawing) return;
|
||||
e.preventDefault();
|
||||
|
||||
const pos = getPosition(e);
|
||||
ctx.lineTo(pos.x, pos.y);
|
||||
ctx.stroke();
|
||||
|
||||
hasSignature = true;
|
||||
document.getElementById('submitButton').disabled = false;
|
||||
}
|
||||
|
||||
function stopDrawing() {
|
||||
isDrawing = false;
|
||||
}
|
||||
|
||||
function getPosition(e) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const scaleX = canvas.width / rect.width;
|
||||
const scaleY = canvas.height / rect.height;
|
||||
|
||||
if (e.touches && e.touches.length > 0) {
|
||||
return {
|
||||
x: (e.touches[0].clientX - rect.left) * scaleX,
|
||||
y: (e.touches[0].clientY - rect.top) * scaleY
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
x: (e.clientX - rect.left) * scaleX,
|
||||
y: (e.clientY - rect.top) * scaleY
|
||||
};
|
||||
}
|
||||
|
||||
// Mouse events
|
||||
canvas.addEventListener('mousedown', startDrawing);
|
||||
canvas.addEventListener('mousemove', draw);
|
||||
canvas.addEventListener('mouseup', stopDrawing);
|
||||
canvas.addEventListener('mouseout', stopDrawing);
|
||||
|
||||
// Touch events
|
||||
canvas.addEventListener('touchstart', startDrawing);
|
||||
canvas.addEventListener('touchmove', draw);
|
||||
canvas.addEventListener('touchend', stopDrawing);
|
||||
|
||||
// Clear button
|
||||
document.getElementById('clearButton').addEventListener('click', () => {
|
||||
clearSignature();
|
||||
});
|
||||
|
||||
function clearSignature() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
hasSignature = false;
|
||||
document.getElementById('submitButton').disabled = true;
|
||||
document.getElementById('successMessage').classList.remove('show');
|
||||
document.getElementById('errorMessage').classList.remove('show');
|
||||
}
|
||||
|
||||
function resetSignatureStation() {
|
||||
console.log('=== SIGNATUR-STATION ZURÜCKSETZEN ===');
|
||||
|
||||
// Clear signature canvas
|
||||
clearSignature();
|
||||
|
||||
// Hide success and error messages
|
||||
document.getElementById('successMessage').classList.remove('show');
|
||||
document.getElementById('errorMessage').classList.remove('show');
|
||||
|
||||
// Hide PDF and signature section, show waiting
|
||||
document.getElementById('signatureSection').style.display = 'none';
|
||||
document.getElementById('pdfDisplay').classList.remove('show');
|
||||
document.getElementById('waitingSection').style.display = 'block';
|
||||
|
||||
// Clear PDF pages container
|
||||
const container = document.getElementById('pdfPagesContainer');
|
||||
if (container) {
|
||||
container.innerHTML = '';
|
||||
}
|
||||
|
||||
// Reset PDF data
|
||||
currentPdf = null;
|
||||
pdfDoc = null;
|
||||
currentPageNum = 1;
|
||||
totalPages = 1;
|
||||
|
||||
console.log('Signatur-Station zurückgesetzt - bereit für neue PDF');
|
||||
}
|
||||
|
||||
// Submit button
|
||||
document.getElementById('submitButton').addEventListener('click', async () => {
|
||||
if (!hasSignature || !ws || ws.readyState !== WebSocket.OPEN) return;
|
||||
|
||||
try {
|
||||
// Get signature as data URL
|
||||
const signatureDataUrl = canvas.toDataURL('image/png');
|
||||
|
||||
// Send to server (no session ID needed)
|
||||
ws.send(JSON.stringify({
|
||||
type: 'signature',
|
||||
signature: signatureDataUrl
|
||||
}));
|
||||
|
||||
// Show success message
|
||||
document.getElementById('successMessage').classList.add('show');
|
||||
document.getElementById('submitButton').disabled = true;
|
||||
|
||||
console.log('Unterschrift gesendet, warte auf Platzierungs-Bestätigung...');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Senden:', error);
|
||||
document.getElementById('errorMessage').classList.add('show');
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user