/** * File: imageFeed.js * Project: comfy_mtb * Author: Mel Massadian * * Copyright (c) 2023 Mel Massadian * */ // forked from pysssss's imageFeed.js import { api } from '../../scripts/api.js' import { app } from '../../scripts/app.js' import { LocalStorageManager } from './comfy_shared.js' const styles = { lighbox: { position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh', background: 'rgba(0,0,0,0.5)', display: 'none', justifyContent: 'center', alignItems: 'center', zIndex: 999, }, lightboxBtn: (extra) => ({ position: 'absolute', top: '50%', background: 'none', border: 'none', color: '#fff', zIndex: 1000, fontSize: '30px', cursor: 'pointer', pointerEvents: 'auto', ...extra, }), img_list: { minHeight: '30px', maxHeight: '300px', width: '100vw', position: 'absolute', bottom: 0, zIndex: 10, background: '#333', overflow: 'auto', }, } let currentImageIndex = 0 const imageUrls = [] let image_menu = null const storage = new LocalStorageManager('mtb') let activated = storage.get('image_feed', false) app.registerExtension({ name: 'mtb.ImageFeed', setup: () => { app.ui.settings.addSetting({ id: 'mtb.Main.image-feed-enabled', category: ['mtb', 'Main', 'image-feed-enabled'], name: 'Enable Image Feed', type: 'boolean', defaultValue: false, attrs: { style: { fontFamily: 'monospace', }, }, async onChange(value) { storage.set('image_feed', value) activated = value }, }) }, init: async () => { if (!activated) { return } const pythongossFeed = app.extensions.find( (e) => e.name === 'pysssss.ImageFeed', ) if (pythongossFeed) { console.warn( "[mtb] - Aborting the loading of mtb's imageFeed in favor of pysssss.ImageFeed", ) activated = false // just in case other methods are added later on return } // - HTML & CSS //- lightbox const lightboxContainer = document.createElement('div') Object.assign(lightboxContainer.style, styles.lighbox) const lightboxImage = document.createElement('img') Object.assign(lightboxImage.style, { maxHeight: '100%', maxWidth: '100%', borderRadius: '5px', }) // previous and next buttons const lightboxPrevBtn = document.createElement('button') const lightboxNextBtn = document.createElement('button') lightboxPrevBtn.textContent = '❮' lightboxNextBtn.textContent = '❯' Object.assign(lightboxPrevBtn.style, styles.lightboxBtn({ left: '0%' })) Object.assign(lightboxNextBtn.style, styles.lightboxBtn({ right: '0%' })) // close button const lightboxCloseBtn = document.createElement('button') Object.assign( lightboxCloseBtn.style, styles.lightboxBtn({ right: '0', top: '0' }), ) lightboxCloseBtn.textContent = '❌' const lightboxButtons = document.createElement('div') Object.assign(lightboxButtons.style, { position: 'absolute', top: '0%', right: '0%', // transform: "translate(50%, -50%)", height: '100%', width: '100%', background: 'none', border: 'none', color: '#fff', fontSize: '30px', cursor: 'pointer', pointerEvents: 'none', }) lightboxButtons.append(lightboxPrevBtn, lightboxNextBtn, lightboxCloseBtn) lightboxContainer.append(lightboxButtons, lightboxImage) //- image list const imageListContainer = document.createElement('div') Object.assign(imageListContainer.style, styles.img_list) const createImgListBtn = (text, style) => { const btn = document.createElement('button') btn.type = 'button' btn.textContent = text Object.assign(btn.style, { ...style, border: 'none', color: '#fff', background: 'none', height: '20px', cursor: 'pointer', position: 'absolute', top: '5px', fontSize: '12px', lineHeight: '12px', }) imageListContainer.append(btn) return btn } const showBtn = document.createElement('button') const closeBtn = createImgListBtn('❌', { width: '20px', textIndent: '-4px', right: '5px', }) const loadButton = createImgListBtn('Load Session History', { right: '90px', }) const clearButton = createImgListBtn('Clear', { right: '30px', }) //- tools popup button showBtn.classList.add('comfy-settings-btn') Object.assign(showBtn.style, { right: '16px', cursor: 'pointer', display: 'none', }) //- append to DOM document.body.append(imageListContainer) showBtn.textContent = '🖼' showBtn.onclick = () => { imageListContainer.style.display = 'block' showBtn.style.display = 'none' } document.querySelector('.comfy-settings-btn').after(showBtn) document.querySelector('.comfy-settings-btn').after(lightboxContainer) // for (const { output } of history) { // if (output?.images) { // for (const src of output.images) { // const img = document.createElement("img"); // const but = document.createElement("button"); //- callbacks closeBtn.onclick = () => { imageListContainer.style.display = 'none' showBtn.style.display = 'unset' } clearButton.onclick = () => { imageListContainer.replaceChildren(closeBtn, clearButton, loadButton) } lightboxNextBtn.onclick = () => { currentImageIndex = (currentImageIndex + 1) % imageUrls.length const imageUrl = imageUrls[currentImageIndex] lightboxImage.src = imageUrl } // Modify the lightboxPrevBtn onclick callback lightboxPrevBtn.onclick = () => { currentImageIndex = (currentImageIndex - 1 + imageUrls.length) % imageUrls.length const imageUrl = imageUrls[currentImageIndex] lightboxImage.src = imageUrl } lightboxCloseBtn.onclick = () => { lightboxContainer.style.display = 'none' } lightboxImage.onclick = lightboxNextBtn.onclick /** * This is the function that creates the image buttons for the image list * They are wrapped in a button so that they can be clicked and open * the image in the lightbox. * @param {*} src */ const createImageBtn = (src) => { console.debug(`making image ${src.filename}`) const img = document.createElement('img') const but = document.createElement('button') Object.assign(but.style, { height: '120px', width: '120px', border: 'none', padding: 0, margin: 0, }) Object.assign(img.style, { width: '100%', height: '100%', objectFit: 'cover', }) img.src = `/view?filename=${encodeURIComponent(src.filename)}&type=${ src.type }&subfolder=${encodeURIComponent(src.subfolder)}` imageUrls.push(img.src) console.debug(img.src) img.onload = () => { but.style.width = `${120 * (img.naturalWidth / img.naturalHeight)}px` } but.onclick = () => { lightboxContainer.style.display = 'flex' // add the same image to the lightbox lightboxImage.src = img.src // lighboxContainer.replaceChildren(lightboxButtons, img); } // add right click menu but.addEventListener('contextmenu', (e) => { e.preventDefault() if (image_menu) { image_menu.remove() } image_menu = document.createElement('div') Object.assign(image_menu.style, { position: 'absolute', top: `${e.clientY}px`, left: `${e.clientX}px`, background: '#333', color: '#fff', padding: '5px', borderRadius: '5px', zIndex: 999, }) const load_img = document.createElement('button') load_img.textContent = 'Load' load_img.onclick = () => { app.handleFile(img.src) } image_menu.appendChild(load_img) document.body.appendChild(image_menu) }) but.append(img) imageListContainer.prepend(but) } loadButton.onclick = async () => { const all_history = await api.getHistory() for (const history of all_history.History) { if (history.outputs) { for (const key of Object.keys(history.outputs)) { console.debug(key) if (history.outputs[key].images) { for (const im of history.outputs[key].images) { console.debug(im) createImageBtn(im) } } } // for (const src of outputs.outputs.images) { // console.debug(src) // makeImage(`${src.subfolder}/${src.filename}`) // } } } } ///////------- // const all_history = await api.getHistory() // for (const history of all_history.History) { // if (history.outputs) { // for (const key of Object.keys(history.outputs)) { // for (const im of history.outputs[key].images) { // makeImage(im) // } // } // // for (const src of outputs.outputs.images) { // // console.debug(src) // // makeImage(`${src.subfolder}/${src.filename}`) // // } // } // } //- Hook into the API api.addEventListener('executed', ({ detail }) => { if (detail?.output?.images) { for (const src of detail.output.images) { console.debug(`Adding ${src} to image feed`) createImageBtn(src) } } }) }, })