Dokumente am Auftrag öffnen denselben PDF-Betrachter mit Zoom (Zwei-Finger, Knöpfe, Doppeltipp) [deploy]
All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 14s

Der alte Dokumenten-Betrachter (eine Seite, nur Vor/Zurück) hatte keinen Zoom —
deshalb ging Finger-auseinanderziehen dort nicht. Jetzt ein Betrachter für alles;
Download bleibt die Originaldatei vom Server.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Eddy 2026-10-15 22:27:11 +02:00
parent 55b9a4f2d0
commit a6cdc7b861

99
app.js
View file

@ -2403,7 +2403,16 @@ async function openPageActionsModal(berichtId, pageId, relpath, note, title) {
/* ============================================================
* PDF-VORSCHAU MODAL
* ============================================================ */
function openPdfModal(blobUrl, filename) {
/**
* Der EINE PDF-Betrachter der App — Berichtsvorschau, Lieferschein und Dokumente am Auftrag.
* Bis 15.10.2026 gab es zwei: die Dokumente öffneten einen älteren ohne Zoom (eine Seite,
* nur Vor/Zurück), deshalb ging „Finger auseinanderziehen" dort nicht.
*
* @param {string} blobUrl
* @param {string} filename
* @param {Function} [beiDownload] eigener Download-Weg (z. B. Originaldatei vom Server)
*/
function openPdfModal(blobUrl, filename, beiDownload) {
const fname = filename || 'bericht.pdf';
const modal = document.createElement('div');
modal.className = 'fullscreen-modal pdfv-modal';
@ -2432,6 +2441,9 @@ function openPdfModal(blobUrl, filename) {
await shareFile(blob, fname, 'application/pdf', fname);
} catch (e) { showToast('Teilen fehlgeschlagen: ' + e.message, 'error'); }
};
if (typeof beiDownload === 'function') {
modal.querySelector('#pdf-download').onclick = (e) => { e.preventDefault(); beiDownload(); };
}
const scroller = modal.querySelector('#pdfv-scroll');
const stack = modal.querySelector('#pdfv-stack');
@ -3268,86 +3280,17 @@ function openFileViewer({ url, blob, mime }, filename, relpath) {
}
async function openPdfViewer(blob, filename, relpath) {
console.log('[PDF] Opening:', filename, 'size:', blob.size, 'type:', blob.type);
if (blob.size === 0) {
if (!blob || blob.size === 0) {
showToast('❌ PDF-Datei ist leer oder nicht gefunden', 'error');
return;
}
const modal = document.createElement('div');
modal.className = 'fullscreen-modal pdf-viewer-modal';
modal.innerHTML = `
<div class="fs-header">
<button class="icon-btn" id="pdf-prev" title="← Seite zurück">‹</button>
<div class="pdf-page-info" id="pdf-page-info">⏳</div>
<button class="icon-btn" id="pdf-next" title="Seite vor →">›</button>
<div class="fs-title">${escapeHtml(filename || '')}</div>
<button class="icon-btn" id="pdf-close">✕</button>
<button class="icon-btn" id="pdf-share" title="Teilen">📤</button>
<button class="icon-btn" id="pdf-download" title="Download">⬇</button>
</div>
<div class="pdf-body" id="pdf-body">
<canvas id="pdf-canvas"></canvas>
</div>
`;
document.body.appendChild(modal);
pushModal(modal);
const canvas = modal.querySelector('#pdf-canvas');
const ctx = canvas.getContext('2d');
const pageInfo = modal.querySelector('#pdf-page-info');
let pdf, currentPage = 1;
try {
const arrayBuffer = await blob.arrayBuffer();
console.log('[PDF] ArrayBuffer size:', arrayBuffer.byteLength);
if (arrayBuffer.byteLength === 0) {
throw new Error('PDF-Datei ist leer');
}
pdf = await pdfjs.getDocument(new Uint8Array(arrayBuffer)).promise;
console.log('[PDF] Loaded:', pdf.numPages, 'pages');
pageInfo.textContent = `1 / ${pdf.numPages}`;
async function renderPage(num) {
if (num < 1 || num > pdf.numPages) return;
currentPage = num;
try {
const page = await pdf.getPage(num);
const viewport = page.getViewport({ scale: window.devicePixelRatio });
canvas.width = viewport.width;
canvas.height = viewport.height;
await page.render({ canvasContext: ctx, viewport }).promise;
pageInfo.textContent = `${num} / ${pdf.numPages}`;
} catch (e) {
console.error('[PDF] Render error on page', num, e);
pageInfo.textContent = `❌ Seite ${num} Fehler`;
}
}
await renderPage(1);
modal.querySelector('#pdf-prev').onclick = () => renderPage(currentPage - 1);
modal.querySelector('#pdf-next').onclick = () => renderPage(currentPage + 1);
modal.querySelector('#pdf-download').onclick = async () => {
const params = new URLSearchParams({ relpath, download: 1 });
window.location.href = window.location.origin + '/custom/bericht/api/photo.php?' + params.toString();
};
modal.querySelector('#pdf-share').onclick = () => {
shareFile(blob, filename || 'dokument.pdf', 'application/pdf', filename);
};
modal.querySelector('#pdf-prev').disabled = false;
modal.querySelector('#pdf-next').disabled = pdf.numPages === 1;
} catch (err) {
pageInfo.textContent = '❌ ' + (err.message || 'PDF-Fehler');
console.error('[PDF] Load error:', err);
showToast('PDF-Fehler: ' + err.message, 'error');
}
modal.querySelector('#pdf-close').onclick = () => {
closeModal(modal);
};
// Derselbe Betrachter wie überall (Zoom per Knopf, Zwei-Finger, Doppeltipp, Strg+Rad).
// Download wie bisher als Originaldatei vom Server, nicht aus dem Zwischenspeicher.
const url = URL.createObjectURL(blob);
openPdfModal(url, filename || 'dokument.pdf', relpath ? () => {
const params = new URLSearchParams({ relpath, download: 1 });
window.location.href = window.location.origin + '/custom/bericht/api/photo.php?' + params.toString();
} : null);
}
/* ============================================================