import { app } from "/scripts/app.js"; import { $el } from "/scripts/ui.js"; import { ImageDrawerConfigSetting, setupUiSettings, createDrawerSelectionWidget, setting_bEnabled, setting_bMasterVisibility, setting_DrawerAnchor, createFlyoutHandle, createLabeledSliderRange, options_LabeledSliderRange } from "../common/SettingsManager.js"; import { ImageDrawerComponent, ClassInstanceFactory, imageDrawerComponentManagerInstance } from "./Core/ImageDrawerModule.js"; import { utilitiesInstance } from "../common/Utilities.js"; // Attribution: pythongsssss's Image Feed. So much brilliance in that original script. class ImageDrawerMain extends ImageDrawerComponent { constructor(args) { super(args); this.imageDrawer; this.drawerOptionsFlyout; this.imageDrawerContextToolbar; this.drawerWidthSlider; this.drawerHeightSlider; this.columnSlider; this._minimumDrawerSize = 15; this._maximumDrawerSize = 100; this.setting_ColumnCount = new ImageDrawerConfigSetting("ImageSize", 4); this.setting_DrawerHeight = new ImageDrawerConfigSetting("DrawerHeight", 25); this.setting_DrawerWidth = new ImageDrawerConfigSetting("DrawerWidth", 25); } getColumnCount() { return this.setting_ColumnCount.value; } setColumnCount(value, bSetColumnCountSliderValue = true) { value = utilitiesInstance.clamp(value, 1, value); this.setting_ColumnCount.value = value; this.imageDrawer?.style.setProperty("--column-count", value); this.columnSlider.title = `Controls the number of columns in the drawer (${value} columns).\nClick label to set custom value.`; if (bSetColumnCountSliderValue && this.columnSlider) { this.columnSlider.setValueDirectly(value, false); // Don't clamp to "max" } } getDrawerWidth() { return this.setting_DrawerWidth.value; } setDrawerWidth(value, bSetDrawerWidthSliderValue = true) { value = utilitiesInstance.clamp(value, this._minimumDrawerSize, this._maximumDrawerSize); this.setting_DrawerWidth.value = value; this.imageDrawer?.style.setProperty("--drawer-width", value); this.drawerWidthSlider.title = `Controls the maximum width of the drawer panel (${value}vw)`; if (bSetDrawerWidthSliderValue && this.drawerWidthSlider) { this.drawerWidthSlider.setValueDirectly(value); } } getDrawerHeight() { return this.setting_DrawerHeight.value; } setDrawerHeight(value, bSetDrawerHeightSliderValue = true) { value = utilitiesInstance.clamp(value, this._minimumDrawerSize, this._maximumDrawerSize); this.setting_DrawerHeight.value = value; this.imageDrawer?.style.setProperty("--drawer-height", value); this.drawerHeightSlider.title = `Controls the maximum height of the drawer panel (${value}vh)`; if (bSetDrawerHeightSliderValue && this.drawerHeightSlider) { this.drawerHeightSlider.setValueDirectly(value); } } setDrawerAnchor(value) { setting_DrawerAnchor.value = value; this.imageDrawer.className = `JNodes-image-drawer JNodes-image-drawer--${value}`; } setContextToolbarWidget(widget) { this.imageDrawerContextToolbar.replaceChildren(widget); } setSortingOptions(options) { this.imageDrawerContextToolbar.replaceChildren(widget); } createDrawerOptionsFlyout() { let widthSliderOptions = new options_LabeledSliderRange(); widthSliderOptions.bPrependValueLabel = true; widthSliderOptions.min = this._minimumDrawerSize; widthSliderOptions.max = this._maximumDrawerSize; widthSliderOptions.value = this.getDrawerWidth(); widthSliderOptions.oninput = (e) => { this.setDrawerWidth(e.target.valueAsNumber, false); }; this.drawerWidthSlider = createLabeledSliderRange(widthSliderOptions); this.setDrawerWidth(this.getDrawerWidth()); let heightSliderOptions = new options_LabeledSliderRange(); heightSliderOptions.bPrependValueLabel = true; heightSliderOptions.min = this._minimumDrawerSize; heightSliderOptions.max = this._maximumDrawerSize; heightSliderOptions.value = this.getDrawerHeight(); heightSliderOptions.oninput = (e) => { this.setDrawerHeight(e.target.valueAsNumber, false); }; this.drawerHeightSlider = createLabeledSliderRange(heightSliderOptions); this.setDrawerHeight(this.getDrawerHeight()); let columnSliderOptions = new options_LabeledSliderRange(); columnSliderOptions.bPrependValueLabel = true; columnSliderOptions.min = 1; columnSliderOptions.max = 10; columnSliderOptions.value = this.getColumnCount(); columnSliderOptions.oninput = (e) => { this.setColumnCount(e.target.valueAsNumber, false); }; this.columnSlider = createLabeledSliderRange(columnSliderOptions); this.setColumnCount(this.getColumnCount()); let flyout = createFlyoutHandle("👁️"); this.drawerOptionsFlyout = flyout.handle; flyout.menu.appendChild( $el("tr.size-control.drawer-width-control", [ $el('td', [$el("span", { textContent: 'Drawer Width', style: { cursor: "pointer", textDecoration: "underline", }, onclick: () => { const value = +prompt("Enter custom drawer width", this.getDrawerWidth()); if (!isNaN(value)) { this.setDrawerWidth(value); } }, })]), $el('td', [this.drawerWidthSlider]) ])); flyout.menu.appendChild( $el("tr.size-control.drawer-height-control", [ $el('td', [$el("span", { textContent: 'Drawer Height', style: { cursor: "pointer", textDecoration: "underline", }, onclick: () => { const value = +prompt("Enter custom drawer height", this.getDrawerHeight()); if (!isNaN(value)) { this.setDrawerHeight(value); } }, })]), $el('td', [this.drawerHeightSlider]) ])); flyout.menu.appendChild( $el("tr.size-control.column-count-control", [ $el('td', [$el("a", { textContent: "Column count", style: { cursor: "pointer", textDecoration: "underline", }, onclick: () => { const value = +prompt("Enter custom column count", this.getColumnCount()); if (!isNaN(value)) { this.setColumnCount(value); } }, })]), $el('td', [this.columnSlider]) ])); flyout.menu.appendChild( // Anchor Select $el("tr.drawer-anchor-control", [ $el('td', [$el("span", { textContent: "Image Drawer Anchor:", })]), $el('td', [createDrawerSelectionWidget((e) => { this.setDrawerAnchor(e.target.value); })]) ])); }; async setup() { const imageDrawerListInstance = imageDrawerComponentManagerInstance.getComponentByName("ImageDrawerList"); const imageDrawerSearchInstance = imageDrawerComponentManagerInstance.getComponentByName("ImageDrawerSearch"); setupUiSettings((e) => { this.setDrawerAnchor(e.target.value); }); if (!setting_bEnabled.value) { return; } // A button shown in the comfy modal to show the drawer after it's been hidden const showButton = $el("button.comfyui-button.comfy-settings-btn", { textContent: "🖼️", title: "Display JNodes Image Drawer" }); utilitiesInstance.setElementVisible(showButton, !setting_bMasterVisibility.value); showButton.addEventListener("click", () => { utilitiesInstance.setElementVisible(this.imageDrawer, true, "flex"); utilitiesInstance.setElementVisible(showButton, false); setting_bMasterVisibility.value = true; }); const comfyMenuBarPush = document.querySelector(".comfyui-menu-push"); if (comfyMenuBarPush) { comfyMenuBarPush.before(showButton); // insert Show after workflow menu } else { document.querySelector(".comfy-settings-btn").after(showButton); // insert Show after Settings } // A button shown in the comfy modal to show the drawer after it's been hidden // { // const baseAutoQueueIntervalButtonTooltipText = "Auto Queue Interval"; // function startAutomaticQueue(intervalInMs) { // if (!isNaN(intervalInMs) && intervalInMs > 1) { // stopAutomaticQueue; // Stop existing auto mode // timerQueueButton.lastAutoQueueInterval = intervalInMs; // timerQueueButton.style.backgroundColor = "red"; // timerQueueButton.title = `${baseAutoQueueIntervalButtonTooltipText} (currently ${intervalInMs} ms)`; // timerQueueButton.timer = setInterval(() => { // app.queuePrompt(0, 1); // }, intervalInMs); // } // } // function stopAutomaticQueue() { // if (timerQueueButton?.timer) { // clearInterval(timerQueueButton.timer); // timerQueueButton.timer = 0; // timerQueueButton.style.backgroundColor = ""; // timerQueueButton.title = baseAutoQueueIntervalButtonTooltipText; // return true; // } // return false; // } // const timerQueueButton = utilitiesInstance.createLongPressableButton( // { // textContent: "⏲️", // title: baseAutoQueueIntervalButtonTooltipText // }, // async () => { // Regular click // if (!stopAutomaticQueue()) { // startAutomaticQueue(timerQueueButton.lastAutoQueueInterval); // } // }, // async () => { // Long press // const value = Math.abs(+prompt("Set automatic queue interval in milliseconds:", timerQueueButton.lastAutoQueueInterval)); // startAutomaticQueue(value); // }, // ["JNodes-auto-queue-interval-btn"]); // timerQueueButton.lastAutoQueueInterval = 60000; // document.querySelector(".comfy-queue-btn").after(timerQueueButton); // insert Show after Settings // } // Remove the drawer widget from view, can be re-opened with showButton const hideButton = $el("button.JNodes-image-drawer-btn.hide-btn", { textContent: "❌", title: "Hide the drawer. Show it again by clicking the image icon on the Comfy menu.", onclick: () => { const imageDrawerListSortingInstance = imageDrawerComponentManagerInstance.getComponentByName("ImageDrawerListSorting"); imageDrawerListSortingInstance.stopAutomaticShuffle(); utilitiesInstance.setElementVisible(this.imageDrawer, false); utilitiesInstance.setElementVisible(showButton, true); setting_bMasterVisibility.value = false; }, style: { width: "fit-content", padding: '3px', }, }); // The main drawer widget const drawerParent = document.querySelector(".comfyui-body-bottom") || document.body; this.imageDrawer = $el("div.JNodes-image-drawer", { parent: drawerParent }); // Initialize Anchor const drawerStartingAnchor = setting_DrawerAnchor.value; this.imageDrawer.className = `JNodes-image-drawer JNodes-image-drawer--${drawerStartingAnchor}`; // Resizing / View options this.createDrawerOptionsFlyout(); const syncButton = $el("button.JNodes-image-drawer-btn.sync-videos-btn", { textContent: "🔄", title: "Sync playback for all currently loaded videos", onclick: () => { for (const video of document.querySelectorAll("video")) { if (video.readyState > 0) { video.currentTime = 0; } } }, style: { width: "fit-content", padding: '3px', }, }); const LeftAffinedControlsGroup = $el("div.JNodes-image-drawer-left-affined-basic-controls-group", { style: { display: "flex", justifycontent: "flex-start", } }, [hideButton, this.drawerOptionsFlyout, syncButton]); const CollapseExpandButton = $el("button.JNodes-image-drawer-menu-collapsible-area-toggle-button", { title: "Toggle the visibility of the controls below", textContent: "▼", style: { background: "none", border: "none", padding: "0px 6px", color: "white", fontWeight: "bolder", cursor: 'pointer', } }); CollapseExpandButton.classList.add("JNodes-interactive-container"); // Add click event listener to toggle button CollapseExpandButton.addEventListener('click', function () { const bIsCurrentlyCollapsed = CollapsibleArea.style.display === "none"; // Toggle content display CollapsibleArea.style.display = bIsCurrentlyCollapsed ? 'block' : 'none'; // Toggle button arrow orientation CollapseExpandButton.textContent = bIsCurrentlyCollapsed ? "▼" : "▶"; }); const batchFavouriteManagerInstance = imageDrawerComponentManagerInstance.getComponentByName("BatchFavouriteManager", this.drawerInstanceIndex); const batchFavouriteManagerWidget = batchFavouriteManagerInstance.makeWidget(); const batchDeletionManagerInstance = imageDrawerComponentManagerInstance.getComponentByName("BatchDeletionManager", this.drawerInstanceIndex); const batchDeletionManagerWidget = batchDeletionManagerInstance.makeWidget(); const batchRemovalManagerInstance = imageDrawerComponentManagerInstance.getComponentByName("BatchRemovalManager", this.drawerInstanceIndex); const batchRemovalManagerWidget = batchRemovalManagerInstance.makeWidget(); const batchSelectionManagerInstance = imageDrawerComponentManagerInstance.getComponentByName("BatchSelectionManager", this.drawerInstanceIndex); const batchSelectionManagerWidget = batchSelectionManagerInstance.makeWidget(); const RightAffinedControlsGroup = $el("div.JNodes-image-drawer-right-affined-basic-controls-group", { style: { display: "flex", justifycontent: "flex-end", } }, [batchFavouriteManagerWidget.container, batchDeletionManagerWidget.container, batchRemovalManagerWidget.container, batchSelectionManagerWidget.container, CollapseExpandButton]); const BasicControlsGroup = $el("div.JNodes-image-drawer-basic-controls-group", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", } }, [LeftAffinedControlsGroup, RightAffinedControlsGroup]); this.drawerOptionsFlyout.determineTransformLayout(); // Call immediately after parenting to avoid first caling being from the center this.imageDrawerContextToolbar = $el("div.JNodes-image-drawer-context-toolbar"); function makeDropDownComboContainer() { // Context and sorting Dropdowns const imageDrawerListSortingInstance = imageDrawerComponentManagerInstance.getComponentByName("ImageDrawerListSorting"); const sortingWidget = imageDrawerListSortingInstance.makeSortingWidget(); // Sorting first since contexts act upon sorting sortingWidget.style.width = '50%'; const imageDrawerContextSelectorInstance = imageDrawerComponentManagerInstance.getComponentByName("ImageDrawerContextSelector"); const contextSelector = imageDrawerContextSelectorInstance.createContextSelector(); contextSelector.style.width = '50%'; const DropDownComboContainer = $el("div.JNodes-context-sorting-menu", { style: { display: "flex", flexDirection: "row" } }, [contextSelector, sortingWidget]); return DropDownComboContainer; } const SearchBarGroup = $el("div.JNodes-search-bar-group", { style: { width: '100%', display: 'flex', flexDirection: 'row', } }, [ imageDrawerSearchInstance.createSearchBar() ]); const CollapsibleArea = $el("div.JNodes-image-drawer-menu-collapsible-area", { style: { transformOrigin: "50% 0%", flex: "0 1 auto" } }, [ makeDropDownComboContainer(), SearchBarGroup, this.imageDrawerContextToolbar, ]); const ImageDrawerMenu = $el("div.JNodes-image-drawer-menu", { style: { minHeight: 'fit-content', minWidth: 'fit-content', position: 'relative', flex: '0 1 min-content', display: 'flex', gap: '3px', padding: '3px', justifyContent: 'flex-start', }, }, [ BasicControlsGroup, CollapsibleArea ]); this.imageDrawer.append(ImageDrawerMenu, imageDrawerListInstance.getImageListElement()); // If not supposed to be visible on startup, close it if (!setting_bMasterVisibility.value) { hideButton.onclick(); } } } const factoryInstance = new ClassInstanceFactory(ImageDrawerMain);