├── README.md └── image-gallery └── imageGallery.js /README.md: -------------------------------------------------------------------------------- 1 | # ComfyUI-Extensions 2 | 3 | Custom javascript extensions for better UX for ComfyUI. 4 | 5 | ## Image Gallery 6 | 7 | Supported nodes: `PreviewImage`, `SaveImage`. Double click on image to open. 8 | 9 | ![tasty-knives-retire-104-198-110-184 loca lt_ (1)](https://user-images.githubusercontent.com/131459485/233774475-de194000-347e-4d75-a9f4-17f84ad735fd.png) 10 | -------------------------------------------------------------------------------- /image-gallery/imageGallery.js: -------------------------------------------------------------------------------- 1 | import { app } from "/scripts/app.js"; 2 | import { $el, ComfyDialog } from "/scripts/ui.js"; 3 | 4 | var styles = ` 5 | .comfy-carousel { 6 | display: none; /* Hidden by default */ 7 | width: 100%; 8 | height: 100%; 9 | position: fixed; 10 | top: 0%; 11 | left: 0%; 12 | justify-content: center; 13 | align-items: center; 14 | background: rgba(0,0,0,0.8); 15 | z-index: 9999; 16 | } 17 | 18 | .comfy-carousel-box { 19 | margin: 0 auto 20px; 20 | text-align: center; 21 | } 22 | 23 | .comfy-carousel-box .slides { 24 | position: relative; 25 | } 26 | 27 | .comfy-carousel-box .slides img { 28 | display: none; 29 | max-height: 90vh; 30 | max-width: 90vw; 31 | margin: auto; 32 | } 33 | 34 | .comfy-carousel-box .slides img.shown { 35 | display: block; 36 | } 37 | 38 | .comfy-carousel-box .prev:before, 39 | .comfy-carousel-box .next:before { 40 | color: #fff; 41 | font-size: 100px; 42 | position: absolute; 43 | top: 35%; 44 | cursor: pointer; 45 | } 46 | 47 | .comfy-carousel-box .prev:before { 48 | content: '❮'; 49 | left: 0; 50 | } 51 | 52 | .comfy-carousel-box .next:before { 53 | content: '❯'; 54 | right: 0; 55 | } 56 | 57 | .comfy-carousel-box .dots img { 58 | height: 32px; 59 | margin: 8px 0 0 8px; 60 | opacity: 0.6; 61 | } 62 | 63 | .comfy-carousel-box .dots img:hover { 64 | opacity: 0.8; 65 | } 66 | 67 | .comfy-carousel-box .dots img.active { 68 | opacity: 1; 69 | } 70 | ` 71 | 72 | var styleSheet = document.createElement("style") 73 | styleSheet.type = "text/css" 74 | styleSheet.innerText = styles 75 | document.head.appendChild(styleSheet) 76 | 77 | class ComfyCarousel extends ComfyDialog { 78 | constructor() { 79 | super(); 80 | this.element.classList.toggle("comfy-modal"); 81 | this.element.classList.toggle("comfy-carousel"); 82 | this.element.addEventListener('click', (e) => { 83 | this.close(); 84 | }); 85 | } 86 | createButtons() { 87 | return []; 88 | } 89 | } 90 | 91 | app.registerExtension({ 92 | name: "Comfy.ImageGallery", 93 | init() { 94 | app.ui.carousel = new ComfyCarousel(); 95 | }, 96 | beforeRegisterNodeDef(nodeType, nodeData) { 97 | if (nodeData.name === "SaveImage" || nodeData.name === "PreviewImage") { 98 | const getActive = () => { 99 | const active = slides.querySelector('.shown'); 100 | const imageIndex = [...slides.childNodes].indexOf(active); 101 | 102 | return [active, imageIndex]; 103 | } 104 | const selectImage = (id) => { 105 | const [_, imageIndex] = getActive(); 106 | if (imageIndex !== -1) { 107 | slides.childNodes[imageIndex].classList.toggle('shown'); 108 | dots.childNodes[imageIndex].classList.toggle('active'); 109 | } 110 | 111 | slides.childNodes[id].classList.toggle('shown'); 112 | dots.childNodes[id].classList.toggle('active'); 113 | } 114 | const slideN = (n) => { 115 | const [_, imageIndex] = getActive(); 116 | 117 | let nth = imageIndex + n; 118 | if (nth < 0) nth = slides.childNodes.length - 1; 119 | else if (nth >= slides.childNodes.length) nth = 0; 120 | 121 | selectImage(nth); 122 | } 123 | const prevSlide = (e) => { 124 | slideN(-1); 125 | e.stopPropagation(); 126 | } 127 | const nextSlide = (e) => { 128 | slideN(1); 129 | e.stopPropagation(); 130 | } 131 | 132 | const slides = $el("div.slides"); 133 | const dots = $el("div.dots"); 134 | const carousel = $el("div.comfy-carousel-box", { }, [ 135 | slides, 136 | dots, 137 | $el("a.prev", { $: (el) => el.addEventListener('click', (e) => prevSlide(e), true), }), 138 | $el("a.next", { $: (el) => el.addEventListener('click', (e) => nextSlide(e), true), }), 139 | ]); 140 | 141 | nodeType.prototype.onMouseUp = function (e, pos, graph) { 142 | // remove all child nodes 143 | slides.innerHTML = ""; 144 | dots.innerHTML = ""; 145 | 146 | if (this.imgs && this.imgs.length) { 147 | for (let imgId in this.imgs) { 148 | slides.append(this.imgs[imgId].cloneNode(true)); 149 | 150 | let dot = this.imgs[imgId].cloneNode(true); 151 | dot.addEventListener('click', (e) => { 152 | selectImage(imgId); 153 | e.stopPropagation(); 154 | }, true); 155 | dots.append(dot); 156 | } 157 | selectImage(this.imageIndex || 0); 158 | app.ui.carousel.show(carousel); 159 | } 160 | } 161 | } 162 | }, 163 | }); 164 | --------------------------------------------------------------------------------