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..dd94fd9 --- /dev/null +++ b/javascript/state.constants.js @@ -0,0 +1,5 @@ +window.state = window.state || {}; + +state.constants = { + LS_PREFIX: 'state-' +}; \ No newline at end of file diff --git a/javascript/state.js b/javascript/state.core.js similarity index 71% rename from javascript/state.js rename to javascript/state.core.js index 6772241..f9f45ee 100644 --- a/javascript/state.js +++ b/javascript/state.core.js @@ -1,12 +1,7 @@ +window.state = window.state || {}; -document.addEventListener('DOMContentLoaded', function() { - onUiLoaded(StateController.init); -}); +state.core = (function () { - -const StateController = (function () { - - const LS_PREFIX = 'state-'; const TABS = ['txt2img', 'img2img']; const ELEMENTS = { 'prompt': 'prompt', @@ -35,13 +30,7 @@ const StateController = (function () { 'resize_mode': 'resize_mode', }; - function triggerEvent(element, event) { - if (! element) { - return; - } - element.dispatchEvent(new Event(event.trim())); - return element; - } + let store = null; function hasSetting(id, tab) { const suffix = tab ? `_${tab}` : ''; @@ -64,6 +53,8 @@ const StateController = (function () { function load(config) { + store = new state.Store(); + loadUI(); restoreTabs(config); @@ -82,20 +73,8 @@ const StateController = (function () { } }); } - } - 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; + handleExtensions(config); } function loadUI() { @@ -112,12 +91,7 @@ const StateController = (function () { 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]); - } - } + store.clearAll(); alert('All state values deleted!'); } }); @@ -135,17 +109,21 @@ const StateController = (function () { return; } - const tabs = bindTabEvents(); - const value = localStorage.getItem(LS_PREFIX + 'tab'); + const tabs = gradioApp().querySelectorAll('#tabs > div:first-child button'); + const value = store.get('tab'); if (value) { for (var i = 0; i < tabs.length; i++) { if (tabs[i].textContent === value) { - triggerEvent(tabs[i], 'click'); + state.utils.triggerEvent(tabs[i], 'click'); break; } } } + + onUiTabChange(function () { + store.set('tab', get_uiCurrentTab().textContent); + }); } function handleSavedInput(id) { @@ -171,7 +149,7 @@ const StateController = (function () { if (this.type && this.type === 'checkbox') { value = this.checked; } - localStorage.setItem(LS_PREFIX + id, value); + store.set(id, value); }); }); @@ -181,38 +159,32 @@ const StateController = (function () { const btn = gradioApp().querySelector(`#${tab}_${id}`); btn.addEventListener('click', () => { setTimeout(() => { - triggerEvent(seedInput, 'change'); + state.utils.triggerEvent(seedInput, 'change'); }, 100); }); }); }); - let value = localStorage.getItem(LS_PREFIX + id); + let value = store.get(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); - } + state.utils.setValue(this, value, 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..ff2096d --- /dev/null +++ b/javascript/state.ext.control-net.js @@ -0,0 +1,160 @@ +window.state = window.state || {}; +window.state.extensions = window.state.extensions || {}; + +state.extensions['control-net'] = (function () { + + let container = null; + let store = null; + let cnTabs = []; + + 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 + 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 handleCheckboxes() { + 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() { + 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 + } + }); + }); + } + + function handleSliders() { + 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() { + 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) { + radio.addEventListener('change', function () { + store.set(id, this.value); + }); + }); + }); + }); + } + + function load() { + handleToggle(); + handleTabs(); + handleCheckboxes(); + handleSelects(); + handleSliders(); + handleRadioButtons(); + } + + 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(); + } + + 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..2a1d1aa --- /dev/null +++ b/javascript/state.utils.js @@ -0,0 +1,56 @@ +window.state = window.state || {}; + +state.utils = { + triggerEvent: function triggerEvent(element, event) { + if (! element) { + return; + } + 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); + } + }, + 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) { + // brute force this to to ensure that the method is called after next few updates + onUiUpdate(this.callXTimes(function () { setTimeout(func, 10); }, 50)); + } +}; 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)