From b9e8b274ced1640d97b1cf95018cda1986ff641a Mon Sep 17 00:00:00 2001 From: Immac Date: Wed, 4 Mar 2026 07:38:40 -0600 Subject: [PATCH] fix: align node preview with ComfyUI pattern (rAF + node.graph.setDirtyCanvas) --- dist/immac_style_mixer/main.js | 4 ++-- ui/src/main.tsx | 41 ++++++++++++---------------------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/dist/immac_style_mixer/main.js b/dist/immac_style_mixer/main.js index bb7859f..e534a1f 100644 --- a/dist/immac_style_mixer/main.js +++ b/dist/immac_style_mixer/main.js @@ -1,5 +1,5 @@ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["immac_style_mixer/StyleMixerPanel-B66F9prP.js","immac_style_mixer/vendor-DPfS0z4g.js"])))=>i.map(i=>d[i]); -import{r as $,a as U,R as T}from"./vendor-DPfS0z4g.js";const z="modulepreload",L=function(u){return"/"+u},R={},A=function(c,h,g){let e=Promise.resolve();if(h&&h.length>0){document.getElementsByTagName("link");const n=document.querySelector("meta[property=csp-nonce]"),i=(n==null?void 0:n.nonce)||(n==null?void 0:n.getAttribute("nonce"));e=Promise.allSettled(h.map(t=>{if(t=L(t),t in R)return;R[t]=!0;const r=t.endsWith(".css"),o=r?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${t}"]${o}`))return;const s=document.createElement("link");if(s.rel=r?"stylesheet":z,r||(s.as="script"),s.crossOrigin="",s.href=t,i&&s.setAttribute("nonce",i),document.head.appendChild(s),r)return new Promise((y,m)=>{s.addEventListener("load",y),s.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${t}`)))})}))}function a(n){const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=n,window.dispatchEvent(i),!i.defaultPrevented)throw n}return e.then(n=>{for(const i of n||[])i.status==="rejected"&&a(i.reason);return c().catch(a)})};var O={exports:{}},E={};/** +import{r as O,a as U,R as T}from"./vendor-DPfS0z4g.js";const z="modulepreload",A=function(f){return"/"+f},$={},L=function(d,g,x){let e=Promise.resolve();if(g&&g.length>0){document.getElementsByTagName("link");const n=document.querySelector("meta[property=csp-nonce]"),i=(n==null?void 0:n.nonce)||(n==null?void 0:n.getAttribute("nonce"));e=Promise.allSettled(g.map(t=>{if(t=A(t),t in $)return;$[t]=!0;const r=t.endsWith(".css"),o=r?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${t}"]${o}`))return;const s=document.createElement("link");if(s.rel=r?"stylesheet":z,r||(s.as="script"),s.crossOrigin="",s.href=t,i&&s.setAttribute("nonce",i),document.head.appendChild(s),r)return new Promise((p,l)=>{s.addEventListener("load",p),s.addEventListener("error",()=>l(new Error(`Unable to preload CSS for ${t}`)))})}))}function a(n){const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=n,window.dispatchEvent(i),!i.defaultPrevented)throw n}return e.then(n=>{for(const i of n||[])i.status==="rejected"&&a(i.reason);return d().catch(a)})};var j={exports:{}},E={};/** * @license React * react-jsx-runtime.production.min.js * @@ -7,4 +7,4 @@ import{r as $,a as U,R as T}from"./vendor-DPfS0z4g.js";const z="modulepreload",L * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var B=$,N=Symbol.for("react.element"),D=Symbol.for("react.fragment"),F=Object.prototype.hasOwnProperty,Z=B.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,J={key:!0,ref:!0,__self:!0,__source:!0};function j(u,c,h){var g,e={},a=null,n=null;h!==void 0&&(a=""+h),c.key!==void 0&&(a=""+c.key),c.ref!==void 0&&(n=c.ref);for(g in c)F.call(c,g)&&!J.hasOwnProperty(g)&&(e[g]=c[g]);if(u&&u.defaultProps)for(g in c=u.defaultProps,c)e[g]===void 0&&(e[g]=c[g]);return{$$typeof:N,type:u,key:a,ref:n,props:e,_owner:Z.current}}E.Fragment=D;E.jsx=j;E.jsxs=j;O.exports=E;var k=O.exports,I={},C=U;I.createRoot=C.createRoot,I.hydrateRoot=C.hydrateRoot;const W=T.lazy(()=>A(()=>import("./StyleMixerPanel-B66F9prP.js"),__vite__mapDeps([0,1])));function M(u=10){var c,h;(h=(c=window.app)==null?void 0:c.graph)==null||h.setDirtyCanvas(!0,!0),u>1&&requestAnimationFrame(()=>M(u-1))}function H(){return new Promise(u=>{function c(){if(window.app){u();return}const h=setInterval(()=>{window.app&&(clearInterval(h),u())},50);setTimeout(()=>{clearInterval(h),console.error("[ImmacStyleMixer] Timeout waiting for ComfyUI app"),u()},5e3)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",c):c()})}async function q(){if(await H(),!window.app){console.error("[ImmacStyleMixer] ComfyUI app not available");return}window.app.extensionManager.registerSidebarTab({id:"immac-style-mixer",icon:"pi pi-palette",title:"Style Mixer",tooltip:"Immac Style Mixer",type:"custom",render:e=>{const a=document.createElement("div");a.id="immac-style-mixer-root",a.style.position="absolute",a.style.inset="0",a.style.overflow="hidden",e.style.position="relative",e.style.height="100%",e.appendChild(a),I.createRoot(a).render(k.jsx(T.StrictMode,{children:k.jsx($.Suspense,{fallback:k.jsx("div",{style:{padding:"1rem"},children:"Loading…"}),children:k.jsx(W,{})})}))}});const u="/immac_style_mixer/api/data";async function c(){const e=await fetch(u);if(!e.ok){alert(`Backup failed: HTTP ${e.status}`);return}const a=await e.json(),n=new Blob([JSON.stringify(a,null,2)],{type:"application/json"}),i=URL.createObjectURL(n),t=document.createElement("a");t.href=i,t.download=`style_mixer_backup_${new Date().toISOString().slice(0,10)}.json`,t.click(),URL.revokeObjectURL(i)}function h(){const e=document.createElement("a");e.href="/immac_style_mixer/api/backup.zip",e.download=`style_mixer_backup_${new Date().toISOString().slice(0,10)}.zip`,e.click()}function g(){const e=document.createElement("input");e.type="file",e.accept=".json,.zip,application/json,application/zip",e.onchange=()=>{var i;const a=(i=e.files)==null?void 0:i[0];if(!a)return;if(a.name.endsWith(".zip")||a.type==="application/zip")a.arrayBuffer().then(async t=>{var r,o;try{const s=await fetch("/immac_style_mixer/api/restore.zip",{method:"POST",headers:{"Content-Type":"application/zip"},body:t});if(!s.ok){const m=await s.text();throw new Error(`HTTP ${s.status}: ${m.slice(0,300)}`)}const y=await s.json();try{(o=(r=window.app)==null?void 0:r.toast)==null||o.add({severity:"success",summary:"Style Mixer",detail:`ZIP restored: ${y.styles} styles, ${y.mixes} mixes, ${y.images} images. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(s){alert(`Failed to restore ZIP backup: ${s.message}`)}}).catch(t=>alert(`Could not read file: ${t.message}`));else{const t=new FileReader;t.onload=async r=>{var o,s,y;try{const m=JSON.parse((o=r.target)==null?void 0:o.result);if(!Array.isArray(m.styles)||!Array.isArray(m.mixes))throw new Error("Invalid backup: missing styles or mixes arrays.");const _=await fetch(u,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(m)});if(!_.ok)throw new Error(`Save failed: HTTP ${_.status}`);try{(y=(s=window.app)==null?void 0:s.toast)==null||y.add({severity:"success",summary:"Style Mixer",detail:`Backup restored: ${m.styles.length} styles, ${m.mixes.length} mixes. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(m){alert(`Failed to restore backup: ${m.message}`)}},t.readAsText(a)}},e.click()}window.app.registerExtension({name:"ImmacStyleMixer",settings:[{id:"ImmacStyleMixer.BackupRestore",name:"Style Mixer — Backup & Restore",type:(e,a,n)=>{const i=document.createElement("div");i.style.cssText="display:flex;gap:8px;align-items:center";const t=document.createElement("button");t.textContent="⬇ Download backup (JSON)",t.title="Save all styles and mixes to a JSON file",t.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",t.onclick=()=>c().catch(console.error);const r=document.createElement("button");r.textContent="⬇ Download ZIP (with images)",r.title="Save styles, mixes and all images to a ZIP file",r.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",r.onclick=()=>h();const o=document.createElement("button");return o.textContent="⬆ Restore (JSON or ZIP)",o.title="Restore from a JSON or ZIP backup — auto-detected",o.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",o.onclick=()=>g(),i.appendChild(t),i.appendChild(r),i.appendChild(o),i},defaultValue:null}],nodeCreated(e){var b;if(e.comfyClass!=="StyleMixImmacStyleMixer"&&e.comfyClass!=="StylePickImmacStyleMixer")return;const a=e.comfyClass==="StylePickImmacStyleMixer",n=a?"style":"mix",i=(b=e.widgets)==null?void 0:b.find(l=>l.name===n);if(!i)return;const t=document.createElement("img");t.style.cssText="width:100%;height:100%;display:none;object-fit:contain;border-radius:4px";const r=document.createElement("div");r.style.cssText="padding:4px;box-sizing:border-box",r.appendChild(t),r.style.setProperty("--comfy-widget-min-height","0"),r.style.setProperty("--comfy-widget-height","0"),e.addDOMWidget("immac_preview","div",r,{serialize:!1});function o(l,p){var P,f;const x=((P=e.size)==null?void 0:P[0])??300,v=p/l,d=Math.round((x-8)*v),S=Math.max(80,Math.min(d,600));r.style.setProperty("--comfy-widget-min-height",String(S)),r.style.setProperty("--comfy-widget-height",String(S));const w=(f=e.computeSize)==null?void 0:f.call(e);w&&e.setSize([Math.max(e.size[0],w[0]),Math.max(e.size[1],w[1])]),M(10)}function s(){var l,p;r.style.setProperty("--comfy-widget-min-height","0"),r.style.setProperty("--comfy-widget-height","0"),t.style.display="none",(p=(l=window.app)==null?void 0:l.graph)==null||p.setDirtyCanvas(!0)}function y(l){t.onload=()=>{t.style.display="block",o(t.naturalWidth,t.naturalHeight)},t.onerror=()=>{s()},t.src=l}async function m(l){var p;try{const x=await fetch("/immac_style_mixer/api/data");if(!x.ok)return;const v=await x.json();if(a){const d=(v.styles??[]).find(w=>w.name===l);if(!(d!=null&&d.image_filename)){s();return}const S=d.image_updated_at?`&t=${d.image_updated_at}`:"";y(`/view?filename=${encodeURIComponent(d.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${S}`)}else{const d=(v.mixes??[]).find(w=>w.name===l);let S="";if(d!=null&&d.image_filename){const w=d.image_updated_at?`&t=${d.image_updated_at}`:"";S=`/view?filename=${encodeURIComponent(d.image_filename)}&subfolder=immac_style_mixer%2Fmixes&type=input${w}`}else if((p=d==null?void 0:d.styles)!=null&&p.length){const w=Object.fromEntries((v.styles??[]).map(f=>[f.id,f])),P=d.styles.filter(f=>f.enabled!==!1).map(f=>w[f.style_id]).find(f=>f==null?void 0:f.image_filename);if(P){const f=P.image_updated_at?`&t=${P.image_updated_at}`:"";S=`/view?filename=${encodeURIComponent(P.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${f}`}}if(!S){s();return}y(S)}}catch(x){console.error("[ImmacStyleMixer] Preview update failed",x)}}const _=i.callback;i.callback=function(...l){var x;_==null||_.apply(this,l);const p=(x=e.widgets)==null?void 0:x.find(v=>v.name===n);m(String((p==null?void 0:p.value)??""))},e._immacUpdatePreview=l=>{var x;const p=(x=e.widgets)==null?void 0:x.find(v=>v.name===n);m(l??String((p==null?void 0:p.value)??""))},e._immacPreviewScheduled=!0,setTimeout(()=>{var p;if(!e._immacPreviewScheduled)return;const l=(p=e.widgets)==null?void 0:p.find(x=>x.name===n);l!=null&&l.value&&m(String(l.value))},0)},loadedGraphNode(e){var r;if(typeof e._immacUpdatePreview!="function")return;e._immacPreviewScheduled=!1;const a=e.comfyClass==="StylePickImmacStyleMixer",n=a?"style":"mix",i=(r=e.widgets)==null?void 0:r.find(o=>o.name===n),t=String((i==null?void 0:i.value)??"");setTimeout(()=>{t==="(no styles saved)"||t==="(no mixes saved)"?fetch("/immac_style_mixer/api/data").then(o=>o.json()).then(o=>{var m,_;const s=a?o.styles??[]:o.mixes??[],y=(m=e.widgets)==null?void 0:m.find(b=>b.name===n);s.length>0&&y&&(y.value=s[0].name,Array.isArray((_=y.options)==null?void 0:_.values)&&(y.options.values=s.map(b=>b.name))),e._immacUpdatePreview()}).catch(()=>e._immacUpdatePreview()):e._immacUpdatePreview(t)},0)}})}q().catch(console.error);export{k as j}; + */var B=O,N=Symbol.for("react.element"),D=Symbol.for("react.fragment"),F=Object.prototype.hasOwnProperty,Z=B.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,J={key:!0,ref:!0,__self:!0,__source:!0};function M(f,d,g){var x,e={},a=null,n=null;g!==void 0&&(a=""+g),d.key!==void 0&&(a=""+d.key),d.ref!==void 0&&(n=d.ref);for(x in d)F.call(d,x)&&!J.hasOwnProperty(x)&&(e[x]=d[x]);if(f&&f.defaultProps)for(x in d=f.defaultProps,d)e[x]===void 0&&(e[x]=d[x]);return{$$typeof:N,type:f,key:a,ref:n,props:e,_owner:Z.current}}E.Fragment=D;E.jsx=M;E.jsxs=M;j.exports=E;var k=j.exports,I={},C=U;I.createRoot=C.createRoot,I.hydrateRoot=C.hydrateRoot;const W=T.lazy(()=>L(()=>import("./StyleMixerPanel-B66F9prP.js"),__vite__mapDeps([0,1])));function q(){return new Promise(f=>{function d(){if(window.app){f();return}const g=setInterval(()=>{window.app&&(clearInterval(g),f())},50);setTimeout(()=>{clearInterval(g),console.error("[ImmacStyleMixer] Timeout waiting for ComfyUI app"),f()},5e3)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",d):d()})}async function H(){if(await q(),!window.app){console.error("[ImmacStyleMixer] ComfyUI app not available");return}window.app.extensionManager.registerSidebarTab({id:"immac-style-mixer",icon:"pi pi-palette",title:"Style Mixer",tooltip:"Immac Style Mixer",type:"custom",render:e=>{const a=document.createElement("div");a.id="immac-style-mixer-root",a.style.position="absolute",a.style.inset="0",a.style.overflow="hidden",e.style.position="relative",e.style.height="100%",e.appendChild(a),I.createRoot(a).render(k.jsx(T.StrictMode,{children:k.jsx(O.Suspense,{fallback:k.jsx("div",{style:{padding:"1rem"},children:"Loading…"}),children:k.jsx(W,{})})}))}});const f="/immac_style_mixer/api/data";async function d(){const e=await fetch(f);if(!e.ok){alert(`Backup failed: HTTP ${e.status}`);return}const a=await e.json(),n=new Blob([JSON.stringify(a,null,2)],{type:"application/json"}),i=URL.createObjectURL(n),t=document.createElement("a");t.href=i,t.download=`style_mixer_backup_${new Date().toISOString().slice(0,10)}.json`,t.click(),URL.revokeObjectURL(i)}function g(){const e=document.createElement("a");e.href="/immac_style_mixer/api/backup.zip",e.download=`style_mixer_backup_${new Date().toISOString().slice(0,10)}.zip`,e.click()}function x(){const e=document.createElement("input");e.type="file",e.accept=".json,.zip,application/json,application/zip",e.onchange=()=>{var i;const a=(i=e.files)==null?void 0:i[0];if(!a)return;if(a.name.endsWith(".zip")||a.type==="application/zip")a.arrayBuffer().then(async t=>{var r,o;try{const s=await fetch("/immac_style_mixer/api/restore.zip",{method:"POST",headers:{"Content-Type":"application/zip"},body:t});if(!s.ok){const l=await s.text();throw new Error(`HTTP ${s.status}: ${l.slice(0,300)}`)}const p=await s.json();try{(o=(r=window.app)==null?void 0:r.toast)==null||o.add({severity:"success",summary:"Style Mixer",detail:`ZIP restored: ${p.styles} styles, ${p.mixes} mixes, ${p.images} images. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(s){alert(`Failed to restore ZIP backup: ${s.message}`)}}).catch(t=>alert(`Could not read file: ${t.message}`));else{const t=new FileReader;t.onload=async r=>{var o,s,p;try{const l=JSON.parse((o=r.target)==null?void 0:o.result);if(!Array.isArray(l.styles)||!Array.isArray(l.mixes))throw new Error("Invalid backup: missing styles or mixes arrays.");const _=await fetch(f,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l)});if(!_.ok)throw new Error(`Save failed: HTTP ${_.status}`);try{(p=(s=window.app)==null?void 0:s.toast)==null||p.add({severity:"success",summary:"Style Mixer",detail:`Backup restored: ${l.styles.length} styles, ${l.mixes.length} mixes. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(l){alert(`Failed to restore backup: ${l.message}`)}},t.readAsText(a)}},e.click()}window.app.registerExtension({name:"ImmacStyleMixer",settings:[{id:"ImmacStyleMixer.BackupRestore",name:"Style Mixer — Backup & Restore",type:(e,a,n)=>{const i=document.createElement("div");i.style.cssText="display:flex;gap:8px;align-items:center";const t=document.createElement("button");t.textContent="⬇ Download backup (JSON)",t.title="Save all styles and mixes to a JSON file",t.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",t.onclick=()=>d().catch(console.error);const r=document.createElement("button");r.textContent="⬇ Download ZIP (with images)",r.title="Save styles, mixes and all images to a ZIP file",r.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",r.onclick=()=>g();const o=document.createElement("button");return o.textContent="⬆ Restore (JSON or ZIP)",o.title="Restore from a JSON or ZIP backup — auto-detected",o.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",o.onclick=()=>x(),i.appendChild(t),i.appendChild(r),i.appendChild(o),i},defaultValue:null}],nodeCreated(e){var b;if(e.comfyClass!=="StyleMixImmacStyleMixer"&&e.comfyClass!=="StylePickImmacStyleMixer")return;const a=e.comfyClass==="StylePickImmacStyleMixer",n=a?"style":"mix",i=(b=e.widgets)==null?void 0:b.find(c=>c.name===n);if(!i)return;const t=document.createElement("img");t.style.cssText="width:100%;height:100%;display:none;object-fit:contain;border-radius:4px";const r=document.createElement("div");r.style.cssText="padding:4px;box-sizing:border-box",r.appendChild(t),r.style.setProperty("--comfy-widget-min-height","0"),r.style.setProperty("--comfy-widget-height","0"),e.addDOMWidget("immac_preview","div",r,{serialize:!1});function o(c,u){var P,y,R;const h=((P=e.size)==null?void 0:P[0])??300,S=u/c,m=Math.round((h-8)*S),v=Math.max(80,Math.min(m,600));r.style.setProperty("--comfy-widget-min-height",String(v)),r.style.setProperty("--comfy-widget-height",String(v));const w=(y=e.computeSize)==null?void 0:y.call(e);w&&e.setSize([Math.max(e.size[0],w[0]),Math.max(e.size[1],w[1])]),(R=e.graph)==null||R.setDirtyCanvas(!0,!0)}function s(){var c;r.style.setProperty("--comfy-widget-min-height","0"),r.style.setProperty("--comfy-widget-height","0"),t.style.display="none",(c=e.graph)==null||c.setDirtyCanvas(!0)}function p(c){t.onload=()=>{t.style.display="block",o(t.naturalWidth,t.naturalHeight)},t.onerror=()=>{s()},t.src=c}async function l(c){var u;try{const h=await fetch("/immac_style_mixer/api/data");if(!h.ok)return;const S=await h.json();if(a){const m=(S.styles??[]).find(w=>w.name===c);if(!(m!=null&&m.image_filename)){s();return}const v=m.image_updated_at?`&t=${m.image_updated_at}`:"";p(`/view?filename=${encodeURIComponent(m.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${v}`)}else{const m=(S.mixes??[]).find(w=>w.name===c);let v="";if(m!=null&&m.image_filename){const w=m.image_updated_at?`&t=${m.image_updated_at}`:"";v=`/view?filename=${encodeURIComponent(m.image_filename)}&subfolder=immac_style_mixer%2Fmixes&type=input${w}`}else if((u=m==null?void 0:m.styles)!=null&&u.length){const w=Object.fromEntries((S.styles??[]).map(y=>[y.id,y])),P=m.styles.filter(y=>y.enabled!==!1).map(y=>w[y.style_id]).find(y=>y==null?void 0:y.image_filename);if(P){const y=P.image_updated_at?`&t=${P.image_updated_at}`:"";v=`/view?filename=${encodeURIComponent(P.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${y}`}}if(!v){s();return}p(v)}}catch(h){console.error("[ImmacStyleMixer] Preview update failed",h)}}const _=i.callback;i.callback=function(...c){var h;_==null||_.apply(this,c);const u=(h=e.widgets)==null?void 0:h.find(S=>S.name===n);l(String((u==null?void 0:u.value)??""))},e._immacUpdatePreview=c=>{var h;const u=(h=e.widgets)==null?void 0:h.find(S=>S.name===n);l(c??String((u==null?void 0:u.value)??""))},e._immacPreviewScheduled=!0,requestAnimationFrame(()=>{var u;if(!e._immacPreviewScheduled)return;const c=(u=e.widgets)==null?void 0:u.find(h=>h.name===n);c!=null&&c.value&&l(String(c.value))})},loadedGraphNode(e){var r;if(typeof e._immacUpdatePreview!="function")return;e._immacPreviewScheduled=!1;const a=e.comfyClass==="StylePickImmacStyleMixer",n=a?"style":"mix",i=(r=e.widgets)==null?void 0:r.find(o=>o.name===n),t=String((i==null?void 0:i.value)??"");requestAnimationFrame(()=>{t==="(no styles saved)"||t==="(no mixes saved)"?fetch("/immac_style_mixer/api/data").then(o=>o.json()).then(o=>{var l,_;const s=a?o.styles??[]:o.mixes??[],p=(l=e.widgets)==null?void 0:l.find(b=>b.name===n);s.length>0&&p&&(p.value=s[0].name,Array.isArray((_=p.options)==null?void 0:_.values)&&(p.options.values=s.map(b=>b.name))),e._immacUpdatePreview()}).catch(()=>e._immacUpdatePreview()):e._immacUpdatePreview(t)})}})}H().catch(console.error);export{k as j}; diff --git a/ui/src/main.tsx b/ui/src/main.tsx index 598c147..3c96226 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -10,20 +10,6 @@ declare global { const StyleMixerPanel = React.lazy(() => import('./components/StyleMixerPanel')) -/** - * Calls setDirtyCanvas on each of the next `frames` rAF ticks. - * - * On initial page load the LiteGraph render loop may not have started yet when - * loadedGraphNode fires, so a single setDirtyCanvas(true) is silently dropped. - * By re-issuing it across multiple consecutive frames we guarantee at least one - * call lands after the loop is live — without needing to know exactly when that - * happens and without any magic timeout. - */ -function scheduleCanvasDirty(frames = 10): void { - window.app?.graph?.setDirtyCanvas(true, true) - if (frames > 1) requestAnimationFrame(() => scheduleCanvasDirty(frames - 1)) -} - function waitForApp(): Promise { return new Promise((resolve) => { function check() { @@ -247,17 +233,16 @@ async function init(): Promise { container.style.setProperty('--comfy-widget-height', String(clamped)) const s = node.computeSize?.() if (s) node.setSize([Math.max(node.size[0], s[0]), Math.max(node.size[1], s[1])]) - // Fire setDirtyCanvas across the next 10 rAF frames. On page load the - // canvas render loop may not have started yet; repeated calls guarantee - // at least one lands once it does, with zero wasted time on fast loads. - scheduleCanvasDirty(10) + // Mirror ComfyUI's own pattern (useNodeImage.ts): call setDirtyCanvas + // once via the node's graph reference after the image has loaded. + node.graph?.setDirtyCanvas(true, true) } function clearImageSize() { container.style.setProperty('--comfy-widget-min-height', '0') container.style.setProperty('--comfy-widget-height', '0') imgEl.style.display = 'none' - window.app?.graph?.setDirtyCanvas(true) + node.graph?.setDirtyCanvas(true) } function showUrl(url: string) { @@ -326,12 +311,15 @@ async function init(): Promise { // For freshly created nodes (not restores) loadedGraphNode is never called, // so we schedule a deferred first-paint here. We set a flag so that if // loadedGraphNode fires for this node (restore path) it can cancel this. + // Mirror ComfyUI's own pattern (useImageUploadWidget.ts): use + // requestAnimationFrame to defer past the synchronous graph configuration + // so the canvas render loop is alive when setDirtyCanvas fires. node._immacPreviewScheduled = true - setTimeout(() => { + requestAnimationFrame(() => { if (!node._immacPreviewScheduled) return // loadedGraphNode already handled it const w = node.widgets?.find((w: any) => w.name === widgetName) if (w?.value) updatePreview(String(w.value)) - }, 0) + }) }, loadedGraphNode(node: any) { @@ -346,11 +334,10 @@ async function init(): Promise { const comboWidget = node.widgets?.find((w: any) => w.name === widgetName) const currentVal = String(comboWidget?.value ?? '') - // Wait one tick so ComfyUI has finished synchronous widget-value patching - // from the saved workflow, then load the preview. setDirtyCanvas calls - // inside applyImageSize will repeat across rAF frames until the canvas - // render loop is live — no canvas-ready polling needed. - setTimeout(() => { + // Mirror ComfyUI's own pattern (useImageUploadWidget.ts): use + // requestAnimationFrame to defer past the synchronous graph configuration + // phase so the canvas loop is live when setDirtyCanvas fires later. + requestAnimationFrame(() => { // If the node was saved while the list was empty (placeholder value), // fetch fresh data and upgrade to the first real item so preview shows. if (currentVal === '(no styles saved)' || currentVal === '(no mixes saved)') { @@ -373,7 +360,7 @@ async function init(): Promise { // reference inside _immacUpdatePreview being up-to-date. node._immacUpdatePreview(currentVal) } - }, 0) + }) }, }) }