mirror of
https://github.com/lllyasviel/Fooocus.git
synced 2026-08-16 13:13:16 +02:00
sync with original main Fooocus repo
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
// based on https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.6.0/javascript/contextMenus.js
|
||||
|
||||
var contextMenuInit = function() {
|
||||
let eventListenerApplied = false;
|
||||
let menuSpecs = new Map();
|
||||
|
||||
const uid = function() {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substring(2);
|
||||
};
|
||||
|
||||
function showContextMenu(event, element, menuEntries) {
|
||||
let posx = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
|
||||
let posy = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
|
||||
|
||||
let oldMenu = gradioApp().querySelector('#context-menu');
|
||||
if (oldMenu) {
|
||||
oldMenu.remove();
|
||||
}
|
||||
|
||||
let baseStyle = window.getComputedStyle(gradioApp().querySelector('button.selected'));
|
||||
|
||||
const contextMenu = document.createElement('nav');
|
||||
contextMenu.id = "context-menu";
|
||||
contextMenu.style.background = baseStyle.background;
|
||||
contextMenu.style.color = baseStyle.color;
|
||||
contextMenu.style.fontFamily = baseStyle.fontFamily;
|
||||
contextMenu.style.top = posy + 'px';
|
||||
contextMenu.style.left = posx + 'px';
|
||||
|
||||
const contextMenuList = document.createElement('ul');
|
||||
contextMenuList.className = 'context-menu-items';
|
||||
contextMenu.append(contextMenuList);
|
||||
|
||||
menuEntries.forEach(function(entry) {
|
||||
let contextMenuEntry = document.createElement('a');
|
||||
contextMenuEntry.innerHTML = entry['name'];
|
||||
contextMenuEntry.addEventListener("click", function() {
|
||||
entry['func']();
|
||||
});
|
||||
contextMenuList.append(contextMenuEntry);
|
||||
|
||||
});
|
||||
|
||||
gradioApp().appendChild(contextMenu);
|
||||
|
||||
let menuWidth = contextMenu.offsetWidth + 4;
|
||||
let menuHeight = contextMenu.offsetHeight + 4;
|
||||
|
||||
let windowWidth = window.innerWidth;
|
||||
let windowHeight = window.innerHeight;
|
||||
|
||||
if ((windowWidth - posx) < menuWidth) {
|
||||
contextMenu.style.left = windowWidth - menuWidth + "px";
|
||||
}
|
||||
|
||||
if ((windowHeight - posy) < menuHeight) {
|
||||
contextMenu.style.top = windowHeight - menuHeight + "px";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function appendContextMenuOption(targetElementSelector, entryName, entryFunction) {
|
||||
|
||||
var currentItems = menuSpecs.get(targetElementSelector);
|
||||
|
||||
if (!currentItems) {
|
||||
currentItems = [];
|
||||
menuSpecs.set(targetElementSelector, currentItems);
|
||||
}
|
||||
let newItem = {
|
||||
id: targetElementSelector + '_' + uid(),
|
||||
name: entryName,
|
||||
func: entryFunction,
|
||||
isNew: true
|
||||
};
|
||||
|
||||
currentItems.push(newItem);
|
||||
return newItem['id'];
|
||||
}
|
||||
|
||||
function removeContextMenuOption(uid) {
|
||||
menuSpecs.forEach(function(v) {
|
||||
let index = -1;
|
||||
v.forEach(function(e, ei) {
|
||||
if (e['id'] == uid) {
|
||||
index = ei;
|
||||
}
|
||||
});
|
||||
if (index >= 0) {
|
||||
v.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addContextMenuEventListener() {
|
||||
if (eventListenerApplied) {
|
||||
return;
|
||||
}
|
||||
gradioApp().addEventListener("click", function(e) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
let oldMenu = gradioApp().querySelector('#context-menu');
|
||||
if (oldMenu) {
|
||||
oldMenu.remove();
|
||||
}
|
||||
});
|
||||
gradioApp().addEventListener("contextmenu", function(e) {
|
||||
let oldMenu = gradioApp().querySelector('#context-menu');
|
||||
if (oldMenu) {
|
||||
oldMenu.remove();
|
||||
}
|
||||
menuSpecs.forEach(function(v, k) {
|
||||
if (e.composedPath()[0].matches(k)) {
|
||||
showContextMenu(e, e.composedPath()[0], v);
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
eventListenerApplied = true;
|
||||
|
||||
}
|
||||
|
||||
return [appendContextMenuOption, removeContextMenuOption, addContextMenuEventListener];
|
||||
};
|
||||
|
||||
var initResponse = contextMenuInit();
|
||||
var appendContextMenuOption = initResponse[0];
|
||||
var removeContextMenuOption = initResponse[1];
|
||||
var addContextMenuEventListener = initResponse[2];
|
||||
|
||||
let cancelGenerateForever = function() {
|
||||
clearInterval(window.generateOnRepeatInterval);
|
||||
};
|
||||
|
||||
(function() {
|
||||
//Start example Context Menu Items
|
||||
let generateOnRepeat = function(genbuttonid, interruptbuttonid) {
|
||||
let genbutton = gradioApp().querySelector(genbuttonid);
|
||||
let interruptbutton = gradioApp().querySelector(interruptbuttonid);
|
||||
if (!interruptbutton.offsetParent) {
|
||||
genbutton.click();
|
||||
}
|
||||
clearInterval(window.generateOnRepeatInterval);
|
||||
window.generateOnRepeatInterval = setInterval(function() {
|
||||
if (!interruptbutton.offsetParent) {
|
||||
genbutton.click();
|
||||
}
|
||||
},
|
||||
500);
|
||||
};
|
||||
|
||||
let generateOnRepeatForButtons = function() {
|
||||
generateOnRepeat('#generate_button', '#stop_button');
|
||||
};
|
||||
|
||||
appendContextMenuOption('#generate_button', 'Generate forever', generateOnRepeatForButtons);
|
||||
// appendContextMenuOption('#stop_button', 'Generate forever', generateOnRepeatForButtons);
|
||||
|
||||
// appendContextMenuOption('#stop_button', 'Cancel generate forever', cancelGenerateForever);
|
||||
// appendContextMenuOption('#generate_button', 'Cancel generate forever', cancelGenerateForever);
|
||||
})();
|
||||
//End example Context Menu Items
|
||||
|
||||
document.onreadystatechange = function () {
|
||||
if (document.readyState == "complete") {
|
||||
addContextMenuEventListener();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
function updateInput(target) {
|
||||
let e = new Event("input", {bubbles: true});
|
||||
Object.defineProperty(e, "target", {value: target});
|
||||
target.dispatchEvent(e);
|
||||
}
|
||||
|
||||
function keyupEditAttention(event) {
|
||||
let target = event.originalTarget || event.composedPath()[0];
|
||||
if (!target.matches("*:is([id*='_prompt'], .prompt) textarea")) return;
|
||||
if (!(event.metaKey || event.ctrlKey)) return;
|
||||
|
||||
let isPlus = event.key == "ArrowUp";
|
||||
let isMinus = event.key == "ArrowDown";
|
||||
if (!isPlus && !isMinus) return;
|
||||
|
||||
let selectionStart = target.selectionStart;
|
||||
let selectionEnd = target.selectionEnd;
|
||||
let text = target.value;
|
||||
|
||||
function selectCurrentParenthesisBlock(OPEN, CLOSE) {
|
||||
if (selectionStart !== selectionEnd) return false;
|
||||
|
||||
// Find opening parenthesis around current cursor
|
||||
const before = text.substring(0, selectionStart);
|
||||
let beforeParen = before.lastIndexOf(OPEN);
|
||||
if (beforeParen == -1) return false;
|
||||
let beforeParenClose = before.lastIndexOf(CLOSE);
|
||||
while (beforeParenClose !== -1 && beforeParenClose > beforeParen) {
|
||||
beforeParen = before.lastIndexOf(OPEN, beforeParen - 1);
|
||||
beforeParenClose = before.lastIndexOf(CLOSE, beforeParenClose - 1);
|
||||
}
|
||||
|
||||
// Find closing parenthesis around current cursor
|
||||
const after = text.substring(selectionStart);
|
||||
let afterParen = after.indexOf(CLOSE);
|
||||
if (afterParen == -1) return false;
|
||||
let afterParenOpen = after.indexOf(OPEN);
|
||||
while (afterParenOpen !== -1 && afterParen > afterParenOpen) {
|
||||
afterParen = after.indexOf(CLOSE, afterParen + 1);
|
||||
afterParenOpen = after.indexOf(OPEN, afterParenOpen + 1);
|
||||
}
|
||||
if (beforeParen === -1 || afterParen === -1) return false;
|
||||
|
||||
// Set the selection to the text between the parenthesis
|
||||
const parenContent = text.substring(beforeParen + 1, selectionStart + afterParen);
|
||||
const lastColon = parenContent.lastIndexOf(":");
|
||||
selectionStart = beforeParen + 1;
|
||||
selectionEnd = selectionStart + lastColon;
|
||||
target.setSelectionRange(selectionStart, selectionEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
function selectCurrentWord() {
|
||||
if (selectionStart !== selectionEnd) return false;
|
||||
const delimiters = ".,\\/!?%^*;:{}=`~() \r\n\t";
|
||||
|
||||
// seek backward until to find beggining
|
||||
while (!delimiters.includes(text[selectionStart - 1]) && selectionStart > 0) {
|
||||
selectionStart--;
|
||||
}
|
||||
|
||||
// seek forward to find end
|
||||
while (!delimiters.includes(text[selectionEnd]) && selectionEnd < text.length) {
|
||||
selectionEnd++;
|
||||
}
|
||||
|
||||
target.setSelectionRange(selectionStart, selectionEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the user hasn't selected anything, let's select their current parenthesis block or word
|
||||
if (!selectCurrentParenthesisBlock('<', '>') && !selectCurrentParenthesisBlock('(', ')')) {
|
||||
selectCurrentWord();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
var closeCharacter = ')';
|
||||
var delta = 0.1;
|
||||
|
||||
if (selectionStart > 0 && text[selectionStart - 1] == '<') {
|
||||
closeCharacter = '>';
|
||||
delta = 0.05;
|
||||
} else if (selectionStart == 0 || text[selectionStart - 1] != "(") {
|
||||
|
||||
// do not include spaces at the end
|
||||
while (selectionEnd > selectionStart && text[selectionEnd - 1] == ' ') {
|
||||
selectionEnd -= 1;
|
||||
}
|
||||
if (selectionStart == selectionEnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
text = text.slice(0, selectionStart) + "(" + text.slice(selectionStart, selectionEnd) + ":1.0)" + text.slice(selectionEnd);
|
||||
|
||||
selectionStart += 1;
|
||||
selectionEnd += 1;
|
||||
}
|
||||
|
||||
var end = text.slice(selectionEnd + 1).indexOf(closeCharacter) + 1;
|
||||
var weight = parseFloat(text.slice(selectionEnd + 1, selectionEnd + 1 + end));
|
||||
if (isNaN(weight)) return;
|
||||
|
||||
weight += isPlus ? delta : -delta;
|
||||
weight = parseFloat(weight.toPrecision(12));
|
||||
if (String(weight).length == 1) weight += ".0";
|
||||
|
||||
if (closeCharacter == ')' && weight == 1) {
|
||||
var endParenPos = text.substring(selectionEnd).indexOf(')');
|
||||
text = text.slice(0, selectionStart - 1) + text.slice(selectionStart, selectionEnd) + text.slice(selectionEnd + endParenPos + 1);
|
||||
selectionStart--;
|
||||
selectionEnd--;
|
||||
} else {
|
||||
text = text.slice(0, selectionEnd + 1) + weight + text.slice(selectionEnd + end);
|
||||
}
|
||||
|
||||
target.focus();
|
||||
target.value = text;
|
||||
target.selectionStart = selectionStart;
|
||||
target.selectionEnd = selectionEnd;
|
||||
|
||||
updateInput(target);
|
||||
|
||||
}
|
||||
|
||||
addEventListener('keydown', (event) => {
|
||||
keyupEditAttention(event);
|
||||
});
|
||||
@@ -0,0 +1,260 @@
|
||||
// From A1111
|
||||
|
||||
function closeModal() {
|
||||
gradioApp().getElementById("lightboxModal").style.display = "none";
|
||||
}
|
||||
|
||||
function showModal(event) {
|
||||
const source = event.target || event.srcElement;
|
||||
const modalImage = gradioApp().getElementById("modalImage");
|
||||
const lb = gradioApp().getElementById("lightboxModal");
|
||||
modalImage.src = source.src;
|
||||
if (modalImage.style.display === 'none') {
|
||||
lb.style.setProperty('background-image', 'url(' + source.src + ')');
|
||||
}
|
||||
lb.style.display = "flex";
|
||||
lb.focus();
|
||||
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function negmod(n, m) {
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
|
||||
function updateOnBackgroundChange() {
|
||||
const modalImage = gradioApp().getElementById("modalImage");
|
||||
if (modalImage && modalImage.offsetParent) {
|
||||
let currentButton = selected_gallery_button();
|
||||
|
||||
if (currentButton?.children?.length > 0 && modalImage.src != currentButton.children[0].src) {
|
||||
modalImage.src = currentButton.children[0].src;
|
||||
if (modalImage.style.display === 'none') {
|
||||
const modal = gradioApp().getElementById("lightboxModal");
|
||||
modal.style.setProperty('background-image', `url(${modalImage.src})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function all_gallery_buttons() {
|
||||
var allGalleryButtons = gradioApp().querySelectorAll('.image_gallery .thumbnails > .thumbnail-item.thumbnail-small');
|
||||
var visibleGalleryButtons = [];
|
||||
allGalleryButtons.forEach(function(elem) {
|
||||
if (elem.parentElement.offsetParent) {
|
||||
visibleGalleryButtons.push(elem);
|
||||
}
|
||||
});
|
||||
return visibleGalleryButtons;
|
||||
}
|
||||
|
||||
function selected_gallery_button() {
|
||||
return all_gallery_buttons().find(elem => elem.classList.contains('selected')) ?? null;
|
||||
}
|
||||
|
||||
function selected_gallery_index() {
|
||||
return all_gallery_buttons().findIndex(elem => elem.classList.contains('selected'));
|
||||
}
|
||||
|
||||
function modalImageSwitch(offset) {
|
||||
var galleryButtons = all_gallery_buttons();
|
||||
|
||||
if (galleryButtons.length > 1) {
|
||||
var currentButton = selected_gallery_button();
|
||||
|
||||
var result = -1;
|
||||
galleryButtons.forEach(function(v, i) {
|
||||
if (v == currentButton) {
|
||||
result = i;
|
||||
}
|
||||
});
|
||||
|
||||
if (result != -1) {
|
||||
var nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)];
|
||||
nextButton.click();
|
||||
const modalImage = gradioApp().getElementById("modalImage");
|
||||
const modal = gradioApp().getElementById("lightboxModal");
|
||||
modalImage.src = nextButton.children[0].src;
|
||||
if (modalImage.style.display === 'none') {
|
||||
modal.style.setProperty('background-image', `url(${modalImage.src})`);
|
||||
}
|
||||
setTimeout(function() {
|
||||
modal.focus();
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveImage() {
|
||||
|
||||
}
|
||||
|
||||
function modalSaveImage(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function modalNextImage(event) {
|
||||
modalImageSwitch(1);
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function modalPrevImage(event) {
|
||||
modalImageSwitch(-1);
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function modalKeyHandler(event) {
|
||||
switch (event.key) {
|
||||
case "s":
|
||||
saveImage();
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
modalPrevImage(event);
|
||||
break;
|
||||
case "ArrowRight":
|
||||
modalNextImage(event);
|
||||
break;
|
||||
case "Escape":
|
||||
closeModal();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function setupImageForLightbox(e) {
|
||||
if (e.dataset.modded) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.dataset.modded = true;
|
||||
e.style.cursor = 'pointer';
|
||||
e.style.userSelect = 'none';
|
||||
|
||||
var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
|
||||
// For Firefox, listening on click first switched to next image then shows the lightbox.
|
||||
// If you know how to fix this without switching to mousedown event, please.
|
||||
// For other browsers the event is click to make it possiblr to drag picture.
|
||||
var event = isFirefox ? 'mousedown' : 'click';
|
||||
|
||||
e.addEventListener(event, function(evt) {
|
||||
if (evt.button == 1) {
|
||||
open(evt.target.src);
|
||||
evt.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (evt.button != 0) return;
|
||||
|
||||
modalZoomSet(gradioApp().getElementById('modalImage'), true);
|
||||
evt.preventDefault();
|
||||
showModal(evt);
|
||||
}, true);
|
||||
|
||||
}
|
||||
|
||||
function modalZoomSet(modalImage, enable) {
|
||||
if (modalImage) modalImage.classList.toggle('modalImageFullscreen', !!enable);
|
||||
}
|
||||
|
||||
function modalZoomToggle(event) {
|
||||
var modalImage = gradioApp().getElementById("modalImage");
|
||||
modalZoomSet(modalImage, !modalImage.classList.contains('modalImageFullscreen'));
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function modalTileImageToggle(event) {
|
||||
const modalImage = gradioApp().getElementById("modalImage");
|
||||
const modal = gradioApp().getElementById("lightboxModal");
|
||||
const isTiling = modalImage.style.display === 'none';
|
||||
if (isTiling) {
|
||||
modalImage.style.display = 'block';
|
||||
modal.style.setProperty('background-image', 'none');
|
||||
} else {
|
||||
modalImage.style.display = 'none';
|
||||
modal.style.setProperty('background-image', `url(${modalImage.src})`);
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
onAfterUiUpdate(function() {
|
||||
var fullImg_preview = gradioApp().querySelectorAll('.image_gallery > div > img');
|
||||
if (fullImg_preview != null) {
|
||||
fullImg_preview.forEach(setupImageForLightbox);
|
||||
}
|
||||
updateOnBackgroundChange();
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
//const modalFragment = document.createDocumentFragment();
|
||||
const modal = document.createElement('div');
|
||||
modal.onclick = closeModal;
|
||||
modal.id = "lightboxModal";
|
||||
modal.tabIndex = 0;
|
||||
modal.addEventListener('keydown', modalKeyHandler, true);
|
||||
|
||||
const modalControls = document.createElement('div');
|
||||
modalControls.className = 'modalControls gradio-container';
|
||||
modal.append(modalControls);
|
||||
|
||||
const modalZoom = document.createElement('span');
|
||||
modalZoom.className = 'modalZoom cursor';
|
||||
modalZoom.innerHTML = '⤡';
|
||||
modalZoom.addEventListener('click', modalZoomToggle, true);
|
||||
modalZoom.title = "Toggle zoomed view";
|
||||
modalControls.appendChild(modalZoom);
|
||||
|
||||
// const modalTileImage = document.createElement('span');
|
||||
// modalTileImage.className = 'modalTileImage cursor';
|
||||
// modalTileImage.innerHTML = '⊞';
|
||||
// modalTileImage.addEventListener('click', modalTileImageToggle, true);
|
||||
// modalTileImage.title = "Preview tiling";
|
||||
// modalControls.appendChild(modalTileImage);
|
||||
//
|
||||
// const modalSave = document.createElement("span");
|
||||
// modalSave.className = "modalSave cursor";
|
||||
// modalSave.id = "modal_save";
|
||||
// modalSave.innerHTML = "🖫";
|
||||
// modalSave.addEventListener("click", modalSaveImage, true);
|
||||
// modalSave.title = "Save Image(s)";
|
||||
// modalControls.appendChild(modalSave);
|
||||
|
||||
const modalClose = document.createElement('span');
|
||||
modalClose.className = 'modalClose cursor';
|
||||
modalClose.innerHTML = '×';
|
||||
modalClose.onclick = closeModal;
|
||||
modalClose.title = "Close image viewer";
|
||||
modalControls.appendChild(modalClose);
|
||||
|
||||
const modalImage = document.createElement('img');
|
||||
modalImage.id = 'modalImage';
|
||||
modalImage.onclick = closeModal;
|
||||
modalImage.tabIndex = 0;
|
||||
modalImage.addEventListener('keydown', modalKeyHandler, true);
|
||||
modal.appendChild(modalImage);
|
||||
|
||||
const modalPrev = document.createElement('a');
|
||||
modalPrev.className = 'modalPrev';
|
||||
modalPrev.innerHTML = '❮';
|
||||
modalPrev.tabIndex = 0;
|
||||
modalPrev.addEventListener('click', modalPrevImage, true);
|
||||
modalPrev.addEventListener('keydown', modalKeyHandler, true);
|
||||
modal.appendChild(modalPrev);
|
||||
|
||||
const modalNext = document.createElement('a');
|
||||
modalNext.className = 'modalNext';
|
||||
modalNext.innerHTML = '❯';
|
||||
modalNext.tabIndex = 0;
|
||||
modalNext.addEventListener('click', modalNextImage, true);
|
||||
modalNext.addEventListener('keydown', modalKeyHandler, true);
|
||||
|
||||
modal.appendChild(modalNext);
|
||||
|
||||
try {
|
||||
gradioApp().appendChild(modal);
|
||||
} catch (e) {
|
||||
gradioApp().body.appendChild(modal);
|
||||
}
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
});
|
||||
@@ -0,0 +1,144 @@
|
||||
var re_num = /^[.\d]+$/;
|
||||
|
||||
var original_lines = {};
|
||||
var translated_lines = {};
|
||||
|
||||
function hasLocalization() {
|
||||
return window.localization && Object.keys(window.localization).length > 0;
|
||||
}
|
||||
|
||||
function textNodesUnder(el) {
|
||||
var n, a = [], walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
|
||||
while ((n = walk.nextNode())) a.push(n);
|
||||
return a;
|
||||
}
|
||||
|
||||
function canBeTranslated(node, text) {
|
||||
if (!text) return false;
|
||||
if (!node.parentElement) return false;
|
||||
var parentType = node.parentElement.nodeName;
|
||||
if (parentType == 'SCRIPT' || parentType == 'STYLE' || parentType == 'TEXTAREA') return false;
|
||||
if (re_num.test(text)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function getTranslation(text) {
|
||||
if (!text) return undefined;
|
||||
|
||||
if (translated_lines[text] === undefined) {
|
||||
original_lines[text] = 1;
|
||||
}
|
||||
|
||||
var tl = localization[text];
|
||||
if (tl !== undefined) {
|
||||
translated_lines[tl] = 1;
|
||||
}
|
||||
|
||||
return tl;
|
||||
}
|
||||
|
||||
function processTextNode(node) {
|
||||
var text = node.textContent.trim();
|
||||
|
||||
if (!canBeTranslated(node, text)) return;
|
||||
|
||||
var tl = getTranslation(text);
|
||||
if (tl !== undefined) {
|
||||
node.textContent = tl;
|
||||
if (text && node.parentElement) {
|
||||
node.parentElement.setAttribute("data-original-text", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(node) {
|
||||
if (node.nodeType == 3) {
|
||||
processTextNode(node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.title) {
|
||||
let tl = getTranslation(node.title);
|
||||
if (tl !== undefined) {
|
||||
node.title = tl;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.placeholder) {
|
||||
let tl = getTranslation(node.placeholder);
|
||||
if (tl !== undefined) {
|
||||
node.placeholder = tl;
|
||||
}
|
||||
}
|
||||
|
||||
textNodesUnder(node).forEach(function(node) {
|
||||
processTextNode(node);
|
||||
});
|
||||
}
|
||||
|
||||
function refresh_style_localization() {
|
||||
processNode(document.querySelector('.style_selections'));
|
||||
}
|
||||
|
||||
function localizeWholePage() {
|
||||
processNode(gradioApp());
|
||||
|
||||
function elem(comp) {
|
||||
var elem_id = comp.props.elem_id ? comp.props.elem_id : "component-" + comp.id;
|
||||
return gradioApp().getElementById(elem_id);
|
||||
}
|
||||
|
||||
for (var comp of window.gradio_config.components) {
|
||||
if (comp.props.webui_tooltip) {
|
||||
let e = elem(comp);
|
||||
|
||||
let tl = e ? getTranslation(e.title) : undefined;
|
||||
if (tl !== undefined) {
|
||||
e.title = tl;
|
||||
}
|
||||
}
|
||||
if (comp.props.placeholder) {
|
||||
let e = elem(comp);
|
||||
let textbox = e ? e.querySelector('[placeholder]') : null;
|
||||
|
||||
let tl = textbox ? getTranslation(textbox.placeholder) : undefined;
|
||||
if (tl !== undefined) {
|
||||
textbox.placeholder = tl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
if (!hasLocalization()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onUiUpdate(function(m) {
|
||||
m.forEach(function(mutation) {
|
||||
mutation.addedNodes.forEach(function(node) {
|
||||
processNode(node);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
localizeWholePage();
|
||||
|
||||
if (localization.rtl) { // if the language is from right to left,
|
||||
(new MutationObserver((mutations, observer) => { // wait for the style to load
|
||||
mutations.forEach(mutation => {
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (node.tagName === 'STYLE') {
|
||||
observer.disconnect();
|
||||
|
||||
for (const x of node.sheet.rules) { // find all rtl media rules
|
||||
if (Array.from(x.media || []).includes('rtl')) {
|
||||
x.media.appendMedium('all'); // enable them
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})).observe(gradioApp(), {childList: true});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,215 @@
|
||||
// based on https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.6.0/script.js
|
||||
function gradioApp() {
|
||||
const elems = document.getElementsByTagName('gradio-app');
|
||||
const elem = elems.length == 0 ? document : elems[0];
|
||||
|
||||
if (elem !== document) {
|
||||
elem.getElementById = function(id) {
|
||||
return document.getElementById(id);
|
||||
};
|
||||
}
|
||||
return elem.shadowRoot ? elem.shadowRoot : elem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently selected top-level UI tab button (e.g. the button that says "Extras").
|
||||
*/
|
||||
function get_uiCurrentTab() {
|
||||
return gradioApp().querySelector('#tabs > .tab-nav > button.selected');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first currently visible top-level UI tab content (e.g. the div hosting the "txt2img" UI).
|
||||
*/
|
||||
function get_uiCurrentTabContent() {
|
||||
return gradioApp().querySelector('#tabs > .tabitem[id^=tab_]:not([style*="display: none"])');
|
||||
}
|
||||
|
||||
var uiUpdateCallbacks = [];
|
||||
var uiAfterUpdateCallbacks = [];
|
||||
var uiLoadedCallbacks = [];
|
||||
var uiTabChangeCallbacks = [];
|
||||
var optionsChangedCallbacks = [];
|
||||
var uiAfterUpdateTimeout = null;
|
||||
var uiCurrentTab = null;
|
||||
|
||||
/**
|
||||
* Register callback to be called at each UI update.
|
||||
* The callback receives an array of MutationRecords as an argument.
|
||||
*/
|
||||
function onUiUpdate(callback) {
|
||||
uiUpdateCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register callback to be called soon after UI updates.
|
||||
* The callback receives no arguments.
|
||||
*
|
||||
* This is preferred over `onUiUpdate` if you don't need
|
||||
* access to the MutationRecords, as your function will
|
||||
* not be called quite as often.
|
||||
*/
|
||||
function onAfterUiUpdate(callback) {
|
||||
uiAfterUpdateCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register callback to be called when the UI is loaded.
|
||||
* The callback receives no arguments.
|
||||
*/
|
||||
function onUiLoaded(callback) {
|
||||
uiLoadedCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register callback to be called when the UI tab is changed.
|
||||
* The callback receives no arguments.
|
||||
*/
|
||||
function onUiTabChange(callback) {
|
||||
uiTabChangeCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register callback to be called when the options are changed.
|
||||
* The callback receives no arguments.
|
||||
* @param callback
|
||||
*/
|
||||
function onOptionsChanged(callback) {
|
||||
optionsChangedCallbacks.push(callback);
|
||||
}
|
||||
|
||||
function executeCallbacks(queue, arg) {
|
||||
for (const callback of queue) {
|
||||
try {
|
||||
callback(arg);
|
||||
} catch (e) {
|
||||
console.error("error running callback", callback, ":", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the execution of the callbacks registered with onAfterUiUpdate.
|
||||
* The callbacks are executed after a short while, unless another call to this function
|
||||
* is made before that time. IOW, the callbacks are executed only once, even
|
||||
* when there are multiple mutations observed.
|
||||
*/
|
||||
function scheduleAfterUiUpdateCallbacks() {
|
||||
clearTimeout(uiAfterUpdateTimeout);
|
||||
uiAfterUpdateTimeout = setTimeout(function() {
|
||||
executeCallbacks(uiAfterUpdateCallbacks);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
var executedOnLoaded = false;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var mutationObserver = new MutationObserver(function(m) {
|
||||
if (!executedOnLoaded && gradioApp().querySelector('#generate_button')) {
|
||||
executedOnLoaded = true;
|
||||
executeCallbacks(uiLoadedCallbacks);
|
||||
}
|
||||
|
||||
executeCallbacks(uiUpdateCallbacks, m);
|
||||
scheduleAfterUiUpdateCallbacks();
|
||||
const newTab = get_uiCurrentTab();
|
||||
if (newTab && (newTab !== uiCurrentTab)) {
|
||||
uiCurrentTab = newTab;
|
||||
executeCallbacks(uiTabChangeCallbacks);
|
||||
}
|
||||
});
|
||||
mutationObserver.observe(gradioApp(), {childList: true, subtree: true});
|
||||
initStylePreviewOverlay();
|
||||
});
|
||||
|
||||
/**
|
||||
* Add a ctrl+enter as a shortcut to start a generation
|
||||
*/
|
||||
document.addEventListener('keydown', function(e) {
|
||||
const isModifierKey = (e.metaKey || e.ctrlKey || e.altKey);
|
||||
const isEnterKey = (e.key == "Enter" || e.keyCode == 13);
|
||||
|
||||
if(isModifierKey && isEnterKey) {
|
||||
const generateButton = gradioApp().querySelector('button:not(.hidden)[id=generate_button]');
|
||||
if (generateButton) {
|
||||
generateButton.click();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const stopButton = gradioApp().querySelector('button:not(.hidden)[id=stop_button]')
|
||||
if(stopButton) {
|
||||
stopButton.click();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function initStylePreviewOverlay() {
|
||||
let overlayVisible = false;
|
||||
const samplesPath = document.querySelector("meta[name='samples-path']").getAttribute("content")
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'stylePreviewOverlay';
|
||||
document.body.appendChild(overlay);
|
||||
document.addEventListener('mouseover', function(e) {
|
||||
const label = e.target.closest('.style_selections label');
|
||||
if (!label) return;
|
||||
label.removeEventListener("mouseout", onMouseLeave);
|
||||
label.addEventListener("mouseout", onMouseLeave);
|
||||
overlayVisible = true;
|
||||
overlay.style.opacity = "1";
|
||||
const originalText = label.querySelector("span").getAttribute("data-original-text");
|
||||
const name = originalText || label.querySelector("span").textContent;
|
||||
overlay.style.backgroundImage = `url("${samplesPath.replace(
|
||||
"fooocus_v2",
|
||||
name.toLowerCase().replaceAll(" ", "_")
|
||||
).replaceAll("\\", "\\\\")}")`;
|
||||
function onMouseLeave() {
|
||||
overlayVisible = false;
|
||||
overlay.style.opacity = "0";
|
||||
overlay.style.backgroundImage = "";
|
||||
label.removeEventListener("mouseout", onMouseLeave);
|
||||
}
|
||||
});
|
||||
document.addEventListener('mousemove', function(e) {
|
||||
if(!overlayVisible) return;
|
||||
overlay.style.left = `${e.clientX}px`;
|
||||
overlay.style.top = `${e.clientY}px`;
|
||||
overlay.className = e.clientY > window.innerHeight / 2 ? "lower-half" : "upper-half";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* checks that a UI element is not in another hidden element or tab content
|
||||
*/
|
||||
function uiElementIsVisible(el) {
|
||||
if (el === document) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const computedStyle = getComputedStyle(el);
|
||||
const isVisible = computedStyle.display !== 'none';
|
||||
|
||||
if (!isVisible) return false;
|
||||
return uiElementIsVisible(el.parentNode);
|
||||
}
|
||||
|
||||
function uiElementInSight(el) {
|
||||
const clRect = el.getBoundingClientRect();
|
||||
const windowHeight = window.innerHeight;
|
||||
const isOnScreen = clRect.bottom > 0 && clRect.top < windowHeight;
|
||||
|
||||
return isOnScreen;
|
||||
}
|
||||
|
||||
function playNotification() {
|
||||
gradioApp().querySelector('#audio_notification audio')?.play();
|
||||
}
|
||||
|
||||
function set_theme(theme) {
|
||||
var gradioURL = window.location.href;
|
||||
if (!gradioURL.includes('?__theme=')) {
|
||||
window.location.replace(gradioURL + '?__theme=' + theme);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
window.main_viewer_height = 512;
|
||||
|
||||
function refresh_grid() {
|
||||
let gridContainer = document.querySelector('#final_gallery .grid-container');
|
||||
let final_gallery = document.getElementById('final_gallery');
|
||||
|
||||
if (gridContainer) if (final_gallery) {
|
||||
let rect = final_gallery.getBoundingClientRect();
|
||||
let cols = Math.ceil((rect.width - 16.0) / rect.height);
|
||||
if (cols < 2) cols = 2;
|
||||
gridContainer.style.setProperty('--grid-cols', cols);
|
||||
}
|
||||
}
|
||||
|
||||
function refresh_grid_delayed() {
|
||||
refresh_grid();
|
||||
setTimeout(refresh_grid, 100);
|
||||
setTimeout(refresh_grid, 500);
|
||||
setTimeout(refresh_grid, 1000);
|
||||
}
|
||||
|
||||
function resized() {
|
||||
let windowHeight = window.innerHeight - 260;
|
||||
let elements = document.getElementsByClassName('main_view');
|
||||
|
||||
if (windowHeight > 745) windowHeight = 745;
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
elements[i].style.height = windowHeight + 'px';
|
||||
}
|
||||
|
||||
window.main_viewer_height = windowHeight;
|
||||
|
||||
refresh_grid();
|
||||
}
|
||||
|
||||
function viewer_to_top(delay = 100) {
|
||||
setTimeout(() => window.scrollTo({top: 0, behavior: 'smooth'}), delay);
|
||||
}
|
||||
|
||||
function viewer_to_bottom(delay = 100) {
|
||||
let element = document.getElementById('positive_prompt');
|
||||
let yPos = window.main_viewer_height;
|
||||
|
||||
if (element) {
|
||||
yPos = element.getBoundingClientRect().top + window.scrollY;
|
||||
}
|
||||
|
||||
setTimeout(() => window.scrollTo({top: yPos - 8, behavior: 'smooth'}), delay);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', (e) => {
|
||||
resized();
|
||||
});
|
||||
|
||||
onUiLoaded(async () => {
|
||||
resized();
|
||||
});
|
||||
|
||||
function on_style_selection_blur() {
|
||||
let target = document.querySelector("#gradio_receiver_style_selections textarea");
|
||||
target.value = "on_style_selection_blur " + Math.random();
|
||||
let e = new Event("input", {bubbles: true})
|
||||
Object.defineProperty(e, "target", {value: target})
|
||||
target.dispatchEvent(e);
|
||||
}
|
||||
|
||||
onUiLoaded(async () => {
|
||||
let spans = document.querySelectorAll('.aspect_ratios span');
|
||||
|
||||
spans.forEach(function (span) {
|
||||
span.innerHTML = span.innerHTML.replace(/</g, '<').replace(/>/g, '>');
|
||||
});
|
||||
|
||||
document.querySelector('.style_selections').addEventListener('focusout', function (event) {
|
||||
setTimeout(() => {
|
||||
if (!this.contains(document.activeElement)) {
|
||||
on_style_selection_blur();
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
|
||||
let inputs = document.querySelectorAll('.lora_weight input[type="range"]');
|
||||
|
||||
inputs.forEach(function (input) {
|
||||
input.style.marginTop = '12px';
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,645 @@
|
||||
onUiLoaded(async() => {
|
||||
// Helper functions
|
||||
|
||||
// Detect whether the element has a horizontal scroll bar
|
||||
function hasHorizontalScrollbar(element) {
|
||||
return element.scrollWidth > element.clientWidth;
|
||||
}
|
||||
|
||||
// Function for defining the "Ctrl", "Shift" and "Alt" keys
|
||||
function isModifierKey(event, key) {
|
||||
switch (key) {
|
||||
case "Ctrl":
|
||||
return event.ctrlKey;
|
||||
case "Shift":
|
||||
return event.shiftKey;
|
||||
case "Alt":
|
||||
return event.altKey;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create hotkey configuration with the provided options
|
||||
function createHotkeyConfig(defaultHotkeysConfig) {
|
||||
const result = {}; // Resulting hotkey configuration
|
||||
for (const key in defaultHotkeysConfig) {
|
||||
result[key] = defaultHotkeysConfig[key];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Default config
|
||||
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,
|
||||
};
|
||||
|
||||
// Loading the configuration from opts
|
||||
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;
|
||||
|
||||
// Create tooltip
|
||||
function createTooltip() {
|
||||
const toolTipElemnt =
|
||||
targetElement.querySelector(".image-container");
|
||||
const tooltip = document.createElement("div");
|
||||
tooltip.className = "canvas-tooltip";
|
||||
|
||||
// Creating an item of information
|
||||
const info = document.createElement("i");
|
||||
info.className = "canvas-tooltip-info";
|
||||
info.textContent = "";
|
||||
|
||||
// Create a container for the contents of the tooltip
|
||||
const tooltipContent = document.createElement("div");
|
||||
tooltipContent.className = "canvas-tooltip-content";
|
||||
|
||||
// Define an array with hotkey information and their actions
|
||||
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"}
|
||||
];
|
||||
|
||||
// Create hotkeys array based on the config values
|
||||
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);
|
||||
|
||||
// Add a hint element to the target element
|
||||
toolTipElemnt.appendChild(tooltip);
|
||||
}
|
||||
|
||||
//Show tool tip if setting enable
|
||||
if (hotkeysConfig.canvas_show_tooltip) {
|
||||
createTooltip();
|
||||
}
|
||||
|
||||
// Reset the zoom level and pan position of the target element to their initial values
|
||||
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 = "";
|
||||
}
|
||||
|
||||
// Toggle the zIndex of the target element between two values, allowing it to overlap or be overlapped by other elements
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the brush size based on the deltaY value from a mouse wheel event
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset zoom when uploading a new image
|
||||
const fileInput = gradioApp().querySelector(
|
||||
`${elemId} input[type="file"][accept="image/*"].svelte-116rqfv`
|
||||
);
|
||||
fileInput.addEventListener("click", resetZoom);
|
||||
|
||||
// Update the zoom level and pan position of the target element based on the values of the zoomLevel, panX and panY variables
|
||||
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;
|
||||
}
|
||||
|
||||
// Change the zoom level based on user interaction
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function fits the target element to the screen by calculating
|
||||
* the required scale and offsets. It also updates the global variables
|
||||
* zoomLevel, panX, and panY to reflect the new state.
|
||||
*/
|
||||
|
||||
function fitToElement() {
|
||||
//Reset Zoom
|
||||
targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`;
|
||||
|
||||
let parentElement;
|
||||
|
||||
parentElement = targetElement.closest('[id^="component-"]');
|
||||
|
||||
// Get element and screen dimensions
|
||||
const elementWidth = targetElement.offsetWidth;
|
||||
const elementHeight = targetElement.offsetHeight;
|
||||
|
||||
const screenWidth = parentElement.clientWidth - 24;
|
||||
const screenHeight = parentElement.clientHeight;
|
||||
|
||||
// Calculate scale and offsets
|
||||
const scaleX = screenWidth / elementWidth;
|
||||
const scaleY = screenHeight / elementHeight;
|
||||
const scale = Math.min(scaleX, scaleY);
|
||||
|
||||
const offsetX =0;
|
||||
const offsetY =0;
|
||||
|
||||
// Apply scale and offsets to the element
|
||||
targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
||||
|
||||
// Update global variables
|
||||
elemData[elemId].zoomLevel = scale;
|
||||
elemData[elemId].panX = offsetX;
|
||||
elemData[elemId].panY = offsetY;
|
||||
|
||||
fullScreenMode = false;
|
||||
toggleOverlap("off");
|
||||
}
|
||||
|
||||
// Undo last action
|
||||
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;
|
||||
}
|
||||
|
||||
// Move undoBtn query outside the if statement to avoid unnecessary queries
|
||||
const undoBtn = document.querySelector(`${activeElement} button[aria-label="Undo"]`);
|
||||
|
||||
if ((isCtrlPressed) && undoBtn ) {
|
||||
e.preventDefault();
|
||||
undoBtn.click();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function fits the target element to the screen by calculating
|
||||
* the required scale and offsets. It also updates the global variables
|
||||
* zoomLevel, panX, and panY to reflect the new state.
|
||||
*/
|
||||
|
||||
// Fullscreen mode
|
||||
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;
|
||||
}
|
||||
|
||||
//Reset Zoom
|
||||
targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`;
|
||||
|
||||
// Get scrollbar width to right-align the image
|
||||
const scrollbarWidth =
|
||||
window.innerWidth - document.documentElement.clientWidth;
|
||||
|
||||
// Get element and screen dimensions
|
||||
const elementWidth = targetElement.offsetWidth;
|
||||
const elementHeight = targetElement.offsetHeight;
|
||||
const screenWidth = window.innerWidth - scrollbarWidth;
|
||||
const screenHeight = window.innerHeight;
|
||||
|
||||
// Get element's coordinates relative to the page
|
||||
const elementRect = targetElement.getBoundingClientRect();
|
||||
const elementY = elementRect.y;
|
||||
const elementX = elementRect.x;
|
||||
|
||||
// Calculate scale and offsets
|
||||
const scaleX = screenWidth / elementWidth;
|
||||
const scaleY = screenHeight / elementHeight;
|
||||
const scale = Math.min(scaleX, scaleY);
|
||||
|
||||
// Get the current transformOrigin
|
||||
const computedStyle = window.getComputedStyle(targetElement);
|
||||
const transformOrigin = computedStyle.transformOrigin;
|
||||
const [originX, originY] = transformOrigin.split(" ");
|
||||
const originXValue = parseFloat(originX);
|
||||
const originYValue = parseFloat(originY);
|
||||
|
||||
// Calculate offsets with respect to the transformOrigin
|
||||
const offsetX =
|
||||
(screenWidth - elementWidth * scale) / 2 -
|
||||
elementX -
|
||||
originXValue * (1 - scale);
|
||||
const offsetY =
|
||||
(screenHeight - elementHeight * scale) / 2 -
|
||||
elementY -
|
||||
originYValue * (1 - scale);
|
||||
|
||||
// Apply scale and offsets to the element
|
||||
targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
||||
|
||||
// Update global variables
|
||||
elemData[elemId].zoomLevel = scale;
|
||||
elemData[elemId].panX = offsetX;
|
||||
elemData[elemId].panY = offsetY;
|
||||
|
||||
fullScreenMode = true;
|
||||
toggleOverlap("on");
|
||||
}
|
||||
|
||||
// Handle keydown events
|
||||
function handleKeyDown(event) {
|
||||
// Disable key locks to make pasting from the buffer work correctly
|
||||
if ((event.ctrlKey && event.code === 'KeyV') || (event.ctrlKey && event.code === 'KeyC') || event.code === "F5") {
|
||||
return;
|
||||
}
|
||||
|
||||
// before activating shortcut, ensure user is not actively typing in an input field
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Get Mouse position
|
||||
function getMousePosition(e) {
|
||||
mouseX = e.offsetX;
|
||||
mouseY = e.offsetY;
|
||||
}
|
||||
|
||||
// Simulation of the function to put a long image into the screen.
|
||||
// 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.
|
||||
// We hide the image and show it to the user when it is ready.
|
||||
|
||||
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);
|
||||
|
||||
//observers
|
||||
// Creating an observer with a callback function to handle DOM changes
|
||||
const observer = new MutationObserver((mutationsList, observer) => {
|
||||
for (let mutation of mutationsList) {
|
||||
// If the style attribute of the canvas has changed, by observation it happens only when the picture changes
|
||||
if (mutation.type === 'attributes' && mutation.attributeName === 'style' &&
|
||||
mutation.target.tagName.toLowerCase() === 'canvas') {
|
||||
targetElement.isExpanded = false;
|
||||
setTimeout(resetZoom, 10);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Apply auto expand if enabled
|
||||
if (hotkeysConfig.canvas_auto_expand) {
|
||||
targetElement.addEventListener("mousemove", autoExpand);
|
||||
// Set up an observer to track attribute changes
|
||||
observer.observe(targetElement, { attributes: true, childList: true, subtree: true });
|
||||
}
|
||||
|
||||
// Handle events only inside the targetElement
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Add mouse event handlers
|
||||
targetElement.addEventListener("mousemove", handleMouseMove);
|
||||
targetElement.addEventListener("mouseleave", handleMouseLeave);
|
||||
|
||||
targetElement.addEventListener("wheel", e => {
|
||||
// change zoom level
|
||||
const operation = e.deltaY > 0 ? "-" : "+";
|
||||
changeZoomLevel(operation, e);
|
||||
|
||||
// Handle brush size adjustment with ctrl key pressed
|
||||
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) {
|
||||
e.preventDefault();
|
||||
|
||||
// Increase or decrease brush size based on scroll direction
|
||||
adjustBrushSize(elemId, e.deltaY);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle the move event for pan functionality. Updates the panX and panY variables and applies the new transform to the target element.
|
||||
function handleMoveKeyDown(e) {
|
||||
|
||||
// Disable key locks to make pasting from the buffer work correctly
|
||||
if ((e.ctrlKey && e.code === 'KeyV') || (e.ctrlKey && e.code === 'KeyC') || e.code === "F5") {
|
||||
return;
|
||||
}
|
||||
|
||||
// before activating shortcut, ensure user is not actively typing in an input field
|
||||
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);
|
||||
|
||||
// Detect zoom level and update the pan speed.
|
||||
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;
|
||||
|
||||
// Delayed redraw of an element
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
// Prevents sticking to the mouse
|
||||
window.onblur = function() {
|
||||
isMoving = false;
|
||||
};
|
||||
|
||||
// Checks for extension
|
||||
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");
|
||||
});
|
||||
Reference in New Issue
Block a user