From f55d656fdd28123499f29dbe1514b8350eef0a1b Mon Sep 17 00:00:00 2001 From: "ilian.iliev" Date: Tue, 21 Mar 2023 20:05:38 +0200 Subject: [PATCH 1/9] Core logic refactoring --- javascript/state.app.js | 4 + javascript/state.constants.js | 5 + javascript/state.core.js | 216 ++++++++++++++++++++++++++++ javascript/state.ext.control-net.js | 43 ++++++ javascript/state.store.js | 30 ++++ javascript/state.utils.js | 11 ++ 6 files changed, 309 insertions(+) create mode 100644 javascript/state.app.js create mode 100644 javascript/state.constants.js create mode 100644 javascript/state.core.js create mode 100644 javascript/state.ext.control-net.js create mode 100644 javascript/state.store.js create mode 100644 javascript/state.utils.js diff --git a/javascript/state.app.js b/javascript/state.app.js new file mode 100644 index 0000000..2933141 --- /dev/null +++ b/javascript/state.app.js @@ -0,0 +1,4 @@ + +document.addEventListener('DOMContentLoaded', function() { + onUiLoaded(state.core.init); +}); diff --git a/javascript/state.constants.js b/javascript/state.constants.js new file mode 100644 index 0000000..bd737ce --- /dev/null +++ b/javascript/state.constants.js @@ -0,0 +1,5 @@ +window.state = window.state || {}; + +state.constants = { + LS_PREFIX: 'store-' +}; \ No newline at end of file diff --git a/javascript/state.core.js b/javascript/state.core.js new file mode 100644 index 0000000..74488b2 --- /dev/null +++ b/javascript/state.core.js @@ -0,0 +1,216 @@ +window.state = window.state || {}; + +state.core = (function () { + + const TABS = ['txt2img', 'img2img']; + const ELEMENTS = { + 'prompt': 'prompt', + 'negative_prompt': 'neg_prompt', + 'sampling': 'sampling', + 'sampling_steps': 'steps', + 'restore_faces': 'restore_faces', + 'tiling': 'tiling', + 'hires_fix': 'enable_hr', + 'hires_upscaler': 'hr_upscaler', + 'hires_steps': 'hires_steps', + 'hires_scale': 'hr_scale', + 'hires_resize_x': 'hr_resize_x', + 'hires_resize_y': 'hr_resize_y', + 'hires_denoising_strength': 'denoising_strength', + 'width': 'width', + 'height': 'height', + 'batch_count': 'batch_count', + 'batch_size': 'batch_size', + 'cfg_scale': 'cfg_scale', + 'denoising_strength': 'denoising_strength', + 'seed': 'seed' + }; + + const ELEMENTS_WITHOUT_PREFIX = { + 'resize_mode': 'resize_mode', + }; + + let store = null; + + function hasSetting(id, tab) { + const suffix = tab ? `_${tab}` : ''; + return this[`state${suffix}`] && this[`state${suffix}`].indexOf(id) > -1; + } + + function init() { + fetch('/state/config.json?_=' + (+new Date())) + .then(response => response.json()) + .then(config => { + try { + config.hasSetting = hasSetting + load(config); + } catch (error) { + console.error('[state]: Error:', error); + } + }) + .catch(error => console.error('[state]: Error getting JSON file:', error)); + } + + function load(config) { + + store = new state.Store(); + + loadUI(); + restoreTabs(config); + + for (const [settingId, element] of Object.entries(ELEMENTS)) { + TABS.forEach(tab => { + if (config.hasSetting(settingId, tab)) { + handleSavedInput(`${tab}_${element}`); + } + }); + } + + for (const [settingId, element] of Object.entries(ELEMENTS_WITHOUT_PREFIX)) { + TABS.forEach(tab => { + if (config.hasSetting(settingId, tab)) { + handleSavedInput(`${element}`); + } + }); + } + + handleExtensions(config); + } + + function storeTab() { + store.set('tab', this.textContent); + bindTabEvents(); + } + + function bindTabEvents() { + const tabs = gradioApp().querySelectorAll('#tabs > div:first-child button'); + tabs.forEach(tab => { // dirty hack here + tab.removeEventListener('click', storeTab); + tab.addEventListener('click', storeTab); + }); + return tabs; + } + + function loadUI() { + + let toolbar = document.createElement("div"); + toolbar.style.minWidth = 0; + toolbar.className = "gr-box relative w-full border-solid border border-gray-200 gr-padded"; + + let resetBtn = document.createElement("button"); + resetBtn.innerHTML = "🔁"; + resetBtn.className = "gr-button gr-button-lg gr-button-tool"; + resetBtn.style.border = "none"; + resetBtn.title = "Reset State"; + resetBtn.addEventListener('click', function () { + let confirmed = confirm('Reset all state values?'); + if (confirmed) { + store.clearAll(); + alert('All state values deleted!'); + } + }); + + toolbar.appendChild(resetBtn); + + let quickSettings = gradioApp().getElementById("quicksettings"); + quickSettings.appendChild(toolbar); + } + + + function restoreTabs(config) { + + if (! config.hasSetting('tabs')) { + return; + } + + const tabs = bindTabEvents(); + const value = store.get('tab'); + + if (value) { + for (var i = 0; i < tabs.length; i++) { + if (tabs[i].textContent === value) { + state.utils.triggerEvent(tabs[i], 'click'); + break; + } + } + } + } + + function handleSavedInput(id) { + + const elements = gradioApp().querySelectorAll(`#${id} textarea, #${id} select, #${id} input`); + const events = ['change', 'input']; + + if (! elements || ! elements.length) { + return; + } + + let forEach = function (action) { + events.forEach(function(event) { + elements.forEach(function (element) { + action.call(element, event); + }); + }); + }; + + forEach(function (event) { + this.addEventListener(event, function () { + let value = this.value; + if (this.type && this.type === 'checkbox') { + value = this.checked; + } + store.set(id, value); + }); + }); + + TABS.forEach(tab => { + const seedInput = gradioApp().querySelector(`#${tab}_seed input`); + ['random_seed', 'reuse_seed'].forEach(id => { + const btn = gradioApp().querySelector(`#${tab}_${id}`); + btn.addEventListener('click', () => { + setTimeout(() => { + state.utils.triggerEvent(seedInput, 'change'); + }, 100); + }); + }); + }); + + let value = store.get(id); + + if (! value) { + return; + } + + forEach(function (event) { + switch (this.type) { + case 'checkbox': + this.checked = value === 'true'; + state.utils.triggerEvent(this, event); + break; + case 'radio': + if (this.value === value) { + this.checked = true; + state.utils.triggerEvent(this, event); + } else { + this.checked = false; + } + break; + default: + this.value = value; + state.utils.triggerEvent(this, event); + } + }); + } + + function handleExtensions(config) { + if (config['state_extensions']) { + config['state_extensions'].forEach(function (ext) { + if (ext in state.extensions) { + state.extensions[ext].init(); + } + }); + } + } + + return { init }; +}()); diff --git a/javascript/state.ext.control-net.js b/javascript/state.ext.control-net.js new file mode 100644 index 0000000..ddcf946 --- /dev/null +++ b/javascript/state.ext.control-net.js @@ -0,0 +1,43 @@ +window.state = window.state || {}; +window.state.extensions = window.state.extensions || {}; + +state.extensions['control-net'] = (function () { + + let container = null; + let store = null; + + function bindTabEvents() { + const tabs = container.querySelectorAll('.tabs > div > button'); + tabs.forEach(tab => { // dirty hack here + tab.removeEventListener('click', onTabClick); + tab.addEventListener('click', onTabClick); + }); + return tabs; + } + + function handleTabs() { + let tabs = bindTabEvents(); + let value = store.get('tab'); + if (value) { + for (var i = 0; i < tabs.length; i++) { + if (tabs[i].textContent === value) { + state.utils.triggerEvent(tabs[i], 'click'); + break; + } + } + } + } + + function onTabClick() { + store.set('tab', this.textContent); + bindTabEvents(); + } + + function init() { + container = gradioApp().getElementById('controlnet'); + store = new state.Store('ext-control-net'); + handleTabs(); + } + + return { init } +}()); diff --git a/javascript/state.store.js b/javascript/state.store.js new file mode 100644 index 0000000..6d315e6 --- /dev/null +++ b/javascript/state.store.js @@ -0,0 +1,30 @@ +window.state = window.state || {}; + +state.Store = function Store (prefix) { + this.prefix = state.constants.LS_PREFIX + (prefix ? prefix + '-' : ''); +} + +state.Store.prototype.set = function(key, value) { + localStorage.setItem(this.prefix + key, value); +}; + +state.Store.prototype.get = function(key) { + return localStorage.getItem(this.prefix + key); +}; + +state.Store.prototype.remove = function(key) { + localStorage.removeItem(this.prefix + key); +}; + +state.Store.prototype.clear = function() { + localStorage.clear(); +}; + +state.Store.prototype.clearAll = function () { + let keys = Object.keys(localStorage); + for (let i = 0; i < keys.length; i++) { + if (keys[i].startsWith(state.constants.LS_PREFIX)) { + localStorage.removeItem(keys[i]); + } + } +}; diff --git a/javascript/state.utils.js b/javascript/state.utils.js new file mode 100644 index 0000000..329e5b7 --- /dev/null +++ b/javascript/state.utils.js @@ -0,0 +1,11 @@ +window.state = window.state || {}; + +state.utils = { + triggerEvent: function triggerEvent(element, event) { + if (! element) { + return; + } + element.dispatchEvent(new Event(event.trim())); + return element; + } +}; From d5f78f08686b6066536789725185416ed86373db Mon Sep 17 00:00:00 2001 From: "ilian.iliev" Date: Tue, 21 Mar 2023 20:12:27 +0200 Subject: [PATCH 2/9] Default prefix type fixed --- javascript/state.constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/state.constants.js b/javascript/state.constants.js index bd737ce..dd94fd9 100644 --- a/javascript/state.constants.js +++ b/javascript/state.constants.js @@ -1,5 +1,5 @@ window.state = window.state || {}; state.constants = { - LS_PREFIX: 'store-' + LS_PREFIX: 'state-' }; \ No newline at end of file From 13daf11d6ba83564a2c4d0b2b6f89e775815915b Mon Sep 17 00:00:00 2001 From: "ilian.iliev" Date: Wed, 22 Mar 2023 10:53:16 +0200 Subject: [PATCH 3/9] setValue method moved to utils --- javascript/state.core.js | 18 +-- javascript/state.ext.control-net.js | 2 +- javascript/state.js | 218 ---------------------------- javascript/state.utils.js | 19 +++ scripts/state_settings.py | 6 + 5 files changed, 27 insertions(+), 236 deletions(-) delete mode 100644 javascript/state.js diff --git a/javascript/state.core.js b/javascript/state.core.js index 74488b2..8927d42 100644 --- a/javascript/state.core.js +++ b/javascript/state.core.js @@ -182,23 +182,7 @@ state.core = (function () { } forEach(function (event) { - switch (this.type) { - case 'checkbox': - this.checked = value === 'true'; - state.utils.triggerEvent(this, event); - break; - case 'radio': - if (this.value === value) { - this.checked = true; - state.utils.triggerEvent(this, event); - } else { - this.checked = false; - } - break; - default: - this.value = value; - state.utils.triggerEvent(this, event); - } + state.utils.setValue(this, value, event); }); } diff --git a/javascript/state.ext.control-net.js b/javascript/state.ext.control-net.js index ddcf946..f630cfd 100644 --- a/javascript/state.ext.control-net.js +++ b/javascript/state.ext.control-net.js @@ -39,5 +39,5 @@ state.extensions['control-net'] = (function () { handleTabs(); } - return { init } + return { init }; }()); diff --git a/javascript/state.js b/javascript/state.js deleted file mode 100644 index 6772241..0000000 --- a/javascript/state.js +++ /dev/null @@ -1,218 +0,0 @@ - -document.addEventListener('DOMContentLoaded', function() { - onUiLoaded(StateController.init); -}); - - -const StateController = (function () { - - const LS_PREFIX = 'state-'; - const TABS = ['txt2img', 'img2img']; - const ELEMENTS = { - 'prompt': 'prompt', - 'negative_prompt': 'neg_prompt', - 'sampling': 'sampling', - 'sampling_steps': 'steps', - 'restore_faces': 'restore_faces', - 'tiling': 'tiling', - 'hires_fix': 'enable_hr', - 'hires_upscaler': 'hr_upscaler', - 'hires_steps': 'hires_steps', - 'hires_scale': 'hr_scale', - 'hires_resize_x': 'hr_resize_x', - 'hires_resize_y': 'hr_resize_y', - 'hires_denoising_strength': 'denoising_strength', - 'width': 'width', - 'height': 'height', - 'batch_count': 'batch_count', - 'batch_size': 'batch_size', - 'cfg_scale': 'cfg_scale', - 'denoising_strength': 'denoising_strength', - 'seed': 'seed' - }; - - const ELEMENTS_WITHOUT_PREFIX = { - 'resize_mode': 'resize_mode', - }; - - function triggerEvent(element, event) { - if (! element) { - return; - } - element.dispatchEvent(new Event(event.trim())); - return element; - } - - function hasSetting(id, tab) { - const suffix = tab ? `_${tab}` : ''; - return this[`state${suffix}`] && this[`state${suffix}`].indexOf(id) > -1; - } - - function init() { - fetch('/state/config.json?_=' + (+new Date())) - .then(response => response.json()) - .then(config => { - try { - config.hasSetting = hasSetting - load(config); - } catch (error) { - console.error('[state]: Error:', error); - } - }) - .catch(error => console.error('[state]: Error getting JSON file:', error)); - } - - function load(config) { - - loadUI(); - restoreTabs(config); - - for (const [settingId, element] of Object.entries(ELEMENTS)) { - TABS.forEach(tab => { - if (config.hasSetting(settingId, tab)) { - handleSavedInput(`${tab}_${element}`); - } - }); - } - - for (const [settingId, element] of Object.entries(ELEMENTS_WITHOUT_PREFIX)) { - TABS.forEach(tab => { - if (config.hasSetting(settingId, tab)) { - handleSavedInput(`${element}`); - } - }); - } - } - - function storeTab() { - localStorage.setItem(LS_PREFIX + 'tab', this.textContent); - bindTabEvents(); - } - - function bindTabEvents() { - const tabs = gradioApp().querySelectorAll('#tabs > div:first-child button'); - tabs.forEach(tab => { // dirty hack here - tab.removeEventListener('click', storeTab); - tab.addEventListener('click', storeTab); - }); - return tabs; - } - - function loadUI() { - - let toolbar = document.createElement("div"); - toolbar.style.minWidth = 0; - toolbar.className = "gr-box relative w-full border-solid border border-gray-200 gr-padded"; - - let resetBtn = document.createElement("button"); - resetBtn.innerHTML = "🔁"; - resetBtn.className = "gr-button gr-button-lg gr-button-tool"; - resetBtn.style.border = "none"; - resetBtn.title = "Reset State"; - resetBtn.addEventListener('click', function () { - let confirmed = confirm('Reset all state values?'); - if (confirmed) { - let keys = Object.keys(localStorage); - for (let i = 0; i < keys.length; i++) { - if (keys[i].startsWith('state-')) { - localStorage.removeItem(keys[i]); - } - } - alert('All state values deleted!'); - } - }); - - toolbar.appendChild(resetBtn); - - let quickSettings = gradioApp().getElementById("quicksettings"); - quickSettings.appendChild(toolbar); - } - - - function restoreTabs(config) { - - if (! config.hasSetting('tabs')) { - return; - } - - const tabs = bindTabEvents(); - const value = localStorage.getItem(LS_PREFIX + 'tab'); - - if (value) { - for (var i = 0; i < tabs.length; i++) { - if (tabs[i].textContent === value) { - triggerEvent(tabs[i], 'click'); - break; - } - } - } - } - - function handleSavedInput(id) { - - const elements = gradioApp().querySelectorAll(`#${id} textarea, #${id} select, #${id} input`); - const events = ['change', 'input']; - - if (! elements || ! elements.length) { - return; - } - - let forEach = function (action) { - events.forEach(function(event) { - elements.forEach(function (element) { - action.call(element, event); - }); - }); - }; - - forEach(function (event) { - this.addEventListener(event, function () { - let value = this.value; - if (this.type && this.type === 'checkbox') { - value = this.checked; - } - localStorage.setItem(LS_PREFIX + id, value); - }); - }); - - TABS.forEach(tab => { - const seedInput = gradioApp().querySelector(`#${tab}_seed input`); - ['random_seed', 'reuse_seed'].forEach(id => { - const btn = gradioApp().querySelector(`#${tab}_${id}`); - btn.addEventListener('click', () => { - setTimeout(() => { - triggerEvent(seedInput, 'change'); - }, 100); - }); - }); - }); - - let value = localStorage.getItem(LS_PREFIX + id); - - if (! value) { - return; - } - - forEach(function (event) { - switch (this.type) { - case 'checkbox': - this.checked = value === 'true'; - triggerEvent(this, event); - break; - case 'radio': - if (this.value === value) { - this.checked = true; - triggerEvent(this, event); - } else { - this.checked = false; - } - break; - default: - this.value = value; - triggerEvent(this, event); - } - }); - } - - return { init }; -}()); diff --git a/javascript/state.utils.js b/javascript/state.utils.js index 329e5b7..6e1a1c7 100644 --- a/javascript/state.utils.js +++ b/javascript/state.utils.js @@ -7,5 +7,24 @@ state.utils = { } element.dispatchEvent(new Event(event.trim())); return element; + }, + setValue: function setValue(element, value, event) { + switch (element.type) { + case 'checkbox': + element.checked = value === 'true'; + this.triggerEvent(element, event); + break; + case 'radio': + if (element.value === value) { + element.checked = true; + this.triggerEvent(element, event); + } else { + element.checked = false; + } + break; + default: + element.value = value; + this.triggerEvent(element, event); + } } }; diff --git a/scripts/state_settings.py b/scripts/state_settings.py index 67ab324..e333e62 100644 --- a/scripts/state_settings.py +++ b/scripts/state_settings.py @@ -56,5 +56,11 @@ def on_ui_settings(): ] }, section=section)) + shared.opts.add_option("state_extensions", shared.OptionInfo([], "Saved elements from extensions", gr.CheckboxGroup, lambda: { + "choices": [ + "control-net" + ] + }, section=section)) + scripts.script_callbacks.on_ui_settings(on_ui_settings) From ff953337271e1e686b575d87251d80eece5a30a8 Mon Sep 17 00:00:00 2001 From: "ilian.iliev" Date: Wed, 22 Mar 2023 11:08:15 +0200 Subject: [PATCH 4/9] Toggle menu handled --- javascript/state.ext.control-net.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/javascript/state.ext.control-net.js b/javascript/state.ext.control-net.js index f630cfd..a83c72b 100644 --- a/javascript/state.ext.control-net.js +++ b/javascript/state.ext.control-net.js @@ -6,6 +6,18 @@ state.extensions['control-net'] = (function () { let container = null; let store = null; + function handleToggle() { + let value = store.get('toggled'); + let toggleBtn = container.querySelector('div.cursor-pointer'); + if (value && value === 'true') { + state.utils.triggerEvent(toggleBtn, 'click'); + } + toggleBtn.addEventListener('click', function () { + let span = this.querySelector('.transition'); + store.set('toggled', !span.classList.contains('rotate-90')); + }); + } + function bindTabEvents() { const tabs = container.querySelectorAll('.tabs > div > button'); tabs.forEach(tab => { // dirty hack here @@ -36,6 +48,7 @@ state.extensions['control-net'] = (function () { function init() { container = gradioApp().getElementById('controlnet'); store = new state.Store('ext-control-net'); + handleToggle(); handleTabs(); } From 6ce4abf56dc04162e8e36afd2dfff2854b273972 Mon Sep 17 00:00:00 2001 From: "ilian.iliev" Date: Wed, 22 Mar 2023 11:38:26 +0200 Subject: [PATCH 5/9] Sliders, checkboxes anad selects handled --- javascript/state.ext.control-net.js | 48 +++++++++++++++++++++++++++++ javascript/state.utils.js | 3 ++ 2 files changed, 51 insertions(+) diff --git a/javascript/state.ext.control-net.js b/javascript/state.ext.control-net.js index a83c72b..3194c3b 100644 --- a/javascript/state.ext.control-net.js +++ b/javascript/state.ext.control-net.js @@ -45,11 +45,59 @@ state.extensions['control-net'] = (function () { bindTabEvents(); } + function handleCheckboxes() { + let checkboxes = container.querySelectorAll('input[type="checkbox"]'); + checkboxes.forEach(function (checkbox) { + let label = checkbox.nextElementSibling; + let id = state.utils.txtToId(label.textContent); + let value = store.get(id); + if (value) { + state.utils.setValue(checkbox, value, 'change'); + } + checkbox.addEventListener('change', function () { + store.set(id, this.checked); + }); + }); + } + + function handleSelects() { + let selects = container.querySelectorAll('select'); + selects.forEach(function (select) { + let label = select.previousElementSibling; + let id = state.utils.txtToId(label.textContent); + let value = store.get(id); + if (value) { + state.utils.setValue(select, value, 'change'); + } + select.addEventListener('change', function () { + store.set(id, this.value); + }); + }); + } + + function handleSliders() { + let sliders = container.querySelectorAll('input[type="range"]'); + sliders.forEach(function (slider) { + let label = slider.previousElementSibling.querySelector('label span'); + let id = state.utils.txtToId(label.textContent); + let value = store.get(id); + if (value) { + state.utils.setValue(slider, value, 'change'); + } + slider.addEventListener('change', function () { + store.set(id, this.value); + }); + }); + } + function init() { container = gradioApp().getElementById('controlnet'); store = new state.Store('ext-control-net'); handleToggle(); handleTabs(); + handleCheckboxes(); + handleSelects(); + handleSliders(); } return { init }; diff --git a/javascript/state.utils.js b/javascript/state.utils.js index 6e1a1c7..c66f6d0 100644 --- a/javascript/state.utils.js +++ b/javascript/state.utils.js @@ -26,5 +26,8 @@ state.utils = { element.value = value; this.triggerEvent(element, event); } + }, + txtToId: function txtToId(txt) { + return txt.split(' ').join('-').toLowerCase(); } }; From 523967cff75b4564aaaadd3b0f99322ca43844c3 Mon Sep 17 00:00:00 2001 From: "ilian.iliev" Date: Wed, 22 Mar 2023 11:55:09 +0200 Subject: [PATCH 6/9] Radio buttons handled --- javascript/state.ext.control-net.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/javascript/state.ext.control-net.js b/javascript/state.ext.control-net.js index 3194c3b..4b59e95 100644 --- a/javascript/state.ext.control-net.js +++ b/javascript/state.ext.control-net.js @@ -90,6 +90,26 @@ state.extensions['control-net'] = (function () { }); } + function handleRadioButtons() { + let fieldsets = container.querySelectorAll('fieldset'); + fieldsets.forEach(function (fieldset) { + let label = fieldset.firstChild.nextElementSibling; + let radios = fieldset.querySelectorAll('input[type="radio"]'); + let id = state.utils.txtToId(label.textContent); + 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 init() { container = gradioApp().getElementById('controlnet'); store = new state.Store('ext-control-net'); @@ -98,6 +118,7 @@ state.extensions['control-net'] = (function () { handleCheckboxes(); handleSelects(); handleSliders(); + handleRadioButtons(); } return { init }; From 7f202acf746cd8577b14920b18196569f28c831a Mon Sep 17 00:00:00 2001 From: "ilian.iliev" Date: Wed, 22 Mar 2023 15:25:07 +0200 Subject: [PATCH 7/9] Tabs logic changed --- javascript/state.core.js | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/javascript/state.core.js b/javascript/state.core.js index 8927d42..f9f45ee 100644 --- a/javascript/state.core.js +++ b/javascript/state.core.js @@ -77,20 +77,6 @@ state.core = (function () { handleExtensions(config); } - function storeTab() { - store.set('tab', this.textContent); - bindTabEvents(); - } - - function bindTabEvents() { - const tabs = gradioApp().querySelectorAll('#tabs > div:first-child button'); - tabs.forEach(tab => { // dirty hack here - tab.removeEventListener('click', storeTab); - tab.addEventListener('click', storeTab); - }); - return tabs; - } - function loadUI() { let toolbar = document.createElement("div"); @@ -123,7 +109,7 @@ state.core = (function () { return; } - const tabs = bindTabEvents(); + const tabs = gradioApp().querySelectorAll('#tabs > div:first-child button'); const value = store.get('tab'); if (value) { @@ -134,6 +120,10 @@ state.core = (function () { } } } + + onUiTabChange(function () { + store.set('tab', get_uiCurrentTab().textContent); + }); } function handleSavedInput(id) { From a05f99c0651442a2a73f0d3ec6880250a6701006 Mon Sep 17 00:00:00 2001 From: "ilian.iliev" Date: Wed, 22 Mar 2023 16:04:43 +0200 Subject: [PATCH 8/9] Fixed dynamic inputs --- javascript/state.ext.control-net.js | 13 ++++++++++--- javascript/state.utils.js | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/javascript/state.ext.control-net.js b/javascript/state.ext.control-net.js index 4b59e95..ec07880 100644 --- a/javascript/state.ext.control-net.js +++ b/javascript/state.ext.control-net.js @@ -72,6 +72,9 @@ state.extensions['control-net'] = (function () { select.addEventListener('change', function () { store.set(id, this.value); }); + if (id === 'preprocessor' && value && value !== 'none') { + state.utils.onNextUiUpdates(handleSliders); // update new sliders if needed + } }); } @@ -110,9 +113,7 @@ state.extensions['control-net'] = (function () { }); } - function init() { - container = gradioApp().getElementById('controlnet'); - store = new state.Store('ext-control-net'); + function load() { handleToggle(); handleTabs(); handleCheckboxes(); @@ -121,5 +122,11 @@ state.extensions['control-net'] = (function () { handleRadioButtons(); } + function init() { + container = gradioApp().getElementById('controlnet'); + store = new state.Store('ext-control-net'); + load(); + } + return { init }; }()); diff --git a/javascript/state.utils.js b/javascript/state.utils.js index c66f6d0..237ec79 100644 --- a/javascript/state.utils.js +++ b/javascript/state.utils.js @@ -29,5 +29,27 @@ state.utils = { }, txtToId: function txtToId(txt) { return txt.split(' ').join('-').toLowerCase(); + }, + callXTimes: function callXTimes(func, times) { + let called = 0; + return function() { + if (called < times) { + called++; + return func.apply(this); + } + } + }, + debounce: function debounce(func, delay) { + let lastCallTime = 0; + return function() { + const currentCallTime = new Date().getTime(); + if (currentCallTime - lastCallTime > delay) { + lastCallTime = currentCallTime; + func.apply(this, arguments); + } + } + }, + onNextUiUpdates: function (func) { + onUiUpdate(this.callXTimes(this.debounce(func, 50), 10)); } }; From 528bfcea4c0c494b6f885729a2927e359a1e780a Mon Sep 17 00:00:00 2001 From: "ilian.iliev" Date: Wed, 22 Mar 2023 17:13:50 +0200 Subject: [PATCH 9/9] Multi ControlNet support implemented --- javascript/state.ext.control-net.js | 122 +++++++++++++++++----------- javascript/state.utils.js | 3 +- 2 files changed, 77 insertions(+), 48 deletions(-) diff --git a/javascript/state.ext.control-net.js b/javascript/state.ext.control-net.js index ec07880..ff2096d 100644 --- a/javascript/state.ext.control-net.js +++ b/javascript/state.ext.control-net.js @@ -5,6 +5,7 @@ state.extensions['control-net'] = (function () { let container = null; let store = null; + let cnTabs = []; function handleToggle() { let value = store.get('toggled'); @@ -46,68 +47,76 @@ state.extensions['control-net'] = (function () { } function handleCheckboxes() { - let checkboxes = container.querySelectorAll('input[type="checkbox"]'); - checkboxes.forEach(function (checkbox) { - let label = checkbox.nextElementSibling; - let id = state.utils.txtToId(label.textContent); - let value = store.get(id); - if (value) { - state.utils.setValue(checkbox, value, 'change'); - } - checkbox.addEventListener('change', function () { - store.set(id, this.checked); - }); + cnTabs.forEach(({ container, store }) => { + let checkboxes = container.querySelectorAll('input[type="checkbox"]'); + checkboxes.forEach(function (checkbox) { + let label = checkbox.nextElementSibling; + let id = state.utils.txtToId(label.textContent); + let value = store.get(id); + if (value) { + state.utils.setValue(checkbox, value, 'change'); + } + checkbox.addEventListener('change', function () { + store.set(id, this.checked); + }); + }); }); } function handleSelects() { - let selects = container.querySelectorAll('select'); - selects.forEach(function (select) { - let label = select.previousElementSibling; - let id = state.utils.txtToId(label.textContent); - let value = store.get(id); - if (value) { - state.utils.setValue(select, value, 'change'); - } - select.addEventListener('change', function () { - store.set(id, this.value); + cnTabs.forEach(({ container, store }) => { + let selects = container.querySelectorAll('select'); + selects.forEach(function (select) { + let label = select.previousElementSibling; + let id = state.utils.txtToId(label.textContent); + let value = store.get(id); + if (value) { + state.utils.setValue(select, value, 'change'); + } + select.addEventListener('change', function () { + store.set(id, this.value); + }); + if (id === 'preprocessor' && value && value !== 'none') { + state.utils.onNextUiUpdates(handleSliders); // update new sliders if needed + } }); - if (id === 'preprocessor' && value && value !== 'none') { - state.utils.onNextUiUpdates(handleSliders); // update new sliders if needed - } }); } function handleSliders() { - let sliders = container.querySelectorAll('input[type="range"]'); - sliders.forEach(function (slider) { - let label = slider.previousElementSibling.querySelector('label span'); - let id = state.utils.txtToId(label.textContent); - let value = store.get(id); - if (value) { - state.utils.setValue(slider, value, 'change'); - } - slider.addEventListener('change', function () { - store.set(id, this.value); + cnTabs.forEach(({ container, store }) => { + let sliders = container.querySelectorAll('input[type="range"]'); + sliders.forEach(function (slider) { + let label = slider.previousElementSibling.querySelector('label span'); + let id = state.utils.txtToId(label.textContent); + let value = store.get(id); + if (value) { + state.utils.setValue(slider, value, 'change'); + } + slider.addEventListener('change', function () { + store.set(id, this.value); + }); }); }); } function handleRadioButtons() { - let fieldsets = container.querySelectorAll('fieldset'); - fieldsets.forEach(function (fieldset) { - let label = fieldset.firstChild.nextElementSibling; - let radios = fieldset.querySelectorAll('input[type="radio"]'); - let id = state.utils.txtToId(label.textContent); - let value = store.get(id); - if (value) { + cnTabs.forEach(({ container, store }) => { + let fieldsets = container.querySelectorAll('fieldset'); + fieldsets.forEach(function (fieldset) { + let label = fieldset.firstChild.nextElementSibling; + let radios = fieldset.querySelectorAll('input[type="radio"]'); + let id = state.utils.txtToId(label.textContent); + let value = store.get(id); + if (value) { + radios.forEach(function (radio) { + state.utils.setValue(radio, value, 'change'); + }); + } radios.forEach(function (radio) { - state.utils.setValue(radio, value, 'change'); - }); - } - radios.forEach(function (radio) { - radio.addEventListener('change', function () { - store.set(id, this.value); + radio.addEventListener('change', function () { + store.set(id, this.value); + }); }); }); }); @@ -123,8 +132,27 @@ state.extensions['control-net'] = (function () { } function init() { + container = gradioApp().getElementById('controlnet'); store = new state.Store('ext-control-net'); + + let tabs = container.querySelectorAll('.tabitem'); + + if (tabs.length) { + cnTabs = []; + tabs.forEach((tabContainer, i) => { + cnTabs.push({ + container: tabContainer, + store: new state.Store('ext-control-net-' + i) + }); + }); + } else { + cnTabs = [{ + container: container, + store: new state.Store('ext-control-net-0') + }]; + } + load(); } diff --git a/javascript/state.utils.js b/javascript/state.utils.js index 237ec79..2a1d1aa 100644 --- a/javascript/state.utils.js +++ b/javascript/state.utils.js @@ -50,6 +50,7 @@ state.utils = { } }, onNextUiUpdates: function (func) { - onUiUpdate(this.callXTimes(this.debounce(func, 50), 10)); + // brute force this to to ensure that the method is called after next few updates + onUiUpdate(this.callXTimes(function () { setTimeout(func, 10); }, 50)); } };