| onUiLoaded(async() => {
|
|
|
|
|
|
|
| function hasHorizontalScrollbar(element) {
|
| return element.scrollWidth > element.clientWidth;
|
| }
|
|
|
|
|
| function isModifierKey(event, key) {
|
| switch (key) {
|
| case "Ctrl":
|
| return event.ctrlKey;
|
| case "Shift":
|
| return event.shiftKey;
|
| case "Alt":
|
| return event.altKey;
|
| default:
|
| return false;
|
| }
|
| }
|
|
|
|
|
| function createHotkeyConfig(defaultHotkeysConfig) {
|
| const result = {};
|
| for (const key in defaultHotkeysConfig) {
|
| result[key] = defaultHotkeysConfig[key];
|
| }
|
| return result;
|
| }
|
|
|
|
|
| const defaultHotkeysConfig = {
|
| canvas_hotkey_zoom: "Shift",
|
| canvas_hotkey_adjust: "Ctrl",
|
| canvas_zoom_undo_extra_key: "Ctrl",
|
| canvas_zoom_hotkey_undo: "KeyZ",
|
| canvas_hotkey_reset: "KeyR",
|
| canvas_hotkey_fullscreen: "KeyS",
|
| canvas_hotkey_move: "KeyF",
|
| canvas_show_tooltip: true,
|
| canvas_auto_expand: true,
|
| canvas_blur_prompt: true,
|
| };
|
|
|
|
|
| const hotkeysConfig = createHotkeyConfig(
|
| defaultHotkeysConfig
|
| );
|
|
|
| let isMoving = false;
|
| let activeElement;
|
|
|
| const elemData = {};
|
|
|
| function applyZoomAndPan(elemId) {
|
| const targetElement = gradioApp().querySelector(elemId);
|
|
|
| if (!targetElement) {
|
| console.log("Element not found");
|
| return;
|
| }
|
|
|
| targetElement.style.transformOrigin = "0 0";
|
|
|
| elemData[elemId] = {
|
| zoom: 1,
|
| panX: 0,
|
| panY: 0
|
| };
|
|
|
| let fullScreenMode = false;
|
|
|
|
|
| function createTooltip() {
|
| const toolTipElemnt =
|
| targetElement.querySelector(".image-container");
|
| const tooltip = document.createElement("div");
|
| tooltip.className = "canvas-tooltip";
|
|
|
|
|
| const info = document.createElement("i");
|
| info.className = "canvas-tooltip-info";
|
| info.textContent = "";
|
|
|
|
|
| const tooltipContent = document.createElement("div");
|
| tooltipContent.className = "canvas-tooltip-content";
|
|
|
|
|
| const hotkeysInfo = [
|
| {
|
| configKey: "canvas_hotkey_zoom",
|
| action: "Zoom canvas",
|
| keySuffix: " + wheel"
|
| },
|
| {
|
| configKey: "canvas_hotkey_adjust",
|
| action: "Adjust brush size",
|
| keySuffix: " + wheel"
|
| },
|
| {configKey: "canvas_zoom_hotkey_undo", action: "Undo last action", keyPrefix: `${hotkeysConfig.canvas_zoom_undo_extra_key} + ` },
|
| {configKey: "canvas_hotkey_reset", action: "Reset zoom"},
|
| {
|
| configKey: "canvas_hotkey_fullscreen",
|
| action: "Fullscreen mode"
|
| },
|
| {configKey: "canvas_hotkey_move", action: "Move canvas"}
|
| ];
|
|
|
|
|
| const hotkeys = hotkeysInfo.map((info) => {
|
| const configValue = hotkeysConfig[info.configKey];
|
|
|
| let key = configValue.slice(-1);
|
|
|
| if (info.keySuffix) {
|
| key = `${configValue}${info.keySuffix}`;
|
| }
|
|
|
| if (info.keyPrefix && info.keyPrefix !== "None + ") {
|
| key = `${info.keyPrefix}${configValue[3]}`;
|
| }
|
|
|
| return {
|
| key,
|
| action: info.action,
|
| };
|
| });
|
|
|
| hotkeys
|
| .forEach(hotkey => {
|
| const p = document.createElement("p");
|
| p.innerHTML = `<b>${hotkey.key}</b> - ${hotkey.action}`;
|
| tooltipContent.appendChild(p);
|
| });
|
|
|
| tooltip.append(info, tooltipContent);
|
|
|
|
|
| toolTipElemnt.appendChild(tooltip);
|
| }
|
|
|
|
|
| if (hotkeysConfig.canvas_show_tooltip) {
|
| createTooltip();
|
| }
|
|
|
|
|
| function resetZoom() {
|
| elemData[elemId] = {
|
| zoomLevel: 1,
|
| panX: 0,
|
| panY: 0
|
| };
|
|
|
| targetElement.style.overflow = "hidden";
|
|
|
| targetElement.isZoomed = false;
|
|
|
| targetElement.style.transform = `scale(${elemData[elemId].zoomLevel}) translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px)`;
|
|
|
| const canvas = gradioApp().querySelector(
|
| `${elemId} canvas[key="interface"]`
|
| );
|
|
|
| toggleOverlap("off");
|
| fullScreenMode = false;
|
|
|
| const closeBtn = targetElement.querySelector("button[aria-label='Remove Image']");
|
| if (closeBtn) {
|
| closeBtn.addEventListener("click", resetZoom);
|
| }
|
|
|
| if (canvas) {
|
| const parentElement = targetElement.closest('[id^="component-"]');
|
| if (
|
| canvas &&
|
| parseFloat(canvas.style.width) > parentElement.offsetWidth &&
|
| parseFloat(targetElement.style.width) > parentElement.offsetWidth
|
| ) {
|
| fitToElement();
|
| return;
|
| }
|
|
|
| }
|
|
|
| targetElement.style.width = "";
|
| }
|
|
|
|
|
| function toggleOverlap(forced = "") {
|
| const zIndex1 = "0";
|
| const zIndex2 = "998";
|
|
|
| targetElement.style.zIndex =
|
| targetElement.style.zIndex !== zIndex2 ? zIndex2 : zIndex1;
|
|
|
| if (forced === "off") {
|
| targetElement.style.zIndex = zIndex1;
|
| } else if (forced === "on") {
|
| targetElement.style.zIndex = zIndex2;
|
| }
|
| }
|
|
|
|
|
| function adjustBrushSize(
|
| elemId,
|
| deltaY,
|
| withoutValue = false,
|
| percentage = 5
|
| ) {
|
| const input =
|
| gradioApp().querySelector(
|
| `${elemId} input[aria-label='Brush radius']`
|
| ) ||
|
| gradioApp().querySelector(
|
| `${elemId} button[aria-label="Use brush"]`
|
| );
|
|
|
| if (input) {
|
| input.click();
|
| if (!withoutValue) {
|
| const maxValue =
|
| parseFloat(input.getAttribute("max")) || 100;
|
| const changeAmount = maxValue * (percentage / 100);
|
| const newValue =
|
| parseFloat(input.value) +
|
| (deltaY > 0 ? -changeAmount : changeAmount);
|
| input.value = Math.min(Math.max(newValue, 0), maxValue);
|
| input.dispatchEvent(new Event("change"));
|
| }
|
| }
|
| }
|
|
|
|
|
| const fileInput = gradioApp().querySelector(
|
| `${elemId} input[type="file"][accept="image/*"].svelte-116rqfv`
|
| );
|
| fileInput.addEventListener("click", resetZoom);
|
|
|
|
|
| function updateZoom(newZoomLevel, mouseX, mouseY) {
|
| newZoomLevel = Math.max(0.1, Math.min(newZoomLevel, 15));
|
|
|
| elemData[elemId].panX +=
|
| mouseX - (mouseX * newZoomLevel) / elemData[elemId].zoomLevel;
|
| elemData[elemId].panY +=
|
| mouseY - (mouseY * newZoomLevel) / elemData[elemId].zoomLevel;
|
|
|
| targetElement.style.transformOrigin = "0 0";
|
| targetElement.style.transform = `translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px) scale(${newZoomLevel})`;
|
| targetElement.style.overflow = "visible";
|
|
|
| toggleOverlap("on");
|
|
|
| return newZoomLevel;
|
| }
|
|
|
|
|
| function changeZoomLevel(operation, e) {
|
| if (isModifierKey(e, hotkeysConfig.canvas_hotkey_zoom)) {
|
| e.preventDefault();
|
|
|
| let zoomPosX, zoomPosY;
|
| let delta = 0.2;
|
|
|
| if (elemData[elemId].zoomLevel > 7) {
|
| delta = 0.9;
|
| } else if (elemData[elemId].zoomLevel > 2) {
|
| delta = 0.6;
|
| }
|
|
|
| zoomPosX = e.clientX;
|
| zoomPosY = e.clientY;
|
|
|
| fullScreenMode = false;
|
| elemData[elemId].zoomLevel = updateZoom(
|
| elemData[elemId].zoomLevel +
|
| (operation === "+" ? delta : -delta),
|
| zoomPosX - targetElement.getBoundingClientRect().left,
|
| zoomPosY - targetElement.getBoundingClientRect().top
|
| );
|
|
|
| targetElement.isZoomed = true;
|
| }
|
| }
|
|
|
| |
| |
| |
| |
|
|
|
|
| function fitToElement() {
|
|
|
| targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`;
|
|
|
| let parentElement;
|
|
|
| parentElement = targetElement.closest('[id^="component-"]');
|
|
|
|
|
| const elementWidth = targetElement.offsetWidth;
|
| const elementHeight = targetElement.offsetHeight;
|
|
|
| const screenWidth = parentElement.clientWidth - 24;
|
| const screenHeight = parentElement.clientHeight;
|
|
|
|
|
| const scaleX = screenWidth / elementWidth;
|
| const scaleY = screenHeight / elementHeight;
|
| const scale = Math.min(scaleX, scaleY);
|
|
|
| const offsetX =0;
|
| const offsetY =0;
|
|
|
|
|
| targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
|
|
|
|
| elemData[elemId].zoomLevel = scale;
|
| elemData[elemId].panX = offsetX;
|
| elemData[elemId].panY = offsetY;
|
|
|
| fullScreenMode = false;
|
| toggleOverlap("off");
|
| }
|
|
|
|
|
| function undoLastAction(e) {
|
| let isCtrlPressed = isModifierKey(e, hotkeysConfig.canvas_zoom_undo_extra_key)
|
| const isAuxButton = e.button >= 3;
|
|
|
| if (isAuxButton) {
|
| isCtrlPressed = true
|
| } else {
|
| if (!isModifierKey(e, hotkeysConfig.canvas_zoom_undo_extra_key)) return;
|
| }
|
|
|
|
|
| const undoBtn = document.querySelector(`${activeElement} button[aria-label="Undo"]`);
|
|
|
| if ((isCtrlPressed) && undoBtn ) {
|
| e.preventDefault();
|
| undoBtn.click();
|
| }
|
| }
|
|
|
| |
| |
| |
| |
|
|
|
|
|
|
| function fitToScreen() {
|
| const canvas = gradioApp().querySelector(
|
| `${elemId} canvas[key="interface"]`
|
| );
|
|
|
| if (!canvas) return;
|
|
|
| targetElement.style.width = (canvas.offsetWidth + 2) + "px";
|
| targetElement.style.overflow = "visible";
|
|
|
| if (fullScreenMode) {
|
| resetZoom();
|
| fullScreenMode = false;
|
| return;
|
| }
|
|
|
|
|
| targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`;
|
|
|
|
|
| const scrollbarWidth =
|
| window.innerWidth - document.documentElement.clientWidth;
|
|
|
|
|
| const elementWidth = targetElement.offsetWidth;
|
| const elementHeight = targetElement.offsetHeight;
|
| const screenWidth = window.innerWidth - scrollbarWidth;
|
| const screenHeight = window.innerHeight;
|
|
|
|
|
| const elementRect = targetElement.getBoundingClientRect();
|
| const elementY = elementRect.y;
|
| const elementX = elementRect.x;
|
|
|
|
|
| const scaleX = screenWidth / elementWidth;
|
| const scaleY = screenHeight / elementHeight;
|
| const scale = Math.min(scaleX, scaleY);
|
|
|
|
|
| const computedStyle = window.getComputedStyle(targetElement);
|
| const transformOrigin = computedStyle.transformOrigin;
|
| const [originX, originY] = transformOrigin.split(" ");
|
| const originXValue = parseFloat(originX);
|
| const originYValue = parseFloat(originY);
|
|
|
|
|
| const offsetX =
|
| (screenWidth - elementWidth * scale) / 2 -
|
| elementX -
|
| originXValue * (1 - scale);
|
| const offsetY =
|
| (screenHeight - elementHeight * scale) / 2 -
|
| elementY -
|
| originYValue * (1 - scale);
|
|
|
|
|
| targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
|
|
|
|
| elemData[elemId].zoomLevel = scale;
|
| elemData[elemId].panX = offsetX;
|
| elemData[elemId].panY = offsetY;
|
|
|
| fullScreenMode = true;
|
| toggleOverlap("on");
|
| }
|
|
|
|
|
| function handleKeyDown(event) {
|
|
|
| if ((event.ctrlKey && event.code === 'KeyV') || (event.ctrlKey && event.code === 'KeyC') || event.code === "F5") {
|
| return;
|
| }
|
|
|
|
|
| if (!hotkeysConfig.canvas_blur_prompt) {
|
| if (event.target.nodeName === 'TEXTAREA' || event.target.nodeName === 'INPUT') {
|
| return;
|
| }
|
| }
|
|
|
| const hotkeyActions = {
|
| [hotkeysConfig.canvas_hotkey_reset]: resetZoom,
|
| [hotkeysConfig.canvas_hotkey_overlap]: toggleOverlap,
|
| [hotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen,
|
| [hotkeysConfig.canvas_zoom_hotkey_undo]: undoLastAction,
|
| };
|
|
|
| const action = hotkeyActions[event.code];
|
| if (action) {
|
| event.preventDefault();
|
| action(event);
|
| }
|
|
|
| if (
|
| isModifierKey(event, hotkeysConfig.canvas_hotkey_zoom) ||
|
| isModifierKey(event, hotkeysConfig.canvas_hotkey_adjust)
|
| ) {
|
| event.preventDefault();
|
| }
|
| }
|
|
|
|
|
| function getMousePosition(e) {
|
| mouseX = e.offsetX;
|
| mouseY = e.offsetY;
|
| }
|
|
|
|
|
|
|
|
|
|
|
| targetElement.isExpanded = false;
|
| function autoExpand() {
|
| const canvas = document.querySelector(`${elemId} canvas[key="interface"]`);
|
| if (canvas) {
|
| if (hasHorizontalScrollbar(targetElement) && targetElement.isExpanded === false) {
|
| targetElement.style.visibility = "hidden";
|
| setTimeout(() => {
|
| fitToScreen();
|
| resetZoom();
|
| targetElement.style.visibility = "visible";
|
| targetElement.isExpanded = true;
|
| }, 10);
|
| }
|
| }
|
| }
|
|
|
| targetElement.addEventListener("mousemove", getMousePosition);
|
| targetElement.addEventListener("auxclick", undoLastAction);
|
|
|
|
|
|
|
| const observer = new MutationObserver((mutationsList, observer) => {
|
| for (let mutation of mutationsList) {
|
|
|
| if (mutation.type === 'attributes' && mutation.attributeName === 'style' &&
|
| mutation.target.tagName.toLowerCase() === 'canvas') {
|
| targetElement.isExpanded = false;
|
| setTimeout(resetZoom, 10);
|
| }
|
| }
|
| });
|
|
|
|
|
| if (hotkeysConfig.canvas_auto_expand) {
|
| targetElement.addEventListener("mousemove", autoExpand);
|
|
|
| observer.observe(targetElement, { attributes: true, childList: true, subtree: true });
|
| }
|
|
|
|
|
| let isKeyDownHandlerAttached = false;
|
|
|
| function handleMouseMove() {
|
| if (!isKeyDownHandlerAttached) {
|
| document.addEventListener("keydown", handleKeyDown);
|
| isKeyDownHandlerAttached = true;
|
|
|
| activeElement = elemId;
|
| }
|
| }
|
|
|
| function handleMouseLeave() {
|
| if (isKeyDownHandlerAttached) {
|
| document.removeEventListener("keydown", handleKeyDown);
|
| isKeyDownHandlerAttached = false;
|
|
|
| activeElement = null;
|
| }
|
| }
|
|
|
|
|
| targetElement.addEventListener("mousemove", handleMouseMove);
|
| targetElement.addEventListener("mouseleave", handleMouseLeave);
|
|
|
| targetElement.addEventListener("wheel", e => {
|
|
|
| const operation = e.deltaY > 0 ? "-" : "+";
|
| changeZoomLevel(operation, e);
|
|
|
|
|
| if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) {
|
| e.preventDefault();
|
|
|
|
|
| adjustBrushSize(elemId, e.deltaY);
|
| }
|
| });
|
|
|
|
|
| function handleMoveKeyDown(e) {
|
|
|
|
|
| if ((e.ctrlKey && e.code === 'KeyV') || (e.ctrlKey && e.code === 'KeyC') || e.code === "F5") {
|
| return;
|
| }
|
|
|
|
|
| if (!hotkeysConfig.canvas_blur_prompt) {
|
| if (e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'INPUT') {
|
| return;
|
| }
|
| }
|
|
|
|
|
| if (e.code === hotkeysConfig.canvas_hotkey_move) {
|
| if (!e.ctrlKey && !e.metaKey && isKeyDownHandlerAttached) {
|
| e.preventDefault();
|
| document.activeElement.blur();
|
| isMoving = true;
|
| }
|
| }
|
| }
|
|
|
| function handleMoveKeyUp(e) {
|
| if (e.code === hotkeysConfig.canvas_hotkey_move) {
|
| isMoving = false;
|
| }
|
| }
|
|
|
| document.addEventListener("keydown", handleMoveKeyDown);
|
| document.addEventListener("keyup", handleMoveKeyUp);
|
|
|
|
|
| function updatePanPosition(movementX, movementY) {
|
| let panSpeed = 2;
|
|
|
| if (elemData[elemId].zoomLevel > 8) {
|
| panSpeed = 3.5;
|
| }
|
|
|
| elemData[elemId].panX += movementX * panSpeed;
|
| elemData[elemId].panY += movementY * panSpeed;
|
|
|
|
|
| requestAnimationFrame(() => {
|
| targetElement.style.transform = `translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px) scale(${elemData[elemId].zoomLevel})`;
|
| toggleOverlap("on");
|
| });
|
| }
|
|
|
| function handleMoveByKey(e) {
|
| if (isMoving && elemId === activeElement) {
|
| updatePanPosition(e.movementX, e.movementY);
|
| targetElement.style.pointerEvents = "none";
|
| targetElement.style.overflow = "visible";
|
| } else {
|
| targetElement.style.pointerEvents = "auto";
|
| }
|
| }
|
|
|
|
|
| window.onblur = function() {
|
| isMoving = false;
|
| };
|
|
|
|
|
| function checkForOutBox() {
|
| const parentElement = targetElement.closest('[id^="component-"]');
|
| if (parentElement.offsetWidth < targetElement.offsetWidth && !targetElement.isExpanded) {
|
| resetZoom();
|
| targetElement.isExpanded = true;
|
| }
|
|
|
| if (parentElement.offsetWidth < targetElement.offsetWidth && elemData[elemId].zoomLevel == 1) {
|
| resetZoom();
|
| }
|
|
|
| if (parentElement.offsetWidth < targetElement.offsetWidth && targetElement.offsetWidth * elemData[elemId].zoomLevel > parentElement.offsetWidth && elemData[elemId].zoomLevel < 1 && !targetElement.isZoomed) {
|
| resetZoom();
|
| }
|
| }
|
|
|
| targetElement.addEventListener("mousemove", checkForOutBox);
|
|
|
| window.addEventListener('resize', (e) => {
|
| resetZoom();
|
|
|
| targetElement.isExpanded = false;
|
| targetElement.isZoomed = false;
|
| });
|
|
|
| gradioApp().addEventListener("mousemove", handleMoveByKey);
|
| }
|
|
|
| applyZoomAndPan("#inpaint_canvas");
|
| applyZoomAndPan("#inpaint_mask_canvas");
|
| });
|
|
|