stable-diffusion-webui/javascript/ComponentControllers.js
Gerschel 374fe636b8 Squashed commit of the following:
commit b030b67ad005bfe29bcda692238a00042dcae816
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Wed Feb 8 16:38:56 2023 -0800

    styling adjustements

commit 80a2acb0230dd77489b0eb466f2efe827a053f6d
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Wed Feb 8 10:49:47 2023 -0800

    badge indicator toggles visibility by selection

commit 898922e025a6422ac947fb45c1fa4f1109882f0a
Merge: 745382a0 31bbfa72
Author: Gerschel <9631031+Gerschel@users.noreply.github.com>
Date:   Wed Feb 8 08:35:26 2023 -0800

    Merge pull request #1 from w-e-w/Rounding-Method

    Rounding Method

commit 31bbfa729a15ef35fa1f905345d3ba2b17b26ab9
Author: w-e-w <40751091+w-e-w@users.noreply.github.com>
Date:   Wed Feb 8 19:41:45 2023 +0900

    use switch

commit 85dbe511c33521d3ac62224bf0e0f3a48194ce63
Author: w-e-w <40751091+w-e-w@users.noreply.github.com>
Date:   Wed Feb 8 16:47:52 2023 +0900

    Rounding Method

commit 745382a0f4b8d16241545a3460d5206915959255
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Tue Feb 7 21:19:20 2023 -0800

    default set to round

commit 728579c618af30ec98a5af0991bd3f28bdaca399
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Tue Feb 7 21:17:03 2023 -0800

    cleaned some commented code out; added indicator

commit 5b288c24a1edd8a5c2f35214b9634316d05b8dae
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Tue Feb 7 18:19:00 2023 -0800

    needs cleaning; attempt at rounding

commit d9f18ae92b929576b0b8c5f1ef8b3b38e441e381
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Tue Feb 7 15:46:25 2023 -0800

    add rounding option in setting for aspect ratio

commit af22106802c9e42205649e4c71c23fcf5b8c62f6
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Tue Feb 7 13:18:45 2023 -0800

    added some ratios, sorted ratios by commonality

commit 11e2fba73cffe8cdbf4cd0860641b94428ca0e74
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Tue Feb 7 10:46:53 2023 -0800

    snaps to mulitples of 8 and along ratio

commit fa00387e07460b10ee82671a1bfa8687e00ee60b
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Mon Feb 6 14:54:59 2023 -0800

    updated slidercomponentcontroller

commit 8059bc111c3e2d1edb3314e05ab21b65120fa1dd
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Mon Feb 6 14:29:11 2023 -0800

    added step size adjustment on number field

commit 641157b9f27a874a24ee7b0a854a092e9eac3eec
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Mon Feb 6 14:12:03 2023 -0800

    added return step size to default when ratio is disabled

commit 5fb75ad28f2476f36100ec93922a8199adbd2a68
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Mon Feb 6 14:09:34 2023 -0800

    added step size adjustment

commit e33532883bc4709cd41c3775cbb646d1d5ab0584
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Mon Feb 6 11:56:15 2023 -0800

    adjusted dropdown size, padding, text-align

commit 81937329cee77f466c5a5b23c268d0c810128f84
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Mon Feb 6 11:39:57 2023 -0800

    added positioning and styling

commit 86eb4583782d92880a9a113a54ffbac9d92f3753
Author: Gerschel <Gerschel_Payne@hotmail.com>
Date:   Mon Feb 6 08:54:45 2023 -0800

    fix typo in defaults; added preventDefault in event
2023-02-08 18:57:32 -08:00

260 lines
9.1 KiB
JavaScript

/* This is a basic library that allows controlling elements that take some form of user input.
This was previously written in typescript, where all controllers implemented an interface. Not
all methods were needed in all the controllers, but it was done to keep a common interface, so
your main app can serve as a controller of controllers.
These controllers were built to work on the shapes of html elements that gradio components use.
There may be some notes in it that only applied to my use case, but I left them to help others
along.
You will need the parent element for these to work.
The parent element can be defined as the element (div) that gets the element id when assigning
an element id to a gradio component.
Example:
gr.TextBox(value="...", elem_id="THISID")
Basic usage, grab an element that is the parent container for the component.
Send it in to the class, like a function, don't forget the "new" keyword so it calls the constructor
and sends back a new object.
Example:
let txt2imgPrompt = new TextComponentController(gradioApp().querySelector("#txt2img_prompt"))
Then use the getVal() method to get the value, or use the setVal(myValue) method to set the value.
Input types that are groups, like Checkbox groups (not individual checkboxes), take in an array of values.
Checkbox group has to reset all values to False (unchecked), then set the values in your array to true (checked).
If you don't hold a reference to the values (the labels in string format), you can acquire them using the getVal() method.
*/
class DropdownComponentController {
constructor(element) {
this.element = element;
this.childSelector = this.element.querySelector('select');
this.children = new Map();
Array.from(this.childSelector.querySelectorAll('option')).forEach(opt => this.children.set(opt.value, opt));
}
getVal() {
return this.childSelector.value;
}
updateVal(optionElement) {
optionElement.selected = true;
}
setVal(name) {
this.updateVal(this.children.get(name));
this.eventHandler();
}
eventHandler() {
this.childSelector.dispatchEvent(new Event("change"));
}
}
class CheckboxComponentController {
constructor(element) {
this.element = element;
this.child = this.element.querySelector('input');
}
getVal() {
return this.child.checked;
}
updateVal(checked) {
this.child.checked = checked;
}
setVal(checked) {
this.updateVal(checked);
this.eventHandler();
}
eventHandler() {
this.child.dispatchEvent(new Event("change"));
}
}
class CheckboxGroupComponentController {
constructor(element) {
this.element = element;
//this.checkBoxes = new Object;
this.children = new Map();
Array.from(this.element.querySelectorAll('input')).forEach(input => this.children.set(input.nextElementSibling.innerText, input));
/* element id gets use fieldset, grab all inputs (the bool val) get the userfriendly label, use as key, put bool value in mapping */
//Array.from(this.component.querySelectorAll("input")).forEach( _input => this.checkBoxes[_input.nextElementSibling.innerText] = _input)
/*Checkboxgroup structure
<fieldset>
<div> css makes translucent
<span>
serves as label for component
</span>
<div data-testid='checkbox-group'> container for checkboxes
<label>
<input type=checkbox>
<span>checkbox words</span>
</label>
...
</div>
</fieldset>
*/
}
updateVal(label) {
/*********
calls updates using a throttle or else the backend does not get updated properly
* ********/
setTimeout(() => this.conditionalToggle(true, this.children.get(label)), 2);
}
setVal(labels) {
/* Handles reset and updates all in array to true */
this.reupdateVals();
labels.forEach(l => this.updateVal(l));
}
getVal() {
//return the list of values that are true
return [...this.children].filter(([k, v]) => v.checked).map(arr => arr[0]);
}
reupdateVals() {
/**************
* for reupdating all vals, first set to false
**************/
this.children.forEach(inputChild => this.conditionalToggle(false, inputChild));
}
conditionalToggle(desiredVal, inputChild) {
//This method behaves like 'set this value to this'
//Using element.checked = true/false, does not register the change, even if you called change afterwards,
// it only sets what it looks like in our case, because there is no form submit, a person then has to click on it twice.
//Options are to use .click() or dispatch an event
if (desiredVal != inputChild.checked) {
inputChild.dispatchEvent(new Event("change")); //using change event instead of click, in case browser ad-blockers blocks the click method
}
}
eventHandler(checkbox) {
checkbox.dispatchEvent(new Event("change"));
}
}
class RadioComponentController {
constructor(element) {
this.element = element;
this.children = new Map();
Array.from(this.element.querySelectorAll("input")).forEach(input => this.children.set(input.value, input));
}
getVal() {
//radio groups have a single element that's checked is true
// as array arr k,v pair element.checked ) -> array of len(1) with [k,v] so either [0] [1].value
return [...this.children].filter(([l, e]) => e.checked)[0][0];
//return Array.from(this.children).filter( ([label, input]) => input.checked)[0][1].value
}
updateVal(child) {
this.eventHandler(child);
}
setVal(name) {
//radio will trigger all false except the one that get the event change
//to keep the api similar, other methods are still called
this.updateVal(this.children.get(name));
}
eventHandler(child) {
child.dispatchEvent(new Event("change"));
}
}
class NumberComponentController {
constructor(element) {
this.element = element;
this.childNumField = element.querySelector('input[type=number]');
}
getVal() {
return this.childNumField.value;
}
updateVal(text) {
this.childNumField.value = text;
}
eventHandler() {
this.element.dispatchEvent(new Event("input"));
}
setVal(text) {
this.updateVal(text);
this.eventHandler();
}
}
class SliderComponentController {
constructor(element) {
this.element = element;
this.childNumField = this.element.querySelector('input[type=number]');
this.childRangeField = this.element.querySelector('input[type=range]');
}
getVal() {
return this.childNumField.value;
}
updateVal(text) {
//both are not needed, either works, both are left in so one is a fallback in case of gradio changes
this.childNumField.value = text;
this.childRangeField.value = text;
}
eventHandler() {
this.element.dispatchEvent(new Event("input"));
this.childNumField.dispatchEvent(new Event("input"));
this.childRangeField.dispatchEvent(new Event("input"));
}
setVal(text) {
this.updateVal(text);
this.eventHandler();
}
}
class TextComponentController {
constructor(element) {
this.element = element;
this.child = element.querySelector('textarea');
}
getVal() {
return this.child.value;
}
eventHandler() {
this.element.dispatchEvent(new Event("input"));
this.child.dispatchEvent(new Event("change"));
//Workaround to solve no target with v(o) on eventhandler, define my own target
let ne = new Event("input");
Object.defineProperty(ne, "target", { value: this.child });
this.child.dispatchEvent(ne);
}
updateVal(text) {
this.child.value = text;
}
appendValue(text) {
//might add delimiter option
this.child.value += ` ${text}`;
}
setVal(text, append = false) {
if (append) {
this.appendValue(text);
}
else {
this.updateVal(text);
}
this.eventHandler();
}
}
class JsonComponentController extends TextComponentController {
constructor(element) {
super(element);
}
getVal() {
return JSON.parse(this.child.value);
}
}
class ColorComponentController {
constructor(element) {
this.element = element;
this.child = this.element.querySelector('input[type=color]');
}
updateVal(text) {
this.child.value = text;
}
getVal() {
return this.child.value;
}
setVal(text) {
this.updateVal(text);
this.eventHandler();
}
eventHandler() {
this.child.dispatchEvent(new Event("input"));
}
}