checkpoint

This commit is contained in:
Will Miao
2025-02-19 12:14:12 +08:00
parent 2f3061ce7c
commit 1d0af7f163
3 changed files with 151 additions and 53 deletions

View File

@@ -29,6 +29,8 @@ body.modal-open {
border: 1px solid var(--lora-border); border: 1px solid var(--lora-border);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
transform: translateZ(0); /* 创建新的堆叠上下文 */ transform: translateZ(0); /* 创建新的堆叠上下文 */
max-height: 90vh; /* 限制最大高度为视窗高度的90% */
overflow-y: auto; /* 添加垂直滚动条 */
} }
/* Delete Modal specific styles */ /* Delete Modal specific styles */
@@ -617,44 +619,66 @@ body.modal-open {
} }
.carousel { .carousel {
display: flex; transition: max-height 0.3s ease-in-out;
flex-direction: column; /* 改为垂直布局 */ overflow: hidden;
gap: var(--space-3);
overflow-y: auto; /* 垂直滚动 */
} }
.carousel img, .carousel.collapsed {
.carousel video { max-height: 0;
}
.carousel-container {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.media-wrapper {
position: relative;
width: 100%; width: 100%;
height: auto; background: var(--lora-surface);
max-height: 70vh; /* 限制单张图片最大高度 */ margin-bottom: var(--space-2);
}
.media-wrapper:last-child {
margin-bottom: 0;
}
.media-wrapper img,
.media-wrapper video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain; object-fit: contain;
border-radius: var(--border-radius-base);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
} }
/* Scroll Indicator */ /* Scroll Indicator */
.scroll-indicator { .scroll-indicator {
position: sticky; cursor: pointer;
top: 0;
left: 0;
right: 0;
text-align: center;
padding: var(--space-2); padding: var(--space-2);
background: linear-gradient(to bottom, background: var(--lora-surface);
var(--lora-surface) 0%, border: 1px solid var(--lora-border);
transparent 100%); border-radius: var(--border-radius-sm);
opacity: 0.8; display: flex;
transition: opacity 0.3s; align-items: center;
pointer-events: none; justify-content: center;
z-index: 1; /* 确保滚动提示在图片上层 */ gap: 8px;
margin-bottom: var(--space-2);
} }
/* Remove horizontal scrolling styles */ .scroll-indicator:hover {
.carousel { background: oklch(var(--lora-accent) / 0.1);
scroll-snap-type: none; }
grid-auto-flow: initial;
overflow-x: initial; .lazy {
opacity: 0;
transition: opacity 0.3s;
}
.lazy[src] {
opacity: 1;
} }
/* Update Trigger Words styles */ /* Update Trigger Words styles */

View File

@@ -171,15 +171,7 @@ export function showLoraModal(lora) {
</div> </div>
<div class="showcase-section"> ${renderShowcaseImages(lora.civitai.images)}
<div class="scroll-indicator">
<i class="fas fa-chevron-down"></i>
Scroll for more examples
</div>
<div class="carousel">
${renderShowcaseImages(lora.civitai.images)}
</div>
</div>
</div> </div>
</div> </div>
`; `;
@@ -275,21 +267,101 @@ function renderTriggerWords(words) {
function renderShowcaseImages(images) { function renderShowcaseImages(images) {
if (!images?.length) return ''; if (!images?.length) return '';
return images.map(img => { return `
if (img.type === 'video') { <div class="showcase-section">
return ` <div class="scroll-indicator" onclick="toggleShowcase(this)">
<video controls autoplay muted loop crossorigin="anonymous" referrerpolicy="no-referrer"> <i class="fas fa-chevron-down"></i>
<source src="${img.url}" type="video/mp4"> <span>Show ${images.length} examples</span>
Your browser does not support video playback </div>
</video> <div class="carousel collapsed">
`; <div class="carousel-container">
${images.map(img => {
// 计算适当的展示高度:
// 1. 保持原始宽高比
// 2. 限制最大高度为视窗高度的60%
// 3. 确保最小高度为容器宽度的40%
const aspectRatio = (img.height / img.width) * 100;
const containerWidth = 800; // modal content的最大宽度
const minHeightPercent = 40; // 最小高度为容器宽度的40%
const maxHeightPercent = (window.innerHeight * 0.6 / containerWidth) * 100;
const heightPercent = Math.max(
minHeightPercent,
Math.min(maxHeightPercent, aspectRatio)
);
if (img.type === 'video') {
return `
<div class="media-wrapper" style="padding-bottom: ${heightPercent}%">
<video controls autoplay muted loop crossorigin="anonymous"
referrerpolicy="no-referrer" data-src="${img.url}"
class="lazy">
<source data-src="${img.url}" type="video/mp4">
Your browser does not support video playback
</video>
</div>
`;
}
return `
<div class="media-wrapper" style="padding-bottom: ${heightPercent}%">
<img data-src="${img.url}"
alt="Preview"
crossorigin="anonymous"
referrerpolicy="no-referrer"
width="${img.width}"
height="${img.height}"
class="lazy">
</div>
`;
}).join('')}
</div>
</div>
</div>
`;
}
// Add this to the window object for global access
export function toggleShowcase(element) {
const carousel = element.nextElementSibling;
const isCollapsed = carousel.classList.contains('collapsed');
const indicator = element.querySelector('span');
const icon = element.querySelector('i');
carousel.classList.toggle('collapsed');
if (isCollapsed) {
indicator.textContent = 'Hide examples';
icon.classList.replace('fa-chevron-down', 'fa-chevron-up');
initLazyLoading(carousel);
} else {
const count = carousel.querySelectorAll('.media-wrapper').length;
indicator.textContent = `Show ${count} examples`;
icon.classList.replace('fa-chevron-up', 'fa-chevron-down');
}
};
// Add lazy loading initialization
export function initLazyLoading(container) {
const lazyElements = container.querySelectorAll('.lazy');
const lazyLoad = (element) => {
if (element.tagName.toLowerCase() === 'video') {
element.src = element.dataset.src;
element.querySelector('source').src = element.dataset.src;
element.load();
} else {
element.src = element.dataset.src;
} }
return ` element.classList.remove('lazy');
<img src="${img.url}" };
alt="Preview"
crossorigin="anonymous" const observer = new IntersectionObserver((entries) => {
referrerpolicy="no-referrer" entries.forEach(entry => {
loading="lazy"> if (entry.isIntersecting) {
`; lazyLoad(entry.target);
}).join(''); observer.unobserve(entry.target);
}
});
});
lazyElements.forEach(element => observer.observe(element));
} }

View File

@@ -2,7 +2,7 @@ import { debounce } from './utils/debounce.js';
import { LoadingManager } from './managers/LoadingManager.js'; import { LoadingManager } from './managers/LoadingManager.js';
import { modalManager } from './managers/ModalManager.js'; import { modalManager } from './managers/ModalManager.js';
import { state } from './state/index.js'; import { state } from './state/index.js';
import { showLoraModal } from './components/LoraCard.js'; import { showLoraModal, toggleShowcase, initLazyLoading } from './components/LoraCard.js';
import { loadMoreLoras, fetchCivitai, deleteModel, replacePreview, resetAndReload, refreshLoras } from './api/loraApi.js'; import { loadMoreLoras, fetchCivitai, deleteModel, replacePreview, resetAndReload, refreshLoras } from './api/loraApi.js';
import { import {
showToast, showToast,
@@ -45,6 +45,7 @@ window.toggleFolderTags = toggleFolderTags;
window.settingsManager = new SettingsManager(); window.settingsManager = new SettingsManager();
window.toggleApiKeyVisibility = toggleApiKeyVisibility; window.toggleApiKeyVisibility = toggleApiKeyVisibility;
window.moveManager = moveManager; window.moveManager = moveManager;
window.toggleShowcase = toggleShowcase;
// Initialize everything when DOM is ready // Initialize everything when DOM is ready
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
@@ -58,6 +59,7 @@ document.addEventListener('DOMContentLoaded', () => {
initTheme(); initTheme();
initFolderTagsVisibility(); initFolderTagsVisibility();
initBackToTop(); initBackToTop();
initLazyLoading();
window.searchManager = new SearchManager(); window.searchManager = new SearchManager();
new LoraContextMenu(); new LoraContextMenu();
}); });