|
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"); |
|
}); |
|
|