diff --git a/static/css/style.css b/static/css/style.css index 46b6d59d..517d14c2 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -583,31 +583,6 @@ body.modal-open { margin: var(--space-2) 0; } -.trigger-words-categories { - flex: 0 0 200px; - border-right: 1px solid var(--lora-border); - padding-right: var(--space-2); -} - -.trigger-category { - padding: 8px; - margin-bottom: 4px; - border-radius: var(--border-radius-xs); - cursor: pointer; - color: var(--text-color); - font-size: 0.9em; - transition: background-color 0.2s; -} - -.trigger-category:hover { - background: var(--lora-border); -} - -.trigger-category.active { - background: var(--lora-accent); - color: white; -} - .trigger-words-list { flex: 1; display: flex; @@ -708,61 +683,80 @@ body.modal-open { /* Toast Notifications */ .toast { position: fixed; - bottom: 20px; - left: 50%; - transform: translateX(-50%) translateY(100px); + top: 20px; /* 改为从顶部显示 */ + right: 20px; /* 改为右对齐 */ + left: auto; /* 移除左对齐 */ + transform: translateX(120%); /* 初始位置在屏幕右侧外 */ + min-width: 300px; /* 设置最小宽度 */ + max-width: 400px; /* 设置最大宽度 */ background: var(--lora-surface); color: var(--text-color); - padding: 12px 24px; + padding: 12px 16px; border-radius: var(--border-radius-sm); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - z-index: calc(var(--z-overlay) + 10); /* 确保 toast 显示在模态窗口之上 */ + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + /* z-index: calc(var(--z-overlay) + 10); */ + z-index: 1000; /* 保证在其他元素之上 */ 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); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + align-items: center; + gap: 12px; } .toast.show { - transform: translateX(-50%) translateY(0); + transform: translateX(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::before { + content: ''; + width: 20px; + height: 20px; + flex-shrink: 0; + background-position: center; + background-repeat: no-repeat; + background-size: contain; } +/* 不同类型的toast样式 */ .toast-success { - border-left: 4px solid #4caf50; + border-left: 4px solid oklch(65% 0.2 142); +} + +.toast-success::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%234caf50'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z'/%3E%3C/svg%3E"); } .toast-error { - border-left: 4px solid #f44336; + border-left: 4px solid oklch(65% 0.2 29); +} + +.toast-error::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23f44336'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z'/%3E%3C/svg%3E"); } .toast-info { - border-left: 4px solid #2196f3; + border-left: 4px solid oklch(65% 0.2 256); +} + +.toast-info::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%232196f3'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z'/%3E%3C/svg%3E"); +} + +/* 多个toast堆叠显示 */ +.toast + .toast { + margin-top: 10px; +} + +/* 响应式调整 */ +@media (max-width: 768px) { + .toast { + width: calc(100% - 40px); /* 左右各留20px间距 */ + max-width: none; + right: 20px; + } } /* Ensure toasts are visible in both themes */ diff --git a/static/js/components/LoraCard.js b/static/js/components/LoraCard.js index e89c3143..13647ddf 100644 --- a/static/js/components/LoraCard.js +++ b/static/js/components/LoraCard.js @@ -33,17 +33,13 @@ export function createLoraCard(lora) {
+ ${!lora.from_civitai ? 'style="opacity: 0.5; cursor: not-allowed"' : ''}> + title="Copy Model Name"> + title="Delete Model">
@@ -53,14 +49,14 @@ export function createLoraCard(lora) {
+ title="Replace Preview Image">
`; + // Main card click event card.addEventListener('click', () => { const meta = JSON.parse(card.dataset.meta || '{}'); if (Object.keys(meta).length) { @@ -75,6 +71,34 @@ export function createLoraCard(lora) { } }); + // Copy button click event + card.querySelector('.fa-copy')?.addEventListener('click', e => { + e.stopPropagation(); + navigator.clipboard.writeText(card.dataset.file_name) + .then(() => showToast('Model name copied', 'success')) + .catch(() => showToast('Copy failed', 'error')); + }); + + // Civitai button click event + if (lora.from_civitai) { + card.querySelector('.fa-globe')?.addEventListener('click', e => { + e.stopPropagation(); + openCivitai(lora.model_name); + }); + } + + // Delete button click event + card.querySelector('.fa-trash')?.addEventListener('click', e => { + e.stopPropagation(); + deleteModel(lora.file_path); + }); + + // Replace preview button click event + card.querySelector('.fa-image')?.addEventListener('click', e => { + e.stopPropagation(); + replacePreview(lora.file_path); + }); + return card; } @@ -140,46 +164,4 @@ export function showLoraModal(lora) { `; modalManager.showModal('loraModal', content); - - document.querySelectorAll('.trigger-category').forEach(category => { - category.addEventListener('click', function() { - const categoryName = this.dataset.category; - document.querySelectorAll('.trigger-category').forEach(c => c.classList.remove('active')); - this.classList.add('active'); - - const wordsList = document.querySelector('.trigger-words-list'); - wordsList.innerHTML = categories[categoryName].map(word => ` -
- ${word} - - - - - -
- `).join(''); - }); - }); -} - -export function initializeLoraCards() { - document.querySelectorAll('.lora-card').forEach(card => { - card.addEventListener('click', () => { - const meta = JSON.parse(card.dataset.meta || '{}'); - if (Object.keys(meta).length) { - showLoraModal(meta); - } else { - showToast(card.dataset.from_civitai === 'True' - ? 'Click "Fetch" to retrieve metadata' - : 'No CivitAI information available', 'info'); - } - }); - - card.querySelector('.fa-copy')?.addEventListener('click', e => { - e.stopPropagation(); - navigator.clipboard.writeText(card.dataset.file_name) - .then(() => showToast('Model name copied', 'success')) - .catch(() => showToast('Copy failed', 'error')); - }); - }); } \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js index 68f9bf61..f6a9e10e 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -2,7 +2,7 @@ import { debounce } from './utils/debounce.js'; import { LoadingManager } from './managers/LoadingManager.js'; import { modalManager } from './managers/ModalManager.js'; import { state } from './state/index.js'; -import { showLoraModal, initializeLoraCards } from './components/LoraCard.js'; +import { showLoraModal } from './components/LoraCard.js'; import { loadMoreLoras, fetchCivitai, deleteModel, replacePreview, resetAndReload, refreshLoras } from './api/loraApi.js'; import { showToast, lazyLoadImages, restoreFolderFilter, initTheme, toggleTheme, toggleFolder, copyTriggerWord, openCivitai } from './utils/uiHelpers.js'; import { initializeInfiniteScroll } from './utils/infiniteScroll.js'; @@ -34,7 +34,6 @@ document.addEventListener('DOMContentLoaded', () => { initializeEventListeners(); lazyLoadImages(); restoreFolderFilter(); - initializeLoraCards(); initTheme(); window.searchManager = new SearchManager(); }); diff --git a/static/js/utils/uiHelpers.js b/static/js/utils/uiHelpers.js index c596aadc..f8539069 100644 --- a/static/js/utils/uiHelpers.js +++ b/static/js/utils/uiHelpers.js @@ -66,18 +66,7 @@ export function toggleFolder(tag) { export function copyTriggerWord(word) { navigator.clipboard.writeText(word).then(() => { - const toast = document.createElement('div'); - toast.className = 'toast toast-copy'; - toast.textContent = 'Copied!'; - document.body.appendChild(toast); - - requestAnimationFrame(() => { - toast.classList.add('show'); - setTimeout(() => { - toast.classList.remove('show'); - setTimeout(() => toast.remove(), 300); - }, 1000); - }); + showToast('Trigger word copied', 'success'); }); }