diff options
-rw-r--r-- | extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js | 235 | ||||
-rw-r--r-- | modules/launch_utils.py | 1 | ||||
-rw-r--r-- | modules/restart.py | 23 | ||||
-rw-r--r-- | modules/shared.py | 9 | ||||
-rw-r--r-- | modules/ui_extensions.py | 11 | ||||
-rw-r--r-- | webui.bat | 2 | ||||
-rw-r--r-- | webui.py | 2 | ||||
-rwxr-xr-x | webui.sh | 1 |
8 files changed, 146 insertions, 138 deletions
diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js index 2a2ed999..4ecb3d36 100644 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js @@ -1,121 +1,125 @@ -// Helper functions -// Get active tab -function getActiveTab(elements, all = false) { - const tabs = elements.img2imgTabs.querySelectorAll("button"); +onUiLoaded(async() => { + const elementIDs = { + img2imgTabs: "#mode_img2img .tab-nav", + inpaint: "#img2maskimg", + inpaintSketch: "#inpaint_sketch", + rangeGroup: "#img2img_column_size", + sketch: "#img2img_sketch", + }; + const tabNameToElementId = { + "Inpaint sketch": elementIDs.inpaintSketch, + "Inpaint": elementIDs.inpaint, + "Sketch": elementIDs.sketch, + }; + + // Helper functions + // Get active tab + function getActiveTab(elements, all = false) { + const tabs = elements.img2imgTabs.querySelectorAll("button"); - if (all) return tabs; + if (all) return tabs; - for (let tab of tabs) { - if (tab.classList.contains("selected")) { - return tab; + for (let tab of tabs) { + if (tab.classList.contains("selected")) { + return tab; + } } } -} -// Get tab ID -function getTabId(elements, elementIDs) { - const activeTab = getActiveTab(elements); - const tabIdLookup = { - "Sketch": elementIDs.sketch, - "Inpaint sketch": elementIDs.inpaintSketch, - "Inpaint": elementIDs.inpaint - }; - return tabIdLookup[activeTab.innerText]; -} - -// Wait until opts loaded -async function waitForOpts() { - return new Promise(resolve => { - const checkInterval = setInterval(() => { - if (window.opts && Object.keys(window.opts).length !== 0) { - clearInterval(checkInterval); - resolve(window.opts); + // Get tab ID + function getTabId(elements) { + const activeTab = getActiveTab(elements); + return tabNameToElementId[activeTab.innerText]; + } + + // Wait until opts loaded + async function waitForOpts() { + for (;;) { + if (window.opts && Object.keys(window.opts).length) { + return window.opts; } - }, 100); - }); -} - -// Check is hotkey valid -function isSingleLetter(value) { - return ( - typeof value === "string" && value.length === 1 && /[a-z]/i.test(value) - ); -} + await new Promise(resolve => setTimeout(resolve, 100)); + } + } -// Create hotkeyConfig from opts -function createHotkeyConfig(defaultHotkeysConfig, hotkeysConfigOpts) { - const result = {}; - const usedKeys = new Set(); + // Check is hotkey valid + function isSingleLetter(value) { + return ( + typeof value === "string" && value.length === 1 && /[a-z]/i.test(value) + ); + } - for (const key in defaultHotkeysConfig) { - if (typeof hotkeysConfigOpts[key] === "boolean") { - result[key] = hotkeysConfigOpts[key]; - continue; - } - if ( - hotkeysConfigOpts[key] && - isSingleLetter(hotkeysConfigOpts[key]) && - !usedKeys.has(hotkeysConfigOpts[key].toUpperCase()) - ) { - // If the property passed the test and has not yet been used, add 'Key' before it and save it - result[key] = "Key" + hotkeysConfigOpts[key].toUpperCase(); - usedKeys.add(hotkeysConfigOpts[key].toUpperCase()); - } else { - // If the property does not pass the test or has already been used, we keep the default value - console.error( - `Hotkey: ${hotkeysConfigOpts[key]} for ${key} is repeated and conflicts with another hotkey or is not 1 letter. The default hotkey is used: ${defaultHotkeysConfig[key][3]}` - ); - result[key] = defaultHotkeysConfig[key]; + // Create hotkeyConfig from opts + function createHotkeyConfig(defaultHotkeysConfig, hotkeysConfigOpts) { + const result = {}; + const usedKeys = new Set(); + + for (const key in defaultHotkeysConfig) { + if (typeof hotkeysConfigOpts[key] === "boolean") { + result[key] = hotkeysConfigOpts[key]; + continue; + } + if ( + hotkeysConfigOpts[key] && + isSingleLetter(hotkeysConfigOpts[key]) && + !usedKeys.has(hotkeysConfigOpts[key].toUpperCase()) + ) { + // If the property passed the test and has not yet been used, add 'Key' before it and save it + result[key] = "Key" + hotkeysConfigOpts[key].toUpperCase(); + usedKeys.add(hotkeysConfigOpts[key].toUpperCase()); + } else { + // If the property does not pass the test or has already been used, we keep the default value + console.error( + `Hotkey: ${hotkeysConfigOpts[key]} for ${key} is repeated and conflicts with another hotkey or is not 1 letter. The default hotkey is used: ${defaultHotkeysConfig[key][3]}` + ); + result[key] = defaultHotkeysConfig[key]; + } } - } - return result; -} + return result; + } -/** - * The restoreImgRedMask function displays a red mask around an image to indicate the aspect ratio. - * If the image display property is set to 'none', the mask breaks. To fix this, the function - * temporarily sets the display property to 'block' and then hides the mask again after 300 milliseconds - * to avoid breaking the canvas. Additionally, the function adjusts the mask to work correctly on - * very long images. - */ + /** + * The restoreImgRedMask function displays a red mask around an image to indicate the aspect ratio. + * If the image display property is set to 'none', the mask breaks. To fix this, the function + * temporarily sets the display property to 'block' and then hides the mask again after 300 milliseconds + * to avoid breaking the canvas. Additionally, the function adjusts the mask to work correctly on + * very long images. + */ + function restoreImgRedMask(elements) { + const mainTabId = getTabId(elements); -function restoreImgRedMask(elements, elementIDs) { - const mainTabId = getTabId(elements, elementIDs); + if (!mainTabId) return; - if (!mainTabId) return; + const mainTab = gradioApp().querySelector(mainTabId); + const img = mainTab.querySelector("img"); + const imageARPreview = gradioApp().querySelector("#imageARPreview"); - const mainTab = gradioApp().querySelector(mainTabId); - const img = mainTab.querySelector("img"); - const imageARPreview = gradioApp().querySelector("#imageARPreview"); + if (!img || !imageARPreview) return; - if (!img || !imageARPreview) return; + imageARPreview.style.transform = ""; + if (parseFloat(mainTab.style.width) > 865) { + const transformString = mainTab.style.transform; + const scaleMatch = transformString.match(/scale\(([-+]?[0-9]*\.?[0-9]+)\)/); + let zoom = 1; // default zoom - imageARPreview.style.transform = ""; - if (parseFloat(mainTab.style.width) > 865) { - const transformString = mainTab.style.transform; - const scaleMatch = transformString.match(/scale\(([-+]?[0-9]*\.?[0-9]+)\)/); - let zoom = 1; // default zoom + if (scaleMatch && scaleMatch[1]) { + zoom = Number(scaleMatch[1]); + } - if (scaleMatch && scaleMatch[1]) { - zoom = Number(scaleMatch[1]); + imageARPreview.style.transformOrigin = "0 0"; + imageARPreview.style.transform = `scale(${zoom})`; } - imageARPreview.style.transformOrigin = "0 0"; - imageARPreview.style.transform = `scale(${zoom})`; - } - - if (img.style.display !== "none") return; + if (img.style.display !== "none") return; - img.style.display = "block"; + img.style.display = "block"; - setTimeout(() => { - img.style.display = "none"; - }, 400); -} + setTimeout(() => { + img.style.display = "none"; + }, 400); + } -// Main -onUiLoaded(async() => { const hotkeysConfigOpts = await waitForOpts(); // Default config @@ -137,38 +141,22 @@ onUiLoaded(async() => { let mouseX, mouseY; let activeElement; - const elementIDs = { - sketch: "#img2img_sketch", - inpaint: "#img2maskimg", - inpaintSketch: "#inpaint_sketch", - img2imgTabs: "#mode_img2img .tab-nav", - rangeGroup: "#img2img_column_size" - }; - - async function getElements() { - const elements = await Promise.all( - Object.values(elementIDs).map(id => gradioApp().querySelector(id)) - ); - return Object.fromEntries( - Object.keys(elementIDs).map((key, index) => [key, elements[index]]) - ); - } - - const elements = await getElements(); + const elements = Object.fromEntries(Object.keys(elementIDs).map((id) => [ + id, + gradioApp().querySelector(elementIDs[id]), + ])); const elemData = {}; // Apply functionality to the range inputs. Restore redmask and correct for long images. - const rangeInputs = elements.rangeGroup ? elements.rangeGroup.querySelectorAll("input") : + const rangeInputs = elements.rangeGroup ? Array.from(elements.rangeGroup.querySelectorAll("input")) : [ gradioApp().querySelector("#img2img_width input[type='range']"), gradioApp().querySelector("#img2img_height input[type='range']") ]; - rangeInputs.forEach(input => { - if (input) { - input.addEventListener("input", () => restoreImgRedMask(elements, elementIDs)); - } - }); + for (const input of rangeInputs) { + input?.addEventListener("input", () => restoreImgRedMask(elements)); + } function applyZoomAndPan(elemId) { const targetElement = gradioApp().querySelector(elemId); @@ -223,12 +211,11 @@ onUiLoaded(async() => { action: "Move canvas" } ]; - hotkeys.forEach(function(hotkey) { + for (const hotkey of hotkeys) { const p = document.createElement("p"); - p.innerHTML = - "<b>" + hotkey.key + "</b>" + " - " + hotkey.action; + p.innerHTML = `<b>${hotkey.key}</b> - ${hotkey.action}`; tooltipContent.appendChild(p); - }); + } // Add information and content elements to the tooltip element tooltip.appendChild(info); diff --git a/modules/launch_utils.py b/modules/launch_utils.py index af8d8b37..609a181e 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -247,6 +247,7 @@ def prepare_environment(): try:
# the existance of this file is a signal to webui.sh/bat that webui needs to be restarted when it stops execution
os.remove(os.path.join(script_path, "tmp", "restart"))
+ os.environ.setdefault('SD_WEBUI_RESTARTING ', '1')
except OSError:
pass
diff --git a/modules/restart.py b/modules/restart.py new file mode 100644 index 00000000..18eacaf3 --- /dev/null +++ b/modules/restart.py @@ -0,0 +1,23 @@ +import os +from pathlib import Path + +from modules.paths_internal import script_path + + +def is_restartable() -> bool: + """ + Return True if the webui is restartable (i.e. there is something watching to restart it with) + """ + return bool(os.environ.get('SD_WEBUI_RESTART')) + + +def restart_program() -> None: + """creates file tmp/restart and immediately stops the process, which webui.bat/webui.sh interpret as a command to start webui again""" + + (Path(script_path) / "tmp" / "restart").touch() + + stop_program() + + +def stop_program() -> None: + os._exit(0) diff --git a/modules/shared.py b/modules/shared.py index 2bd7c6ec..c9ee2dd1 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -853,12 +853,3 @@ def walk_files(path, allowed_extensions=None): continue
yield os.path.join(root, filename)
-
-
-def restart_program():
- """creates file tmp/restart and immediately stops the process, which webui.bat/webui.sh interpret as a command to start webui again"""
-
- with open(os.path.join(script_path, "tmp", "restart"), "w"):
- pass
-
- os._exit(0)
diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 1ae516d7..4379a641 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -11,7 +11,7 @@ import html import shutil
import errno
-from modules import extensions, shared, paths, config_states, errors
+from modules import extensions, shared, paths, config_states, errors, restart
from modules.paths_internal import config_states_dir
from modules.call_queue import wrap_gradio_gpu_call
@@ -49,7 +49,11 @@ def apply_and_restart(disable_list, update_list, disable_all): shared.opts.disabled_extensions = disabled
shared.opts.disable_all_extensions = disable_all
shared.opts.save(shared.config_filename)
- shared.restart_program()
+
+ if restart.is_restartable():
+ restart.restart_program()
+ else:
+ restart.stop_program()
def save_config_state(name):
@@ -509,7 +513,8 @@ def create_ui(): with gr.TabItem("Installed", id="installed"):
with gr.Row(elem_id="extensions_installed_top"):
- apply = gr.Button(value="Apply and restart UI", variant="primary")
+ apply_label = ("Apply and restart UI" if restart.is_restartable() else "Apply and quit")
+ apply = gr.Button(value=apply_label, variant="primary")
check = gr.Button(value="Check for updates")
extensions_disable_all = gr.Radio(label="Disable all extensions", choices=["none", "extra", "all"], value=shared.opts.disable_all_extensions, elem_id="extensions_disable_all")
extensions_disabled_list = gr.Text(elem_id="extensions_disabled_list", visible=False).style(container=False)
@@ -3,7 +3,7 @@ if not defined PYTHON (set PYTHON=python)
if not defined VENV_DIR (set "VENV_DIR=%~dp0%venv")
-
+set SD_WEBUI_RESTART=tmp/restart
set ERROR_REPORTING=FALSE
mkdir tmp 2>NUL
@@ -396,7 +396,7 @@ def webui(): ssl_verify=cmd_opts.disable_tls_verify,
debug=cmd_opts.gradio_debug,
auth=gradio_auth_creds,
- inbrowser=cmd_opts.autolaunch,
+ inbrowser=cmd_opts.autolaunch and os.getenv('SD_WEBUI_RESTARTING ') != '1',
prevent_thread_lock=True,
allowed_paths=cmd_opts.gradio_allowed_path,
app_kwargs={
@@ -204,6 +204,7 @@ prepare_tcmalloc() { } KEEP_GOING=1 +export SD_WEBUI_RESTART=tmp/restart while [[ "$KEEP_GOING" -eq "1" ]]; do if [[ ! -z "${ACCELERATE}" ]] && [ ${ACCELERATE}="True" ] && [ -x "$(command -v accelerate)" ]; then printf "\n%s\n" "${delimiter}" |