need to fix bug with row width.

This commit is contained in:
Sj-Si 2024-04-09 17:26:56 -04:00
parent ec80d57e8b
commit fc7a8a41e5
8 changed files with 557 additions and 395 deletions

View file

@ -17,12 +17,6 @@ class Clusterize {
content_elem = null;
scroll_id = null;
content_id = null;
default_sort_mode_str = "";
default_sort_dir_str = "ascending";
default_filter_str = "";
sort_mode_str = this.default_sort_mode_str;
sort_dir_str = this.default_sort_dir_str;
filter_str = this.default_filter_str;
options = {
rows_in_block: 50,
cols_in_block: 1,
@ -32,13 +26,9 @@ class Clusterize {
no_data_class: "clusterize-no-data",
no_data_text: "No data",
keep_parity: true,
callbacks: {
initData: this.initDataDefault,
fetchData: this.fetchDataDefault,
sortData: this.sortDataDefault,
filterData: this.filterDataDefault,
},
callbacks: {},
};
setup_has_run = false;
#is_mac = null;
#ie = null;
#max_items = null;
@ -60,17 +50,17 @@ class Clusterize {
}
}
if (!isNullOrUndefined(this.options.callbacks.initData)) {
this.options.callbacks.initData = this.initDataDefault;
if (isNullOrUndefined(this.options.callbacks.initData)) {
this.options.callbacks.initData = this.initDataDefaultCallback;
}
if (!isNullOrUndefined(this.options.callbacks.fetchData)) {
this.options.callbacks.fetchData = this.fetchDataDefault;
if (isNullOrUndefined(this.options.callbacks.fetchData)) {
this.options.callbacks.fetchData = this.fetchDataDefaultCallback;
}
if (!isNullOrUndefined(this.options.callbacks.sortData)) {
this.options.callbacks.sortData = this.sortDataDefault;
if (isNullOrUndefined(this.options.callbacks.sortData)) {
this.options.callbacks.sortData = this.sortDataDefaultCallback;
}
if (!isNullOrUndefined(this.options.callbacks.filterData)) {
this.options.callbacks.filterData = this.filterDataDefault;
if (isNullOrUndefined(this.options.callbacks.filterData)) {
this.options.callbacks.filterData = this.filterDataDefaultCallback;
}
// detect ie9 and lower
@ -105,15 +95,25 @@ class Clusterize {
// ==== PUBLIC FUNCTIONS ====
async setup() {
if (this.setup_has_run) {
return;
}
await this.#insertToDOM();
this.scroll_elem.scrollTop = this.#scroll_top;
this.#setupEvent("scroll", this.scroll_elem, this.#onScroll);
this.#setupElementObservers();
this.#setupResizeObservers();
this.setup_has_run = true;
}
clear() {
if (!this.setup_has_run) {
return;
}
this.#html(this.#generateEmptyRow().join(""));
}
@ -122,15 +122,25 @@ class Clusterize {
this.#teardownElementObservers();
this.#teardownResizeObservers();
this.clear();
this.setup_has_run = false;
}
async refresh(force) {
if (!this.setup_has_run) {
return;
}
if (this.#getRowsHeight() || force) {
await this.update()
}
}
async update() {
if (!this.setup_has_run) {
return;
}
this.#scroll_top = this.scroll_elem.scrollTop;
// fixes #39
if (this.#max_rows * this.options.item_height < this.#scroll_top) {
@ -151,6 +161,11 @@ class Clusterize {
}
async setMaxItems(max_items) {
if (!this.setup_has_run) {
this.#max_items = max_items;
return;
}
if (max_items === this.#max_items) {
// No change. do nothing.
return;
@ -161,60 +176,57 @@ class Clusterize {
await this.refresh(true);
// Apply sort to the updated data.
await this.sortData(this.sort_mode_str, this.sort_dir_str);
}
async filterData(filter_str) {
if (this.filter_str === filter_str) {
return;
}
this.filter_str = isNullOrUndefined(filter_str) ? this.default_filter_str : filter_str;
// Filter is applied to entire dataset.
const max_items = await this.options.callbacks.filterData(this.filter_str);
// If the number of items changed after filter, we need to update the cluster.
if (max_items !== this.#max_items) {
this.#max_items = max_items;
this.refresh(true);
}
// Apply sort to the new filtered data.
await this.sortData(this.sort_mode_str, this.sort_dir_str);
}
async sortData(sort_mode_str, sort_dir_str) {
if (this.sort_mode_str === sort_mode_str && this.sort_dir_str === sort_dir_str) {
return;
}
this.sort_mode_str = isNullOrUndefined(sort_mode_str) ? this.default_sort_mode_str : sort_mode_str;
this.sort_dir_str = isNullOrUndefined(sort_dir_str) ? this.default_sort_dir_str : sort_dir_str;
// Sort is applied to the filtered data.
await this.options.callbacks.sortData(this.sort_mode_str, this.sort_dir_str === "descending");
await this.#insertToDOM();
await this.sortData();
}
// ==== PRIVATE FUNCTIONS ====
initDataDefault() {
initDataDefaultCallback() {
return Promise.resolve({});
}
fetchDataDefault() {
async initData() {
return await this.options.callbacks.initData.call(this);
}
fetchDataDefaultCallback() {
return Promise.resolve([]);
}
sortDataDefault() {
async fetchData(idx_start, idx_end) {
return await this.options.callbacks.fetchData.call(this, idx_start, idx_end);
}
sortDataDefaultCallback() {
return Promise.resolve();
}
filterDataDefault() {
async sortData() {
if (!this.setup_has_run) {
return;
}
// Sort is applied to the filtered data.
await this.options.callbacks.sortData.call(this);
await this.#insertToDOM();
}
filterDataDefaultCallback() {
return Promise.resolve(0);
}
async filterData() {
if (!this.setup_has_run) {
return;
}
// Filter is applied to entire dataset.
const max_items = await this.options.callbacks.filterData.call(this);
await this.setMaxItems(max_items);
}
#exploreEnvironment(rows, cache) {
this.options.content_tag = this.content_elem.tagName.toLowerCase();
if (!rows.length) {
if (isNullOrUndefined(rows) || !rows.length) {
return;
}
if (this.#ie && this.#ie <= 9 && !this.options.tag) {
@ -241,7 +253,11 @@ class Clusterize {
return;
}
const nodes = this.content_elem.querySelectorAll(":not(.clusterize-row)");
const rows = this.content_elem.children;
if (!rows.length) {
return;
}
const nodes = rows[0].children;
if (!nodes.length) {
return;
}
@ -269,6 +285,7 @@ class Clusterize {
// Update rows in block to match the number of elements that can fit in the scroll element view.
this.options.rows_in_block = calcRowsPerCol(this.scroll_elem, node);
this.options.cols_in_block = calcColsPerRow(this.content_elem, node);
console.log("HERE:", this.scroll_elem, this.content_elem, node, this.options.rows_in_block, this.options.cols_in_block);
this.options.block_height = this.options.item_height * this.options.rows_in_block;
this.options.block_width = this.options.item_width * this.options.cols_in_block;
@ -323,18 +340,19 @@ class Clusterize {
const idx_start = Math.max(0, rows_start * this.options.cols_in_block);
const idx_end = rows_end * this.options.cols_in_block;
const this_cluster_rows = await this.options.callbacks.fetchData(idx_start, idx_end);
const this_cluster_rows = await this.fetchData(idx_start, idx_end);
return {
top_offset: top_offset,
bottom_offset: bottom_offset,
rows_above: rows_above,
rows: this_cluster_rows,
rows: Array.isArray(this_cluster_rows) ? this_cluster_rows : [],
};
}
async #insertToDOM() {
if (!this.options.cluster_height || !this.options.cluster_width) {
const rows = await this.options.callbacks.fetchData(0, 1);
console.log("HERE");
const rows = await this.fetchData(0, 1);
this.#exploreEnvironment(rows, this.#cache);
}

View file

@ -22,7 +22,7 @@ const initialUiOptionsLoaded = {state: false};
//
const popup = contents => {
function popup(contents) {
if (!globalPopup) {
globalPopup = document.createElement('div');
globalPopup.classList.add('global-popup');
@ -44,45 +44,52 @@ const popup = contents => {
globalPopupInner.appendChild(contents);
globalPopup.style.display = "flex";
};
}
const popupId = id => {
function popupId(id) {
if (!storedPopupIds[id]) {
storedPopupIds[id] = gradioApp().getElementById(id);
}
popup(storedPopupIds[id]);
};
}
const closePopup = () => {
function closePopup() {
if (!globalPopup) return;
globalPopup.style.display = "none";
};
}
// ==== GENERAL EXTRA NETWORKS FUNCTIONS ====
const extraNetworksClusterizersLoadTab = async (
{
tabname_full = "",
selected = false,
fetch_data = false,
function extraNetworksClusterizersEnable(tabname_full) {
for (const [_tabname_full, tab_clusterizers] of Object.entries(clusterizers)) {
for (const v of Object.values(tab_clusterizers)) {
v.enable(_tabname_full === tabname_full);
}
}
) => {
}
async function extraNetworksClusterizersLoadTab({
tabname_full,
selected = false,
fetch_data = false,
}) {
if (!keyExistsLogError(clusterizers, tabname_full)) {
return;
}
for (const v of Object.values(clusterizers[tabname_full])) {
if (fetch_data) {
await v.initDataDefault();
} else {
await v.refresh(true);
}
if (selected) {
extraNetworksClusterizersEnable(tabname_full);
}
};
const extraNetworksRegisterPromptForTab = (tabname, id) => {
for (const v of Object.values(clusterizers[tabname_full])) {
await v.load(fetch_data);
await v.refresh(true);
}
}
function extraNetworksRegisterPromptForTab(tabname, id) {
var textarea = gradioApp().querySelector(`#${id} > label > textarea`);
if (!activePromptTextarea[tabname]) {
@ -92,9 +99,9 @@ const extraNetworksRegisterPromptForTab = (tabname, id) => {
textarea.addEventListener("focus", function() {
activePromptTextarea[tabname] = textarea;
});
};
}
const extraNetworksMovePromptToTab = (tabname, id, showPrompt, showNegativePrompt) => {
function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt) {
if (!gradioApp().querySelector('.toprow-compact-tools')) return; // only applicable for compact prompt layout
var promptContainer = gradioApp().getElementById(`${tabname}_prompt_container`);
@ -117,16 +124,16 @@ const extraNetworksMovePromptToTab = (tabname, id, showPrompt, showNegativePromp
if (elem) {
elem.classList.toggle('extra-page-prompts-active', showNegativePrompt || showPrompt);
}
};
}
const extraNetworksShowControlsForPage = (tabname, tabname_full) => {
function extraNetworksShowControlsForPage(tabname, tabname_full) {
gradioApp().querySelectorAll(`#${tabname}_extra_tabs .extra-network-controls-div > div`).forEach((elem) => {
let show = `${tabname_full}_controls` === elem.id;
elem.classList.toggle("hidden", !show);
});
};
}
const extraNetworksRemoveFromPrompt = (textarea, text, is_neg) => {
function extraNetworksRemoveFromPrompt(textarea, text, is_neg) {
let match = text.match(is_neg ? re_extranet_neg : re_extranet);
let replaced = false;
let res;
@ -167,17 +174,17 @@ const extraNetworksRemoveFromPrompt = (textarea, text, is_neg) => {
}
return false;
};
}
const extraNetworksUpdatePrompt = (textarea, text, is_neg) => {
function extraNetworksUpdatePrompt(textarea, text, is_neg) {
if (!extraNetworksRemoveFromPrompt(textarea, text, is_neg)) {
textarea.value = textarea.value + opts.extra_networks_add_text_separator + text;
}
updateInput(textarea);
};
}
const extraNetworksSaveCardPreview = (event, tabname, filename) => {
function extraNetworksSaveCardPreview(event, tabname, filename) {
const textarea = gradioApp().querySelector(`#${tabname}_preview_filename > label > textarea`);
const button = gradioApp().getElementById(`${tabname}_save_preview`);
@ -188,9 +195,9 @@ const extraNetworksSaveCardPreview = (event, tabname, filename) => {
event.stopPropagation();
event.preventDefault();
};
}
const extraNetworksFlattenMetadata = obj => {
function extraNetworksFlattenMetadata(obj) {
const result = {};
// Convert any stringified JSON objects to actual objects
@ -239,9 +246,9 @@ const extraNetworksFlattenMetadata = obj => {
}
return result;
};
}
const extraNetworksShowMetadata = text => {
function extraNetworksShowMetadata(text) {
try {
let parsed = JSON.parse(text);
if (parsed && typeof parsed === 'object') {
@ -260,9 +267,9 @@ const extraNetworksShowMetadata = text => {
popup(elem);
return;
};
}
const extraNetworksRefreshSingleCard = (tabname, extra_networks_tabname, name) => {
function extraNetworksRefreshSingleCard(tabname, extra_networks_tabname, name) {
requestGet(
"./sd_extra_networks/get-single-card",
{tabname: tabname, extra_networks_tabname: extra_networks_tabname, name: name},
@ -279,9 +286,9 @@ const extraNetworksRefreshSingleCard = (tabname, extra_networks_tabname, name) =
}
},
);
};
}
const extraNetworksRefreshTab = async tabname_full => {
async function extraNetworksRefreshTab(tabname_full) {
/** called from python when user clicks the extra networks refresh tab button */
// Reapply controls since they don't change on refresh.
const controls = gradioApp().getElementById(`${tabname_full}_controls`);
@ -305,9 +312,9 @@ const extraNetworksRefreshTab = async tabname_full => {
fetch_data: true,
});
}
};
}
const extraNetworksAutoSetTreeWidth = pane => {
function extraNetworksAutoSetTreeWidth(pane) {
if (!isElementLogError(pane)) {
return;
}
@ -343,14 +350,14 @@ const extraNetworksAutoSetTreeWidth = pane => {
// Mimicks resizeHandle.js::setLeftColGridTemplate().
row.style.gridTemplateColumns = `${max_width}px ${pad}px 1fr`;
};
}
const extraNetworksApplyFilter = tabname_full => {
function extraNetworksApplyFilter(tabname_full) {
if (!keyExistsLogError(clusterizers, tabname_full)) {
return;
}
const pane = gradioApp.getElementById(`${tabname_full}_pane`);
const pane = gradioApp().getElementById(`${tabname_full}_pane`);
if (!isElementLogError(pane)) {
return;
}
@ -361,7 +368,7 @@ const extraNetworksApplyFilter = tabname_full => {
}
// We only want to filter/sort the cards list.
clusterizers[tabname_full].cards_list.filterData(txt_search.value.toLowerCase());
clusterizers[tabname_full].cards_list.setFilterStr(txt_search.value.toLowerCase());
// If the search input has changed since selecting a button to populate it
// then we want to disable the button that previously populated the search input.
@ -375,70 +382,72 @@ const extraNetworksApplyFilter = tabname_full => {
if (isElement(btn) && btn.textContent.trim() !== txt_search.value) {
delete btn.dataset.selected;
}
};
}
// ==== EVENT HANDLING ====
const extraNetworksInitCardsData = async () => {
return await requestGetPromise(
async function extraNetworksInitCardsData(tabname, extra_networks_tabname) {
const res = await requestGetPromise(
"./sd_extra_networks/init-cards-data",
{
tabname: tabname,
extra_networks_tabname: extra_networks_tabname,
},
);
};
return JSON.parse(res);
}
const extraNetworksInitTreeData = async () => {
return await requestGetPromise(
async function extraNetworksInitTreeData(tabname, extra_networks_tabname) {
const res = await requestGetPromise(
"./sd_extra_networks/init-tree-data",
{
tabname: tabname,
extra_networks_tabname: extra_networks_tabname,
},
);
};
return JSON.parse(res);
}
const extraNetworksOnInitData = async class_name => {
async function extraNetworksOnInitData(tabname, extra_networks_tabname, class_name) {
if (class_name === "ExtraNetworksClusterizeTreeList") {
return await extraNetworksInitCardsData();
return await extraNetworksInitTreeData(tabname, extra_networks_tabname);
} else if (class_name === "ExtraNetworksClusterizeCardsList") {
return await extraNetworksInitTreeData();
return await extraNetworksInitCardsData(tabname, extra_networks_tabname);
}
};
}
const extraNetworksFetchCardsData = async (extra_networks_tabname, div_ids) => {
return await requestGetPromise(
async function extraNetworksFetchCardsData(extra_networks_tabname, div_ids) {
const res = await requestGetPromise(
"./sd_extra_networks/fetch-cards-data",
{
extra_networks_tabname: extra_networks_tabname,
div_ids: div_ids,
},
);
};
return JSON.parse(res);
}
const extraNetworksFetchTreeData = async (extra_networks_tabname, div_ids) => {
return await requestGetPromise(
async function extraNetworksFetchTreeData(extra_networks_tabname, div_ids) {
const res = await requestGetPromise(
"./sd_extra_networks/fetch-tree-data",
{
extra_networks_tabname: extra_networks_tabname,
div_ids: div_ids,
},
);
};
return JSON.parse(res);
}
const extraNetworksOnFetchData = async (class_name, extra_networks_tabname, div_ids) => {
async function extraNetworksOnFetchData(class_name, extra_networks_tabname, div_ids) {
if (class_name === "ExtraNetworksClusterizeTreeList") {
return await extraNetworksFetchCardsData(extra_networks_tabname, div_ids);
} else if (class_name === "ExtraNetworksClusterizeCardsList") {
return await extraNetworksFetchTreeData(extra_networks_tabname, div_ids);
} else if (class_name === "ExtraNetworksClusterizeCardsList") {
return await extraNetworksFetchCardsData(extra_networks_tabname, div_ids);
}
};
}
const extraNetworksFetchMetadata = (extra_networks_tabname, card_name) => {
const _showError = () => {
extraNetworksShowMetadata("there was an error getting metadata");
};
function extraNetworksFetchMetadata(extra_networks_tabname, card_name) {
const _showError = () => { extraNetworksShowMetadata("there was an error getting metadata"); };
requestGet(
"./sd_extra_networks/metadata",
@ -452,40 +461,51 @@ const extraNetworksFetchMetadata = (extra_networks_tabname, card_name) => {
},
_showError,
);
};
}
const extraNetworksUnrelatedTabSelected = tabname => {
function extraNetworksUnrelatedTabSelected(tabname) {
/** called from python when user selects an unrelated tab (generate) */
extraNetworksMovePromptToTab(tabname, '', false, false);
extraNetworksShowControlsForPage(tabname, null);
};
}
const extraNetworksTabSelected = async (tabname, id, showPrompt, showNegativePrompt, tabname_full) => {
async function extraNetworksTabSelected(
tabname,
id,
showPrompt,
showNegativePrompt,
tabname_full,
) {
/** called from python when user selects an extra networks tab */
extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt);
extraNetworksShowControlsForPage(tabname, tabname_full);
await waitForKeyInObject({k: tabname_full, obj: clusterizers});
await extraNetworksClusterizersLoadTab(tabname_full);
};
await extraNetworksClusterizersLoadTab({
tabname_full: tabname_full,
selected: true,
fetch_data: false,
});
}
const extraNetworksBtnDirsViewItemOnClick = (event, tabname_full) => {
function extraNetworksBtnDirsViewItemOnClick(event, tabname_full) {
/** Handles `onclick` events for buttons in the directory view. */
const txt_search = gradioApp().querySelector(`#${tabname_full}_controls .extra-network-control--search-text`);
const _deselect_all_buttons = () => {
gradioApp().querySelectorAll(".extra-network-dirs-view-button").forEach((elem) => {
delete elem.dataset.selected;
});
};
const _select_button = elem => {
const _select_button = (elem) => {
_deselect_all_buttons();
// Update search input with select button's path.
elem.dataset.selected = "";
txt_search.value = elem.textContent.trim();
};
const _deselect_button = elem => {
const _deselect_button = (elem) => {
delete elem.dataset.selected;
txt_search.value = "";
};
@ -498,9 +518,9 @@ const extraNetworksBtnDirsViewItemOnClick = (event, tabname_full) => {
updateInput(txt_search);
extraNetworksApplyFilter(tabname_full);
};
}
const extraNetworksControlSearchClearOnClick = (event, tabname_full) => {
function extraNetworksControlSearchClearOnClick(event, tabname_full) {
/** Dispatches custom event when the `clear` button in a search input is clicked. */
let clear_btn = event.target.closest(".extra-network-control--search-clear");
let txt_search = clear_btn.previousElementSibling;
@ -511,9 +531,9 @@ const extraNetworksControlSearchClearOnClick = (event, tabname_full) => {
{bubbles: true, detail: {tabname_full: tabname_full}},
)
);
};
}
const extraNetworksControlSortModeOnClick = (event, tabname_full) => {
function extraNetworksControlSortModeOnClick(event, tabname_full) {
/** Handles `onclick` events for Sort Mode buttons. */
event.currentTarget.parentElement.querySelectorAll('.extra-network-control--sort-mode').forEach(elem => {
delete elem.dataset.selected;
@ -526,11 +546,10 @@ const extraNetworksControlSortModeOnClick = (event, tabname_full) => {
}
const sort_mode_str = event.currentTarget.dataset.sortMode.toLowerCase();
clusterizers[tabname_full].cards_list.sort_mode_str = sort_mode_str;
extraNetworksApplyFilter(tabname_full);
};
clusterizers[tabname_full].cards_list.setSortMode(sort_mode_str);
}
const extraNetworksControlSortDirOnClick = (event, tabname_full) => {
function extraNetworksControlSortDirOnClick(event, tabname_full) {
/** Handles `onclick` events for the Sort Direction button.
*
* Modifies the data attributes of the Sort Direction button to cycle between
@ -550,11 +569,10 @@ const extraNetworksControlSortDirOnClick = (event, tabname_full) => {
return;
}
clusterizers[tabname_full].cards_list.sort_dir_str = sort_dir_str;
extraNetworksApplyFilter(tabname_full);
};
clusterizers[tabname_full].cards_list.setSortDir(sort_dir_str);
}
const extraNetworksControlTreeViewOnClick = (event, tabname_full) => {
function extraNetworksControlTreeViewOnClick(event, tabname_full) {
/** Handles `onclick` events for the Tree View button.
*
* Toggles the tree view in the extra networks pane.
@ -572,10 +590,10 @@ const extraNetworksControlTreeViewOnClick = (event, tabname_full) => {
return;
}
clusterizers[tabname_full].tree_list.scroll_elem.parentElement.classList.toggle("hidden", !show);
//clusterizers[tabname_full].tree_list.enable(show);
};
clusterizers[tabname_full].tree_list.enable(show);
}
const extraNetworksControlDirsViewOnClick = (event, tabname_full) => {
function extraNetworksControlDirsViewOnClick(event, tabname_full) {
/** Handles `onclick` events for the Dirs View button.
*
* Toggles the directory view in the extra networks pane.
@ -589,9 +607,9 @@ const extraNetworksControlDirsViewOnClick = (event, tabname_full) => {
const pane = gradioApp().getElementById(`${tabname_full}_pane`);
pane.querySelector(".extra-network-content--dirs-view").classList.toggle("hidden", !show);
};
}
const extraNetworksControlRefreshOnClick = (event, tabname_full) => {
function extraNetworksControlRefreshOnClick(event, tabname_full) {
/** Handles `onclick` events for the Refresh Page button.
*
* In order to actually call the python functions in `ui_extra_networks.py`
@ -612,9 +630,9 @@ const extraNetworksControlRefreshOnClick = (event, tabname_full) => {
// Fire an event for this button click.
gradioApp().getElementById(`${tabname_full}_extra_refresh_internal`).dispatchEvent(new Event("click"));
};
}
const extraNetworksCardOnClick = (event, tabname) => {
function extraNetworksCardOnClick(event, tabname) {
const elem = event.currentTarget;
const prompt_elem = gradioApp().querySelector(`#${tabname}_prompt > label > textarea`);
const neg_prompt_elem = gradioApp().querySelector(`#${tabname}_neg_prompt > label > textarea`);
@ -626,17 +644,17 @@ const extraNetworksCardOnClick = (event, tabname) => {
} else {
extraNetworksUpdatePrompt(prompt_elem, elem.dataset.prompt);
}
};
}
const extraNetworksTreeFileOnClick = (event, btn, tabname_full) => {
function extraNetworksTreeFileOnClick(event, btn, tabname_full) {
return;
};
}
const extraNetworksTreeDirectoryOnClick = (event, btn, tabname_full) => {
function extraNetworksTreeDirectoryOnClick(event, btn, tabname_full) {
return;
};
}
const extraNetworksTreeOnClick = (event, tabname_full) => {
function extraNetworksTreeOnClick(event, tabname_full) {
const btn = event.target.closest(".tree-list-item");
if (!isElementLogError(btn)) {
return;
@ -649,14 +667,14 @@ const extraNetworksTreeOnClick = (event, tabname_full) => {
}
event.stopPropagation();
};
}
const extraNetworksBtnShowMetadataOnClick = (event, extra_networks_tabname, card_name) => {
function extraNetworksBtnShowMetadataOnClick(event, extra_networks_tabname, card_name) {
extraNetworksFetchMetadata(extra_networks_tabname, card_name);
event.stopPropagation();
};
}
const extraNetworksBtnEditMetadataOnClick = (event, tabname_full, card_name) => {
function extraNetworksBtnEditMetadataOnClick(event, tabname_full, card_name) {
const id = `${tabname_full}_edit_user_metadata`;
let editor = extraPageUserMetadataEditors[id];
if (isNullOrUndefined(editor)) {
@ -673,16 +691,16 @@ const extraNetworksBtnEditMetadataOnClick = (event, tabname_full, card_name) =>
editor.button.click();
popup(editor.page);
};
}
const extraNetworksBtnCopyPathOnClick = (event, path) => {
function extraNetworksBtnCopyPathOnClick(event, path) {
copyToClipboard(path);
event.stopPropagation();
};
}
// ==== MAIN SETUP ====
const extraNetworksSetupEventDelegators = () => {
function extraNetworksSetupEventDelegators() {
/** Sets up event delegators for all extraNetworks tabs.
*
* These event handlers are not tied to any specific elements on the page.
@ -724,20 +742,15 @@ const extraNetworksSetupEventDelegators = () => {
closePopup();
}
});
};
}
const extraNetworksSetupTabContent = async (tabname, pane, controls_div) => {
async function extraNetworksSetupTabContent(tabname, pane, controls_div) {
const tabname_full = pane.id;
const extra_networks_tabname = tabname_full.replace(`${tabname}_`, "");
const controls = await waitForElement(`#${tabname_full}_pane .extra-network-controls`);
const tree_scroll_elem = await waitForElement(`#${tabname_full}_tree_list_scroll_area`);
const tree_content_elem = await waitForElement(`#${tabname_full}_tree_list_content_area`);
const cards_scroll_elem = await waitForElement(`#${tabname_full}_cards_list_scroll_area`);
const cards_content_elem = await waitForElement(`#${tabname_full}_cards_list_content_area`);
await waitForElement(`#${tabname_full}_pane .extra-network-content--dirs-view`);
console.log("BEFORE:", tree_scroll_elem, cards_scroll_elem);
controls.id = `${tabname_full}_controls`;
controls_div.insertBefore(controls, null);
@ -745,8 +758,8 @@ const extraNetworksSetupTabContent = async (tabname, pane, controls_div) => {
tree_list: new ExtraNetworksClusterizeTreeList({
tabname: tabname,
extra_networks_tabname: extra_networks_tabname,
scrollElem: tree_scroll_elem,
contentElem: tree_content_elem,
scrollId: `${tabname_full}_tree_list_scroll_area`,
contentId: `${tabname_full}_tree_list_content_area`,
tag: "div",
callbacks: {
initData: extraNetworksOnInitData,
@ -756,8 +769,8 @@ const extraNetworksSetupTabContent = async (tabname, pane, controls_div) => {
cards_list: new ExtraNetworksClusterizeCardsList({
tabname: tabname,
extra_networks_tabname: extra_networks_tabname,
scrollElem: cards_scroll_elem,
contentElem: cards_content_elem,
scrollId: `${tabname_full}_cards_list_scroll_area`,
contentId: `${tabname_full}_cards_list_content_area`,
tag: "div",
callbacks: {
initData: extraNetworksOnInitData,
@ -766,18 +779,15 @@ const extraNetworksSetupTabContent = async (tabname, pane, controls_div) => {
}),
};
await clusterizers[tabname_full].tree_list.setup();
await clusterizers[tabname_full].cards_list.setup();
if (pane.style.display !== "none") {
extraNetworksShowControlsForPage(tabname, tabname_full);
}
}
await extraNetworksClusterizersLoadTab({
tabname_full: tabname_full,
selected: false,
fetch_data: true,
});
};
const extraNetworksSetupTab = async (tabname) => {
async function extraNetworksSetupTab(tabname) {
let controls_div;
const this_tab = await waitForElement(`#${tabname}_extra_tabs`);
@ -793,15 +803,15 @@ const extraNetworksSetupTab = async (tabname) => {
}
extraNetworksRegisterPromptForTab(tabname, `${tabname}_prompt`);
extraNetworksRegisterPromptForTab(tabname, `${tabname}_neg_prompt`);
};
}
const extraNetworksSetup = async () => {
async function extraNetworksSetup() {
await waitForBool(initialUiOptionsLoaded);
extraNetworksSetupTab('txt2img');
extraNetworksSetupTab('img2img');
extraNetworksSetupEventDelegators();
};
}
onUiLoaded(extraNetworksSetup);
onOptionsChanged(() => initialUiOptionsLoaded.state = true);

View file

@ -18,6 +18,7 @@ class ExtraNetworksClusterize extends Clusterize {
sort_fn = this.default_sort_fn;
tabname = "";
extra_networks_tabname = "";
enabled = false;
// Override base class defaults
default_sort_mode_str = "divId";
@ -27,17 +28,54 @@ class ExtraNetworksClusterize extends Clusterize {
sort_dir_str = this.default_sort_dir_str;
filter_str = this.default_filter_str;
constructor(...args) {
super(...args);
// finish initialization
this.tabname = getValueThrowError(...args, "tabname");
this.extra_networks_tabname = getValueThrowError(...args, "extra_networks_tabname");
constructor(args) {
super(args);
this.tabname = getValueThrowError(args, "tabname");
this.extra_networks_tabname = getValueThrowError(args, "extra_networks_tabname");
}
sortByDivId() {
sortByDivId(data) {
/** Sort data_obj keys (div_id) as numbers. */
this.data_obj_keys_sorted = Object.keys(this.data_obj).sort(INT_COLLATOR.compare);
return Object.keys(data).sort(INT_COLLATOR.compare);
}
async reinitData() {
await this.initData();
// can't use super class' sort since it relies on setup being run first.
// but we do need to make sure to sort the new data before continuing.
await this.options.callbacks.sortData.call(this);
await this.setMaxItems(Object.keys(this.data_obj).length);
}
async setup() {
if (this.setup_has_run) {
return;
}
await this.reinitData();
if (this.enabled) {
await super.setup();
}
}
async load(force_init_data) {
if (!this.enabled) {
return;
}
if (!this.setup_has_run) {
await this.setup();
} else if (force_init_data) {
await this.reinitData();
} else {
await this.refresh(true);
}
}
enable(state) {
// if no state is passed, we enable by default.
this.enabled = state !== false;
}
clear() {
@ -46,31 +84,54 @@ class ExtraNetworksClusterize extends Clusterize {
super.clear();
}
async initDataDefault() {
/**Fetches the initial data.
*
* This data should be minimal and only contain div IDs and other necessary
* information such as sort keys and terms for filtering.
*/
throw new NotImplementedError();
}
setSortMode(sort_mode_str) {
if (this.sort_mode_str === sort_mode_str) {
return;
}
async fetchDataDefault(idx_start, idx_end) {
throw new NotImplementedError();
}
async sortDataDefault(sort_mode_str, sort_dir_str) {
this.sort_mode_str = sort_mode_str;
this.sort_dir_str = sort_dir_str;
this.sort_reverse = sort_dir_str === "descending";
this.sortData();
}
setSortDir(sort_dir_str) {
const reverse = (sort_dir_str === "descending");
if (this.sort_reverse === reverse) {
return;
}
this.sort_dir_str = sort_dir_str;
this.sort_reverse = reverse;
this.sortData();
}
setFilterStr(filter_str) {
if (isString(filter_str) && this.filter_str !== filter_str.toLowerCase()) {
this.filter_str = filter_str.toLowerCase();
} else if (isNullOrUndefined(this.filter_str)) {
this.filter_str = this.default_filter_str;
} else {
return;
}
this.filterData();
}
async initDataDefaultCallback() {
throw new NotImplementedError();
}
async fetchDataDefaultCallback() {
throw new NotImplementedError();
}
async sortDataDefaultCallback() {
this.data_obj_keys_sorted = this.sort_fn(this.data_obj);
if (this.sort_reverse) {
this.data_obj_keys_sorted = this.data_obj_keys_sorted.reverse();
}
}
async filterDataDefault(filter_str) {
async filterDataDefaultCallback() {
throw new NotImplementedError();
}
}
@ -79,8 +140,10 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
selected_div_id = null;
constructor(args) {
args.no_data_text = "Directory is empty.";
super(args);
super({
...args,
no_data_text: "Directory is empty.",
});
}
@ -147,6 +210,42 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
this.selected_div_id = "selected" in elem.dataset ? div_id : null;
}
getMaxRowWidth() {
/** Calculates the width of the widest row in the list. */
if (!this.enabled) {
// Inactive list is not displayed on screen. Can't calculate size.
return false;
}
if (this.content_elem.children.length === 0) {
// If there is no data then just skip.
return false;
}
let max_width = 0;
for (let i = 0; i < this.content_elem.children.length; i += this.n_cols) {
let row_width = 0;
for (let j = 0; j < this.n_cols; j++) {
const child = this.content_elem.children[i + j];
const child_style = window.getComputedStyle(child, null);
const prev_style = child.style.cssText;
const n_cols = child_style.getPropertyValue("grid-template-columns").split(" ").length;
child.style.gridTemplateColumns = `repeat(${n_cols}, max-content)`;
row_width += child.scrollWidth;
// Restore previous style.
child.style.cssText = prev_style;
}
max_width = Math.max(max_width, row_width);
}
if (max_width <= 0) {
return;
}
// Adds the scroll element's border and the scrollbar's width to the result.
// If scrollbar isn't visible, then only the element border is added.
max_width += this.scroll_elem.offsetWidth - this.scroll_elem.clientWidth;
return max_width;
}
async onRowExpandClick(div_id, elem) {
/** Expands or collapses a row to show/hide children. */
if (!keyExistsLogError(this.data_obj, div_id)){
@ -165,7 +264,7 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
await this.setMaxItems(Object.values(this.data_obj).filter(v => v.visible).length);
}
async initDataDefault() {
async initData() {
/*Expects an object like the following:
{
parent: null or div_id,
@ -174,11 +273,18 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
expanded: bool,
}
*/
console.log("BLAH:", this.options.callbacks.initData);
this.data_obj = await this.options.callbacks.initData(this.constructor.name);
this.data_obj = await this.options.callbacks.initData.call(
this,
this.tabname,
this.extra_networks_tabname,
this.constructor.name,
);
}
async fetchDataDefault(idx_start, idx_end) {
async fetchData(idx_start, idx_end) {
if (!this.enabled) {
return [];
}
const n_items = idx_end - idx_start;
const div_ids = [];
for (const div_id of this.data_obj_keys_sorted.slice(idx_start)) {
@ -190,7 +296,8 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
}
}
const data = await this.options.callbacks.fetchData(
const data = await this.options.callbacks.fetchData.call(
this,
this.constructor.name,
this.extra_networks_tabname,
div_ids,
@ -202,8 +309,8 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
const text_size = style.getPropertyValue("--button-large-text-size");
const res = [];
for (const [div_id, item] of Object.entries(data)) {
const parsed_html = htmlStringToElement(item);
for (const [div_id, html_str] of Object.entries(data)) {
const parsed_html = htmlStringToElement(html_str);
const depth = Number(parsed_html.dataset.depth);
parsed_html.style.paddingLeft = `calc(${depth} * ${text_size})`;
parsed_html.style.boxShadow = this.getBoxShadow(depth);
@ -216,14 +323,10 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
res.push(parsed_html.outerHTML);
}
return rows;
return res;
}
async sortDataDefault(sort_mode, sort_dir) {
throw new NotImplementedError();
}
async filterDataDefault(filter_str) {
async filterDataDefaultCallback() {
// just return the number of visible objects in our data.
return Object.values(this.data_obj).filter(v => v.visible).length;
}
@ -231,8 +334,10 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize {
constructor(args) {
args.no_data_text = "No files matching filter.";
super(args);
super({
...args,
no_data_text: "No files matching filter.",
});
}
sortByName(data) {
@ -259,18 +364,25 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize {
});
}
async initDataDefault() {
async initData() {
/*Expects an object like the following:
{
search_keys: array of strings,
sort_<mode>: string, (for various sort modes)
}
*/
console.log("HERE:", this.options.callbacks);
this.data_obj = await this.options.callbacks.initData(this.constructor.name);
this.data_obj = await this.options.callbacks.initData.call(
this,
this.tabname,
this.extra_networks_tabname,
this.constructor.name,
);
}
async fetchDataDefault(idx_start, idx_end) {
async fetchData(idx_start, idx_end) {
if (!this.enabled) {
return;
}
const n_items = idx_end - idx_start;
const div_ids = [];
for (const div_id of this.data_obj_keys_sorted.slice(idx_start)) {
@ -282,7 +394,8 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize {
}
}
const data = await this.options.callbacks.fetchData(
const data = await this.options.callbacks.fetchData.call(
this,
this.constructor.name,
this.extra_networks_tabname,
div_ids,
@ -291,35 +404,29 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize {
return Object.values(data);
}
async sortDataDefault(sort_mode_str, sort_dir_str) {
switch (sort_mode_str) {
async sortData() {
switch (this.sort_mode_str) {
case "name":
this.sort_fn = this.sortByName;
break;
case "path":
this.sort_fn = this.sortByPath;
break;
case "created":
case "date_created":
this.sort_fn = this.sortByDateCreated;
break;
case "modified":
case "date_modified":
this.sort_fn = this.sortByDateModified;
break;
default:
this.sort_fn = this.default_sort_fn;
break;
}
await super.sortDataDefault(sort_mode_str, sort_dir_str)
await super.sortData()
}
async filterDataDefault(filter_str) {
async filterDataDefaultCallback() {
/** Filters data by a string and returns number of items after filter. */
if (isString(filter_str)) {
this.filter_str = filter_str.toLowerCase();
} else if (isNullOrUndefined(this.filter_str)) {
this.filter_str = this.default_filter_str;
}
let n_visible = 0;
for (const [div_id, v] of Object.entries(this.data_obj)) {
let visible = v.search_terms.indexOf(this.filter_str) != -1;

View file

@ -1,156 +1,161 @@
/** Helper functions for checking types and simplifying logging/error handling. */
/** Collators used for sorting. */
const INT_COLLATOR = new Intl.Collator([], {numeric: true});
const STR_COLLATOR = new Intl.Collator("en", {numeric: true, sensitivity: "base"});
const isNumber = x => typeof x === "number" && isFinite(x);
const isNumberLogError = x => {
/** Helper functions for checking types and simplifying logging/error handling. */
function isNumber(x) { return typeof x === "number" && isFinite(x); }
function isNumberLogError(x) {
if (isNumber(x)) {
return true;
}
console.error(`expected number, got: ${typeof x}`);
return false;
};
const isNumberThrowError = x => {
}
function isNumberThrowError(x) {
if (isNumber(x)) {
return;
}
throw new Error(`expected number, got: ${typeof x}`);
};
}
const isString = x => typeof x === "string" || x instanceof String;
const isStringLogError = x => {
function isString(x) { return typeof x === "string" || x instanceof String; }
function isStringLogError(x) {
if (isString(x)) {
return true;
}
console.error(`expected string, got: ${typeof x}`);
return false;
};
const isStringThrowError = x => {
}
function isStringThrowError(x) {
if (isString(x)) {
return;
}
throw new Error(`expected string, got: ${typeof x}`);
};
}
const isNull = x => x === null;
const isUndefined = x => typeof x === "undefined" || x === undefined;
function isNull(x) { return x === null; }
function isUndefined(x) { return typeof x === "undefined" || x === undefined; }
// checks both null and undefined for simplicity sake.
const isNullOrUndefined = x => isNull(x) || isUndefined(x);
const isNullOrUndefinedLogError = x => {
function isNullOrUndefined(x) { return isNull(x) || isUndefined(x); }
function isNullOrUndefinedLogError(x) {
if (isNullOrUndefined(x)) {
console.error("Variable is null/undefined.");
return true;
}
return false;
};
const isNullOrUndefinedThrowError = x => {
}
function isNullOrUndefinedThrowError(x) {
if (!isNullOrUndefined(x)) {
return;
}
throw new Error("Variable is null/undefined.");
};
}
const isElement = x => x instanceof Element;
const isElementLogError = x => {
function isElement(x) { return x instanceof Element; }
function isElementLogError(x) {
if (isElement(x)) {
return true;
}
console.error(`expected element type, got: ${typeof x}`);
return false;
};
const isElementThrowError = x => {
}
function isElementThrowError(x) {
if (isElement(x)) {
return;
}
throw new Error(`expected element type, got: ${typeof x}`);
};
}
const isFunction = x => typeof x === "function";
const isFunctionLogError = x => {
function isFunction(x) { return typeof x === "function"; }
function isFunctionLogError(x) {
if (isFunction(x)) {
return true;
}
console.error(`expected function type, got: ${typeof x}`);
return false;
};
const isFunctionThrowError = x => {
}
function isFunctionThrowError(x) {
if (isFunction(x)) {
return;
}
throw new Error(`expected function type, got: ${typeof x}`);
};
}
const isObject = x => typeof x === "object" && !Array.isArray(x);
const isObjectLogError = x => {
function isObject(x) { return typeof x === "object" && !Array.isArray(x); }
function isObjectLogError(x) {
if (isObject(x)) {
return true;
}
console.error(`expected object type, got: ${typeof x}`);
return false;
};
const isObjectThrowError = x => {
}
function isObjectThrowError(x) {
if (isObject(x)) {
return;
}
throw new Error(`expected object type, got: ${typeof x}`);
};
}
const keyExists = (obj, k) => isObject(obj) && isString(k) && k in obj;
const keyExistsLogError = (obj, k) => {
function keyExists(obj, k) {
return isObject(obj) && isString(k) && k in obj;
}
function keyExistsLogError(obj, k) {
if (keyExists(obj, k)) {
return true;
}
console.error(`key does not exist in object: ${k}`);
return false;
};
const keyExistsThrowError = (obj, k) => {
}
function keyExistsThrowError(obj, k) {
if (keyExists(obj, k)) {
return;
}
throw new Error(`key does not exist in object: ${k}`)
};
}
const getValue = (obj, k) => {
function getValue(obj, k) {
/** Returns value of object for given key if it exists, otherwise returns null. */
if (keyExists(obj, k)) {
return obj[k];
}
return null;
};
const getValueLogError = (obj, k) => {
}
function getValueLogError(obj, k) {
if (keyExistsLogError(obj, k)) {
return obj[k];
}
return null;
};
const getValueThrowError = (obj, k) => {
}
function getValueThrowError(obj, k) {
keyExistsThrowError(obj, k);
return obj[k];
};
}
const getElementByIdLogError = selector => {
function getElementByIdLogError(selector) {
const elem = gradioApp().getElementById(selector);
isElementLogError(elem);
return elem;
};
const getElementByIdThrowError = selector => {
}
function getElementByIdThrowError(selector) {
const elem = gradioApp().getElementById(selector);
isElementThrowError(elem);
return elem;
};
}
const querySelectorLogError = selector => {
function querySelectorLogError(selector) {
const elem = gradioApp().querySelector(selector);
isElementLogError(elem);
return elem;
};
const querySelectorThrowError = selector => {
}
function querySelectorThrowError(selector) {
const elem = gradioApp().querySelector(selector);
isElementThrowError(elem);
return elem;
};
}
/** Functions for getting dimensions of elements. */
const getComputedPropertyDims = (elem, prop) => {
function getComputedPropertyDims(elem, prop) {
/** Returns the top/left/bottom/right float dimensions of an element for the specified property. */
const style = window.getComputedStyle(elem, null);
return {
@ -159,27 +164,27 @@ const getComputedPropertyDims = (elem, prop) => {
bottom: parseFloat(style.getPropertyValue(`${prop}-bottom`)),
right: parseFloat(style.getPropertyValue(`${prop}-right`)),
};
};
}
const getComputedMarginDims = elem => {
function getComputedMarginDims(elem) {
/** Returns the width/height of the computed margin of an element. */
const dims = getComputedPropertyDims(elem, "margin");
return {
width: dims.left + dims.right,
height: dims.top + dims.bottom,
};
};
}
const getComputedPaddingDims = elem => {
function getComputedPaddingDims(elem) {
/** Returns the width/height of the computed padding of an element. */
const dims = getComputedPropertyDims(elem, "padding");
return {
width: dims.left + dims.right,
height: dims.top + dims.bottom,
};
};
}
const getComputedBorderDims = elem => {
function getComputedBorderDims(elem) {
/** Returns the width/height of the computed border of an element. */
// computed border will always start with the pixel width so thankfully
// the parseFloat() conversion will just give us the width and ignore the rest.
@ -189,9 +194,9 @@ const getComputedBorderDims = elem => {
width: dims.left + dims.right,
height: dims.top + dims.bottom,
};
};
}
const getComputedDims = elem => {
function getComputedDims(elem) {
/** Returns the full width and height of an element including its margin, padding, and border. */
const width = elem.scrollWidth;
const height = elem.scrollHeight;
@ -202,24 +207,24 @@ const getComputedDims = elem => {
width: width + margin.width + padding.width + border.width,
height: height + margin.height + padding.height + border.height,
};
};
}
const calcColsPerRow = function (parent, child) {
function calcColsPerRow(parent, child) {
/** Calculates the number of columns of children that can fit in a parent's visible width. */
const parent_inner_width = parent.offsetWidth - getComputedPaddingDims(parent).width;
return parseInt(parent_inner_width / getComputedDims(child).width, 10);
};
}
const calcRowsPerCol = function (parent, child) {
function calcRowsPerCol(parent, child) {
/** Calculates the number of rows of children that can fit in a parent's visible height. */
const parent_inner_height = parent.offsetHeight - getComputedPaddingDims(parent).height;
return parseInt(parent_inner_height / getComputedDims(child).height, 10);
};
}
/** Functions for asynchronous operations. */
const debounce = (handler, timeout_ms) => {
function debounce(handler, timeout_ms) {
/** Debounces a function call.
*
* NOTE: This will NOT work if called from within a class.
@ -244,9 +249,9 @@ const debounce = (handler, timeout_ms) => {
clearTimeout(timer);
timer = setTimeout(() => handler(...args), timeout_ms);
};
};
}
const waitForElement = selector => {
function waitForElement(selector) {
/** Promise that waits for an element to exist in DOM. */
return new Promise(resolve => {
if (document.querySelector(selector)) {
@ -265,9 +270,9 @@ const waitForElement = selector => {
subtree: true
});
});
};
}
const waitForBool = o => {
function waitForBool(o) {
/** Promise that waits for a boolean to be true.
*
* `o` must be an Object of the form:
@ -283,9 +288,9 @@ const waitForBool = o => {
setTimeout(_waitForBool, 100);
})();
});
};
}
const waitForKeyInObject = o => {
function waitForKeyInObject(o) {
/** Promise that waits for a key to exist in an object.
*
* `o` must be an Object of the form:
@ -304,9 +309,9 @@ const waitForKeyInObject = o => {
setTimeout(_waitForKeyInObject, 100);
})();
});
};
}
const waitForValueInObject = o => {
function waitForValueInObject(o) {
/** Promise that waits for a key value pair in an Object.
*
* `o` must be an Object of the form:
@ -329,11 +334,11 @@ const waitForValueInObject = o => {
})();
});
});
};
}
/** Requests */
const requestGet = (url, data, handler, errorHandler) => {
function requestGet(url, data, handler, errorHandler) {
var xhr = new XMLHttpRequest();
var args = Object.keys(data).map(function (k) {
return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]);
@ -357,9 +362,9 @@ const requestGet = (url, data, handler, errorHandler) => {
};
var js = JSON.stringify(data);
xhr.send(js);
};
}
const requestGetPromise = (url, data) => {
function requestGetPromise(url, data) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
let args = Object.keys(data).map(k => {
@ -367,39 +372,40 @@ const requestGetPromise = (url, data) => {
}).join("&");
xhr.open("GET", url + "?" + args, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
resolve(xhr.responseText);
} catch (error) {
reject(error);
}
} else {
reject({ status: this.status, statusText: xhr.statusText });
}
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText);
} else {
reject({status: xhr.status, response: xhr.responseText});
}
};
xhr.send(JSON.stringify(data));
xhr.onerror = () => {
reject({status: xhr.status, response: xhr.responseText});
};
const payload = JSON.stringify(data);
xhr.send(payload);
});
};
}
/** Misc helper functions. */
const clamp = (x, min, max) => Math.max(min, Math.min(x, max));
function clamp(x, min, max) {
return Math.max(min, Math.min(x, max));
}
const getStyle = (prop, elem) => {
function getStyle(prop, elem) {
return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop];
};
}
const htmlStringToElement = function (str) {
function htmlStringToElement(s) {
/** Converts an HTML string into an Element type. */
let parser = new DOMParser();
let tmp = parser.parseFromString(str, "text/html");
let tmp = parser.parseFromString(s, "text/html");
return tmp.body.firstElementChild;
};
}
const toggleCss = (key, css, enable) => {
function toggleCss(key, css, enable) {
var style = document.getElementById(key);
if (enable && !style) {
style = document.createElement('style');
@ -414,10 +420,10 @@ const toggleCss = (key, css, enable) => {
style.innerHTML == '';
style.appendChild(document.createTextNode(css));
}
};
}
const copyToClipboard = s => {
function copyToClipboard(s) {
/** Copies the passed string to the clipboard. */
isStringThrowError(s);
navigator.clipboard.writeText(s);
};
}