/* * canvas-crop.js * In-place crop tool for cabinet and shelf photos. * Renders a draggable crop rectangle on the boundary canvas overlay, * then POSTs pixel coordinates to the server to permanently crop the image. * * Entry point: startCropMode(type, id) — called from events.js 'crop-start'. * Disables boundary drag events while active (checked via S._cropMode). * * Depends on: S (state.js); req, toast (api.js / helpers.js); * drawBnd (canvas-boundary.js) — called in cancelCrop to restore * the boundary overlay after the crop UI is dismissed * Provides: startCropMode(), cancelCrop(), confirmCrop() */ // ── Crop state ─────────────────────────────────────────────────────────────── let _cropState = null; // {x1,y1,x2,y2} fractions; null = not in crop mode let _cropDragPart = null; // 'tl','tr','bl','br','t','b','l','r','move' | null let _cropDragStart = null; // {fx,fy,x1,y1,x2,y2} snapshot at drag start // ── Public entry point ─────────────────────────────────────────────────────── function startCropMode(type, id) { const canvas = document.getElementById('bnd-canvas'); const wrap = document.getElementById('bnd-wrap'); if (!canvas || !wrap) return; S._cropMode = {type, id}; _cropState = {x1: 0.05, y1: 0.05, x2: 0.95, y2: 0.95}; canvas.addEventListener('pointerdown', cropPointerDown); canvas.addEventListener('pointermove', cropPointerMove); canvas.addEventListener('pointerup', cropPointerUp); document.getElementById('crop-bar')?.remove(); const bar = document.createElement('div'); bar.id = 'crop-bar'; bar.style.cssText = 'margin-top:10px;display:flex;gap:8px'; bar.innerHTML = ''; wrap.after(bar); document.getElementById('crop-ok').addEventListener('click', confirmCrop); document.getElementById('crop-cancel').addEventListener('click', cancelCrop); drawCropOverlay(); } // ── Drawing ────────────────────────────────────────────────────────────────── function drawCropOverlay() { const canvas = document.getElementById('bnd-canvas'); if (!canvas || !_cropState) return; const ctx = canvas.getContext('2d'); const W = canvas.width, H = canvas.height; const {x1, y1, x2, y2} = _cropState; const px1=x1*W, py1=y1*H, px2=x2*W, py2=y2*H; ctx.clearRect(0, 0, W, H); // Dark shadow outside crop rect ctx.fillStyle = 'rgba(0,0,0,0.55)'; ctx.fillRect(0, 0, W, H); ctx.clearRect(px1, py1, px2-px1, py2-py1); // Bright border ctx.strokeStyle = '#38bdf8'; ctx.lineWidth = 2; ctx.setLineDash([]); ctx.strokeRect(px1, py1, px2-px1, py2-py1); // Corner handles const hs = 9; ctx.fillStyle = '#38bdf8'; [[px1,py1],[px2,py1],[px1,py2],[px2,py2]].forEach(([x,y]) => ctx.fillRect(x-hs/2, y-hs/2, hs, hs)); } // ── Hit testing ────────────────────────────────────────────────────────────── function _cropFracFromEvt(e) { const canvas = document.getElementById('bnd-canvas'); const r = canvas.getBoundingClientRect(); return {fx: (e.clientX-r.left)/r.width, fy: (e.clientY-r.top)/r.height}; } function _getCropPart(fx, fy) { if (!_cropState) return null; const {x1, y1, x2, y2} = _cropState; const th = 0.05; const inX=fx>=x1&&fx<=x2, inY=fy>=y1&&fy<=y2; const nX1=Math.abs(fx-x1)