CoolFace
Apppublic

SD-online/Fooocus-Docker

sourceHugging Facegpl-3.0updated 3y agoView on Hugging Face
2likes
zoom.js646 linesDownload Raw Back to javascript
1onUiLoaded(async() => {2    // Helper functions3 4    // Detect whether the element has a horizontal scroll bar5    function hasHorizontalScrollbar(element) {6        return element.scrollWidth > element.clientWidth;7    }8 9    // Function for defining the "Ctrl", "Shift" and "Alt" keys10    function isModifierKey(event, key) {11        switch (key) {12        case "Ctrl":13            return event.ctrlKey;14        case "Shift":15            return event.shiftKey;16        case "Alt":17            return event.altKey;18        default:19            return false;20        }21    }22 23    // Create hotkey configuration with the provided options24    function createHotkeyConfig(defaultHotkeysConfig) {25        const result = {}; // Resulting hotkey configuration26        for (const key in defaultHotkeysConfig) {27            result[key] = defaultHotkeysConfig[key];28        }29        return result;30    }31 32    // Default config33    const defaultHotkeysConfig = {34        canvas_hotkey_zoom: "Shift",35        canvas_hotkey_adjust: "Ctrl",36        canvas_zoom_undo_extra_key: "Ctrl",37        canvas_zoom_hotkey_undo: "KeyZ",38        canvas_hotkey_reset: "KeyR",39        canvas_hotkey_fullscreen: "KeyS",40        canvas_hotkey_move: "KeyF",41        canvas_show_tooltip: true,42        canvas_auto_expand: true,43        canvas_blur_prompt: true,44    };45 46    // Loading the configuration from opts47    const hotkeysConfig = createHotkeyConfig(48        defaultHotkeysConfig49    );50 51    let isMoving = false;52    let activeElement;53 54    const elemData = {};55 56    function applyZoomAndPan(elemId) {57        const targetElement = gradioApp().querySelector(elemId);58 59        if (!targetElement) {60            console.log("Element not found");61            return;62        }63 64        targetElement.style.transformOrigin = "0 0";65 66        elemData[elemId] = {67            zoom: 1,68            panX: 0,69            panY: 070        };71 72        let fullScreenMode = false;73 74        // Create tooltip75        function createTooltip() {76            const toolTipElemnt =77                targetElement.querySelector(".image-container");78            const tooltip = document.createElement("div");79            tooltip.className = "canvas-tooltip";80 81            // Creating an item of information82            const info = document.createElement("i");83            info.className = "canvas-tooltip-info";84            info.textContent = "";85 86            // Create a container for the contents of the tooltip87            const tooltipContent = document.createElement("div");88            tooltipContent.className = "canvas-tooltip-content";89 90            // Define an array with hotkey information and their actions91            const hotkeysInfo = [92                {93                    configKey: "canvas_hotkey_zoom",94                    action: "Zoom canvas",95                    keySuffix: " + wheel"96                },97                {98                    configKey: "canvas_hotkey_adjust",99                    action: "Adjust brush size",100                    keySuffix: " + wheel"101                },102                {configKey: "canvas_zoom_hotkey_undo", action: "Undo last action", keyPrefix: `${hotkeysConfig.canvas_zoom_undo_extra_key} + ` },103                {configKey: "canvas_hotkey_reset", action: "Reset zoom"},104                {105                    configKey: "canvas_hotkey_fullscreen",106                    action: "Fullscreen mode"107                },108                {configKey: "canvas_hotkey_move", action: "Move canvas"}109            ];110 111            // Create hotkeys array based on the config values112            const hotkeys = hotkeysInfo.map((info) => {113                const configValue = hotkeysConfig[info.configKey];114        115                let key = configValue.slice(-1);116        117                if (info.keySuffix) {118                  key = `${configValue}${info.keySuffix}`;119                }120        121                if (info.keyPrefix && info.keyPrefix !== "None + ") {122                  key = `${info.keyPrefix}${configValue[3]}`;123                }124        125                return {126                  key,127                  action: info.action,128                };129              });130        131              hotkeys132                .forEach(hotkey => {133                  const p = document.createElement("p");134                  p.innerHTML = `<b>${hotkey.key}</b> - ${hotkey.action}`;135                  tooltipContent.appendChild(p);136                });137        138              tooltip.append(info, tooltipContent);139 140              // Add a hint element to the target element141              toolTipElemnt.appendChild(tooltip);142        }143 144        //Show tool tip if setting enable145        if (hotkeysConfig.canvas_show_tooltip) {146            createTooltip();147        }148 149        // Reset the zoom level and pan position of the target element to their initial values150        function resetZoom() {151            elemData[elemId] = {152                zoomLevel: 1,153                panX: 0,154                panY: 0155            };156 157            targetElement.style.overflow = "hidden";158 159            targetElement.isZoomed = false;160 161            targetElement.style.transform = `scale(${elemData[elemId].zoomLevel}) translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px)`;162 163            const canvas = gradioApp().querySelector(164                `${elemId} canvas[key="interface"]`165            );166 167            toggleOverlap("off");168            fullScreenMode = false;169 170            const closeBtn = targetElement.querySelector("button[aria-label='Remove Image']");171            if (closeBtn) {172                closeBtn.addEventListener("click", resetZoom);173            }174 175            if (canvas) {176                const parentElement = targetElement.closest('[id^="component-"]');177                if (178                    canvas &&179                    parseFloat(canvas.style.width) > parentElement.offsetWidth &&180                    parseFloat(targetElement.style.width) > parentElement.offsetWidth181                ) {182                    fitToElement();183                    return;184                }185 186            }187 188            targetElement.style.width = "";189        }190 191        // Toggle the zIndex of the target element between two values, allowing it to overlap or be overlapped by other elements192        function toggleOverlap(forced = "") {193            const zIndex1 = "0";194            const zIndex2 = "998";195 196            targetElement.style.zIndex =197                targetElement.style.zIndex !== zIndex2 ? zIndex2 : zIndex1;198 199            if (forced === "off") {200                targetElement.style.zIndex = zIndex1;201            } else if (forced === "on") {202                targetElement.style.zIndex = zIndex2;203            }204        }205 206        // Adjust the brush size based on the deltaY value from a mouse wheel event207        function adjustBrushSize(208            elemId,209            deltaY,210            withoutValue = false,211            percentage = 5212        ) {213            const input =214                gradioApp().querySelector(215                    `${elemId} input[aria-label='Brush radius']`216                ) ||217                gradioApp().querySelector(218                    `${elemId} button[aria-label="Use brush"]`219                );220 221            if (input) {222                input.click();223                if (!withoutValue) {224                    const maxValue =225                        parseFloat(input.getAttribute("max")) || 100;226                    const changeAmount = maxValue * (percentage / 100);227                    const newValue =228                        parseFloat(input.value) +229                        (deltaY > 0 ? -changeAmount : changeAmount);230                    input.value = Math.min(Math.max(newValue, 0), maxValue);231                    input.dispatchEvent(new Event("change"));232                }233            }234        }235 236        // Reset zoom when uploading a new image237        const fileInput = gradioApp().querySelector(238            `${elemId} input[type="file"][accept="image/*"].svelte-116rqfv`239        );240        fileInput.addEventListener("click", resetZoom);241 242        // Update the zoom level and pan position of the target element based on the values of the zoomLevel, panX and panY variables243        function updateZoom(newZoomLevel, mouseX, mouseY) {244            newZoomLevel = Math.max(0.1, Math.min(newZoomLevel, 15));245 246            elemData[elemId].panX +=247                mouseX - (mouseX * newZoomLevel) / elemData[elemId].zoomLevel;248            elemData[elemId].panY +=249                mouseY - (mouseY * newZoomLevel) / elemData[elemId].zoomLevel;250 251            targetElement.style.transformOrigin = "0 0";252            targetElement.style.transform = `translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px) scale(${newZoomLevel})`;253            targetElement.style.overflow = "visible";254 255            toggleOverlap("on");256 257            return newZoomLevel;258        }259 260        // Change the zoom level based on user interaction261        function changeZoomLevel(operation, e) {262            if (isModifierKey(e, hotkeysConfig.canvas_hotkey_zoom)) {263                e.preventDefault();264 265                let zoomPosX, zoomPosY;266                let delta = 0.2;267 268                if (elemData[elemId].zoomLevel > 7) {269                    delta = 0.9;270                } else if (elemData[elemId].zoomLevel > 2) {271                    delta = 0.6;272                }273 274                zoomPosX = e.clientX;275                zoomPosY = e.clientY;276 277                fullScreenMode = false;278                elemData[elemId].zoomLevel = updateZoom(279                    elemData[elemId].zoomLevel +280                    (operation === "+" ? delta : -delta),281                    zoomPosX - targetElement.getBoundingClientRect().left,282                    zoomPosY - targetElement.getBoundingClientRect().top283                );284 285                targetElement.isZoomed = true;286            }287        }288 289        /**290         * This function fits the target element to the screen by calculating291         * the required scale and offsets. It also updates the global variables292         * zoomLevel, panX, and panY to reflect the new state.293         */294 295        function fitToElement() {296            //Reset Zoom297            targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`;298 299            let parentElement;300 301            parentElement = targetElement.closest('[id^="component-"]');302 303            // Get element and screen dimensions304            const elementWidth = targetElement.offsetWidth;305            const elementHeight = targetElement.offsetHeight;306 307            const screenWidth = parentElement.clientWidth - 24;308            const screenHeight = parentElement.clientHeight;309 310            // Calculate scale and offsets311            const scaleX = screenWidth / elementWidth;312            const scaleY = screenHeight / elementHeight;313            const scale = Math.min(scaleX, scaleY);314 315            const offsetX =0;316            const offsetY =0;317 318            // Apply scale and offsets to the element319            targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;320 321            // Update global variables322            elemData[elemId].zoomLevel = scale;323            elemData[elemId].panX = offsetX;324            elemData[elemId].panY = offsetY;325 326            fullScreenMode = false;327            toggleOverlap("off");328        }329 330        // Undo last action331        function undoLastAction(e) {332            let isCtrlPressed = isModifierKey(e, hotkeysConfig.canvas_zoom_undo_extra_key)333            const isAuxButton = e.button >= 3;334            335            if (isAuxButton) {336              isCtrlPressed = true337            } else {338              if (!isModifierKey(e, hotkeysConfig.canvas_zoom_undo_extra_key)) return;339            }340 341            // Move undoBtn query outside the if statement to avoid unnecessary queries342            const undoBtn = document.querySelector(`${activeElement} button[aria-label="Undo"]`);343        344            if ((isCtrlPressed) && undoBtn ) {345                e.preventDefault();346                undoBtn.click();347            }348        }349 350        /**351         * This function fits the target element to the screen by calculating352         * the required scale and offsets. It also updates the global variables353         * zoomLevel, panX, and panY to reflect the new state.354         */355 356        // Fullscreen mode357        function fitToScreen() {358            const canvas = gradioApp().querySelector(359                `${elemId} canvas[key="interface"]`360            );361 362            if (!canvas) return;363 364            targetElement.style.width = (canvas.offsetWidth + 2) + "px";365            targetElement.style.overflow = "visible";366 367            if (fullScreenMode) {368                resetZoom();369                fullScreenMode = false;370                return;371            }372 373            //Reset Zoom374            targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`;375 376            // Get scrollbar width to right-align the image377            const scrollbarWidth =378                window.innerWidth - document.documentElement.clientWidth;379 380            // Get element and screen dimensions381            const elementWidth = targetElement.offsetWidth;382            const elementHeight = targetElement.offsetHeight;383            const screenWidth = window.innerWidth - scrollbarWidth;384            const screenHeight = window.innerHeight;385 386            // Get element's coordinates relative to the page387            const elementRect = targetElement.getBoundingClientRect();388            const elementY = elementRect.y;389            const elementX = elementRect.x;390 391            // Calculate scale and offsets392            const scaleX = screenWidth / elementWidth;393            const scaleY = screenHeight / elementHeight;394            const scale = Math.min(scaleX, scaleY);395 396            // Get the current transformOrigin397            const computedStyle = window.getComputedStyle(targetElement);398            const transformOrigin = computedStyle.transformOrigin;399            const [originX, originY] = transformOrigin.split(" ");400            const originXValue = parseFloat(originX);401            const originYValue = parseFloat(originY);402 403            // Calculate offsets with respect to the transformOrigin404            const offsetX =405                (screenWidth - elementWidth * scale) / 2 -406                elementX -407                originXValue * (1 - scale);408            const offsetY =409                (screenHeight - elementHeight * scale) / 2 -410                elementY -411                originYValue * (1 - scale);412 413            // Apply scale and offsets to the element414            targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;415 416            // Update global variables417            elemData[elemId].zoomLevel = scale;418            elemData[elemId].panX = offsetX;419            elemData[elemId].panY = offsetY;420 421            fullScreenMode = true;422            toggleOverlap("on");423        }424 425        // Handle keydown events426        function handleKeyDown(event) {427            // Disable key locks to make pasting from the buffer work correctly428            if ((event.ctrlKey && event.code === 'KeyV') || (event.ctrlKey && event.code === 'KeyC') || event.code === "F5") {429                return;430            }431 432            // before activating shortcut, ensure user is not actively typing in an input field433            if (!hotkeysConfig.canvas_blur_prompt) {434                if (event.target.nodeName === 'TEXTAREA' || event.target.nodeName === 'INPUT') {435                    return;436                }437            }438 439            const hotkeyActions = {440                [hotkeysConfig.canvas_hotkey_reset]: resetZoom,441                [hotkeysConfig.canvas_hotkey_overlap]: toggleOverlap,442                [hotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen,443                [hotkeysConfig.canvas_zoom_hotkey_undo]: undoLastAction,444            };445 446            const action = hotkeyActions[event.code];447            if (action) {448                event.preventDefault();449                action(event);450            }451 452            if (453                isModifierKey(event, hotkeysConfig.canvas_hotkey_zoom) ||454                isModifierKey(event, hotkeysConfig.canvas_hotkey_adjust)455            ) {456                event.preventDefault();457            }458        }459 460        // Get Mouse position461        function getMousePosition(e) {462            mouseX = e.offsetX;463            mouseY = e.offsetY;464        }465 466        // Simulation of the function to put a long image into the screen.467        // We detect if an image has a scroll bar or not, make a fullscreen to reveal the image, then reduce it to fit into the element.468        // We hide the image and show it to the user when it is ready.469 470        targetElement.isExpanded = false;471        function autoExpand() {472            const canvas = document.querySelector(`${elemId} canvas[key="interface"]`);473            if (canvas) {474                if (hasHorizontalScrollbar(targetElement) && targetElement.isExpanded === false) {475                    targetElement.style.visibility = "hidden";476                    setTimeout(() => {477                        fitToScreen();478                        resetZoom();479                        targetElement.style.visibility = "visible";480                        targetElement.isExpanded = true;481                    }, 10);482                }483            }484        }485 486        targetElement.addEventListener("mousemove", getMousePosition);487        targetElement.addEventListener("auxclick", undoLastAction);488 489        //observers490        // Creating an observer with a callback function to handle DOM changes491        const observer = new MutationObserver((mutationsList, observer) => {492            for (let mutation of mutationsList) {493              // If the style attribute of the canvas has changed, by observation it happens only when the picture changes494              if (mutation.type === 'attributes' && mutation.attributeName === 'style' &&495                mutation.target.tagName.toLowerCase() === 'canvas') {496                targetElement.isExpanded = false;497                setTimeout(resetZoom, 10);498              }499            }500          });501      502          // Apply auto expand if enabled503          if (hotkeysConfig.canvas_auto_expand) {504            targetElement.addEventListener("mousemove", autoExpand);505            // Set up an observer to track attribute changes506            observer.observe(targetElement, { attributes: true, childList: true, subtree: true });507          }508 509        // Handle events only inside the targetElement510        let isKeyDownHandlerAttached = false;511 512        function handleMouseMove() {513            if (!isKeyDownHandlerAttached) {514                document.addEventListener("keydown", handleKeyDown);515                isKeyDownHandlerAttached = true;516 517                activeElement = elemId;518            }519        }520 521        function handleMouseLeave() {522            if (isKeyDownHandlerAttached) {523                document.removeEventListener("keydown", handleKeyDown);524                isKeyDownHandlerAttached = false;525 526                activeElement = null;527            }528        }529 530        // Add mouse event handlers531        targetElement.addEventListener("mousemove", handleMouseMove);532        targetElement.addEventListener("mouseleave", handleMouseLeave);533 534        targetElement.addEventListener("wheel", e => {535            // change zoom level536            const operation = e.deltaY > 0 ? "-" : "+";537            changeZoomLevel(operation, e);538 539            // Handle brush size adjustment with ctrl key pressed540            if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) {541                e.preventDefault();542 543                // Increase or decrease brush size based on scroll direction544                adjustBrushSize(elemId, e.deltaY);545            }546        });547 548        // Handle the move event for pan functionality. Updates the panX and panY variables and applies the new transform to the target element.549        function handleMoveKeyDown(e) {550 551            // Disable key locks to make pasting from the buffer work correctly552            if ((e.ctrlKey && e.code === 'KeyV') || (e.ctrlKey && e.code === 'KeyC') || e.code === "F5") {553                return;554            }555 556            // before activating shortcut, ensure user is not actively typing in an input field557            if (!hotkeysConfig.canvas_blur_prompt) {558                if (e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'INPUT') {559                    return;560                }561            }562 563 564            if (e.code === hotkeysConfig.canvas_hotkey_move) {565                if (!e.ctrlKey && !e.metaKey && isKeyDownHandlerAttached) {566                    e.preventDefault();567                    document.activeElement.blur();568                    isMoving = true;569                }570            }571        }572 573        function handleMoveKeyUp(e) {574            if (e.code === hotkeysConfig.canvas_hotkey_move) {575                isMoving = false;576            }577        }578 579        document.addEventListener("keydown", handleMoveKeyDown);580        document.addEventListener("keyup", handleMoveKeyUp);581 582        // Detect zoom level and update the pan speed.583        function updatePanPosition(movementX, movementY) {584            let panSpeed = 2;585 586            if (elemData[elemId].zoomLevel > 8) {587                panSpeed = 3.5;588            }589 590            elemData[elemId].panX += movementX * panSpeed;591            elemData[elemId].panY += movementY * panSpeed;592 593            // Delayed redraw of an element594            requestAnimationFrame(() => {595                targetElement.style.transform = `translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px) scale(${elemData[elemId].zoomLevel})`;596                toggleOverlap("on");597            });598        }599 600        function handleMoveByKey(e) {601            if (isMoving && elemId === activeElement) {602                updatePanPosition(e.movementX, e.movementY);603                targetElement.style.pointerEvents = "none";604                targetElement.style.overflow = "visible";605            } else {606                targetElement.style.pointerEvents = "auto";607            }608        }609 610        // Prevents sticking to the mouse611        window.onblur = function() {612            isMoving = false;613        };614 615        // Checks for extension616        function checkForOutBox() {617            const parentElement = targetElement.closest('[id^="component-"]');618            if (parentElement.offsetWidth < targetElement.offsetWidth && !targetElement.isExpanded) {619                resetZoom();620                targetElement.isExpanded = true;621            }622 623            if (parentElement.offsetWidth < targetElement.offsetWidth && elemData[elemId].zoomLevel == 1) {624                resetZoom();625            }626 627            if (parentElement.offsetWidth < targetElement.offsetWidth && targetElement.offsetWidth * elemData[elemId].zoomLevel > parentElement.offsetWidth && elemData[elemId].zoomLevel < 1 && !targetElement.isZoomed) {628                resetZoom();629            }630        }631 632        targetElement.addEventListener("mousemove", checkForOutBox);633 634        window.addEventListener('resize', (e) => {635            resetZoom();636 637            targetElement.isExpanded = false;638            targetElement.isZoomed = false;639        });640 641        gradioApp().addEventListener("mousemove", handleMoveByKey);642    }643 644    applyZoomAndPan("#inpaint_canvas");645});646