Refactor out custom input directory dropdown auto-suggest
This commit is contained in:
@@ -49,30 +49,127 @@ const modelNodeType = {
|
||||
"vae_approx": undefined,
|
||||
};
|
||||
|
||||
const dropdownSelectClass = "search-dropdown-selected";
|
||||
const DROPDOWN_DIRECTORY_SELECTION_CLASS = "search-dropdown-selected";
|
||||
|
||||
/**
|
||||
* @param {HTMLDivElement} dropdown
|
||||
* @param {Array.<{name: string, childCount: ?int, childIndex: ?int}>} directories
|
||||
* @param {string} modelType
|
||||
* @param {string} filter
|
||||
* @param {string} sep
|
||||
* @param {HTMLInputElement} inputElement
|
||||
* @param {Function} updateModelDropdown
|
||||
* @param {Function} updateModelGrid
|
||||
* @typedef {Object} DirectoryItem
|
||||
* @param {string} name
|
||||
* @param {number | undefined} childCount
|
||||
* @param {number | undefined} childIndex
|
||||
*/
|
||||
function updateDirectorySuggestionDropdown(
|
||||
dropdown,
|
||||
directories,
|
||||
modelType,
|
||||
filter,
|
||||
sep,
|
||||
inputElement,
|
||||
updateModelDropdown,
|
||||
updateModelGrid
|
||||
) {
|
||||
let options = [];
|
||||
if (filter[0] === sep) {
|
||||
|
||||
class DirectoryDropdown {
|
||||
/** @type {HTMLDivElement} */
|
||||
element = undefined;
|
||||
|
||||
/** @type {HTMLInputElement} */
|
||||
#input = undefined;
|
||||
|
||||
/** @type {Function} */
|
||||
#submitSearch = null;
|
||||
|
||||
/**
|
||||
* @param {HTMLInputElement} input
|
||||
* @param {Function} updateDropdown
|
||||
* @param {Function} submitSearch
|
||||
*/
|
||||
constructor(input, updateDropdown, submitSearch) {
|
||||
/** @type {HTMLDivElement} */
|
||||
const dropdown = $el("div.search-dropdown", { // TODO: change to `search-directory-dropdown`
|
||||
style: { display: "none" },
|
||||
});
|
||||
this.element = dropdown;
|
||||
this.#input = input;
|
||||
this.#submitSearch = submitSearch;
|
||||
|
||||
input.addEventListener("input", () => updateDropdown());
|
||||
input.addEventListener("focus", () => updateDropdown());
|
||||
input.addEventListener("blur", () => { dropdown.style.display = "none"; });
|
||||
input.addEventListener(
|
||||
"keydown",
|
||||
(e) => {
|
||||
const children = dropdown.children;
|
||||
let iChild;
|
||||
for (iChild = 0; iChild < children.length; iChild++) {
|
||||
const child = children[iChild];
|
||||
if (child.classList.contains(DROPDOWN_DIRECTORY_SELECTION_CLASS)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
if (iChild < children.length) {
|
||||
// remove select
|
||||
const child = children[iChild];
|
||||
child.classList.remove(DROPDOWN_DIRECTORY_SELECTION_CLASS);
|
||||
}
|
||||
else {
|
||||
e.target.blur();
|
||||
}
|
||||
}
|
||||
else if (e.key === "Enter") {
|
||||
e.stopPropagation();
|
||||
submitSearch(e.target, children[iChild]);
|
||||
}
|
||||
else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
e.stopPropagation();
|
||||
let iNext = children.length;
|
||||
if (iChild < children.length) {
|
||||
const child = children[iChild];
|
||||
child.classList.remove(DROPDOWN_DIRECTORY_SELECTION_CLASS);
|
||||
const delta = e.key === "ArrowDown" ? 1 : -1;
|
||||
iNext = iChild + delta;
|
||||
if (0 <= iNext && iNext < children.length) {
|
||||
const nextChild = children[iNext];
|
||||
nextChild.classList.add(DROPDOWN_DIRECTORY_SELECTION_CLASS);
|
||||
}
|
||||
}
|
||||
else if (iChild === children.length) {
|
||||
iNext = e.key === "ArrowDown" ? 0 : children.length-1;
|
||||
const nextChild = children[iNext]
|
||||
nextChild.classList.add(DROPDOWN_DIRECTORY_SELECTION_CLASS);
|
||||
}
|
||||
if (0 <= iNext && iNext < children.length) {
|
||||
let scrollTop = dropdown.scrollTop;
|
||||
const dropdownHeight = dropdown.offsetHeight;
|
||||
const child = children[iNext];
|
||||
const childHeight = child.offsetHeight;
|
||||
const childTop = child.offsetTop;
|
||||
scrollTop = Math.max(scrollTop, childTop - dropdownHeight + childHeight);
|
||||
scrollTop = Math.min(scrollTop, childTop);
|
||||
dropdown.scrollTop = scrollTop;
|
||||
}
|
||||
else {
|
||||
dropdown.scrollTop = 0;
|
||||
const children = dropdown.children;
|
||||
for (iChild = 0; iChild < children.length; iChild++) {
|
||||
const child = children[iChild];
|
||||
if (child.classList.contains(DROPDOWN_DIRECTORY_SELECTION_CLASS)) {
|
||||
child.classList.remove(DROPDOWN_DIRECTORY_SELECTION_CLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DirectoryItem[]} directories
|
||||
* @param {string} modelType
|
||||
* @param {string} sep
|
||||
*/
|
||||
update(directories, modelType, sep) {
|
||||
const dropdown = this.element;
|
||||
const input = this.#input;
|
||||
const submitSearch = this.#submitSearch;
|
||||
|
||||
const filter = input.value;
|
||||
if (filter[0] !== sep) {
|
||||
dropdown.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
let cwd = null;
|
||||
const root = directories[0];
|
||||
const rootChildIndex = root["childIndex"];
|
||||
@@ -85,22 +182,21 @@ function updateDirectorySuggestionDropdown(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: directories === undefined
|
||||
let filterIndex0 = 1;
|
||||
// TODO: directories === undefined?
|
||||
let indexLastWord = 1;
|
||||
while (true) {
|
||||
const filterIndex1 = filter.indexOf(sep, filterIndex0);
|
||||
if (filterIndex1 === -1) {
|
||||
const indexNextWord = filter.indexOf(sep, indexLastWord);
|
||||
if (indexNextWord === -1) {
|
||||
// end of filter
|
||||
break;
|
||||
}
|
||||
|
||||
const item = directories[cwd];
|
||||
if (item["childCount"] === undefined) {
|
||||
const childCount = item["childCount"];
|
||||
if (childCount === undefined) {
|
||||
// file
|
||||
break;
|
||||
}
|
||||
|
||||
const childCount = item["childCount"];
|
||||
if (childCount === 0) {
|
||||
// directory is empty
|
||||
break;
|
||||
@@ -108,7 +204,7 @@ function updateDirectorySuggestionDropdown(
|
||||
const childIndex = item["childIndex"];
|
||||
const items = directories.slice(childIndex, childIndex + childCount);
|
||||
|
||||
const word = filter.substring(filterIndex0, filterIndex1);
|
||||
const word = filter.substring(indexLastWord, indexNextWord);
|
||||
cwd = null;
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
const itemName = items[itemIndex]["name"];
|
||||
@@ -122,82 +218,69 @@ function updateDirectorySuggestionDropdown(
|
||||
// directory does not exist
|
||||
break;
|
||||
}
|
||||
filterIndex0 = filterIndex1 + 1;
|
||||
indexLastWord = indexNextWord + 1;
|
||||
}
|
||||
if (cwd === null) {
|
||||
dropdown.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
if (cwd !== null) {
|
||||
const lastWord = filter.substring(filterIndex0);
|
||||
const item = directories[cwd];
|
||||
if (item["childIndex"] !== undefined) {
|
||||
const childIndex = item["childIndex"];
|
||||
const childCount = item["childCount"];
|
||||
const items = directories.slice(childIndex, childIndex + childCount);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const itemName = items[i]["name"];
|
||||
if (itemName.startsWith(lastWord)) {
|
||||
options.push(itemName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const filename = item["name"];
|
||||
if (filename.startsWith(lastWord)) {
|
||||
options.push(filename);
|
||||
let options = [];
|
||||
const lastWord = filter.substring(indexLastWord);
|
||||
const item = directories[cwd];
|
||||
if (item["childIndex"] !== undefined) {
|
||||
const childIndex = item["childIndex"];
|
||||
const childCount = item["childCount"];
|
||||
const items = directories.slice(childIndex, childIndex + childCount);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const itemName = items[i]["name"];
|
||||
if (itemName.startsWith(lastWord)) {
|
||||
options.push(itemName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setActiveMouseOver = (e) => {
|
||||
const children = dropdown.children;
|
||||
let iChild;
|
||||
for (iChild = 0; iChild < children.length; iChild++) {
|
||||
const child = children[iChild];
|
||||
if (child.classList.contains(dropdownSelectClass)) {
|
||||
child.classList.remove(dropdownSelectClass);
|
||||
}
|
||||
else {
|
||||
const filename = item["name"];
|
||||
if (filename.startsWith(lastWord)) {
|
||||
options.push(filename);
|
||||
}
|
||||
e.target.classList.add(dropdownSelectClass);
|
||||
};
|
||||
|
||||
const onMouseEnter = (e) => {
|
||||
e.stopPropagation();
|
||||
setActiveMouseOver(e);
|
||||
};
|
||||
}
|
||||
if (options.length === 0) {
|
||||
dropdown.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
if (!e.target.classList.contains(dropdownSelectClass)) {
|
||||
const selection_select = (e) => {
|
||||
const selection = e.target;
|
||||
if (!selection.classList.contains(DROPDOWN_DIRECTORY_SELECTION_CLASS)) {
|
||||
// assumes only one will ever selected at a time
|
||||
e.stopPropagation();
|
||||
setActiveMouseOver(e);
|
||||
const children = dropdown.children;
|
||||
let iChild;
|
||||
for (iChild = 0; iChild < children.length; iChild++) {
|
||||
const child = children[iChild];
|
||||
child.classList.remove(DROPDOWN_DIRECTORY_SELECTION_CLASS);
|
||||
}
|
||||
selection.classList.add(DROPDOWN_DIRECTORY_SELECTION_CLASS);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseLeave = (e) => {
|
||||
const selection_deselect = (e) => {
|
||||
e.stopPropagation();
|
||||
e.target.classList.remove(dropdownSelectClass);
|
||||
e.target.classList.remove(DROPDOWN_DIRECTORY_SELECTION_CLASS);
|
||||
};
|
||||
|
||||
const onMouseDown = (e) => {
|
||||
const selection_submit = (e) => {
|
||||
e.stopPropagation();
|
||||
//e.target.classList.remove(dropdownSelectClass);
|
||||
appendDropdownSelectionToInput(
|
||||
inputElement,
|
||||
e.target,
|
||||
sep,
|
||||
updateModelDropdown,
|
||||
updateModelGrid
|
||||
);
|
||||
submitSearch(input, e.target);
|
||||
};
|
||||
|
||||
const innerHtml = options.map((text) => {
|
||||
/** @type {HTMLParagraphElement} */
|
||||
const p = $el(
|
||||
"p",
|
||||
{
|
||||
onmouseenter: (e) => onMouseEnter(e),
|
||||
onmousemove: (e) => onMouseMove(e),
|
||||
onmouseleave: (e) => onMouseLeave(e),
|
||||
onmousedown: (e) => onMouseDown(e),
|
||||
onmouseenter: (e) => selection_select(e),
|
||||
onmousemove: (e) => selection_select(e),
|
||||
onmouseleave: (e) => selection_deselect(e),
|
||||
onmousedown: (e) => selection_submit(e),
|
||||
},
|
||||
[
|
||||
text
|
||||
@@ -207,80 +290,8 @@ function updateDirectorySuggestionDropdown(
|
||||
});
|
||||
dropdown.innerHTML = "";
|
||||
dropdown.append.apply(dropdown, innerHtml);
|
||||
dropdown.style.display = options.length === 0 ? "none" : "block";
|
||||
dropdown.style.display = "block";
|
||||
}
|
||||
else {
|
||||
dropdown.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLInputElement} inputElement
|
||||
* @param {HTMLParagraphElement} child
|
||||
* @param {string} sep
|
||||
* @param {Function} updateModelDropdown
|
||||
* @param {Function} updateModelGrid
|
||||
*/
|
||||
function appendDropdownSelectionToInput(inputElement, child, sep, updateModelDropdown, updateModelGrid) {
|
||||
if (child !== undefined && child !== null) {
|
||||
child.classList.remove(dropdownSelectClass);
|
||||
const selectedText = child.innerText;
|
||||
const oldFilterText = inputElement.value;
|
||||
const iSep = oldFilterText.lastIndexOf(sep);
|
||||
const previousPath = oldFilterText.substring(0, iSep + 1);
|
||||
const newFilterText = previousPath + selectedText;
|
||||
|
||||
inputElement.value = newFilterText;
|
||||
updateModelDropdown(); // TODO: should this be here?
|
||||
}
|
||||
updateModelGrid(); // TODO: GENERALIZATION -> this shouldn't be here
|
||||
inputElement.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLDivElement} modelGrid
|
||||
* @param {Object} models
|
||||
* @param {HTMLSelectElement} modelSelect
|
||||
* @param {Object.<{value: string}>} previousModelType
|
||||
* @param {Object} settings
|
||||
* @param {Array} previousModelFilters
|
||||
* @param {HTMLInputElement} modelFilter
|
||||
*/
|
||||
function updateModelGrid(modelGrid, models, modelSelect, previousModelType, settings, previousModelFilters, modelFilter) {
|
||||
let modelType = modelSelect.value;
|
||||
if (models[modelType] === undefined) {
|
||||
modelType = "checkpoints"; // TODO: magic value
|
||||
}
|
||||
|
||||
if (modelType !== previousModelType.value) {
|
||||
if (settings["model-persistent-search"].checked) {
|
||||
previousModelFilters.splice(0, previousModelFilters.length); // TODO: make sure this actually worked!
|
||||
}
|
||||
else {
|
||||
// cache previous filter text
|
||||
previousModelFilters[previousModelType.value] = modelFilter.value;
|
||||
// read cached filter text
|
||||
modelFilter.value = previousModelFilters[modelType] ?? "";
|
||||
}
|
||||
previousModelType.value = modelType;
|
||||
}
|
||||
|
||||
let modelTypeOptions = [];
|
||||
for (const [key, value] of Object.entries(models)) {
|
||||
const el = $el("option", [key]);
|
||||
modelTypeOptions.push(el);
|
||||
}
|
||||
modelSelect.innerHTML = "";
|
||||
modelTypeOptions.forEach(option => modelSelect.add(option));
|
||||
modelSelect.value = modelType;
|
||||
|
||||
const searchAppend = settings["model-search-always-append"].value;
|
||||
const searchText = modelFilter.value + " " + searchAppend;
|
||||
const modelList = ModelGrid.filter(models[modelType], searchText);
|
||||
|
||||
modelGrid.innerHTML = "";
|
||||
const modelGridModels = ModelGrid.generateInnerHtml(modelList, modelType, settings);
|
||||
modelGrid.append.apply(modelGrid, modelGridModels);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -563,7 +574,7 @@ class ModelGrid {
|
||||
* @param {string} searchString
|
||||
* @returns {Array}
|
||||
*/
|
||||
static filter(list, searchString) {
|
||||
static #filter(list, searchString) {
|
||||
/** @type {string[]} */
|
||||
const keywords = searchString
|
||||
.replace("*", " ")
|
||||
@@ -739,7 +750,7 @@ class ModelGrid {
|
||||
* @param {Object.<HTMLInputElement>} settingsElements
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
static generateInnerHtml(models, modelType, settingsElements) {
|
||||
static #generateInnerHtml(models, modelType, settingsElements) {
|
||||
// TODO: seperate text and model logic; getting too messy
|
||||
// TODO: fallback on button failure to copy text?
|
||||
const canShowButtons = modelNodeType[modelType] !== undefined;
|
||||
@@ -802,6 +813,52 @@ class ModelGrid {
|
||||
return [$el("h2", ["No Models"])];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLDivElement} modelGrid
|
||||
* @param {Object} models
|
||||
* @param {HTMLSelectElement} modelSelect
|
||||
* @param {Object.<{value: string}>} previousModelType
|
||||
* @param {Object} settings
|
||||
* @param {Array} previousModelFilters
|
||||
* @param {HTMLInputElement} modelFilter
|
||||
*/
|
||||
static update(modelGrid, models, modelSelect, previousModelType, settings, previousModelFilters, modelFilter) {
|
||||
let modelType = modelSelect.value;
|
||||
if (models[modelType] === undefined) {
|
||||
modelType = "checkpoints"; // TODO: magic value
|
||||
}
|
||||
|
||||
if (modelType !== previousModelType.value) {
|
||||
if (settings["model-persistent-search"].checked) {
|
||||
previousModelFilters.splice(0, previousModelFilters.length); // TODO: make sure this actually worked!
|
||||
}
|
||||
else {
|
||||
// cache previous filter text
|
||||
previousModelFilters[previousModelType.value] = modelFilter.value;
|
||||
// read cached filter text
|
||||
modelFilter.value = previousModelFilters[modelType] ?? "";
|
||||
}
|
||||
previousModelType.value = modelType;
|
||||
}
|
||||
|
||||
let modelTypeOptions = [];
|
||||
for (const [key, value] of Object.entries(models)) {
|
||||
const el = $el("option", [key]);
|
||||
modelTypeOptions.push(el);
|
||||
}
|
||||
modelSelect.innerHTML = "";
|
||||
modelTypeOptions.forEach(option => modelSelect.add(option));
|
||||
modelSelect.value = modelType;
|
||||
|
||||
const searchAppend = settings["model-search-always-append"].value;
|
||||
const searchText = modelFilter.value + " " + searchAppend;
|
||||
const modelList = ModelGrid.#filter(models[modelType], searchText);
|
||||
|
||||
modelGrid.innerHTML = "";
|
||||
const modelGridModels = ModelGrid.#generateInnerHtml(modelList, modelType, settings);
|
||||
modelGrid.append.apply(modelGrid, modelGridModels);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -854,14 +911,14 @@ class ModelManager extends ComfyDialog {
|
||||
|
||||
/** @type {HTMLDivElement} */ modelGrid: null,
|
||||
/** @type {HTMLSelectElement} */ modelTypeSelect: null,
|
||||
/** @type {HTMLDivElement} */ modelDirectorySearchOptions: null,
|
||||
/** @type {HTMLDivElement} */ searchDirectoryDropdown: null,
|
||||
/** @type {HTMLInputElement} */ modelContentFilter: null,
|
||||
|
||||
/** @type {HTMLDivElement} */ sidebarButtons: null,
|
||||
|
||||
/** @type {HTMLDivElement} */ settingsTab: null,
|
||||
/** @type {HTMLButtonElement} */ reloadSettingsBtn: null,
|
||||
/** @type {HTMLButtonElement} */ saveSettingsBtn: null,
|
||||
/** @type {HTMLButtonElement} */ settings_reloadBtn: null,
|
||||
/** @type {HTMLButtonElement} */ settings_saveBtn: null,
|
||||
settings: {
|
||||
//"sidebar-default-height": null,
|
||||
//"sidebar-default-width": null,
|
||||
@@ -879,13 +936,13 @@ class ModelManager extends ComfyDialog {
|
||||
#data = {
|
||||
/** @type {Array} */ sources: [],
|
||||
/** @type {Object} */ models: {},
|
||||
/** @type {{name: string, childCount: ?int, childIndex: ?int}[]} */ modelDirectories: null,
|
||||
/** @type {DirectoryItem[]} */ modelDirectories: [],
|
||||
/** @type {Array} */ previousModelFilters: [],
|
||||
/** @type {Object.<{value: string}>} */ previousModelType: { value: undefined },
|
||||
};
|
||||
|
||||
/** @type {string} */
|
||||
sep = "/";
|
||||
#sep = "/";
|
||||
|
||||
/** @type {SourceList} */
|
||||
#sourceList = null;
|
||||
@@ -931,8 +988,8 @@ class ModelManager extends ComfyDialog {
|
||||
),
|
||||
$tabs([
|
||||
$tab("Install", this.#createSourceInstall()),
|
||||
$tab("Models", this.#createModelTabHtml()),
|
||||
$tab("Settings", this.#createSettingsTabHtml()),
|
||||
$tab("Models", this.#modelTab_new()),
|
||||
$tab("Settings", [this.#settingsTab_new()]),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
@@ -942,9 +999,9 @@ class ModelManager extends ComfyDialog {
|
||||
}
|
||||
|
||||
#init() {
|
||||
this.#reloadSettings(false);
|
||||
this.#settingsTab_reload(false);
|
||||
this.#refreshSourceList();
|
||||
this.#modelGridRefresh();
|
||||
this.#modelTab_updateModels();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1077,118 +1134,52 @@ class ModelManager extends ComfyDialog {
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {DirectoryDropdown} */
|
||||
#modelContentFilterDirectoryDropdown = null;
|
||||
|
||||
/**
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
#createModelTabHtml() {
|
||||
#modelTab_new() {
|
||||
/** @type {HTMLDivElement} */
|
||||
const modelGrid = $el("div.comfy-grid");
|
||||
this.#el.modelGrid = modelGrid;
|
||||
|
||||
/** @type {HTMLDivElement} */
|
||||
const searchDropdown = $el("div.search-dropdown", {
|
||||
$: (el) => (this.#el.modelDirectorySearchOptions = el),
|
||||
style: { display: "none" },
|
||||
const searchInput = $el("input.search-text-area", {
|
||||
$: (el) => (this.#el.modelContentFilter = el),
|
||||
placeholder: "example: /0/1.5/styles/clothing -.pt",
|
||||
});
|
||||
|
||||
const searchDropdown = new DirectoryDropdown(
|
||||
searchInput,
|
||||
this.#modelTab_updateDirectoryDropdown,
|
||||
this.#modelTab_submitSearch
|
||||
);
|
||||
this.#modelContentFilterDirectoryDropdown = searchDropdown;
|
||||
|
||||
return [
|
||||
$el("div.row.tab-header", [
|
||||
$el("div.row.tab-header-flex-block", [
|
||||
$el("button.icon-button", {
|
||||
type: "button",
|
||||
textContent: "⟳",
|
||||
onclick: () => this.#modelGridRefresh(),
|
||||
onclick: () => this.#modelTab_updateModels(),
|
||||
}),
|
||||
$el("select.model-type-dropdown", {
|
||||
$: (el) => (this.#el.modelTypeSelect = el),
|
||||
name: "model-type",
|
||||
onchange: () => this.#modelGridUpdate(),
|
||||
onchange: () => this.#modelTab_updateModelGrid(),
|
||||
}),
|
||||
]),
|
||||
$el("div.row.tab-header-flex-block", [
|
||||
$el("div.search-models", [
|
||||
$el("input.search-text-area", {
|
||||
$: (el) => (this.#el.modelContentFilter = el),
|
||||
placeholder: "example: /0/1.5/styles/clothing -.pt",
|
||||
onkeydown: (e) => {
|
||||
const children = searchDropdown.children;
|
||||
let iChild;
|
||||
for (iChild = 0; iChild < children.length; iChild++) {
|
||||
const child = children[iChild];
|
||||
if (child.classList.contains(dropdownSelectClass)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
if (iChild < children.length) {
|
||||
const child = children[iChild];
|
||||
child.classList.remove(dropdownSelectClass);
|
||||
}
|
||||
else {
|
||||
e.target.blur();
|
||||
}
|
||||
}
|
||||
else if (e.key === "Enter") {
|
||||
e.stopPropagation();
|
||||
appendDropdownSelectionToInput(
|
||||
e.target,
|
||||
children[iChild],
|
||||
this.sep,
|
||||
this.#modelUpdateFilterDropdown,
|
||||
this.#modelGridUpdate
|
||||
);
|
||||
}
|
||||
else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
e.stopPropagation();
|
||||
let iNext = children.length;
|
||||
if (iChild < children.length) {
|
||||
const child = children[iChild];
|
||||
child.classList.remove(dropdownSelectClass);
|
||||
const delta = e.key === "ArrowDown" ? 1 : -1;
|
||||
iNext = iChild + delta;
|
||||
if (0 <= iNext && iNext < children.length) {
|
||||
const nextChild = children[iNext];
|
||||
nextChild.classList.add(dropdownSelectClass);
|
||||
}
|
||||
}
|
||||
else if (iChild === children.length) {
|
||||
iNext = e.key === "ArrowDown" ? 0 : children.length-1;
|
||||
const nextChild = children[iNext]
|
||||
nextChild.classList.add(dropdownSelectClass);
|
||||
}
|
||||
if (0 <= iNext && iNext < children.length) {
|
||||
let scrollTop = searchDropdown.scrollTop;
|
||||
const dropdownHeight = searchDropdown.offsetHeight;
|
||||
const child = children[iNext];
|
||||
const childHeight = child.offsetHeight;
|
||||
const childTop = child.offsetTop;
|
||||
scrollTop = Math.max(scrollTop, childTop - dropdownHeight + childHeight);
|
||||
scrollTop = Math.min(scrollTop, childTop);
|
||||
searchDropdown.scrollTop = scrollTop;
|
||||
}
|
||||
else {
|
||||
searchDropdown.scrollTop = 0;
|
||||
const children = searchDropdown.children;
|
||||
for (iChild = 0; iChild < children.length; iChild++) {
|
||||
const child = children[iChild];
|
||||
if (child.classList.contains(dropdownSelectClass)) {
|
||||
child.classList.remove(dropdownSelectClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
oninput: () => this.#modelUpdateFilterDropdown(),
|
||||
onfocus: () => this.#modelUpdateFilterDropdown(),
|
||||
onblur: () => { searchDropdown.style.display = "none"; },
|
||||
}),
|
||||
searchDropdown,
|
||||
searchInput,
|
||||
searchDropdown.element,
|
||||
]),
|
||||
$el("button.icon-button", {
|
||||
type: "button",
|
||||
textContent: "🔍︎",
|
||||
onclick: () => this.#modelGridUpdate(),
|
||||
onclick: () => this.#modelTab_updateModelGrid(),
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
@@ -1196,7 +1187,7 @@ class ModelManager extends ComfyDialog {
|
||||
];
|
||||
}
|
||||
|
||||
#modelGridUpdate = () => updateModelGrid(
|
||||
#modelTab_updateModelGrid = () => ModelGrid.update(
|
||||
this.#el.modelGrid,
|
||||
this.#data.models,
|
||||
this.#el.modelTypeSelect,
|
||||
@@ -1206,60 +1197,40 @@ class ModelManager extends ComfyDialog {
|
||||
this.#el.modelContentFilter
|
||||
);
|
||||
|
||||
async #modelGridRefresh() {
|
||||
async #modelTab_updateModels() {
|
||||
this.#data.models = await request("/model-manager/models");
|
||||
this.#data.modelDirectories = await request("/model-manager/model-directory-list");
|
||||
this.#modelGridUpdate();
|
||||
this.#modelTab_updateModelGrid();
|
||||
}
|
||||
|
||||
#modelUpdateFilterDropdown = () => {
|
||||
#modelTab_updateDirectoryDropdown = () => {
|
||||
const modelType = this.#el.modelTypeSelect.value;
|
||||
const filter = this.#el.modelContentFilter.value;
|
||||
updateDirectorySuggestionDropdown(
|
||||
this.#el.modelDirectorySearchOptions,
|
||||
this.#modelContentFilterDirectoryDropdown.update(
|
||||
this.#data.modelDirectories,
|
||||
modelType,
|
||||
filter,
|
||||
this.sep,
|
||||
this.#el.modelContentFilter,
|
||||
this.#modelUpdateFilterDropdown,
|
||||
this.#modelGridUpdate,
|
||||
this.#sep,
|
||||
);
|
||||
this.#data.previousModelFilters[modelType] = filter;
|
||||
const value = this.#el.modelContentFilter.value;
|
||||
this.#data.previousModelFilters[modelType] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
* @param {HTMLInputElement} input
|
||||
* @param {HTMLParagraphElement} selection
|
||||
*/
|
||||
#setSidebar(event) {
|
||||
// TODO: settings["sidebar-default-width"]
|
||||
// TODO: settings["sidebar-default-height"]
|
||||
// TODO: draggable resize?
|
||||
const button = event.target;
|
||||
const modelManager = this.element;
|
||||
const sidebarButtons = this.#el.sidebarButtons.children;
|
||||
|
||||
let buttonIndex;
|
||||
for (buttonIndex = 0; buttonIndex < sidebarButtons.length; buttonIndex++) {
|
||||
if (sidebarButtons[buttonIndex] === button) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const sidebarStates = ["sidebar-left", "sidebar-top", "sidebar-bottom", "sidebar-right"];
|
||||
let stateIndex;
|
||||
for (stateIndex = 0; stateIndex < sidebarStates.length; stateIndex++) {
|
||||
const state = sidebarStates[stateIndex];
|
||||
if (modelManager.classList.contains(state)) {
|
||||
modelManager.classList.remove(state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stateIndex != buttonIndex) {
|
||||
const newSidebarState = sidebarStates[buttonIndex];
|
||||
modelManager.classList.add(newSidebarState);
|
||||
#modelTab_submitSearch = (input, selection) => {
|
||||
if (selection !== undefined && selection !== null) {
|
||||
selection.classList.remove(DROPDOWN_DIRECTORY_SELECTION_CLASS);
|
||||
const selectedText = selection.innerText;
|
||||
const oldFilterText = input.value;
|
||||
const iSep = oldFilterText.lastIndexOf(this.#sep);
|
||||
const previousPath = oldFilterText.substring(0, iSep + 1);
|
||||
const newFilterText = previousPath + selectedText;
|
||||
input.value = newFilterText;
|
||||
this.#modelTab_updateDirectoryDropdown();
|
||||
}
|
||||
input.blur();
|
||||
this.#modelTab_updateModelGrid();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1285,21 +1256,21 @@ class ModelManager extends ComfyDialog {
|
||||
if (reloadData) {
|
||||
// Is this slow?
|
||||
this.#refreshSourceList();
|
||||
this.#modelGridRefresh();
|
||||
this.#modelTab_updateModels();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} reloadData
|
||||
*/
|
||||
async #reloadSettings(reloadData) {
|
||||
async #settingsTab_reload(reloadData) {
|
||||
const data = await request("/model-manager/settings/load");
|
||||
const settings = data["settings"];
|
||||
this.#setSettings(settings, reloadData);
|
||||
buttonAlert(this.#el.reloadSettingsBtn, true);
|
||||
buttonAlert(this.#el.settings_reloadBtn, true);
|
||||
}
|
||||
|
||||
async #saveSettings() {
|
||||
async #settingsTab_save() {
|
||||
let settings = {};
|
||||
for (const [setting, el] of Object.entries(this.#el.settings)) {
|
||||
if (!el) { continue; } // hack
|
||||
@@ -1327,27 +1298,27 @@ class ModelManager extends ComfyDialog {
|
||||
const settings = data["settings"];
|
||||
this.#setSettings(settings, true);
|
||||
}
|
||||
buttonAlert(this.#el.saveSettingsBtn, success);
|
||||
buttonAlert(this.#el.settings_saveBtn, success);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {HTMLElement[]}
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
#createSettingsTabHtml() {
|
||||
#settingsTab_new() {
|
||||
const settingsTab = $el("div.model-manager-settings", [
|
||||
$el("h1", ["Settings"]),
|
||||
$el("div", [
|
||||
$el("button", {
|
||||
$: (el) => (this.#el.reloadSettingsBtn = el),
|
||||
$: (el) => (this.#el.settings_reloadBtn = el),
|
||||
type: "button",
|
||||
textContent: "Reload", // ⟳
|
||||
onclick: () => this.#reloadSettings(true),
|
||||
onclick: () => this.#settingsTab_reload(true),
|
||||
}),
|
||||
$el("button", {
|
||||
$: (el) => (this.#el.saveSettingsBtn = el),
|
||||
$: (el) => (this.#el.settings_saveBtn = el),
|
||||
type: "button",
|
||||
textContent: "Save", // 💾︎
|
||||
onclick: () => this.#saveSettings(),
|
||||
onclick: () => this.#settingsTab_save(),
|
||||
}),
|
||||
]),
|
||||
/*
|
||||
@@ -1441,6 +1412,40 @@ class ModelManager extends ComfyDialog {
|
||||
this.#el.settingsTab = settingsTab;
|
||||
return [settingsTab];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} e
|
||||
*/
|
||||
#setSidebar(e) {
|
||||
// TODO: settings["sidebar-default-width"]
|
||||
// TODO: settings["sidebar-default-height"]
|
||||
// TODO: draggable resize?
|
||||
const button = e.target;
|
||||
const modelManager = this.element;
|
||||
const sidebarButtons = this.#el.sidebarButtons.children;
|
||||
|
||||
let buttonIndex;
|
||||
for (buttonIndex = 0; buttonIndex < sidebarButtons.length; buttonIndex++) {
|
||||
if (sidebarButtons[buttonIndex] === button) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const sidebarStates = ["sidebar-left", "sidebar-top", "sidebar-bottom", "sidebar-right"];
|
||||
let stateIndex;
|
||||
for (stateIndex = 0; stateIndex < sidebarStates.length; stateIndex++) {
|
||||
const state = sidebarStates[stateIndex];
|
||||
if (modelManager.classList.contains(state)) {
|
||||
modelManager.classList.remove(state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stateIndex != buttonIndex) {
|
||||
const newSidebarState = sidebarStates[buttonIndex];
|
||||
modelManager.classList.add(newSidebarState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let instance;
|
||||
|
||||
Reference in New Issue
Block a user