stable-diffusion-webui-state/javascript/state.ext.control-net.js

385 lines
14 KiB
JavaScript

window.state = window.state || {};
window.state.extensions = window.state.extensions || {};
state = window.state;
function ControlNetTabContext(tabName, container) {
this.tabName = tabName;
this.container = container;
this.store = new state.Store(`ext-control-net-${this.tabName}`);
this.tabElements = [];
this.cnTabs = [];
// Try multiple selectors for compatibility with different Gradio/Forge versions
let tabs = this.container.querySelectorAll(':scope > div > div > .tabs > .tabitem');
if (!tabs.length) {
tabs = this.container.querySelectorAll('.tabitem');
}
if (!tabs.length) {
tabs = this.container.querySelectorAll('[id*="tabitem"]');
}
if (!tabs.length) {
tabs = this.container.querySelectorAll('.tabs > div[role="tabpanel"]');
}
if (tabs.length) {
tabs.forEach((tabContainer, i) => {
this.cnTabs.push({
container: tabContainer,
store: new state.Store(`ext-control-net-${this.tabName}-${i}`)
});
});
} else {
this.cnTabs.push({
container: container,
store: new state.Store(`ext-control-net-${this.tabName}-0`)
});
}
}
state.extensions['control-net'] = (function () {
let contexts = [];
function handleToggle() {
const id = 'toggled';
contexts.forEach(context => {
// Try multiple selectors for compatibility
let elements = context.container.querySelectorAll(`:scope > .label-wrap`);
if (!elements.length) {
elements = context.container.querySelectorAll('.label-wrap');
}
elements.forEach(element => {
if (context.store.get(id) === 'true') {
state.utils.clickToggleMenu(element);
load();
}
element.addEventListener('click', function () {
// Check for open state using multiple methods for compatibility
let isOpen = this.classList.contains('open') ||
this.parentNode.classList.contains('open') ||
state.utils.isAccordionOpen(this.parentNode);
context.store.set(id, isOpen);
load();
});
});
});
}
function bindTabEvents() {
contexts.forEach(context => {
// Try multiple selectors for compatibility
let tabs = context.container.querySelectorAll(':scope > div > div > .tabs > div > button');
if (!tabs.length) {
tabs = context.container.querySelectorAll('.tabs .tab-nav button');
}
if (!tabs.length) {
tabs = context.container.querySelectorAll('.tabs > div > button');
}
if (!tabs.length) {
tabs = context.container.querySelectorAll('button[role="tab"]');
}
function onTabClick() {
context.store.set('tab', this.textContent);
bindTabEvents();
}
tabs.forEach(tab => { // dirty hack here
tab.removeEventListener('click', onTabClick);
tab.addEventListener('click', onTabClick);
});
context.tabElements = tabs;
});
}
function handleTabs() {
bindTabEvents();
contexts.forEach(context => {
let value = context.store.get('tab');
if (value) {
for (var i = 0; i < context.tabElements.length; i++) {
if (context.tabElements[i].textContent === value) {
state.utils.triggerEvent(context.tabElements[i], 'click');
break;
}
}
}
});
}
function handleContext(action) {
contexts.forEach(context => {
context.cnTabs.forEach(({ container, store }) => {
action(container, store);
});
});
}
// Helper to find checkbox label text
function getCheckboxLabel(checkbox) {
// Try nextElementSibling (common pattern)
if (checkbox.nextElementSibling && checkbox.nextElementSibling.textContent) {
return checkbox.nextElementSibling.textContent;
}
// Try parent label
let parentLabel = checkbox.closest('label');
if (parentLabel) {
// Get text excluding the checkbox itself
let clone = parentLabel.cloneNode(true);
let input = clone.querySelector('input');
if (input) input.remove();
if (clone.textContent && clone.textContent.trim()) {
return clone.textContent.trim();
}
}
// Try aria-label or title
if (checkbox.getAttribute('aria-label')) return checkbox.getAttribute('aria-label');
if (checkbox.title) return checkbox.title;
return null;
}
// Helper to find select/dropdown label text
function getSelectLabel(select) {
// Try label inside the select container
let label = select.querySelector('label');
if (label) {
if (label.firstChild && label.firstChild.textContent) {
return label.firstChild.textContent;
}
if (label.textContent) return label.textContent;
}
// Try span with label class
let span = select.querySelector('span[data-testid="block-label"], span[class*="label"]');
if (span && span.textContent) return span.textContent;
// Try previous sibling
if (select.previousElementSibling) {
let prevLabel = select.previousElementSibling.querySelector('label, span');
if (prevLabel && prevLabel.textContent) return prevLabel.textContent;
}
return null;
}
// Helper to find textarea label text
function getTextareaLabel(textarea) {
// Try previousElementSibling
if (textarea.previousElementSibling && textarea.previousElementSibling.textContent) {
return textarea.previousElementSibling.textContent;
}
// Try parent container for label
let parent = textarea.closest('.gradio-textbox, [class*="textbox"]');
if (parent) {
let label = parent.querySelector('label, span[data-testid="block-label"]');
if (label && label.textContent) return label.textContent;
}
// Try aria-label or placeholder
if (textarea.getAttribute('aria-label')) return textarea.getAttribute('aria-label');
if (textarea.placeholder) return textarea.placeholder;
return null;
}
function handleCheckboxes() {
handleContext((container, store) => {
let checkboxes = container.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(function (checkbox, idx) {
let labelText = getCheckboxLabel(checkbox);
// Use index-based fallback if no label found
let id = labelText ? state.utils.txtToId(labelText) : `checkbox-${idx}`;
let value = store.get(id);
if (value) {
state.utils.setValue(checkbox, value, 'change');
}
checkbox.addEventListener('change', function () {
store.set(id, this.checked);
});
});
});
}
function handleSelects() {
handleContext((container, store) => {
// Use compatibility helper to find dropdowns
let dropdowns = state.utils.findDropdowns(container);
dropdowns.forEach(function (select, idx) {
let labelText = getSelectLabel(select);
// Use index-based fallback if no label found
let id = labelText ? state.utils.txtToId(labelText) : `select-${idx}`;
let value = store.get(id);
state.utils.handleSelect(select, id, store);
if (id === 'preprocessor' && value && value.toLowerCase() !== 'none') {
state.utils.onNextUiUpdates(handleSliders); // update new sliders if needed
}
});
});
}
// Helper to find slider label text with multiple fallback methods
function getSliderLabel(slider) {
// Try previousElementSibling first (old structure)
if (slider.previousElementSibling) {
let label = slider.previousElementSibling.querySelector('label span');
if (label && label.textContent) return label.textContent;
// Try just the label
label = slider.previousElementSibling.querySelector('label');
if (label && label.textContent) return label.textContent;
// Try span directly
label = slider.previousElementSibling.querySelector('span');
if (label && label.textContent) return label.textContent;
}
// Try parent container for label (Forge/Gradio 4.x structure)
let parent = slider.closest('.gradio-slider, .slider, [class*="slider"]');
if (parent) {
let label = parent.querySelector('label span, label, span[data-testid="block-label"]');
if (label && label.textContent) return label.textContent;
}
// Try looking for label in parent's previous sibling
if (slider.parentElement && slider.parentElement.previousElementSibling) {
let label = slider.parentElement.previousElementSibling.querySelector('span, label');
if (label && label.textContent) return label.textContent;
}
return null;
}
// Helper to find fieldset/radio label text
function getFieldsetLabel(fieldset) {
// Try firstChild.nextElementSibling (old structure)
if (fieldset.firstChild && fieldset.firstChild.nextElementSibling) {
let label = fieldset.firstChild.nextElementSibling;
if (label && label.textContent) return label.textContent;
}
// Try legend element
let legend = fieldset.querySelector('legend');
if (legend && legend.textContent) return legend.textContent;
// Try label or span
let label = fieldset.querySelector('label, span[class*="label"]');
if (label && label.textContent) return label.textContent;
return null;
}
function handleSliders() {
handleContext((container, store) => {
let sliders = container.querySelectorAll('input[type="range"]');
sliders.forEach(function (slider, idx) {
let labelText = getSliderLabel(slider);
// Use index-based fallback if no label found
let id = labelText ? state.utils.txtToId(labelText) : `slider-${idx}`;
let value = store.get(id);
if (value) {
state.utils.setValue(slider, value, 'change');
}
slider.addEventListener('change', function () {
store.set(id, this.value);
});
});
});
}
function handleRadioButtons() {
handleContext((container, store) => {
let fieldsets = container.querySelectorAll('fieldset');
fieldsets.forEach(function (fieldset, idx) {
let radios = fieldset.querySelectorAll('input[type="radio"]');
let labelText = getFieldsetLabel(fieldset);
// Use index-based fallback if no label found
let id = labelText ? state.utils.txtToId(labelText) : `radio-${idx}`;
let value = store.get(id);
if (value) {
radios.forEach(function (radio) {
state.utils.setValue(radio, value, 'change');
});
}
radios.forEach(function (radio) {
radio.addEventListener('change', function () {
store.set(id, this.value);
});
});
});
});
}
function handleTextareas() {
handleContext((container, store) => {
let textareas = container.querySelectorAll('textarea');
textareas.forEach(function (textarea, idx) {
let labelText = getTextareaLabel(textarea);
// Use index-based fallback if no label found
let id = labelText ? state.utils.txtToId(labelText) : `textarea-${idx}`;
let value = store.get(id);
if (value) {
state.utils.setValue(textarea, value, 'change');
}
textarea.addEventListener('change', function () {
store.set(id, this.value);
});
});
});
}
function load() {
setTimeout(function () {
handleTabs();
handleCheckboxes();
handleSelects();
handleSliders();
handleRadioButtons();
handleTextareas();
}, 500);
}
function init() {
// Try multiple selectors for ControlNet container (Forge vs A1111 compatibility)
let elements = gradioApp().querySelectorAll('#controlnet');
if (!elements.length) {
elements = gradioApp().querySelectorAll('[id*="controlnet"]');
}
if (!elements.length) {
elements = gradioApp().querySelectorAll('#txt2img_controlnet, #img2img_controlnet');
}
// Forge built-in ControlNet uses different IDs
if (!elements.length) {
elements = gradioApp().querySelectorAll('[id*="forge_controlnet"], [id*="sd_forge_controlnet"]');
}
if (!elements.length) {
state.logging.log('ControlNet extension not found');
return;
}
// Handle both single container and separate txt2img/img2img containers
if (elements.length >= 2) {
contexts[0] = new ControlNetTabContext('txt2img', elements[0]);
contexts[1] = new ControlNetTabContext('img2img', elements[1]);
} else if (elements.length === 1) {
// Single container mode
contexts[0] = new ControlNetTabContext('main', elements[0]);
}
handleToggle();
load();
}
return { init };
}());