/** @odoo-module **/ import { patch } from "@web/core/utils/patch"; import { PDFIframe } from "@sign/components/sign_request/PDF_iframe"; import { SignablePDFIframe } from "@sign/components/sign_request/signable_PDF_iframe"; console.log("Sign Image Field: Initializing stabilized patches..."); /** * Shared utility for lazy loading image fields. */ const triggerLazyLoad = (el) => { if (!el) return; const lazyImg = el.querySelector('.o_sign_image_lazy'); if (lazyImg && (lazyImg.dataset.src || lazyImg.src)) { const targetSrc = lazyImg.dataset.src || lazyImg.src; const currentSrc = lazyImg.getAttribute('src'); // Only trigger if we haven't loaded the real image yet if (!currentSrc || currentSrc.startsWith('data:image/gif') || currentSrc === '') { lazyImg.src = targetSrc; lazyImg.onload = () => { lazyImg.classList.remove('o_sign_image_loading'); el.classList.remove('o_sign_image_loading_container'); lazyImg.style.opacity = 1; }; } else { // Cleanup in case of re-rendering lazyImg.classList.remove('o_sign_image_loading'); el.classList.remove('o_sign_image_loading_container'); lazyImg.style.opacity = 1; } } }; /** * Shared logic for setting up an observer on any image field. */ const setupLazyObserver = (el) => { if (!el) return; // Immediate check + safety fallback (2 seconds) // This ensures images load even if IntersectionObserver is throttled setTimeout(() => triggerLazyLoad(el), 2000); if (window.IntersectionObserver) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { triggerLazyLoad(el); observer.disconnect(); } }); }, { rootMargin: '200px' }); observer.observe(el); } else { triggerLazyLoad(el); } }; // 1. Patch PDFIframe (Used for observers and read-only views) patch(PDFIframe.prototype, { enableCustom(signItem) { // ALWAYS call the base implementation FIRST to preserve Odoo behavior super.enableCustom(...arguments); // OUR LOGIC: Setup lazy loading if it's an image if (signItem && signItem.data && signItem.data.type === 'image' && signItem.el) { setupLazyObserver(signItem.el); } } }); // 2. Patch SignablePDFIframe (Used for active signers) patch(SignablePDFIframe.prototype, { enableCustom(signItem) { // ALWAYS call the base implementation FIRST to preserve text/signature population super.enableCustom(...arguments); // OUR LOGIC: Setup lazy loading and upload support if (signItem && signItem.data && signItem.data.type === 'image' && signItem.el) { const el = signItem.el; const data = signItem.data; // Shared lazy loading observer setupLazyObserver(el); // Only attach upload listeners if this person is responsible and not readonly if (this.readonly || (data.responsible > 0 && data.responsible !== this.currentRole)) { return; } el.setAttribute('data-type', 'image'); const input = el.querySelector('.o_sign_image_upload_input'); if (input) { el.addEventListener('click', (e) => { if (e.target !== input) { input.click(); } }); input.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (readerEvent) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); let width = img.width; let height = img.height; const MAX_SIZE = 1080; if (width > height) { if (width > MAX_SIZE) { height *= MAX_SIZE / width; width = MAX_SIZE; } } else if (height > MAX_SIZE) { width *= MAX_SIZE / height; height = MAX_SIZE; } canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, width, height); const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.7); data.value = compressedDataUrl; el.dataset.value = compressedDataUrl; const placeholder = el.querySelector('.o_placeholder'); if (placeholder) placeholder.remove(); let existingImg = el.querySelector('img'); if (!existingImg) { existingImg = document.createElement('img'); existingImg.style.maxWidth = '100%'; existingImg.style.maxHeight = '100%'; existingImg.style.objectFit = 'contain'; const body = el.querySelector('.sign_item_body') || el; body.appendChild(existingImg); } existingImg.src = compressedDataUrl; if (this.handleInput) { this.handleInput(); } }; img.src = readerEvent.target.result; }; reader.readAsDataURL(file); }); } } }, getSignatureValueFromElement(item) { if (item.data.type === 'image') { return item.el.dataset.value || item.data.value || false; } return super.getSignatureValueFromElement(...arguments); } });