diff --git a/static/src/js/sign_image_upload.js b/static/src/js/sign_image_upload.js index c6be760..559384c 100755 --- a/static/src/js/sign_image_upload.js +++ b/static/src/js/sign_image_upload.js @@ -4,7 +4,7 @@ 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..."); +console.log("Sign Image Field: Initializing universal stability patches..."); /** * Shared utility for lazy loading image fields. @@ -12,36 +12,46 @@ console.log("Sign Image Field: Initializing stabilized patches..."); 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; - } + if (!lazyImg) return; + + const dataSrc = lazyImg.dataset.src; + if (!dataSrc) return; + + const finalize = () => { + lazyImg.classList.remove('o_sign_image_loading'); + el.classList.remove('o_sign_image_loading_container'); + lazyImg.style.opacity = 1; + }; + + // If already loaded or assigned + if (lazyImg.src && !lazyImg.src.startsWith('data:image/gif') && lazyImg.src !== '') { + finalize(); + return; + } + + // Assign BEFORE src to avoid race with cache + lazyImg.onload = finalize; + lazyImg.src = dataSrc; + + // Immediate check if already complete (from cache) + if (lazyImg.complete) { + finalize(); } }; /** - * Shared logic for setting up an observer on any image field. + * Shared logic for setting up an observer. */ 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); + // Safety fallback: Load after 1.5s regardless of intersection + // This is vital for remote connections where observer triggers might be erratic + setTimeout(() => { + if (el.classList.contains('o_sign_image_loading_container')) { + triggerLazyLoad(el); + } + }, 1500); if (window.IntersectionObserver) { const observer = new IntersectionObserver((entries) => { @@ -51,18 +61,37 @@ const setupLazyObserver = (el) => { observer.disconnect(); } }); - }, { rootMargin: '200px' }); + }, { rootMargin: '300px' }); observer.observe(el); } else { triggerLazyLoad(el); } }; -// 1. Patch PDFIframe (Used for observers and read-only views) +/** + * Restore the Odoo-Iframe bridge if it's missing (Odoo 19 systemic issue) + * This fixes the "refreshSignItemsForPage is not a function" error in viewer.js + */ +const restoreBridge = (instance) => { + if (instance.root && instance.root.defaultView && instance.root.defaultView.frameElement) { + const iframe = instance.root.defaultView.frameElement; + if (!iframe.odoo_iframe_instance) { + console.log("Sign Image Field: Restoring odoo_iframe_instance bridge on iframe"); + iframe.odoo_iframe_instance = instance; + } + } +}; + +// 1. Patch PDFIframe (ROOT) patch(PDFIframe.prototype, { enableCustom(signItem) { - // ALWAYS call the base implementation FIRST to preserve Odoo behavior - super.enableCustom(...arguments); + // Restore communication bridge immediately to prevent viewer.js crashes + restoreBridge(this); + + // Defensive super call to preserve original Odoo behavior and other patches + if (typeof super.enableCustom === 'function') { + super.enableCustom(...arguments); + } // OUR LOGIC: Setup lazy loading if it's an image if (signItem && signItem.data && signItem.data.type === 'image' && signItem.el) { @@ -71,21 +100,24 @@ patch(PDFIframe.prototype, { } }); -// 2. Patch SignablePDFIframe (Used for active signers) +// 2. Patch SignablePDFIframe (Signer subclass) patch(SignablePDFIframe.prototype, { enableCustom(signItem) { - // ALWAYS call the base implementation FIRST to preserve text/signature population + restoreBridge(this); + + // Setup observer early to ensure it runs for all documents/roles + if (signItem && signItem.data && signItem.data.type === 'image' && signItem.el) { + setupLazyObserver(signItem.el); + } + + // Call original implementation (which handles text/signature etc) 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 + // Strictly check responsibility for UPLOAD logic if (this.readonly || (data.responsible > 0 && data.responsible !== this.currentRole)) { return; } @@ -93,11 +125,13 @@ patch(SignablePDFIframe.prototype, { 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(); - } - }); + // Ensure we don't double-attach if enableCustom is called multiple times + if (!el._hasImgListener) { + el.addEventListener('click', (e) => { + if (e.target !== input) input.click(); + }); + el._hasImgListener = true; + } input.addEventListener('change', (e) => { const file = e.target.files[0]; @@ -125,7 +159,7 @@ patch(SignablePDFIframe.prototype, { const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, width, height); - const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.7); + const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.8); data.value = compressedDataUrl; el.dataset.value = compressedDataUrl; @@ -135,17 +169,19 @@ patch(SignablePDFIframe.prototype, { let existingImg = el.querySelector('img'); if (!existingImg) { existingImg = document.createElement('img'); - existingImg.style.maxWidth = '100%'; - existingImg.style.maxHeight = '100%'; - existingImg.style.objectFit = 'contain'; + Object.assign(existingImg.style, { + maxWidth: '100%', + maxHeight: '100%', + objectFit: 'contain', + opacity: 1 + }); const body = el.querySelector('.sign_item_body') || el; body.appendChild(existingImg); } existingImg.src = compressedDataUrl; + el.classList.remove('o_sign_image_loading_container'); - if (this.handleInput) { - this.handleInput(); - } + if (this.handleInput) this.handleInput(); }; img.src = readerEvent.target.result; };