class ForgeCouple { /** The fc_mapping \ */ static container = { "t2i": undefined, "i2i": undefined }; /** The actual \ */ static mappingTable = { "t2i": undefined, "i2i": undefined }; /** The floating \s for row controls */ static rowButtons = { "t2i": undefined, "i2i": undefined }; /** The \ for preview resolution */ static previewResolution = { "t2i": undefined, "i2i": undefined }; /** The \ to trigger preview */ static previewButton = { "t2i": undefined, "i2i": undefined }; /** The ForgeCoupleDataframe class */ static dataframe = { "t2i": undefined, "i2i": undefined }; /** The ForgeCoupleBox class */ static bbox = { "t2i": undefined, "i2i": undefined }; /** The \ for SendTo buttons */ static pasteField = { "t2i": undefined, "i2i": undefined }; /** The \ for internal updates */ static entryField = { "t2i": undefined, "i2i": undefined }; /** * After updating the mappings, trigger a preview * @param {string} mode "t2i" | "i2i" */ static async preview(mode) { setTimeout(async () => { var res = null; if (mode === "t2i") { const w = parseInt(document.getElementById("txt2img_width").querySelector("input").value); const h = parseInt(document.getElementById("txt2img_height").querySelector("input").value); res = `${Math.max(64, w)}x${Math.max(64, h)}`; } else { const i2i_size = document.getElementById("img2img_column_size").querySelector(".tab-nav"); if (i2i_size.children[0].classList.contains("selected")) { // Resize to const w = parseInt(document.getElementById("img2img_width").querySelector("input").value); const h = parseInt(document.getElementById("img2img_height").querySelector("input").value); res = `${Math.max(64, w)}x${Math.max(64, h)}`; } else { // Resize by res = document.getElementById("img2img_scale_resolution_preview")?.querySelector(".resolution")?.textContent; } } res ??= "1024x1024"; this.previewResolution[mode].value = res; updateInput(this.previewResolution[mode]); this.previewButton[mode].click(); }, (mode === "t2i") ? 25 : 50); } /** * Update the color of the rows based on the order and selection * @param {string} mode "t2i" | "i2i" */ static updateColors(mode) { const [color, row] = this.dataframe[mode].updateColors(); if (color) { this.bbox[mode].showBox(color, row); return row; } else { this.bbox[mode].hideBox(); this.rowButtons[mode].style.display = "none"; return null; } } /** * When using SendTo buttons, refresh the table * @param {string} mode "t2i" | "i2i" */ static onPaste(mode) { const vals = JSON.parse(this.pasteField[mode].value); this.dataframe[mode].onPaste(vals); this.preview(mode); } /** * When clicking on a row, update the index * @param {string} mode "t2i" | "i2i" */ static onSelect(mode) { const cell = this.updateColors(mode); if (cell) { const bounding = cell.querySelector("td").getBoundingClientRect(); const bounding_container = this.container[mode].getBoundingClientRect(); this.rowButtons[mode].style.top = `calc(${bounding.top - bounding_container.top}px - 1.5em)`; this.rowButtons[mode].style.display = "block"; } else this.rowButtons[mode].style.display = "none"; } /** * When editing the mapping, update the internal JSON * @param {string} mode "t2i" | "i2i" */ static onEntry(mode) { const rows = this.mappingTable[mode].querySelectorAll("tr"); const vals = Array.from(rows, row => { return Array.from(row.querySelectorAll("td")) .slice(0, -1).map(cell => parseFloat(cell.textContent)); }); const json = JSON.stringify(vals); this.entryField[mode].value = json; updateInput(this.entryField[mode]); } /** * Link the buttons related to the mapping * @param {Element} ex * @param {string} mode "t2i" | "i2i" */ static #registerButtons(ex, mode) { ex.querySelector(".fc_reset_btn").onclick = () => { this.dataframe[mode].reset(); }; ex.querySelector("#fc_up_btn").onclick = (e) => { this.dataframe[mode].newRowAbove(e.shiftKey); }; ex.querySelector("#fc_dn_btn").onclick = (e) => { this.dataframe[mode].newRowBelow(e.shiftKey); }; ex.querySelector("#fc_del_btn").onclick = (e) => { this.dataframe[mode].deleteRow(e.shiftKey); }; } /** Hook some elements to automatically refresh the resolution */ static #registerResolutionHandles() { [["txt2img", "t2i"], ["img2img", "i2i"]].forEach(([tab, mode]) => { const btns = document.getElementById(`${tab}_dimensions_row`)?.querySelectorAll("button"); if (btns != null) btns.forEach((btn) => { btn.onclick = () => { this.preview(mode); } }); }); const i2i_size_btns = document.getElementById("img2img_column_size").querySelector(".tab-nav"); i2i_size_btns.addEventListener("click", () => { this.preview("i2i"); }); const tabs = document.getElementById('tabs').querySelector('.tab-nav'); tabs.addEventListener("click", () => { if (tabs.children[0].classList.contains("selected")) this.preview("t2i"); if (tabs.children[1].classList.contains("selected")) this.preview("i2i"); }); } static setup() { ["t2i", "i2i"].forEach((mode) => { const ex = document.getElementById(`forge_couple_${mode}`); const mapping_btns = ex.querySelector(".fc_mapping_btns"); this.container[mode] = ex.querySelector(".fc_mapping"); this.container[mode].appendChild(mapping_btns); this.dataframe[mode] = new ForgeCoupleDataframe( this.container[mode], mode, ex.querySelector(".fc_separator").querySelector("input") ); this.mappingTable[mode] = this.container[mode].querySelector("tbody"); this.rowButtons[mode] = ex.querySelector(".fc_row_btns"); this.rowButtons[mode].style.display = "none"; this.rowButtons[mode].querySelectorAll("button").forEach((btn) => { btn.setAttribute('style', 'margin: auto !important'); }); this.container[mode].appendChild(this.rowButtons[mode]); this.previewResolution[mode] = ex.querySelector(".fc_preview_res").querySelector("input"); this.previewButton[mode] = ex.querySelector(".fc_preview"); const preview_img = ex.querySelector("img"); preview_img.ondragstart = (e) => { e.preventDefault(); return false; }; preview_img.parentElement.style.overflow = "visible"; this.bbox[mode] = new ForgeCoupleBox(preview_img, mode); const bg_btns = ex.querySelector(".fc_bg_btns"); preview_img.parentElement.appendChild(bg_btns); ForgeCoupleImageLoader.setup(preview_img, bg_btns.querySelectorAll("button")) this.pasteField[mode] = ex.querySelector(".fc_paste_field").querySelector("textarea"); this.entryField[mode] = ex.querySelector(".fc_entry_field").querySelector("textarea"); this.#registerButtons(ex, mode); ForgeCoupleObserver.observe( mode, document.getElementById(`${mode === "t2i" ? "txt" : "img"}2img_prompt`).querySelector("textarea"), () => { this.dataframe[mode].syncPrompt(); } ); }); this.#registerResolutionHandles(); } } onUiLoaded(async () => { ForgeCouple.setup(); })