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:
parent
56b184f8fa
commit
bf025d2ab8
4 changed files with 108 additions and 54 deletions
|
@ -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: "";
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
41
js/index.js
41
js/index.js
|
@ -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");
|
||||||
|
|
97
js/lib/ui.js
97
js/lib/ui.js
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue