mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 14:42:11 -03:00
feat: implement various UI helpers including clipboard, toasts, theme toggling, and Civitai integration, and add RecipeModal component.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -16,192 +16,196 @@ import { eventManager } from './EventManager.js';
|
|||||||
* @returns {Promise<boolean>} - Promise that resolves to true if copy was successful
|
* @returns {Promise<boolean>} - Promise that resolves to true if copy was successful
|
||||||
*/
|
*/
|
||||||
export async function copyToClipboard(text, successMessage = null) {
|
export async function copyToClipboard(text, successMessage = null) {
|
||||||
const defaultSuccessMessage = successMessage || translate('uiHelpers.clipboard.copied', {}, 'Copied to clipboard');
|
const defaultSuccessMessage = successMessage || translate('uiHelpers.clipboard.copied', {}, 'Copied to clipboard');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Modern clipboard API
|
// Modern clipboard API
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
} else {
|
} else {
|
||||||
// Fallback for older browsers
|
// Fallback for older browsers
|
||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement('textarea');
|
||||||
textarea.value = text;
|
textarea.value = text;
|
||||||
textarea.style.position = 'absolute';
|
textarea.style.position = 'absolute';
|
||||||
textarea.style.left = '-99999px';
|
textarea.style.left = '-99999px';
|
||||||
document.body.appendChild(textarea);
|
document.body.appendChild(textarea);
|
||||||
textarea.select();
|
textarea.select();
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
document.body.removeChild(textarea);
|
document.body.removeChild(textarea);
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultSuccessMessage) {
|
|
||||||
showToast('uiHelpers.clipboard.copied', {}, 'success');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Copy failed:', err);
|
|
||||||
showToast('uiHelpers.clipboard.copyFailed', {}, 'error');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (defaultSuccessMessage) {
|
||||||
|
showToast('uiHelpers.clipboard.copied', {}, 'success');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Copy failed:', err);
|
||||||
|
showToast('uiHelpers.clipboard.copyFailed', {}, 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showToast(key, params = {}, type = 'info', fallback = null) {
|
export function showToast(key, params = {}, type = 'info', fallback = null) {
|
||||||
const message = translate(key, params, fallback);
|
const message = translate(key, params, fallback);
|
||||||
const toast = document.createElement('div');
|
const toast = document.createElement('div');
|
||||||
toast.className = `toast toast-${type}`;
|
toast.className = `toast toast-${type}`;
|
||||||
toast.textContent = message;
|
toast.textContent = message;
|
||||||
|
|
||||||
// Get or create toast container
|
// Get or create toast container
|
||||||
let toastContainer = document.querySelector('.toast-container');
|
let toastContainer = document.querySelector('.toast-container');
|
||||||
if (!toastContainer) {
|
if (!toastContainer) {
|
||||||
toastContainer = document.createElement('div');
|
toastContainer = document.createElement('div');
|
||||||
toastContainer.className = 'toast-container';
|
toastContainer.className = 'toast-container';
|
||||||
document.body.append(toastContainer);
|
document.body.append(toastContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
toastContainer.append(toast);
|
||||||
|
|
||||||
|
// Calculate vertical position for stacked toasts
|
||||||
|
const existingToasts = Array.from(toastContainer.querySelectorAll('.toast'));
|
||||||
|
const toastIndex = existingToasts.indexOf(toast);
|
||||||
|
const topOffset = 20; // Base offset from top
|
||||||
|
const spacing = 10; // Space between toasts
|
||||||
|
|
||||||
|
// Set position based on existing toasts
|
||||||
|
toast.style.top = `${topOffset + (toastIndex * (toast.offsetHeight || 60 + spacing))}px`;
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
toast.classList.add('show');
|
||||||
|
|
||||||
|
// Set timeout based on type
|
||||||
|
let timeout = 2000; // Default (info)
|
||||||
|
if (type === 'warning' || type === 'error') {
|
||||||
|
timeout = 5000;
|
||||||
}
|
}
|
||||||
|
|
||||||
toastContainer.append(toast);
|
|
||||||
|
|
||||||
// Calculate vertical position for stacked toasts
|
setTimeout(() => {
|
||||||
const existingToasts = Array.from(toastContainer.querySelectorAll('.toast'));
|
toast.classList.remove('show');
|
||||||
const toastIndex = existingToasts.indexOf(toast);
|
toast.addEventListener('transitionend', () => {
|
||||||
const topOffset = 20; // Base offset from top
|
toast.remove();
|
||||||
const spacing = 10; // Space between toasts
|
|
||||||
|
|
||||||
// Set position based on existing toasts
|
|
||||||
toast.style.top = `${topOffset + (toastIndex * (toast.offsetHeight || 60 + spacing))}px`;
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
// Reposition remaining toasts
|
||||||
toast.classList.add('show');
|
if (toastContainer) {
|
||||||
|
const remainingToasts = Array.from(toastContainer.querySelectorAll('.toast'));
|
||||||
// Set timeout based on type
|
remainingToasts.forEach((t, index) => {
|
||||||
let timeout = 2000; // Default (info)
|
t.style.top = `${topOffset + (index * (t.offsetHeight || 60 + spacing))}px`;
|
||||||
if (type === 'warning' || type === 'error') {
|
});
|
||||||
timeout = 5000;
|
|
||||||
|
// Remove container if empty
|
||||||
|
if (remainingToasts.length === 0) {
|
||||||
|
toastContainer.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
setTimeout(() => {
|
}, timeout);
|
||||||
toast.classList.remove('show');
|
});
|
||||||
toast.addEventListener('transitionend', () => {
|
|
||||||
toast.remove();
|
|
||||||
|
|
||||||
// Reposition remaining toasts
|
|
||||||
if (toastContainer) {
|
|
||||||
const remainingToasts = Array.from(toastContainer.querySelectorAll('.toast'));
|
|
||||||
remainingToasts.forEach((t, index) => {
|
|
||||||
t.style.top = `${topOffset + (index * (t.offsetHeight || 60 + spacing))}px`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove container if empty
|
|
||||||
if (remainingToasts.length === 0) {
|
|
||||||
toastContainer.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, timeout);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function restoreFolderFilter() {
|
export function restoreFolderFilter() {
|
||||||
const activeFolder = getStorageItem('activeFolder');
|
const activeFolder = getStorageItem('activeFolder');
|
||||||
const folderTag = activeFolder && document.querySelector(`.tag[data-folder="${activeFolder}"]`);
|
const folderTag = activeFolder && document.querySelector(`.tag[data-folder="${activeFolder}"]`);
|
||||||
if (folderTag) {
|
if (folderTag) {
|
||||||
folderTag.classList.add('active');
|
folderTag.classList.add('active');
|
||||||
filterByFolder(activeFolder);
|
filterByFolder(activeFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initTheme() {
|
export function initTheme() {
|
||||||
const savedTheme = getStorageItem('theme') || 'auto';
|
const savedTheme = getStorageItem('theme') || 'auto';
|
||||||
applyTheme(savedTheme);
|
applyTheme(savedTheme);
|
||||||
|
|
||||||
// Update theme when system preference changes (for 'auto' mode)
|
// Update theme when system preference changes (for 'auto' mode)
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||||
const currentTheme = getStorageItem('theme') || 'auto';
|
const currentTheme = getStorageItem('theme') || 'auto';
|
||||||
if (currentTheme === 'auto') {
|
if (currentTheme === 'auto') {
|
||||||
applyTheme('auto');
|
applyTheme('auto');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleTheme() {
|
export function toggleTheme() {
|
||||||
const currentTheme = getStorageItem('theme') || 'auto';
|
const currentTheme = getStorageItem('theme') || 'auto';
|
||||||
let newTheme;
|
let newTheme;
|
||||||
|
|
||||||
if (currentTheme === 'light') {
|
if (currentTheme === 'light') {
|
||||||
newTheme = 'dark';
|
newTheme = 'dark';
|
||||||
} else {
|
} else {
|
||||||
newTheme = 'light';
|
newTheme = 'light';
|
||||||
}
|
}
|
||||||
|
|
||||||
setStorageItem('theme', newTheme);
|
setStorageItem('theme', newTheme);
|
||||||
applyTheme(newTheme);
|
applyTheme(newTheme);
|
||||||
|
|
||||||
// Force a repaint to ensure theme changes are applied immediately
|
// Force a repaint to ensure theme changes are applied immediately
|
||||||
document.body.style.display = 'none';
|
document.body.style.display = 'none';
|
||||||
document.body.offsetHeight; // Trigger a reflow
|
document.body.offsetHeight; // Trigger a reflow
|
||||||
document.body.style.display = '';
|
document.body.style.display = '';
|
||||||
|
|
||||||
return newTheme;
|
return newTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new helper function to apply the theme
|
// Add a new helper function to apply the theme
|
||||||
function applyTheme(theme) {
|
function applyTheme(theme) {
|
||||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
const htmlElement = document.documentElement;
|
const htmlElement = document.documentElement;
|
||||||
|
|
||||||
// Remove any existing theme attributes
|
// Remove any existing theme attributes
|
||||||
htmlElement.removeAttribute('data-theme');
|
htmlElement.removeAttribute('data-theme');
|
||||||
|
|
||||||
// Apply the appropriate theme
|
// Apply the appropriate theme
|
||||||
if (theme === 'dark' || (theme === 'auto' && prefersDark)) {
|
if (theme === 'dark' || (theme === 'auto' && prefersDark)) {
|
||||||
htmlElement.setAttribute('data-theme', 'dark');
|
htmlElement.setAttribute('data-theme', 'dark');
|
||||||
document.body.dataset.theme = 'dark';
|
document.body.dataset.theme = 'dark';
|
||||||
} else {
|
} else {
|
||||||
htmlElement.setAttribute('data-theme', 'light');
|
htmlElement.setAttribute('data-theme', 'light');
|
||||||
document.body.dataset.theme = 'light';
|
document.body.dataset.theme = 'light';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the theme-toggle icon state
|
// Update the theme-toggle icon state
|
||||||
updateThemeToggleIcons(theme);
|
updateThemeToggleIcons(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
// New function to update theme toggle icons
|
// New function to update theme toggle icons
|
||||||
function updateThemeToggleIcons(theme) {
|
function updateThemeToggleIcons(theme) {
|
||||||
const themeToggle = document.querySelector('.theme-toggle');
|
const themeToggle = document.querySelector('.theme-toggle');
|
||||||
if (!themeToggle) return;
|
if (!themeToggle) return;
|
||||||
|
|
||||||
// Remove any existing active classes
|
// Remove any existing active classes
|
||||||
themeToggle.classList.remove('theme-light', 'theme-dark', 'theme-auto');
|
themeToggle.classList.remove('theme-light', 'theme-dark', 'theme-auto');
|
||||||
|
|
||||||
// Add the appropriate class based on current theme
|
// Add the appropriate class based on current theme
|
||||||
themeToggle.classList.add(`theme-${theme}`);
|
themeToggle.classList.add(`theme-${theme}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterByFolder(folderPath) {
|
function filterByFolder(folderPath) {
|
||||||
document.querySelectorAll('.model-card').forEach(card => {
|
document.querySelectorAll('.model-card').forEach(card => {
|
||||||
card.style.display = card.dataset.folder === folderPath ? '' : 'none';
|
card.style.display = card.dataset.folder === folderPath ? '' : 'none';
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openCivitaiByMetadata(civitaiId, versionId, modelName = null) {
|
||||||
|
if (civitaiId) {
|
||||||
|
let url = `https://civitai.com/models/${civitaiId}`;
|
||||||
|
if (versionId) {
|
||||||
|
url += `?modelVersionId=${versionId}`;
|
||||||
|
}
|
||||||
|
window.open(url, '_blank');
|
||||||
|
} else if (modelName) {
|
||||||
|
// 如果没有ID,尝试使用名称搜索
|
||||||
|
window.open(`https://civitai.com/models?query=${encodeURIComponent(modelName)}`, '_blank');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openCivitai(filePath) {
|
export function openCivitai(filePath) {
|
||||||
const loraCard = document.querySelector(`.model-card[data-filepath="${filePath}"]`);
|
const loraCard = document.querySelector(`.model-card[data-filepath="${filePath}"]`);
|
||||||
if (!loraCard) return;
|
if (!loraCard) return;
|
||||||
|
|
||||||
const metaData = JSON.parse(loraCard.dataset.meta);
|
const metaData = JSON.parse(loraCard.dataset.meta);
|
||||||
const civitaiId = metaData.modelId;
|
const civitaiId = metaData.modelId;
|
||||||
const versionId = metaData.id;
|
const versionId = metaData.id;
|
||||||
|
const modelName = loraCard.dataset.name;
|
||||||
if (civitaiId) {
|
|
||||||
let url = `https://civitai.com/models/${civitaiId}`;
|
openCivitaiByMetadata(civitaiId, versionId, modelName);
|
||||||
if (versionId) {
|
|
||||||
url += `?modelVersionId=${versionId}`;
|
|
||||||
}
|
|
||||||
window.open(url, '_blank');
|
|
||||||
} else {
|
|
||||||
// 如果没有ID,尝试使用名称搜索
|
|
||||||
const modelName = loraCard.dataset.name;
|
|
||||||
window.open(`https://civitai.com/models?query=${encodeURIComponent(modelName)}`, '_blank');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,90 +213,90 @@ export function openCivitai(filePath) {
|
|||||||
* based on the current layout and folder tags container height
|
* based on the current layout and folder tags container height
|
||||||
*/
|
*/
|
||||||
export function updatePanelPositions() {
|
export function updatePanelPositions() {
|
||||||
const searchOptionsPanel = document.getElementById('searchOptionsPanel');
|
const searchOptionsPanel = document.getElementById('searchOptionsPanel');
|
||||||
const filterPanel = document.getElementById('filterPanel');
|
const filterPanel = document.getElementById('filterPanel');
|
||||||
|
|
||||||
if (!searchOptionsPanel && !filterPanel) return;
|
if (!searchOptionsPanel && !filterPanel) return;
|
||||||
|
|
||||||
// Get the header element
|
// Get the header element
|
||||||
const header = document.querySelector('.app-header');
|
const header = document.querySelector('.app-header');
|
||||||
if (!header) return;
|
if (!header) return;
|
||||||
|
|
||||||
// Calculate the position based on the bottom of the header
|
// Calculate the position based on the bottom of the header
|
||||||
const headerRect = header.getBoundingClientRect();
|
const headerRect = header.getBoundingClientRect();
|
||||||
const topPosition = headerRect.bottom + 5; // Add 5px padding
|
const topPosition = headerRect.bottom + 5; // Add 5px padding
|
||||||
|
|
||||||
// Set the positions
|
// Set the positions
|
||||||
|
if (searchOptionsPanel) {
|
||||||
|
searchOptionsPanel.style.top = `${topPosition}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterPanel) {
|
||||||
|
filterPanel.style.top = `${topPosition}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust panel horizontal position based on the search container
|
||||||
|
const searchContainer = document.querySelector('.header-search');
|
||||||
|
if (searchContainer) {
|
||||||
|
const searchRect = searchContainer.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Position the search options panel aligned with the search container
|
||||||
if (searchOptionsPanel) {
|
if (searchOptionsPanel) {
|
||||||
searchOptionsPanel.style.top = `${topPosition}px`;
|
searchOptionsPanel.style.right = `${window.innerWidth - searchRect.right}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Position the filter panel aligned with the filter button
|
||||||
if (filterPanel) {
|
if (filterPanel) {
|
||||||
filterPanel.style.top = `${topPosition}px`;
|
const filterButton = document.getElementById('filterButton');
|
||||||
}
|
if (filterButton) {
|
||||||
|
const filterRect = filterButton.getBoundingClientRect();
|
||||||
// Adjust panel horizontal position based on the search container
|
filterPanel.style.right = `${window.innerWidth - filterRect.right}px`;
|
||||||
const searchContainer = document.querySelector('.header-search');
|
|
||||||
if (searchContainer) {
|
|
||||||
const searchRect = searchContainer.getBoundingClientRect();
|
|
||||||
|
|
||||||
// Position the search options panel aligned with the search container
|
|
||||||
if (searchOptionsPanel) {
|
|
||||||
searchOptionsPanel.style.right = `${window.innerWidth - searchRect.right}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position the filter panel aligned with the filter button
|
|
||||||
if (filterPanel) {
|
|
||||||
const filterButton = document.getElementById('filterButton');
|
|
||||||
if (filterButton) {
|
|
||||||
const filterRect = filterButton.getBoundingClientRect();
|
|
||||||
filterPanel.style.right = `${window.innerWidth - filterRect.right}px`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initBackToTop() {
|
export function initBackToTop() {
|
||||||
const button = document.getElementById('backToTopBtn');
|
const button = document.getElementById('backToTopBtn');
|
||||||
if (!button) return;
|
if (!button) return;
|
||||||
|
|
||||||
// Get the scrollable container
|
// Get the scrollable container
|
||||||
const scrollContainer = document.querySelector('.page-content');
|
const scrollContainer = document.querySelector('.page-content');
|
||||||
|
|
||||||
// Show/hide button based on scroll position
|
|
||||||
const toggleBackToTop = () => {
|
|
||||||
const scrollThreshold = window.innerHeight * 0.3;
|
|
||||||
if (scrollContainer.scrollTop > scrollThreshold) {
|
|
||||||
button.classList.add('visible');
|
|
||||||
} else {
|
|
||||||
button.classList.remove('visible');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Smooth scroll to top
|
// Show/hide button based on scroll position
|
||||||
button.addEventListener('click', () => {
|
const toggleBackToTop = () => {
|
||||||
scrollContainer.scrollTo({
|
const scrollThreshold = window.innerHeight * 0.3;
|
||||||
top: 0,
|
if (scrollContainer.scrollTop > scrollThreshold) {
|
||||||
behavior: 'smooth'
|
button.classList.add('visible');
|
||||||
});
|
} else {
|
||||||
|
button.classList.remove('visible');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Smooth scroll to top
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
scrollContainer.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Listen for scroll events on the scrollable container
|
// Listen for scroll events on the scrollable container
|
||||||
scrollContainer.addEventListener('scroll', toggleBackToTop);
|
scrollContainer.addEventListener('scroll', toggleBackToTop);
|
||||||
|
|
||||||
// Initial check
|
// Initial check
|
||||||
toggleBackToTop();
|
toggleBackToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNSFWLevelName(level) {
|
export function getNSFWLevelName(level) {
|
||||||
if (level === 0) return 'Unknown';
|
if (level === 0) return 'Unknown';
|
||||||
if (level >= 32) return 'Blocked';
|
if (level >= 32) return 'Blocked';
|
||||||
if (level >= 16) return 'XXX';
|
if (level >= 16) return 'XXX';
|
||||||
if (level >= 8) return 'X';
|
if (level >= 8) return 'X';
|
||||||
if (level >= 4) return 'R';
|
if (level >= 4) return 'R';
|
||||||
if (level >= 2) return 'PG13';
|
if (level >= 2) return 'PG13';
|
||||||
if (level >= 1) return 'PG';
|
if (level >= 1) return 'PG';
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseUsageTipNumber(value) {
|
function parseUsageTipNumber(value) {
|
||||||
@@ -666,25 +670,25 @@ async function sendLoraToNodes(nodeIds, nodesMap, loraSyntax, replaceMode, synta
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(requestBody)
|
body: JSON.stringify(requestBody)
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// Use different toast messages based on syntax type
|
// Use different toast messages based on syntax type
|
||||||
if (syntaxType === 'recipe') {
|
if (syntaxType === 'recipe') {
|
||||||
const messageKey = replaceMode ?
|
const messageKey = replaceMode ?
|
||||||
'uiHelpers.workflow.recipeReplaced' :
|
'uiHelpers.workflow.recipeReplaced' :
|
||||||
'uiHelpers.workflow.recipeAdded';
|
'uiHelpers.workflow.recipeAdded';
|
||||||
showToast(messageKey, {}, 'success');
|
showToast(messageKey, {}, 'success');
|
||||||
} else {
|
} else {
|
||||||
const messageKey = replaceMode ?
|
const messageKey = replaceMode ?
|
||||||
'uiHelpers.workflow.loraReplaced' :
|
'uiHelpers.workflow.loraReplaced' :
|
||||||
'uiHelpers.workflow.loraAdded';
|
'uiHelpers.workflow.loraAdded';
|
||||||
showToast(messageKey, {}, 'success');
|
showToast(messageKey, {}, 'success');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
const messageKey = syntaxType === 'recipe' ?
|
const messageKey = syntaxType === 'recipe' ?
|
||||||
'uiHelpers.workflow.recipeFailedToSend' :
|
'uiHelpers.workflow.recipeFailedToSend' :
|
||||||
'uiHelpers.workflow.loraFailedToSend';
|
'uiHelpers.workflow.loraFailedToSend';
|
||||||
showToast(messageKey, {}, 'error');
|
showToast(messageKey, {}, 'error');
|
||||||
@@ -692,7 +696,7 @@ async function sendLoraToNodes(nodeIds, nodesMap, loraSyntax, replaceMode, synta
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to send to workflow:', error);
|
console.error('Failed to send to workflow:', error);
|
||||||
const messageKey = syntaxType === 'recipe' ?
|
const messageKey = syntaxType === 'recipe' ?
|
||||||
'uiHelpers.workflow.recipeFailedToSend' :
|
'uiHelpers.workflow.recipeFailedToSend' :
|
||||||
'uiHelpers.workflow.loraFailedToSend';
|
'uiHelpers.workflow.loraFailedToSend';
|
||||||
showToast(messageKey, {}, 'error');
|
showToast(messageKey, {}, 'error');
|
||||||
@@ -773,7 +777,7 @@ let nodeSelectorState = {
|
|||||||
function showNodeSelector(nodes, options = {}) {
|
function showNodeSelector(nodes, options = {}) {
|
||||||
const selector = document.getElementById('nodeSelector');
|
const selector = document.getElementById('nodeSelector');
|
||||||
if (!selector) return;
|
if (!selector) return;
|
||||||
|
|
||||||
// Clean up any existing state
|
// Clean up any existing state
|
||||||
hideNodeSelector();
|
hideNodeSelector();
|
||||||
|
|
||||||
@@ -787,7 +791,7 @@ function showNodeSelector(nodes, options = {}) {
|
|||||||
nodeSelectorState.currentNodes = safeNodes;
|
nodeSelectorState.currentNodes = safeNodes;
|
||||||
nodeSelectorState.onSend = onSend;
|
nodeSelectorState.onSend = onSend;
|
||||||
nodeSelectorState.enableSendAll = options.enableSendAll !== false;
|
nodeSelectorState.enableSendAll = options.enableSendAll !== false;
|
||||||
|
|
||||||
// Generate node list HTML with icons and proper colors
|
// Generate node list HTML with icons and proper colors
|
||||||
const nodeItems = Object.entries(safeNodes).map(([nodeKey, node]) => {
|
const nodeItems = Object.entries(safeNodes).map(([nodeKey, node]) => {
|
||||||
const iconClass = NODE_TYPE_ICONS[node.type] || 'fas fa-question-circle';
|
const iconClass = NODE_TYPE_ICONS[node.type] || 'fas fa-question-circle';
|
||||||
@@ -803,7 +807,7 @@ function showNodeSelector(nodes, options = {}) {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
// Add header with action mode indicator
|
// Add header with action mode indicator
|
||||||
const actionType = options.actionType ?? translate('uiHelpers.nodeSelector.lora', {}, 'LoRA');
|
const actionType = options.actionType ?? translate('uiHelpers.nodeSelector.lora', {}, 'LoRA');
|
||||||
const actionMode = options.actionMode ?? translate('uiHelpers.nodeSelector.replace', {}, 'Replace');
|
const actionMode = options.actionMode ?? translate('uiHelpers.nodeSelector.replace', {}, 'Replace');
|
||||||
@@ -819,7 +823,7 @@ function showNodeSelector(nodes, options = {}) {
|
|||||||
<span>${sendToAllText}</span>
|
<span>${sendToAllText}</span>
|
||||||
</div>`
|
</div>`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
selector.innerHTML = `
|
selector.innerHTML = `
|
||||||
<div class="node-selector-header">
|
<div class="node-selector-header">
|
||||||
<span class="selector-action-type">${actionMode} ${actionType}</span>
|
<span class="selector-action-type">${actionMode} ${actionType}</span>
|
||||||
@@ -828,17 +832,17 @@ function showNodeSelector(nodes, options = {}) {
|
|||||||
${nodeItems}
|
${nodeItems}
|
||||||
${sendAllMarkup}
|
${sendAllMarkup}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Position near mouse
|
// Position near mouse
|
||||||
positionNearMouse(selector);
|
positionNearMouse(selector);
|
||||||
|
|
||||||
// Show selector
|
// Show selector
|
||||||
selector.style.display = 'block';
|
selector.style.display = 'block';
|
||||||
nodeSelectorState.isActive = true;
|
nodeSelectorState.isActive = true;
|
||||||
|
|
||||||
// Update event manager state
|
// Update event manager state
|
||||||
eventManager.setState('nodeSelectorActive', true);
|
eventManager.setState('nodeSelectorActive', true);
|
||||||
|
|
||||||
// Setup event listeners with proper cleanup through event manager
|
// Setup event listeners with proper cleanup through event manager
|
||||||
setupNodeSelectorEvents(selector);
|
setupNodeSelectorEvents(selector);
|
||||||
}
|
}
|
||||||
@@ -850,7 +854,7 @@ function showNodeSelector(nodes, options = {}) {
|
|||||||
function setupNodeSelectorEvents(selector) {
|
function setupNodeSelectorEvents(selector) {
|
||||||
// Clean up any existing event listeners
|
// Clean up any existing event listeners
|
||||||
cleanupNodeSelectorEvents();
|
cleanupNodeSelectorEvents();
|
||||||
|
|
||||||
// Register click outside handler with event manager
|
// Register click outside handler with event manager
|
||||||
eventManager.addHandler('click', 'nodeSelector-outside', (e) => {
|
eventManager.addHandler('click', 'nodeSelector-outside', (e) => {
|
||||||
if (!selector.contains(e.target)) {
|
if (!selector.contains(e.target)) {
|
||||||
@@ -861,12 +865,12 @@ function setupNodeSelectorEvents(selector) {
|
|||||||
priority: 200, // High priority to handle before other click handlers
|
priority: 200, // High priority to handle before other click handlers
|
||||||
onlyWhenNodeSelectorActive: true
|
onlyWhenNodeSelectorActive: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register node selection handler with event manager
|
// Register node selection handler with event manager
|
||||||
eventManager.addHandler('click', 'nodeSelector-selection', async (e) => {
|
eventManager.addHandler('click', 'nodeSelector-selection', async (e) => {
|
||||||
const nodeItem = e.target.closest('.node-item');
|
const nodeItem = e.target.closest('.node-item');
|
||||||
if (!nodeItem) return false; // Continue with other handlers
|
if (!nodeItem) return false; // Continue with other handlers
|
||||||
|
|
||||||
const onSend = nodeSelectorState.onSend;
|
const onSend = nodeSelectorState.onSend;
|
||||||
if (typeof onSend !== 'function') {
|
if (typeof onSend !== 'function') {
|
||||||
hideNodeSelector();
|
hideNodeSelector();
|
||||||
@@ -874,11 +878,11 @@ function setupNodeSelectorEvents(selector) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const action = nodeItem.dataset.action;
|
const action = nodeItem.dataset.action;
|
||||||
const nodeId = nodeItem.dataset.nodeId;
|
const nodeId = nodeItem.dataset.nodeId;
|
||||||
const nodes = nodeSelectorState.currentNodes || {};
|
const nodes = nodeSelectorState.currentNodes || {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (action === 'send-all') {
|
if (action === 'send-all') {
|
||||||
if (!nodeSelectorState.enableSendAll) {
|
if (!nodeSelectorState.enableSendAll) {
|
||||||
@@ -908,7 +912,7 @@ function cleanupNodeSelectorEvents() {
|
|||||||
// Remove event handlers from event manager
|
// Remove event handlers from event manager
|
||||||
eventManager.removeHandler('click', 'nodeSelector-outside');
|
eventManager.removeHandler('click', 'nodeSelector-outside');
|
||||||
eventManager.removeHandler('click', 'nodeSelector-selection');
|
eventManager.removeHandler('click', 'nodeSelector-selection');
|
||||||
|
|
||||||
// Clear legacy references
|
// Clear legacy references
|
||||||
nodeSelectorState.clickHandler = null;
|
nodeSelectorState.clickHandler = null;
|
||||||
nodeSelectorState.selectorClickHandler = null;
|
nodeSelectorState.selectorClickHandler = null;
|
||||||
@@ -923,14 +927,14 @@ function hideNodeSelector() {
|
|||||||
selector.style.display = 'none';
|
selector.style.display = 'none';
|
||||||
selector.innerHTML = ''; // Clear content to prevent memory leaks
|
selector.innerHTML = ''; // Clear content to prevent memory leaks
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up event listeners
|
// Clean up event listeners
|
||||||
cleanupNodeSelectorEvents();
|
cleanupNodeSelectorEvents();
|
||||||
nodeSelectorState.isActive = false;
|
nodeSelectorState.isActive = false;
|
||||||
nodeSelectorState.currentNodes = {};
|
nodeSelectorState.currentNodes = {};
|
||||||
nodeSelectorState.onSend = null;
|
nodeSelectorState.onSend = null;
|
||||||
nodeSelectorState.enableSendAll = true;
|
nodeSelectorState.enableSendAll = true;
|
||||||
|
|
||||||
// Update event manager state
|
// Update event manager state
|
||||||
eventManager.setState('nodeSelectorActive', false);
|
eventManager.setState('nodeSelectorActive', false);
|
||||||
}
|
}
|
||||||
@@ -943,28 +947,28 @@ function positionNearMouse(element) {
|
|||||||
// Get current mouse position from last mouse event or use default
|
// Get current mouse position from last mouse event or use default
|
||||||
const mouseX = window.lastMouseX || window.innerWidth / 2;
|
const mouseX = window.lastMouseX || window.innerWidth / 2;
|
||||||
const mouseY = window.lastMouseY || window.innerHeight / 2;
|
const mouseY = window.lastMouseY || window.innerHeight / 2;
|
||||||
|
|
||||||
// Show element temporarily to get dimensions
|
// Show element temporarily to get dimensions
|
||||||
element.style.visibility = 'hidden';
|
element.style.visibility = 'hidden';
|
||||||
element.style.display = 'block';
|
element.style.display = 'block';
|
||||||
|
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
const viewportWidth = document.documentElement.clientWidth;
|
const viewportWidth = document.documentElement.clientWidth;
|
||||||
const viewportHeight = document.documentElement.clientHeight;
|
const viewportHeight = document.documentElement.clientHeight;
|
||||||
|
|
||||||
// Calculate position with offset from mouse
|
// Calculate position with offset from mouse
|
||||||
let x = mouseX + 10;
|
let x = mouseX + 10;
|
||||||
let y = mouseY + 10;
|
let y = mouseY + 10;
|
||||||
|
|
||||||
// Ensure element doesn't go offscreen
|
// Ensure element doesn't go offscreen
|
||||||
if (x + rect.width > viewportWidth) {
|
if (x + rect.width > viewportWidth) {
|
||||||
x = mouseX - rect.width - 10;
|
x = mouseX - rect.width - 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (y + rect.height > viewportHeight) {
|
if (y + rect.height > viewportHeight) {
|
||||||
y = mouseY - rect.height - 10;
|
y = mouseY - rect.height - 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply position
|
// Apply position
|
||||||
element.style.left = `${x}px`;
|
element.style.left = `${x}px`;
|
||||||
element.style.top = `${y}px`;
|
element.style.top = `${y}px`;
|
||||||
@@ -1002,9 +1006,9 @@ export async function openExampleImagesFolder(modelHash) {
|
|||||||
model_hash: modelHash
|
model_hash: modelHash
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const message = translate('uiHelpers.exampleImages.openingFolder', {}, 'Opening example images folder');
|
const message = translate('uiHelpers.exampleImages.openingFolder', {}, 'Opening example images folder');
|
||||||
showToast('uiHelpers.exampleImages.opened', {}, 'success');
|
showToast('uiHelpers.exampleImages.opened', {}, 'success');
|
||||||
|
|||||||
Reference in New Issue
Block a user