152 lines
5.2 KiB
JavaScript
152 lines
5.2 KiB
JavaScript
'use strict';
|
||
|
||
(function() {
|
||
const overlay = document.getElementById('crop-modal-overlay');
|
||
const BELEG_ID = overlay.dataset.belegId;
|
||
const FILE_URL = '/belege/' + BELEG_ID + '/file';
|
||
const canvas = document.getElementById('crop-canvas');
|
||
const confirmBtn = document.getElementById('crop-confirm-btn');
|
||
const sizeHint = document.getElementById('crop-size-hint');
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
let img = null;
|
||
let scaleX = 1, scaleY = 1;
|
||
let sel = null; // {x,y,w,h} in Canvas-Koordinaten
|
||
let dragging = false;
|
||
let startX = 0, startY = 0;
|
||
|
||
const spinner = document.getElementById('crop-spinner');
|
||
|
||
window.openCropModal = function() {
|
||
overlay.style.display = 'flex';
|
||
if (img) { drawCanvas(); return; }
|
||
|
||
spinner.style.display = 'flex';
|
||
img = new Image();
|
||
img.onload = function() {
|
||
// Canvas auf max. 90vw / 72vh skalieren
|
||
const maxW = Math.min(window.innerWidth * 0.90, 1200);
|
||
const maxH = Math.min(window.innerHeight * 0.72, 900);
|
||
const ratio = Math.min(maxW / img.naturalWidth, maxH / img.naturalHeight, 1);
|
||
canvas.width = Math.round(img.naturalWidth * ratio);
|
||
canvas.height = Math.round(img.naturalHeight * ratio);
|
||
scaleX = img.naturalWidth / canvas.width;
|
||
scaleY = img.naturalHeight / canvas.height;
|
||
sel = null;
|
||
spinner.style.display = 'none';
|
||
drawCanvas();
|
||
};
|
||
img.src = FILE_URL + '?nocache=' + Date.now();
|
||
};
|
||
|
||
window.closeCropModal = function() {
|
||
overlay.style.display = 'none';
|
||
};
|
||
|
||
function drawCanvas() {
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||
if (!sel || sel.w === 0 || sel.h === 0) return;
|
||
|
||
// Abdunkeln außerhalb der Auswahl
|
||
ctx.fillStyle = 'rgba(0,0,0,0.45)';
|
||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||
|
||
// Ausschnitt freistellen
|
||
ctx.drawImage(img,
|
||
sel.x * scaleX, sel.y * scaleY, sel.w * scaleX, sel.h * scaleY,
|
||
sel.x, sel.y, sel.w, sel.h);
|
||
|
||
// Rahmen
|
||
ctx.strokeStyle = '#fff';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.setLineDash([5, 3]);
|
||
ctx.strokeRect(sel.x + 0.5, sel.y + 0.5, sel.w, sel.h);
|
||
ctx.setLineDash([]);
|
||
|
||
const realW = Math.round(sel.w * scaleX);
|
||
const realH = Math.round(sel.h * scaleY);
|
||
sizeHint.textContent = realW + ' × ' + realH + ' px';
|
||
confirmBtn.disabled = (realW < 10 || realH < 10);
|
||
}
|
||
|
||
function canvasPos(e) {
|
||
const r = canvas.getBoundingClientRect();
|
||
const cx = e.touches ? e.touches[0].clientX : e.clientX;
|
||
const cy = e.touches ? e.touches[0].clientY : e.clientY;
|
||
return {
|
||
x: Math.max(0, Math.min(canvas.width, Math.round(cx - r.left))),
|
||
y: Math.max(0, Math.min(canvas.height, Math.round(cy - r.top))),
|
||
};
|
||
}
|
||
|
||
canvas.addEventListener('mousedown', startDrag);
|
||
canvas.addEventListener('touchstart', startDrag, {passive: true});
|
||
|
||
function startDrag(e) {
|
||
const p = canvasPos(e);
|
||
startX = p.x; startY = p.y;
|
||
sel = {x: p.x, y: p.y, w: 0, h: 0};
|
||
dragging = true;
|
||
confirmBtn.disabled = true;
|
||
sizeHint.textContent = '';
|
||
}
|
||
|
||
window.addEventListener('mousemove', moveDrag);
|
||
window.addEventListener('touchmove', moveDrag, {passive: true});
|
||
|
||
function moveDrag(e) {
|
||
if (!dragging) return;
|
||
const p = canvasPos(e);
|
||
sel = {
|
||
x: Math.min(startX, p.x),
|
||
y: Math.min(startY, p.y),
|
||
w: Math.abs(p.x - startX),
|
||
h: Math.abs(p.y - startY),
|
||
};
|
||
drawCanvas();
|
||
}
|
||
|
||
window.addEventListener('mouseup', endDrag);
|
||
window.addEventListener('touchend', endDrag);
|
||
|
||
function endDrag() { dragging = false; }
|
||
|
||
window.confirmCrop = async function() {
|
||
if (!sel || sel.w < 1 || sel.h < 1) return;
|
||
confirmBtn.disabled = true;
|
||
confirmBtn.textContent = 'Wird gespeichert…';
|
||
|
||
const payload = {
|
||
x: Math.round(sel.x * scaleX),
|
||
y: Math.round(sel.y * scaleY),
|
||
w: Math.round(sel.w * scaleX),
|
||
h: Math.round(sel.h * scaleY),
|
||
};
|
||
|
||
try {
|
||
const res = await fetch('/belege/' + BELEG_ID + '/crop', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify(payload),
|
||
});
|
||
const data = await res.json();
|
||
if (!res.ok) throw new Error(data.error || 'Unbekannter Fehler');
|
||
location.reload();
|
||
} catch (err) {
|
||
alert('Fehler beim Zuschneiden: ' + err.message);
|
||
confirmBtn.disabled = false;
|
||
confirmBtn.textContent = 'Zuschnitt übernehmen';
|
||
}
|
||
};
|
||
|
||
// ESC schließt Modal
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape' && overlay.style.display !== 'none') closeCropModal();
|
||
});
|
||
|
||
// Klick auf Overlay-Hintergrund schließt
|
||
overlay.addEventListener('click', function(e) {
|
||
if (e.target === overlay) closeCropModal();
|
||
});
|
||
}());
|