multiselect now also custom autocomplete

This is mainly to free space for the prompt history on #87

Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
Victor Seiji Hariki 2022-12-11 16:01:22 -03:00
parent 56b184f8fa
commit bf025d2ab8
4 changed files with 108 additions and 54 deletions

View file

@ -145,6 +145,24 @@ div.autocomplete > .autocomplete-list > .autocomplete-option:hover {
background-color: #dddf; background-color: #dddf;
} }
div.autocomplete > .autocomplete-list > .autocomplete-option.selected::after {
content: "";
position: absolute;
right: 5px;
top: 0;
height: 100%;
aspect-ratio: 1;
background-color: darkgreen;
-webkit-mask-image: url("/res/icons/check.svg");
-webkit-mask-size: contain;
mask-image: url("/res/icons/check.svg");
mask-size: contain;
}
/* Select Input */ /* Select Input */
select > option:checked::after { select > option:checked::after {
content: ""; content: "";

View file

@ -62,11 +62,7 @@
<textarea id="negPrompt"></textarea> <textarea id="negPrompt"></textarea>
</div> </div>
<label for="styleSelect">Styles:</label> <label for="styleSelect">Styles:</label>
<select <div id="style-ac-mselect"></div>
id="styleSelect"
class="wideSelect"
onchange="changeStyles()"
multiple></select>
<!-- <hr /> --> <!-- <hr /> -->
</div> </div>
<!-- SD section --> <!-- SD section -->

View file

@ -447,6 +447,12 @@ const makeSlider = (
}); });
}; };
const styleAutoComplete = createAutoComplete(
"Style",
document.getElementById("style-ac-mselect"),
{multiple: true}
);
const modelAutoComplete = createAutoComplete( const modelAutoComplete = createAutoComplete(
"Model", "Model",
document.getElementById("models-ac-select") document.getElementById("models-ac-select")
@ -797,28 +803,23 @@ async function getStyles() {
stored = []; stored = [];
} }
data.forEach((style) => { styleAutoComplete.options = data.map((style) => ({
const option = document.createElement("option"); name: style.name,
option.classList.add("style-select-option"); value: style.name,
option.text = style.name; title: `prompt: ${style.prompt}\nnegative: ${style.negative_prompt}`,
option.value = style.name; }));
option.title = `prompt: ${style.prompt}\nnegative: ${style.negative_prompt}`; styleAutoComplete.onchange.on(({value}) => {
if (stored.length === 0) option.selected = style.name === "None"; let selected = [];
else if (value.find((v) => v === "None")) {
option.selected = !!stored.find( styleAutoComplete.value = [];
(styleName) => style.name === styleName } else {
); selected = value;
styleSelect.add(option);
});
changeStyles();
stored.forEach((styleName, index) => {
if (!data.findIndex((style) => style.name === styleName)) {
stored.splice(index, 1);
} }
stableDiffusionData.styles = selected;
localStorage.setItem("promptStyle", JSON.stringify(selected));
}); });
styleAutoComplete.value = stored;
localStorage.setItem("promptStyle", JSON.stringify(stored)); localStorage.setItem("promptStyle", JSON.stringify(stored));
} catch (e) { } catch (e) {
console.warn("[index] Failed to fetch prompt styles"); console.warn("[index] Failed to fetch prompt styles");

View file

@ -199,11 +199,13 @@ function createSlider(name, wrapper, options = {}) {
* @param {string} name Name of the AutoComplete Select Element * @param {string} name Name of the AutoComplete Select Element
* @param {HTMLDivElement} wrapper The div element that will wrap the input elements * @param {HTMLDivElement} wrapper The div element that will wrap the input elements
* @param {object} options Extra options * @param {object} options Extra options
* @param {{name: string, value: string}} options.options Options to add to the selector * @param {boolean} options.multiple Whether multiple options can be selected
* @param {{name: string, value: string}[]} options.options Options to add to the selector
* @returns {AutoCompleteElement} * @returns {AutoCompleteElement}
*/ */
function createAutoComplete(name, wrapper, options = {}) { function createAutoComplete(name, wrapper, options = {}) {
defaultOpt(options, { defaultOpt(options, {
multiple: false,
options: [], options: [],
}); });
@ -226,27 +228,46 @@ function createAutoComplete(name, wrapper, options = {}) {
const acobj = { const acobj = {
name, name,
wrapper, wrapper,
_title: null, _selectedOptions: new Set(),
_value: null,
_options: [], _options: [],
/** @type {Observer<{name:string, value: string}>} */ /** @type {Observer<{name:string, value: string}>} */
onchange: new Observer(), onchange: new Observer(),
get value() { get value() {
return this._value; const v = this._selectedOptions.map((opt) => opt.value);
return options.multiple ? v : v[0];
}, },
set value(val) { set value(values) {
const opt = this.options.find((option) => option.value === val); this._selectedOptions.clear();
if (!opt) return; for (const val of options.multiple ? values : [values]) {
const opt = this.options.find((option) => option.value === val);
this._title = opt.name; if (!opt) continue; // Ignore invalid options
this._value = opt.value;
inputEl.value = opt.name;
inputEl.title = opt.name;
this.onchange.emit({name: opt.name, value: opt.value}); this._selectedOptions.add(opt);
}
this._sync();
},
_sync() {
const val = Array.from(this._selectedOptions).map((opt) => opt.value);
const name = Array.from(this._selectedOptions).map((opt) => opt.name);
for (const opt of this._options) {
if (acobj._selectedOptions.has(opt))
opt.optionElement.classList.add("selected");
else opt.optionElement.classList.remove("selected");
}
updateInputField();
this.onchange.emit({
name: options.multiple ? name : name[0],
value: options.multiple ? val : val[0],
});
}, },
get options() { get options() {
@ -261,15 +282,17 @@ function createAutoComplete(name, wrapper, options = {}) {
// Add options // Add options
val.forEach((opt) => { val.forEach((opt) => {
const {name, value} = opt; const {name, value, title} = opt;
const option = {name, value};
const optionEl = document.createElement("option"); const optionEl = document.createElement("option");
optionEl.classList.add("autocomplete-option"); optionEl.classList.add("autocomplete-option");
optionEl.title = option.name; optionEl.title = title || name;
optionEl.addEventListener("click", () => select(option));
this._options.push({name, value, optionElement: optionEl}); const option = {name, value, optionElement: optionEl};
this._options.push(option);
optionEl.addEventListener("click", () => select(option));
autocompleteEl.appendChild(optionEl); autocompleteEl.appendChild(optionEl);
}); });
@ -278,6 +301,15 @@ function createAutoComplete(name, wrapper, options = {}) {
}, },
}; };
function updateInputField() {
inputEl.value = Array.from(acobj._selectedOptions)
.map((o) => o.name)
.join(", ");
inputEl.title = Array.from(acobj._selectedOptions)
.map((o) => o.name)
.join(", ");
}
function updateOptions() { function updateOptions() {
const text = inputEl.value.toLowerCase().trim(); const text = inputEl.value.toLowerCase().trim();
@ -310,15 +342,26 @@ function createAutoComplete(name, wrapper, options = {}) {
}); });
} }
function select(options) { function select(opt) {
ontext = false; ontext = false;
onlist = false; if (!options.multiple) {
onlist = false;
acobj._selectedOptions.clear();
autocompleteEl.classList.add("display-none");
for (const child of autocompleteEl.children) {
child.classList.remove("selected");
}
}
acobj._title = options.name; if (options.multiple && acobj._selectedOptions.has(opt)) {
inputEl.value = options.name; acobj._selectedOptions.delete(opt);
acobj.value = options.value; opt.optionElement.classList.remove("selected");
} else {
acobj._selectedOptions.add(opt);
opt.optionElement.classList.add("selected");
}
autocompleteEl.classList.add("display-none"); acobj._sync();
} }
inputEl.addEventListener("focus", () => { inputEl.addEventListener("focus", () => {
@ -331,9 +374,7 @@ function createAutoComplete(name, wrapper, options = {}) {
ontext = false; ontext = false;
if (!onlist && !ontext) { if (!onlist && !ontext) {
inputEl.value = ""; updateInputField();
updateOptions();
inputEl.value = acobj._title;
autocompleteEl.classList.add("display-none"); autocompleteEl.classList.add("display-none");
} }
@ -347,9 +388,7 @@ function createAutoComplete(name, wrapper, options = {}) {
onlist = false; onlist = false;
if (!onlist && !ontext) { if (!onlist && !ontext) {
inputEl.value = ""; updateInputField();
updateOptions();
inputEl.value = acobj._title;
autocompleteEl.classList.add("display-none"); autocompleteEl.classList.add("display-none");
} }