diff --git a/javascript/dragdrop.js b/javascript/dragdrop.js index eca83496..2e6f20b9 100644 --- a/javascript/dragdrop.js +++ b/javascript/dragdrop.js @@ -1,25 +1,16 @@ -// allows drag-dropping files into gradio image elements, and also pasting images from clipboard +// allows drag-and-drop files into prompt fields, and also pasting images from clipboard function isValidImageList(files) { - return ( - files && - files?.length === 1 && - ["image/png", "image/gif", "image/jpeg"].includes(files[0].type) - ); + return files != null && files.length === 1 && files[0].type.startsWith("image"); } function dropReplaceImage(imgWrap, files) { - if (!isValidImageList(files)) { - return; - } + if (!isValidImageList(files)) return; const tmpFile = files[0]; - imgWrap - .querySelector( - ".modify-upload button + button, .touch-none + div button + button", - ) - ?.click(); + imgWrap.querySelector(".modify-upload button + button, .touch-none + div button + button")?.click(); + const callback = () => { const fileInput = imgWrap.querySelector('input[type="file"]'); if (fileInput) { @@ -40,8 +31,7 @@ function dropReplaceImage(imgWrap, files) { function eventHasFiles(e) { if (!e.dataTransfer || !e.dataTransfer.files) return false; if (e.dataTransfer.files.length > 0) return true; - if (e.dataTransfer.items.length > 0 && e.dataTransfer.items[0].kind == "file") - return true; + if (e.dataTransfer.items.length > 0 && e.dataTransfer.items[0].kind == "file") return true; return false; } @@ -56,10 +46,8 @@ function isURL(url) { } function dragDropTargetIsPrompt(target) { - if (target?.placeholder && target?.placeholder.indexOf("Prompt") >= 0) - return true; - if (target?.parentNode?.parentNode?.className?.indexOf("prompt") > 0) - return true; + if (target?.placeholder && target?.placeholder.indexOf("Prompt") >= 0) return true; + if (target?.parentNode?.parentNode?.className?.indexOf("prompt") > 0) return true; return false; } @@ -67,7 +55,7 @@ window.document.addEventListener("dragover", (e) => { const target = e.composedPath()[0]; if (!eventHasFiles(e)) return; - let targetImage = target.closest('[data-testid="image"]'); + const targetImage = target.closest('[data-testid="image"]'); if (!dragDropTargetIsPrompt(target) && !targetImage) return; e.stopPropagation(); @@ -77,9 +65,7 @@ window.document.addEventListener("dragover", (e) => { window.document.addEventListener("drop", async (e) => { const target = e.composedPath()[0]; - const url = - e.dataTransfer.getData("text/uri-list") || - e.dataTransfer.getData("text/plain"); + const url = e.dataTransfer.getData("text/uri-list") || e.dataTransfer.getData("text/plain"); if (!eventHasFiles(e) && !isURL(url)) return; if (dragDropTargetIsPrompt(target)) { @@ -87,9 +73,7 @@ window.document.addEventListener("drop", async (e) => { e.preventDefault(); const isImg2img = get_tab_index("tabs") == 1; - let prompt_image_target = isImg2img - ? "img2img_prompt_image" - : "txt2img_prompt_image"; + const prompt_image_target = isImg2img ? "img2img_prompt_image" : "txt2img_prompt_image"; const imgParent = gradioApp().getElementById(prompt_image_target); const files = e.dataTransfer.files; @@ -115,7 +99,7 @@ window.document.addEventListener("drop", async (e) => { } } - let targetImage = target.closest('[data-testid="image"]'); + const targetImage = target.closest('[data-testid="image"]'); if (targetImage) { e.stopPropagation(); e.preventDefault(); @@ -127,28 +111,18 @@ window.document.addEventListener("drop", async (e) => { window.addEventListener("paste", (e) => { const files = e.clipboardData.files; - if (!isValidImageList(files)) { - return; - } + if (!isValidImageList(files)) return; - const visibleImageFields = [ - ...gradioApp().querySelectorAll('[data-testid="image"]'), - ] + const visibleImageFields = [...gradioApp().querySelectorAll('[data-testid="image"]')] .filter((el) => uiElementIsVisible(el)) .sort((a, b) => uiElementInSight(b) - uiElementInSight(a)); - if (!visibleImageFields.length) { - return; - } + if (!visibleImageFields.length) return; - const firstFreeImageField = visibleImageFields.filter( - (el) => !el.querySelector("img"), - )?.[0]; + const firstFreeImageField = visibleImageFields.filter((el) => !el.querySelector("img"))?.[0]; dropReplaceImage( - firstFreeImageField - ? firstFreeImageField - : visibleImageFields[visibleImageFields.length - 1], + firstFreeImageField ? firstFreeImageField : visibleImageFields[visibleImageFields.length - 1], files, ); }); diff --git a/javascript/imageDragAndDrop.js b/javascript/imageDragAndDrop.js index f8b3f51d..4338286a 100644 --- a/javascript/imageDragAndDrop.js +++ b/javascript/imageDragAndDrop.js @@ -1,55 +1,27 @@ (function () { - function extractImageFromHTML(html) { - if (!html) return null; - const match = html.match(/]+src="([^">]+)"/i); - return match ? match[1] : null; - } - /** @param {HTMLDivElement} gradioImage */ function patchDragAndDrop(gradioImage) { - let isDroppingImage = false; - gradioImage.addEventListener("dragover", (e) => { const dt = e.dataTransfer; - isDroppingImage = dt.types.includes("text/uri-list") || dt.types.includes("text/html"); - if (isDroppingImage) e.preventDefault(); - }); - - gradioImage.addEventListener("drop", async (e) => { + const isDroppingImage = dt.types.includes("text/uri-list") || dt.types.includes("text/html"); if (!isDroppingImage) return; - const dt = e.dataTransfer; - - const url = dt.getData("text/uri-list") || extractImageFromHTML(dt.getData("text/html")); - if (!url) return; - - e.preventDefault(); - - try { - const closeButton = gradioImage.querySelector('button[aria-label="Remove Image"]'); - closeButton.click(); - - const res = await fetch(url); - const blob = await res.blob(); - - const file = new File([blob], "dropped-image", { - type: blob.type || "image/png", - }); - - const newDT = new DataTransfer(); - newDT.items.add(file); - - const fileInput = gradioImage.querySelector('input[type="file"]'); - fileInput.files = newDT.files; - - fileInput.dispatchEvent(new Event("change", { bubbles: true })); - } catch { - console.log("Failed to replace image..."); - } + const closeButton = gradioImage.querySelector('button[aria-label="Remove Image"]'); + if (closeButton) closeButton.click(); }); } + function setup() { + if (opts.remove_image_on_hover === false) return; + if (opts.remove_image_on_hover === undefined) { + setTimeout(setup, 50); + return; + } + + for (const id of ["extras_image", "pnginfo_image"]) patchDragAndDrop(document.getElementById(id)); + } + onUiLoaded(() => { - for (const id of ["extras_image", "pnginfo_image"]) patchDragAndDrop(document.getElementById(id)); + setTimeout(setup, 100); }); })(); diff --git a/modules/shared_options.py b/modules/shared_options.py index b6b966c7..b3f47dc6 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -425,6 +425,7 @@ options_templates.update( "ctrl_enter_interrupt": OptionInfo(False, "Revert [Ctrl + Enter] to only interrupt the generation").info('the current "intended" behavior is to interrupt the current generation then immediately start a new one'), "quicksettings_accordion": OptionInfo(False, "Place the Quicksettings under an Accordion").needs_reload_ui(), "quicksettings_accordion_starts_closed": OptionInfo(False, "Close the Accordion on startup").info("for the above option").needs_reload_ui(), + "remove_image_on_hover": OptionInfo(True, "For image inputs in Extras and PNG Info, remove the current image when dragging another image over it").info("allow you to drag-and-drop images onto the input similar to AUTOMATIC1111 behavior").needs_reload_ui(), "forbidden_knowledge": OptionInfo(False, "Forbidden Knowledge").info('replace "DPM++ 2s a RF" with "Flux Realistic"').needs_restart(), "div_prompt": OptionDiv(), "prompt_box_style": OptionInfo("Default", "Prompt Layout", gr.Radio, {"choices": ("Default", "Compact", "Scrollable", "Accordion")}).html(f"""