diff --git a/css/icons.css b/css/icons.css index ee50b80..2249b6e 100644 --- a/css/icons.css +++ b/css/icons.css @@ -37,3 +37,8 @@ mask-image: url("/res/icons/chevron-first.svg"); transform: rotate(-90deg); } + +.ui.icon > .icon-settings { + -webkit-mask-image: url("/res/icons/settings.svg"); + mask-image: url("/res/icons/settings.svg"); +} diff --git a/css/index.css b/css/index.css index 64b7cc8..abc7604 100644 --- a/css/index.css +++ b/css/index.css @@ -20,6 +20,10 @@ body { overflow: clip; } +.invisible { + display: none !important; +} + .collapsible { background-color: rgb(0, 0, 0); color: rgb(255, 255, 255); @@ -35,6 +39,10 @@ body { margin-bottom: 5px; } +.display-none { + display: none; +} + .collapsible:hover { background-color: #777; } @@ -64,6 +72,89 @@ body { cursor: auto; } +#page-overlay-wrapper { + position: fixed; + + display: flex; + align-items: center; + justify-content: center; + + top: 0; + left: 0; + bottom: 0; + right: 0; + + background-color: #fff6; + backdrop-filter: blur(5px); + + transition-duration: 50ms; + + z-index: 1000; +} + +.page-overlay-window { + display: flex; + flex-direction: column; + align-items: stretch; + + border-radius: 10px; + + color: var(--c-text); + + overflow: hidden; + + position: absolute; + + margin: auto; + + background-color: var(--c-primary); +} + +.page-overlay-window .close { + position: absolute; + + cursor: pointer; + + top: 0; + right: 0; + + margin: 5px; + + width: 25px; + height: 25px; + + -webkit-mask-image: url("/res/icons/x.svg"); + mask-image: url("/res/icons/x.svg"); + + background-color: var(--c-text); +} + +.page-overlay-window .close:hover { + transform: scale(1.1); +} + +.page-overlay-window .title { + padding: 10px; + padding-top: 7px; + + font-size: large; + font-weight: bold; + + margin: auto; + + background-color: var(--c-primary); +} + +#page-overlay { + border: 0; + + max-width: 300px; + max-height: 400px; + + width: 100%; + height: 100%; +} + /* Mask colors for mask inversion */ /* Filters are some magic acquired at https://codepen.io/sosuke/pen/Pjoqqp */ .mask-canvas { @@ -182,6 +273,31 @@ input#host { box-sizing: border-box; } +/* Settings button */ +.ui.icon.header-button { + padding: 0; + border: 0; + + cursor: pointer; + + background-color: transparent; +} + +.ui.icon.header-button > *:first-child { + background-color: black; + + -webkit-mask-size: contain; + mask-size: contain; + + width: 28px; + height: 28px; + transition-duration: 30ms; +} + +.ui.icon.header-button:hover > *:last-child { + transform: scale(1.1); +} + /* Prompt Fields */ div.prompt-wrapper { diff --git a/css/ui/generic.css b/css/ui/generic.css index a7c2271..7be86b1 100644 --- a/css/ui/generic.css +++ b/css/ui/generic.css @@ -7,15 +7,18 @@ } .floating-window-title { + display: flex; + align-items: center; + justify-content: center; + cursor: move; background-color: rgba(104, 104, 104, 0.75); user-select: none; - padding-left: 5px; - padding-right: 5px; - padding-top: 5px; - padding-bottom: 5px; + padding: 5px; + padding-left: 10px; + margin-bottom: auto; font-size: 1.5em; color: black; @@ -40,6 +43,8 @@ div.slider-wrapper { position: relative; height: 20px; border-radius: 5px; + + overflow-y: visible; } div.slider-wrapper * { @@ -95,6 +100,51 @@ div.slider-wrapper > input.text { background-color: transparent; } +/* Autocomplete Select */ +div.autocomplete { + border-radius: 5px; +} + +div.autocomplete > .autocomplete-text { + box-sizing: border-box; + + border-radius: 5px; + + width: 100%; +} + +div.autocomplete > .autocomplete-list { + position: absolute; + + background-color: white; + + overflow-y: auto; + + margin-top: 0; + margin-left: 0; + + max-height: 200px; + min-width: 100%; + max-width: 800px; + + width: fit-content; + z-index: 200; +} + +div.autocomplete > .autocomplete-list > .autocomplete-option { + cursor: pointer; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + padding: 3px; +} + +div.autocomplete > .autocomplete-list > .autocomplete-option:hover { + background-color: #dddf; +} + /* Select Input */ select > option:checked::after { content: ""; diff --git a/index.html b/index.html index f425892..1b27704 100644 --- a/index.html +++ b/index.html @@ -32,6 +32,10 @@ style="left: 10px; top: 10px">
openOutpaint 🐠 +
+
+ + diff --git a/js/index.js b/js/index.js index 28d1acd..7a3049d 100644 --- a/js/index.js +++ b/js/index.js @@ -111,7 +111,6 @@ function startup() { }; drawBackground(); - changeSampler(); changeMaskBlur(); changeSmoothRendering(); changeSeed(); @@ -392,16 +391,6 @@ function drawMarchingAnts(ctx, bb, offset, options) { ctx.restore(); } -function changeSampler() { - if (!document.getElementById("samplerSelect").value == "") { - // must be done, since before getSamplers is done, the options are empty - console.log(document.getElementById("samplerSelect").value == ""); - stableDiffusionData.sampler_index = - document.getElementById("samplerSelect").value; - localStorage.setItem("sampler", stableDiffusionData.sampler_index); - } -} - const makeSlider = ( label, el, @@ -435,6 +424,21 @@ const makeSlider = ( }); }; +const modelAutoComplete = createAutoComplete( + "Model", + document.getElementById("models-ac-select") +); + +const samplerAutoComplete = createAutoComplete( + "Sampler", + document.getElementById("sampler-ac-select") +); + +const upscalerAutoComplete = createAutoComplete( + "Upscaler", + document.getElementById("upscaler-ac-select") +); + makeSlider( "Resolution", document.getElementById("resolution"), @@ -538,7 +542,7 @@ function drawBackground() { } } -function getUpscalers() { +async function getUpscalers() { /* so for some reason when upscalers request returns upscalers, the real-esrgan model names are incorrect, and need to be fetched from /sdapi/v1/realesrgan-models also the realesrgan models returned are not all correct, extra fun! @@ -551,12 +555,9 @@ function getUpscalers() { */ // hacky way to get the correct list of upscalers - var upscalerSelect = document.getElementById("upscalers"); var extras_url = document.getElementById("host").value + "/sdapi/v1/extra-single-image/"; // endpoint for upscaling, needed for the hacky way to get the correct list of upscalers - var empty_image = new Image(512, 512); - empty_image.src = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAFCAAAAABCAYAAAChpRsuAAAALklEQVR42u3BAQ0AAAgDoJvc6LeHAybtBgAAAAAAAAAAAAAAAAAAAAAAAAB47QD2wAJ/LnnqGgAAAABJRU5ErkJggg=="; //transparent pixel + var empty_image = new Image(1, 1); var purposefully_incorrect_data = { "resize-mode": 0, // 0 = just resize, 1 = crop and resize, 2 = resize and fill i assume based on theimg2img tabs options upscaling_resize: 2, @@ -564,27 +565,36 @@ function getUpscalers() { image: empty_image.src, }; - fetch(extras_url, { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify(purposefully_incorrect_data), - }) - .then((response) => response.json()) - .then((data) => { - console.log("purposefully_incorrect_data response, ignore above error"); - // result = purposefully_incorrect_data response: Invalid upscaler, needs to be on of these: None , Lanczos , Nearest , LDSR , BSRGAN , R-ESRGAN General 4xV3 , R-ESRGAN 4x+ Anime6B , ScuNET , ScuNET PSNR , SwinIR_4x - let upscalers = data.detail.split(": ")[1].trim().split(" , "); // converting the result to a list of upscalers - for (var i = 0; i < upscalers.length; i++) { - // if(upscalers[i] == "LDSR") continue; // Skip LDSR, see reason in the first comment // readded because worksonmymachine.jpg but leaving it here in case of, uh, future disaster? - var option = document.createElement("option"); - option.text = upscalers[i]; - option.value = upscalers[i]; - upscalerSelect.add(option); - } + try { + const response = await fetch(extras_url, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(purposefully_incorrect_data), }); + const data = await response.json(); + + console.log( + "[index] purposefully_incorrect_data response, ignore above error" + ); + // result = purposefully_incorrect_data response: Invalid upscaler, needs to be on of these: None , Lanczos , Nearest , LDSR , BSRGAN , R-ESRGAN General 4xV3 , R-ESRGAN 4x+ Anime6B , ScuNET , ScuNET PSNR , SwinIR_4x + const upscalers = data.detail + .split(": ")[1] + .split(",") + .map((v) => v.trim()) + .filter((v) => v !== "None"); // converting the result to a list of upscalers + + upscalerAutoComplete.options = upscalers.map((u) => { + return {name: u, value: u}; + }); + + upscalerAutoComplete.value = upscalers[0]; + } catch (e) { + console.warn("[index] Failed to fetch upscalers:"); + console.warn(e); + } /* THE NON HACKY WAY THAT I SIMPLY COULD NOT GET TO PRODUCE A LIST WITHOUT NON WORKING UPSCALERS, FEEL FREE TO TRY AND FIGURE IT OUT @@ -631,62 +641,59 @@ function getUpscalers() { } async function getModels() { - var modelSelect = document.getElementById("models"); var url = document.getElementById("host").value + "/sdapi/v1/sd-models"; - await fetch(url) - .then((response) => response.json()) - .then((data) => { - //console.log(data); All models - for (var i = 0; i < data.length; i++) { - var option = document.createElement("option"); - option.text = data[i].model_name; - option.value = data[i].title; - modelSelect.add(option); - } - }); + try { + const response = await fetch(url); + const data = await response.json(); - // get currently loaded model + modelAutoComplete.options = data.map((option) => ({ + name: option.title, + value: option.title, + })); - await fetch(document.getElementById("host").value + "/sdapi/v1/options") - .then((response) => response.json()) - .then((data) => { - var model = data.sd_model_checkpoint; - console.log("Current model: " + model); - modelSelect.value = model; - }); -} - -function changeModel() { - // change the model - console.log("changing model to " + document.getElementById("models").value); - var model_title = document.getElementById("models").value; - var payload = { - sd_model_checkpoint: model_title, - }; - var url = document.getElementById("host").value + "/sdapi/v1/options/"; - fetch(url, { - method: "POST", - mode: "cors", // no-cors, *cors, same-origin - cache: "default", // *default, no-cache, reload, force-cache, only-if-cached - credentials: "same-origin", // include, *same-origin, omit - redirect: "follow", // manual, *follow, error - referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify(payload), - }) - .then((response) => response.json()) - .then(() => { - alert("Model changed to " + model_title); - }) - .catch((error) => { - alert( - "Error changing model, please check console for additional info\n" + - error + try { + const optResponse = await fetch( + document.getElementById("host").value + "/sdapi/v1/options" ); - }); + const optData = await optResponse.json(); + + const model = optData.sd_model_checkpoint; + console.log("Current model: " + model); + modelAutoComplete.value = model; + } catch (e) { + console.warn("[index] Failed to fetch current model:"); + console.warn(e); + } + } catch (e) { + console.warn("[index] Failed to fetch models:"); + console.warn(e); + } + + modelAutoComplete.onchange.on(async ({value}) => { + console.log(`[index] Changing model to [${value}]`); + var payload = { + sd_model_checkpoint: value, + }; + var url = document.getElementById("host").value + "/sdapi/v1/options/"; + try { + await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + alert(`Model changed to [${value}]`); + } catch (e) { + console.warn("[index] Error changing model"); + console.warn(e); + + alert( + "Error changing model, please check console for additional information" + ); + } + }); } async function getConfig() { @@ -820,35 +827,33 @@ function changeStyles() { stableDiffusionData.styles = selectedString; } -function getSamplers() { - var samplerSelect = document.getElementById("samplerSelect"); +async function getSamplers() { var url = document.getElementById("host").value + "/sdapi/v1/samplers"; - fetch(url) - .then((response) => response.json()) - .then((data) => { - //console.log(data); All samplers - for (var i = 0; i < data.length; i++) { - // PLMS SAMPLER DOES NOT WORK FOR ANY IMAGES BEYOND FOR THE INITIAL IMAGE (for me at least), GIVES ASGI Exception; AttributeError: 'PLMSSampler' object has no attribute 'stochastic_encode' - var option = document.createElement("option"); - option.text = data[i].name; - option.value = data[i].name; - samplerSelect.add(option); - } - if (localStorage.getItem("sampler") != null) { - samplerSelect.value = localStorage.getItem("sampler"); - } else { - // needed now, as hardcoded sampler cant be guaranteed to be in the list - samplerSelect.value = data[0].name; - localStorage.setItem("sampler", samplerSelect.value); - } - }) - .catch((error) => { - alert( - "Error getting samplers, please check console for additional info\n" + - error - ); + try { + const response = await fetch(url); + const data = await response.json(); + samplerAutoComplete.options = data.map((sampler) => ({ + name: sampler.name, + value: sampler.name, + })); + + // Initial sampler + if (localStorage.getItem("sampler") != null) { + samplerAutoComplete.value = localStorage.getItem("sampler"); + } else { + samplerAutoComplete.value = data[0].name; + localStorage.setItem("sampler", samplerAutoComplete.value); + } + + samplerAutoComplete.onchange.on(({value}) => { + stableDiffusionData.sampler_index = value; + localStorage.setItem("sampler", value); }); + } catch (e) { + console.warn("[index] Failed to fetch samplers"); + console.warn(e); + } } async function upscaleAndDownload() { // Future improvements: some upscalers take a while to upscale, so we should show a loading bar or something, also a slider for the upscale amount @@ -857,7 +862,7 @@ async function upscaleAndDownload() { var upscale_factor = localStorage.getItem("upscale_x") ? localStorage.getItem("upscale_x") : 2; - var upscaler = document.getElementById("upscalers").value; + var upscaler = upscalerAutoComplete.value; var croppedCanvas = cropCanvas( uil.getVisible({ x: 0, @@ -867,7 +872,6 @@ async function upscaleAndDownload() { }) ); if (croppedCanvas != null) { - var upscaler = document.getElementById("upscalers").value; var url = document.getElementById("host").value + "/sdapi/v1/extra-single-image/"; var imgdata = croppedCanvas.canvas.toDataURL("image/png"); @@ -917,10 +921,6 @@ function loadSettings() { localStorage.getItem("neg_prompt") == null ? "people, person, humans, human, divers, diver, glitch, error, text, watermark, bad quality, blurry" : localStorage.getItem("neg_prompt"); - var _sampler = - localStorage.getItem("sampler") == null - ? "DDIM" - : localStorage.getItem("sampler"); var _mask_blur = localStorage.getItem("mask_blur") == null ? 0 @@ -938,7 +938,6 @@ function loadSettings() { document.getElementById("prompt").title = String(_prompt); document.getElementById("negPrompt").value = String(_negprompt); document.getElementById("negPrompt").title = String(_negprompt); - document.getElementById("samplerSelect").value = String(_sampler); document.getElementById("maskBlur").value = Number(_mask_blur); document.getElementById("seed").value = Number(_seed); document.getElementById("cbxHRFix").checked = Boolean(_enable_hr); diff --git a/js/initalize/layers.populate.js b/js/initalize/layers.populate.js index b867f0f..4094e85 100644 --- a/js/initalize/layers.populate.js +++ b/js/initalize/layers.populate.js @@ -1,7 +1,14 @@ // Layering const imageCollection = layers.registerCollection( "image", - {w: 2560, h: 1536}, + { + w: parseInt( + (localStorage && localStorage.getItem("settings.canvas-width")) || 2048 + ), + h: parseInt( + (localStorage && localStorage.getItem("settings.canvas-height")) || 2048 + ), + }, { name: "Image Layers", } @@ -62,12 +69,6 @@ mouse.registerContext( ctx.coords.prev.x = ctx.coords.pos.x; ctx.coords.prev.y = ctx.coords.pos.y; - if (evn.layerX !== evn.clientX || evn.layerY !== evn.clientY) { - ctx.coords.pos.x = evn.layerX; - ctx.coords.pos.y = evn.layerY; - return; - } - // Get element bounding rect const bb = imageCollection.element.getBoundingClientRect(); diff --git a/js/initalize/ui.populate.js b/js/initalize/ui.populate.js index ba8b43c..0b9eb58 100644 --- a/js/initalize/ui.populate.js +++ b/js/initalize/ui.populate.js @@ -1,3 +1,6 @@ +/** + * Floating window setup + */ document.querySelectorAll(".floating-window").forEach( /** * Runs for each floating window @@ -24,6 +27,9 @@ document.querySelectorAll(".floating-window").forEach( } ); +/** + * Collapsible element setup + */ var coll = document.getElementsByClassName("collapsible"); for (var i = 0; i < coll.length; i++) { let active = false; @@ -55,3 +61,14 @@ for (var i = 0; i < coll.length; i++) { } }); } + +/** + * Settings overlay setup + */ +document.getElementById("settings-btn").addEventListener("click", () => { + document.getElementById("page-overlay-wrapper").classList.toggle("invisible"); +}); + +document.getElementById("settings-btn-close").addEventListener("click", () => { + document.getElementById("page-overlay-wrapper").classList.toggle("invisible"); +}); diff --git a/js/lib/layers.js b/js/lib/layers.js index 7c32372..eedd272 100644 --- a/js/lib/layers.js +++ b/js/lib/layers.js @@ -28,6 +28,9 @@ const layers = { options: {}, }, + // Input multiplier (Size of the input element div) + inputSizeMultiplier: 3, + // Target targetElement: document.getElementById("layer-render"), @@ -35,6 +38,8 @@ const layers = { resolution: size, }); + if (options.inputSizeMultiplier % 2 === 0) options.inputSizeMultiplier++; + // Path used for logging purposes const _logpath = "layers.collections." + key; @@ -51,8 +56,6 @@ const layers = { // Input element (overlay element for input handling) const inputel = document.createElement("div"); inputel.id = `collection-input-${id}`; - inputel.style.width = `${size.w}px`; - inputel.style.height = `${size.h}px`; inputel.addEventListener("mouseover", (evn) => { document.activeElement.blur(); }); @@ -73,6 +76,28 @@ const layers = { name: options.name, element, inputElement: inputel, + _inputOffset: null, + get inputOffset() { + return this._inputOffset; + }, + + _resizeInputDiv() { + // Set offset + this._inputOffset = { + x: -Math.floor(options.inputSizeMultiplier / 2) * size.w, + y: -Math.floor(options.inputSizeMultiplier / 2) * size.h, + }; + + // Resize the input element + this.inputElement.style.left = `${this.inputOffset.x}px`; + this.inputElement.style.top = `${this.inputOffset.y}px`; + this.inputElement.style.width = `${ + size.w * options.inputSizeMultiplier + }px`; + this.inputElement.style.height = `${ + size.h * options.inputSizeMultiplier + }px`; + }, size, resolution: options.resolution, @@ -278,9 +303,12 @@ const layers = { else console.debug(`[layers] Anonymous layer '${lobj.id}' deleted`); }, }, - _logpath + _logpath, + ["_inputOffset"] ); + collection._resizeInputDiv(); + layers._collections.push(collection); layers.collections[key] = collection; diff --git a/js/lib/ui.js b/js/lib/ui.js index 0fb0373..bbb3f3a 100644 --- a/js/lib/ui.js +++ b/js/lib/ui.js @@ -192,3 +192,175 @@ function createSlider(name, wrapper, options = {}) { }, }; } + +/** + * A function to transform a div into a autocompletable select element + * + * @param {string} name Name of the AutoComplete Select Element + * @param {HTMLDivElement} wrapper The div element that will wrap the input elements + * @param {object} options Extra options + * @param {{name: string, value: string}} options.options Options to add to the selector + * @returns {AutoCompleteElement} + */ +function createAutoComplete(name, wrapper, options = {}) { + defaultOpt(options, { + options: [], + }); + + wrapper.classList.add("autocomplete"); + + const inputEl = document.createElement("input"); + inputEl.type = "text"; + inputEl.classList.add("autocomplete-text"); + + const autocompleteEl = document.createElement("div"); + autocompleteEl.classList.add("autocomplete-list", "display-none"); + + let timeout = null; + let ontext = false; + let onlist = false; + + wrapper.appendChild(inputEl); + wrapper.appendChild(autocompleteEl); + + const acobj = { + name, + wrapper, + _title: null, + _value: null, + _options: [], + + /** @type {Observer<{name:string, value: string}>} */ + onchange: new Observer(), + + get value() { + return this._value; + }, + set value(val) { + const opt = this.options.find((option) => option.value === val); + + if (!opt) return; + + this._title = opt.name; + this._value = opt.value; + inputEl.value = opt.name; + inputEl.title = opt.name; + + this.onchange.emit({name: opt.name, value: opt.value}); + }, + + get options() { + return this._options; + }, + set options(val) { + this._options = []; + + while (autocompleteEl.lastChild) { + autocompleteEl.removeChild(autocompleteEl.lastChild); + } + + // Add options + val.forEach((opt) => { + const {name, value} = opt; + const option = {name, value}; + + const optionEl = document.createElement("option"); + optionEl.classList.add("autocomplete-option"); + optionEl.title = option.name; + optionEl.addEventListener("click", () => select(option)); + + this._options.push({name, value, optionElement: optionEl}); + + autocompleteEl.appendChild(optionEl); + }); + + updateOptions(); + }, + }; + + function updateOptions() { + const text = inputEl.value.toLowerCase().trim(); + + acobj._options.forEach((opt) => { + const textLocation = opt.name.toLowerCase().indexOf(text); + + while (opt.optionElement.lastChild) { + opt.optionElement.removeChild(opt.optionElement.lastChild); + } + + opt.optionElement.append( + document.createTextNode(opt.name.substring(0, textLocation)) + ); + const span = document.createElement("span"); + span.style.fontWeight = "bold"; + span.textContent = opt.name.substring( + textLocation, + textLocation + text.length + ); + opt.optionElement.appendChild(span); + opt.optionElement.appendChild( + document.createTextNode( + opt.name.substring(textLocation + text.length, opt.name.length) + ) + ); + + if (textLocation !== -1) { + opt.optionElement.classList.remove("display-none"); + } else opt.optionElement.classList.add("display-none"); + }); + } + + function select(options) { + ontext = false; + onlist = false; + + acobj._title = options.name; + inputEl.value = options.name; + acobj.value = options.value; + + autocompleteEl.classList.add("display-none"); + } + + inputEl.addEventListener("focus", () => { + ontext = true; + + autocompleteEl.classList.remove("display-none"); + inputEl.select(); + }); + inputEl.addEventListener("blur", () => { + ontext = false; + + if (!onlist && !ontext) { + inputEl.value = ""; + updateOptions(); + inputEl.value = acobj._title; + + autocompleteEl.classList.add("display-none"); + } + }); + + autocompleteEl.addEventListener("mouseenter", () => { + onlist = true; + }); + + autocompleteEl.addEventListener("mouseleave", () => { + onlist = false; + + if (!onlist && !ontext) { + inputEl.value = ""; + updateOptions(); + inputEl.value = acobj._title; + + autocompleteEl.classList.add("display-none"); + } + }); + + // Filter + inputEl.addEventListener("input", () => { + updateOptions(); + }); + + acobj.options = options.options; + + return acobj; +} diff --git a/js/ui/tool/colorbrush.js b/js/ui/tool/colorbrush.js index dfaa640..b3b7805 100644 --- a/js/ui/tool/colorbrush.js +++ b/js/ui/tool/colorbrush.js @@ -166,7 +166,7 @@ const colorBrushTool = () => const vcp = {x: evn.evn.clientX, y: evn.evn.clientY}; // draw drawing cursor - uiCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); + uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height); uiCtx.beginPath(); uiCtx.arc( diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js index b466e33..fdd5733 100644 --- a/js/ui/tool/dream.js +++ b/js/ui/tool/dream.js @@ -122,8 +122,8 @@ const _generate = async ( // Images to select through let at = 0; - /** @type {Image[]} */ - const images = []; + /** @type {Array} */ + const images = [null]; /** @type {HTMLDivElement} */ let imageSelectMenu = null; @@ -135,8 +135,8 @@ const _generate = async ( const makeElement = (type, x, y) => { const el = document.createElement(type); el.style.position = "absolute"; - el.style.left = `${x}px`; - el.style.top = `${y}px`; + el.style.left = `${x - imageCollection.inputOffset.x}px`; + el.style.top = `${y - imageCollection.inputOffset.y}px`; // We can use the input element to add interactible html elements in the world imageCollection.inputElement.appendChild(el); @@ -145,6 +145,8 @@ const _generate = async ( }; const redraw = (url = images[at]) => { + if (url === null) + layer.ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height); if (!url) return; const image = new Image(); @@ -203,6 +205,7 @@ const _generate = async ( imageCollection.inputElement.appendChild(interruptButton); images.push(...(await _dream(endpoint, requestCopy))); stopDrawingStatus = true; + at = 1; } catch (e) { alert( `Error generating images. Please try again or see consolde for more details` @@ -219,7 +222,7 @@ const _generate = async ( at--; if (at < 0) at = images.length - 1; - imageindextxt.textContent = `${at + 1}/${images.length}`; + imageindextxt.textContent = `${at}/${images.length}`; redraw(); }; @@ -227,7 +230,7 @@ const _generate = async ( at++; if (at >= images.length) at = 0; - imageindextxt.textContent = `${at + 1}/${images.length}`; + imageindextxt.textContent = `${at}/${images.length}`; redraw(); }; @@ -253,7 +256,7 @@ const _generate = async ( interruptButton.disabled = false; imageCollection.inputElement.appendChild(interruptButton); images.push(...(await _dream(endpoint, requestCopy))); - imageindextxt.textContent = `${at + 1}/${images.length}`; + imageindextxt.textContent = `${at}/${images.length}`; } catch (e) { alert( `Error generating images. Please try again or see consolde for more details` @@ -327,11 +330,11 @@ const _generate = async ( imageSelectMenu = makeElement("div", bb.x, bb.y + bb.h); const imageindextxt = document.createElement("button"); - imageindextxt.textContent = `${at + 1}/${images.length}`; + imageindextxt.textContent = `${at}/${images.length}`; imageindextxt.addEventListener("click", () => { at = 0; - imageindextxt.textContent = `${at + 1}/${images.length}`; + imageindextxt.textContent = `${at}/${images.length}`; redraw(); }); diff --git a/pages/configuration.html b/pages/configuration.html new file mode 100644 index 0000000..7203f32 --- /dev/null +++ b/pages/configuration.html @@ -0,0 +1,83 @@ + + + + + openOutpaint 🐠 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/icons/settings.svg b/res/icons/settings.svg new file mode 100644 index 0000000..88b4797 --- /dev/null +++ b/res/icons/settings.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/res/icons/x.svg b/res/icons/x.svg new file mode 100644 index 0000000..95d4bc1 --- /dev/null +++ b/res/icons/x.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file