mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-26 07:35:44 -03:00
Add toast notification functionality and enhance user feedback in modal interactions
This commit is contained in:
@@ -609,3 +609,69 @@ body.modal-open {
|
|||||||
.close:hover {
|
.close:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Toast Notifications */
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) translateY(100px);
|
||||||
|
background: var(--lora-surface);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: calc(var(--z-overlay) + 10); /* 确保 toast 显示在模态窗口之上 */
|
||||||
|
opacity: 0;
|
||||||
|
transition: transform 0.3s ease-out, opacity 0.3s ease-out;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 90%;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border: 1px solid var(--lora-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 当模态窗口打开时的 toast 样式 */
|
||||||
|
body.modal-open .toast {
|
||||||
|
bottom: 50% !important; /* 强制覆盖默认位置 */
|
||||||
|
transform: translate(-50%, 50%) !important; /* 强制覆盖默认变换 */
|
||||||
|
background: var(--lora-accent);
|
||||||
|
color: white;
|
||||||
|
z-index: 9999; /* 确保显示在最上层 */
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.show {
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保在模态窗口打开时,不同类型的 toast 依然可辨识 */
|
||||||
|
body.modal-open .toast-success {
|
||||||
|
background: oklch(65% 0.2 142); /* 绿色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.modal-open .toast-error {
|
||||||
|
background: oklch(65% 0.2 29); /* 红色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.modal-open .toast-info {
|
||||||
|
background: oklch(65% 0.2 256); /* 蓝色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-success {
|
||||||
|
border-left: 4px solid #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-error {
|
||||||
|
border-left: 4px solid #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-info {
|
||||||
|
border-left: 4px solid #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure toasts are visible in both themes */
|
||||||
|
[data-theme="dark"] .toast {
|
||||||
|
background: var(--lora-surface);
|
||||||
|
color: var(--lora-text);
|
||||||
|
}
|
||||||
@@ -131,6 +131,32 @@ class ModalManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加 toast 通知功能
|
||||||
|
function showToast(message, type = 'info') {
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = `toast toast-${type}`;
|
||||||
|
toast.textContent = message;
|
||||||
|
|
||||||
|
// 移除任何现有的 toast
|
||||||
|
document.querySelectorAll('.toast').forEach(t => t.remove());
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
// 如果模态窗口打开,调整 toast 位置
|
||||||
|
if (document.body.classList.contains('modal-open')) {
|
||||||
|
toast.style.transform = 'translate(-50%, 50%)'; // 在屏幕中间显示
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发动画
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
toast.classList.add('show');
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.classList.remove('show');
|
||||||
|
setTimeout(() => toast.remove(), 300);
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const wsClient = new WebSocketClient(`ws://${window.location.host}/ws`);
|
const wsClient = new WebSocketClient(`ws://${window.location.host}/ws`);
|
||||||
@@ -351,8 +377,32 @@ document.querySelectorAll('.lora-card').forEach(card => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 更新卡片复制操作
|
||||||
|
document.querySelectorAll('.lora-card').forEach(card => {
|
||||||
|
const copyBtn = card.querySelector('.fa-copy');
|
||||||
|
if (copyBtn) {
|
||||||
|
copyBtn.onclick = (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
navigator.clipboard.writeText(card.dataset.file_name)
|
||||||
|
.then(() => showToast('Model name copied to clipboard', 'success'))
|
||||||
|
.catch(() => showToast('Failed to copy model name', 'error'));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为没有元数据的卡片添加点击反馈
|
||||||
|
card.addEventListener('click', () => {
|
||||||
|
const meta = JSON.parse(card.dataset.meta || '{}');
|
||||||
|
if (Object.keys(meta).length === 0) {
|
||||||
|
showToast('This model is not available on Civitai. No additional information to display.', 'info');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function showModal(lora) {
|
function showModal(lora) {
|
||||||
const modal = document.getElementById('loraModal');
|
const modal = document.getElementById('loraModal');
|
||||||
|
const escapedWords = lora.trainedWords?.length ?
|
||||||
|
lora.trainedWords.join(', ').toUpperCase().replace(/'/g, '\\\'') : '';
|
||||||
|
|
||||||
modal.innerHTML = `
|
modal.innerHTML = `
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h2>${lora.model.name}</h2>
|
<h2>${lora.model.name}</h2>
|
||||||
@@ -362,9 +412,9 @@ function showModal(lora) {
|
|||||||
<div class="description">About this version: ${lora.description ? lora.description : 'N/A'}</div>
|
<div class="description">About this version: ${lora.description ? lora.description : 'N/A'}</div>
|
||||||
<div class="trigger-words">
|
<div class="trigger-words">
|
||||||
<strong>Trigger Words:</strong>
|
<strong>Trigger Words:</strong>
|
||||||
<span class="word-list">${lora.trainedWords?.length ? lora.trainedWords.join(', ').toUpperCase() : 'N/A'}</span>
|
<span class="word-list">${escapedWords || 'N/A'}</span>
|
||||||
${lora.trainedWords?.length ? `
|
${escapedWords ? `
|
||||||
<button class="copy-btn" onclick="navigator.clipboard.writeText('${lora.trainedWords.join(', ').toUpperCase()}')">
|
<button class="copy-btn" onclick="copyTriggerWords(\`${escapedWords}\`)">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24">
|
<svg width="16" height="16" viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
|
<path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -377,24 +427,47 @@ function showModal(lora) {
|
|||||||
<button class="close" onclick="closeModal()">×</button>
|
<button class="close" onclick="closeModal()">×</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
modal.style.display = 'block';
|
modal.style.display = 'block';
|
||||||
document.body.classList.add('modal-open');
|
document.body.classList.add('modal-open');
|
||||||
|
|
||||||
// 添加点击事件监听器
|
|
||||||
modal.onclick = function (event) {
|
modal.onclick = function (event) {
|
||||||
// 如果点击的是模态窗口的背景(不是内容区域),则关闭模态窗口
|
|
||||||
if (event.target === modal) {
|
if (event.target === modal) {
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal() {
|
function copyTriggerWords(words) {
|
||||||
const modal = document.getElementById('loraModal');
|
if (!words) return;
|
||||||
modal.style.display = 'none';
|
|
||||||
document.body.classList.remove('modal-open');
|
navigator.clipboard.writeText(words)
|
||||||
// 移除点击事件监听器
|
.then(() => {
|
||||||
modal.onclick = null;
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'toast toast-success';
|
||||||
|
toast.textContent = 'Trigger words copied to clipboard';
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
// Force recalculation of toast position for modal context
|
||||||
|
toast.style.position = 'fixed';
|
||||||
|
toast.style.zIndex = '9999'; // 确保显示在最上层
|
||||||
|
toast.style.bottom = '50%';
|
||||||
|
toast.style.transform = 'translate(-50%, 50%)';
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
toast.classList.add('show');
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.classList.remove('show');
|
||||||
|
setTimeout(() => toast.remove(), 300);
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'toast toast-error';
|
||||||
|
toast.textContent = 'Failed to copy trigger words';
|
||||||
|
// ... 相同的 toast 显示逻辑
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebSocket handling for progress updates
|
// WebSocket handling for progress updates
|
||||||
@@ -512,8 +585,6 @@ function initTheme() {
|
|||||||
// 键盘导航
|
// 键盘导航
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape') closeModal();
|
if (e.key === 'Escape') closeModal();
|
||||||
if (e.key === 'ArrowLeft') prevImage();
|
|
||||||
if (e.key === 'ArrowRight') nextImage();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 图片预加载
|
// 图片预加载
|
||||||
@@ -569,18 +640,6 @@ async function fetchCivitai() {
|
|||||||
file_path: filePath
|
file_path: filePath
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// if(!response.ok) {
|
|
||||||
// const errorText = await response.text();
|
|
||||||
// throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Optional: Update the card with new metadata
|
|
||||||
// const result = await response.json();
|
|
||||||
// if (result.success && result.metadata) {
|
|
||||||
// card.dataset.meta = JSON.stringify(result.metadata);
|
|
||||||
// // Update card display if needed
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Completion handling
|
// Completion handling
|
||||||
|
|||||||
Reference in New Issue
Block a user