sd-civitai-browser-plus/javascript/civitai-html.js

520 lines
20 KiB
JavaScript

"use strict";
// Selects a model by pressing on card
function select_model(model_name, bool = false, content_type = null) {
const output = bool ? gradioApp().querySelector('#model_sent textarea') : gradioApp().querySelector('#model_select textarea');
if (output && model_name) {
const randomNumber = Math.floor(Math.random() * 1000);
const paddedNumber = String(randomNumber).padStart(3, '0');
output.value = model_name + "." + paddedNumber;
updateInput(output);
}
if (content_type) {
const outputType = gradioApp().querySelector('#type_sent textarea');
const randomNumber = Math.floor(Math.random() * 1000);
const paddedNumber = String(randomNumber).padStart(3, '0');
outputType.value = content_type + "." + paddedNumber;
updateInput(outputType);
}
}
// Changes the card size
function updateCardSize(width, height) {
var styleSheet = document.styleSheets[0];
var dimensionsKeyframes = `width: ${width}em !important; height: ${height}em !important;`;
var fontSize = (width / 8) * 100;
var textKeyframes = `font-size: ${fontSize}% !important;`;
addOrUpdateRule(styleSheet, '.civmodelcard img', dimensionsKeyframes);
addOrUpdateRule(styleSheet, '.civmodelcard .video-bg', dimensionsKeyframes);
addOrUpdateRule(styleSheet, '.civmodelcard figcaption', textKeyframes);
}
// Toggles NSFW display
function toggleNSFWContent(hideAndBlur) {
const sheet = document.styleSheets[0];
const toggleRule = (selector, rules) => addOrUpdateRule(sheet, selector, rules);
toggleRule('.civcardnsfw', hideAndBlur ? 'display: block;' : 'display: none;');
toggleRule('.civnsfw img', hideAndBlur ? 'filter: none;' : 'filter: blur(10px);');
const dateSections = document.querySelectorAll('.date-section');
dateSections.forEach((section) => {
const cards = section.querySelectorAll('.civmodelcard');
const nsfwCards = section.querySelectorAll('.civmodelcard.civcardnsfw');
section.style.display = !hideAndBlur && cards.length === nsfwCards.length ? 'none' : 'block';
});
}
// Updates site with css insertions
function addOrUpdateRule(styleSheet, selector, newRules) {
for (let i = 0; i < styleSheet.cssRules.length; i++) {
let rule = styleSheet.cssRules[i];
if (rule.selectorText === selector) {
rule.style.cssText = newRules;
return;
}
}
styleSheet.insertRule(`${selector} { ${newRules} }`, styleSheet.cssRules.length);
}
// Updates card border
function updateCard(modelNameWithSuffix) {
const lastDotIndex = modelNameWithSuffix.lastIndexOf('.');
const modelName = modelNameWithSuffix.slice(0, lastDotIndex);
const suffix = modelNameWithSuffix.slice(lastDotIndex + 1);
let additionalClassName = '';
switch(suffix) {
case 'None':
additionalClassName = '';
break;
case 'Old':
additionalClassName = 'civmodelcardoutdated';
break;
case 'New':
additionalClassName = 'civmodelcardinstalled';
break;
default:
console.error('Unknown suffix', suffix);
return;
}
const parentDiv = document.querySelector('.civmodellist');
if (parentDiv) {
const cards = parentDiv.querySelectorAll('.civmodelcard');
cards.forEach((card) => {
const onclickAttr = card.getAttribute('onclick');
if (onclickAttr && onclickAttr.includes(`select_model('${modelName}')`)) {
card.className = `civmodelcard ${additionalClassName}`;
}
});
}
}
// Enables refresh with alt+enter and ctrl+enter
document.addEventListener('keydown', function(e) {
var handled = false;
if (e.key !== undefined) {
if ((e.key == "Enter" && (e.metaKey || e.ctrlKey || e.altKey))) handled = true;
} else if (e.keyCode !== undefined) {
if ((e.keyCode == 13 && (e.metaKey || e.ctrlKey || e.altKey))) handled = true;
}
if (handled) {
var currentTabContent = get_uiCurrentTabContent();
if (currentTabContent && currentTabContent.id === "tab_civitai_interface") {
var refreshButton = currentTabContent.querySelector('#refreshBtn');
if (!refreshButton) {
refreshButton = currentTabContent.querySelector('#refreshBtnL');
}
if (refreshButton) {
refreshButton.click();
}
e.preventDefault();
}
}
});
function BackToTop() {
const c = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
if (c > 0) {
window.requestAnimationFrame(BackToTop);
document.body.scrollTop = c - c / 8;
document.documentElement.scrollTop = c - c / 8;
}
}
// Function to adjust alignment of Filter Accordion
function adjustFilterBoxAndButtons() {
const element = document.querySelector("#filterBox") || document.querySelector("#filterBoxL");
if (!element) return;
const childDiv = element.querySelector("div:nth-child(3)");
if (!childDiv) return;
const isLargeScreen = window.innerWidth >= 1250;
const isMediumScreen = window.innerWidth < 1250 && window.innerWidth > 915;
const isNarrowScreen = window.innerWidth < 800;
const modelBlocks = document.querySelectorAll("#civitai_preview_html .model-block");
if (modelBlocks) {
modelBlocks.forEach(modelBlock => {
if (isNarrowScreen) {
modelBlock.style.flexWrap = "wrap";
modelBlock.style.justifyContent = "center";
} else {
modelBlock.style.flexWrap = "nowrap";
modelBlock.style.justifyContent = "flex-start";
}
});
}
childDiv.style.marginLeft = isLargeScreen ? "0px" : isMediumScreen ? `${1250 - window.innerWidth}px` : "0px";
element.style.justifyContent = isLargeScreen || isMediumScreen ? "center" : "flex-start";
const pageBtn1 = document.querySelector("#pageBtn1");
const pageBtn2 = document.querySelector("#pageBtn2");
const pageBox = document.querySelector("#pageBox");
const pageBoxMobile = document.querySelector("#pageBoxMobile");
if (window.innerWidth < 530) {
childDiv.style.width = "300px";
if (pageBoxMobile) {
pageBtn1 && pageBoxMobile.appendChild(pageBtn1);
pageBtn2 && pageBoxMobile.appendChild(pageBtn2);
pageBoxMobile.style.paddingBottom = "15px";
}
} else {
childDiv.style.width = "400px";
if (pageBox) {
pageBtn1 && pageBox.insertBefore(pageBtn1, pageBox.firstChild);
pageBtn2 && pageBox.appendChild(pageBtn2);
pageBoxMobile.style.paddingBottom = "0px";
}
}
}
// Calls the function above whenever the window is resized
window.addEventListener("resize", adjustFilterBoxAndButtons);
// Function to trigger refresh button with extra checks for page slider
function pressRefresh() {
setTimeout(() => {
const input = document.querySelector("#pageSlider > div:nth-child(2) > div > input");
if (document.activeElement === input) {
input.addEventListener('keydown', function(event) {
if (event.key === 'Enter' || event.keyCode === 13) {
input.blur();
}
});
input.addEventListener('blur', function() {
return;
});
return;
}
let button = document.querySelector("#refreshBtn");
if (!button) {
button = document.querySelector("#refreshBtnL");
}
if (button) {
button.click();
} else {
console.error("Both buttons with IDs #refreshBtn and #refreshBtnL not found.");
}
}, 200);
}
// Update SVG Icons based on dark theme or light theme
function updateSVGIcons() {
const isDark = document.body.classList.contains('dark');
const filterIconUrl = isDark ? "https://gistcdn.githack.com/BlafKing/a20124cedafad23d4eecc1367ec22896/raw/04a4dae0771353377747dadf57c91d55bf841bed/filter-light.svg" : "https://gistcdn.githack.com/BlafKing/686c3438f5d0d13e7e47135f25445ef3/raw/46477777faac7209d001829a171462d9a2ff1467/filter-dark.svg";
const searchIconUrl = isDark ? "https://gistcdn.githack.com/BlafKing/3f95619089bac3b4fd5470a986e1b3bb/raw/ebaa9cceee3436711eb560a7a65e151f1d651c6a/search-light.svg" : "https://gistcdn.githack.com/BlafKing/57573592d5857e102a4bfde852f62639/raw/aa213e9e82d705651603507e26545eb0ffe60c90/search-dark.svg";
const element = document.querySelector("#filterBox, #filterBoxL");
const childDiv = element?.querySelector("div:nth-child(3)");
if (childDiv) {
childDiv.style.cssText = `box-shadow: ${isDark ? '#ffffff' : '#000000'} 0px 0px 2px 0px; display: none;`;
}
const style = document.createElement('style');
style.innerHTML = `
#filterBox > div:nth-child(2) > span:nth-child(2)::before,
#filterBoxL > div:nth-child(2) > span:nth-child(2)::before {
background: url('${filterIconUrl}') no-repeat center center;
background-size: contain;
}
`;
document.head.appendChild(style);
const refreshBtn = document.querySelector("#refreshBtn, #refreshBtnL");
const targetSearchElement = refreshBtn?.firstChild || refreshBtnL?.firstChild;
if (targetSearchElement) {
targetSearchElement.src = searchIconUrl;
}
}
// Creates a tooltip if the user wants to filter liked models without a personal API key
function createTooltipOnHover() {
const toggle4L = document.getElementById('toggle4L');
const toggle4 = document.getElementById('toggle4');
if (toggle4L || toggle4) {
const targetElement = toggle4L || toggle4;
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.textContent = 'Requires an API Key\nConfigurable in CivitAI settings tab';
tooltip.style.cssText = 'display: none; text-align: center; white-space: pre;';
targetElement.addEventListener('mouseover', () => {
tooltip.style.display = 'block';
});
targetElement.addEventListener('mouseout', () => {
tooltip.style.display = 'none';
});
targetElement.appendChild(tooltip);
}
}
// Changes the Tab title
function changeTabTitle() {
const tabElement = document.getElementById('rc-tabs-0-tab-tab_civitai_interface');
if (tabElement) {
tabElement.textContent = 'CivitAI Browser+';
}
}
// Function that closes filter dropdown if clicked outside the dropdown
function setupClickOutsideListener() {
var filterBox = document.getElementById("filterBoxL") || document.getElementById("filterBox");
var filterButton = filterBox.getElementsByTagName("div")[1];
var dropDown = filterBox.getElementsByTagName("div")[2];
function clickOutsideHandler(event) {
var target = event.target;
if (!filterBox.contains(target)) {
if (!dropDown.contains(target)) {
if (filterButton.className.endsWith("open")) {
filterButton.click();
}
}
}
}
document.addEventListener("click", clickOutsideHandler);
}
// Create hyperlink in settings to CivitAI account settings
function createLink(infoElement) {
const existingText = "(You can create your own API key in your CivitAI account settings, Requires UI reload)";
const linkText = "CivitAI account settings";
const [textBefore, textAfter] = existingText.split(linkText);
const link = document.createElement('a');
link.textContent = linkText;
link.href = 'https://civitai.com/user/account';
link.target = '_blank';
while (infoElement.firstChild) infoElement.removeChild(infoElement.firstChild);
infoElement.appendChild(document.createTextNode(textBefore));
infoElement.appendChild(link);
infoElement.appendChild(document.createTextNode(textAfter));
}
// Function to update the visibility of backToTopDiv based on the intersection with civitaiDiv
function updateBackToTopVisibility(entries) {
var backToTopDiv = document.getElementById('backToTopContainer');
var civitaiDiv = document.getElementById('civitai_preview_html');
if (civitaiDiv.clientHeight > 0 && entries[0].isIntersecting && window.scrollY !== 0) {
backToTopDiv.style.visibility = 'visible';
} else {
backToTopDiv.style.visibility = 'hidden';
}
}
// Options for the Intersection Observer
var options = {
root: null,
rootMargin: '0px 0px -60px 0px',
threshold: 0
};
// Create an Intersection Observer instance
const observer = new IntersectionObserver(updateBackToTopVisibility, options);
function handleCivitaiDivChanges() {
var civitaiDiv = document.getElementById('civitai_preview_html');
observer.unobserve(civitaiDiv);
observer.observe(civitaiDiv);
}
document.addEventListener("scroll", handleCivitaiDivChanges)
// Create the accordion dropdown inside the settings tab
function createAccordion(containerDiv, subfolders, name) {
if (containerDiv == null || subfolders.length == 0) {
return;
}
var accordionContainer = document.createElement('div');
accordionContainer.id = 'settings-accordion';
var toggleButton = document.createElement('button');
toggleButton.id = 'accordionToggle';
toggleButton.innerHTML = name + '<div style="transition: transform 0.15s; transform: rotate(90deg)">▼</div>';
toggleButton.onclick = function () {
accordionDiv.style.display = (accordionDiv.style.display === 'none') ? 'block' : 'none';
toggleButton.lastChild.style.transform = accordionDiv.style.display === 'none' ? 'rotate(90deg)' : 'rotate(0)';
};
accordionContainer.appendChild(toggleButton);
var accordionDiv = document.createElement('div');
accordionDiv.classList.add('accordion');
accordionDiv.append(...subfolders);
accordionDiv.style.display = 'none';
accordionContainer.appendChild(accordionDiv);
containerDiv.appendChild(accordionContainer);
}
// Adds a button to the cards in txt2img and img2img
function createCardButtons(event) {
const clickedElement = event.target;
const validButtonNames = ['Textual Inversion', 'Hypernetworks', 'Checkpoints', 'Lora'];
const validParentIds = ['txt2img_textual_inversion_cards_html', 'txt2img_hypernetworks_cards_html', 'txt2img_checkpoints_cards_html', 'txt2img_lora_cards_html'];
const hasMatchingButtonName = validButtonNames.some(buttonName =>
clickedElement.innerText.trim() === buttonName
);
const flexboxDivs = document.querySelectorAll('.layoutkit-flexbox');
let isLobeTheme = false;
flexboxDivs.forEach(div => {
const anchorElements = div.querySelectorAll('a');
const hasGitHubLink = Array.from(anchorElements).some(anchor => anchor.href === 'https://github.com/lobehub/sd-webui-lobe-theme/releases');
if (hasGitHubLink) {
isLobeTheme = true;
}
});
if (hasMatchingButtonName || isLobeTheme) {
const checkForCardDivs = setInterval(() => {
const cardDivs = document.querySelectorAll('.card');
if (cardDivs.length > 0) {
clearInterval(checkForCardDivs);
cardDivs.forEach(cardDiv => {
const buttonRow = cardDiv.querySelector('.button-row');
const actions = cardDiv.querySelector('.actions');
const nameSpan = actions.querySelector('.name');
let modelName = nameSpan.textContent.trim();
let currentElement = cardDiv.parentElement;
let content_type = null;
while (currentElement) {
const parentId = currentElement.id;
if (validParentIds.includes(parentId)) {
content_type = parentId;
break;
}
currentElement = currentElement.parentElement;
}
const existingDiv = buttonRow.querySelector('.goto-civitbrowser.card-button');
if (existingDiv) {
return;
}
const metaDataButton = buttonRow.querySelector('.metadata-button.card-button')
const newDiv = document.createElement('div');
newDiv.classList.add('goto-civitbrowser', 'card-button');
newDiv.addEventListener('click', function (event) {
event.stopPropagation();
sendModelToBrowser(modelName, content_type);
});
const svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
if (isLobeTheme) {
svgIcon.setAttribute('width', '25');
svgIcon.setAttribute('height', '25');
} else {
if (metaDataButton) {
metaDataButton.style.paddingTop = '5px';
metaDataButton.style.width = '42px';
metaDataButton.style.fontSize = '230%';
}
svgIcon.setAttribute('width', '40');
svgIcon.setAttribute('height', '40');
}
svgIcon.setAttribute('viewBox', '75 0 500 500');
svgIcon.setAttribute('fill', 'white');
svgIcon.innerHTML = `
<path d="M 352.79 218.85 L 319.617 162.309 L 203.704 162.479 L 146.28 259.066 L 203.434 355.786 L 319.373 355.729 L 352.773 299.386 L 411.969 299.471 L 348.861 404.911 L 174.065 404.978 L 87.368 259.217 L 174.013 113.246 L 349.147 113.19 L 411.852 218.782 L 352.79 218.85 Z"/>
<path d="M 304.771 334.364 L 213.9 334.429 L 169.607 259.146 L 214.095 183.864 L 305.132 183.907 L 330.489 227.825 L 311.786 259.115 L 330.315 290.655 Z M 278.045 290.682 L 259.294 259.18 L 278.106 227.488 L 240.603 227.366 L 221.983 259.128 L 240.451 291.026 Z"/>
`;
newDiv.appendChild(svgIcon);
buttonRow.insertBefore(newDiv, buttonRow.firstChild);
});
}
}, 100);
}
}
document.addEventListener('click', createCardButtons);
function sendModelToBrowser(modelName, content_type) {
const tabNav = document.querySelector('.tab-nav');
const buttons = tabNav.querySelectorAll('button');
for (const button of buttons) {
if (button.textContent.includes('Civitai')) {
button.click();
const firstButton = document.querySelector('#tab_civitai_interface > div > div > div > button');
if (firstButton) {
firstButton.click();
}
}
}
select_model(modelName, true, content_type);
}
// Clicks the first item in the browser cards list
function clickFirstFigureInColumn() {
const columnDiv = document.querySelector('.column.civmodellist');
if (columnDiv) {
const firstFigure = columnDiv.querySelector('figure');
if (firstFigure) {
firstFigure.click();
}
}
}
// Runs all functions when the page is fully loaded
function onPageLoad() {
const divElement = document.getElementById('setting_custom_api_key');
var civitaiDiv = document.getElementById('civitai_preview_html');
const infoElement = divElement?.querySelector('.info');
if (!infoElement) {
return;
}
var subfolderDiv = document.querySelector("#settings_civitai_browser_plus > div > div");
var subfolders = subfolderDiv.querySelectorAll("[id$='subfolder']");
createAccordion(subfolderDiv, subfolders, "Default sub folders");
var upscalerDiv = document.querySelector("#settings_civitai_browser_plus > div > div > #settings-accordion > div");
var upscalers = upscalerDiv.querySelectorAll("[id$='upscale_subfolder']");
createAccordion(upscalerDiv, upscalers, "Upscalers");
observer.observe(civitaiDiv);
clearInterval(intervalID);
updateSVGIcons();
adjustFilterBoxAndButtons();
createTooltipOnHover();
changeTabTitle();
setupClickOutsideListener();
createLink(infoElement);
updateBackToTopVisibility([{isIntersecting: false}]);
}
// Checks every second if the page is fully loaded
let intervalID = setInterval(onPageLoad, 1000);