mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-05-06 08:26:45 -03:00
feat(layout): implement responsive edge-to-edge card grid with density-aware column calculation
- Add dynamic column calculation based on container width and min card width - Prevent tiny cards on narrow windows by respecting density-based minimums: - Default: 240px, Medium: 200px, Compact: 170px - Fix edge-to-edge layout with proper CSS selector (.virtual-scroll-item.model-card) - Add hamburger menu for mobile/small screens with proper translations - Update all locale files with 'common.actions.menu' key Fixes: Cards becoming too small/overlapping on narrow window widths (e.g., 1156px) Changes: 15 files, +569/-114 lines
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
transition: transform 160ms ease-out;
|
||||
aspect-ratio: 896/1152; /* Preserve aspect ratio */
|
||||
max-width: 260px; /* Base size */
|
||||
min-width: 200px; /* Prevent cards from becoming too narrow */
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
cursor: pointer;
|
||||
@@ -370,7 +371,16 @@
|
||||
text-shadow: 0 0 5px rgba(255, 193, 7, 0.5);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.card-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
}
|
||||
|
||||
.model-card {
|
||||
max-width: 240px;
|
||||
min-width: 180px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.card-grid {
|
||||
grid-template-columns: minmax(260px, 1fr); /* Adjusted minimum size for mobile */
|
||||
@@ -378,6 +388,7 @@
|
||||
|
||||
.model-card {
|
||||
max-width: 100%; /* Allow cards to fill available space on mobile */
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,8 +574,13 @@ body.hide-card-version .civitai-version {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
transition: transform 160ms ease-out;
|
||||
margin: 0; /* Remove margins, positioning is handled by VirtualScroller */
|
||||
width: 100%; /* Allow width to be set by the VirtualScroller */
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Allow cards to grow beyond 260px in virtual scroll mode */
|
||||
.virtual-scroll-item.model-card {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.virtual-scroll-item:hover {
|
||||
@@ -576,11 +592,11 @@ body.hide-card-version .civitai-version {
|
||||
.card-grid.virtual-scroll {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
margin: 0; /* Remove auto margins - positioning handled by VirtualScroller leftOffset */
|
||||
padding: 4px 0; /* Add top/bottom padding equivalent to card padding */
|
||||
height: auto;
|
||||
width: 100%;
|
||||
max-width: 1400px; /* Keep the max-width from original grid */
|
||||
max-width: none; /* Remove max-width constraint - handled by VirtualScroller */
|
||||
box-sizing: border-box; /* Include padding in width calculation */
|
||||
overflow-x: hidden; /* Prevent horizontal overflow */
|
||||
}
|
||||
|
||||
@@ -22,6 +22,22 @@
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Left section: Logo + Navigation */
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Right section: Controls */
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Responsive header container for larger screens */
|
||||
@media (min-width: 2150px) {
|
||||
.header-container {
|
||||
@@ -77,6 +93,7 @@
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nav-item:hover,
|
||||
@@ -97,13 +114,100 @@
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Header search */
|
||||
/* Header search - Centered with VS Code command palette style */
|
||||
.header-search {
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
/* VS Code command palette style search container */
|
||||
.header-search .search-container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--input-bg, var(--card-bg));
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-sm, 6px);
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header-search .search-container:focus-within {
|
||||
border-color: var(--lora-accent);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12), 0 0 0 1px var(--lora-accent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.header-search input {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
padding-left: 2.25rem !important;
|
||||
padding-right: 5rem !important;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-color);
|
||||
font-size: 0.95rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.header-search input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.header-search .search-icon {
|
||||
position: absolute;
|
||||
left: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.header-search .search-options-toggle,
|
||||
.header-search .search-filter-toggle {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
border-radius: var(--border-radius-xs, 4px);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.header-search .search-options-toggle {
|
||||
right: 2.25rem;
|
||||
}
|
||||
|
||||
.header-search .search-options-toggle:hover,
|
||||
.header-search .search-filter-toggle:hover {
|
||||
background: var(--lora-surface-hover, oklch(95% 0.02 256));
|
||||
color: var(--lora-accent);
|
||||
}
|
||||
|
||||
.header-search .filter-badge {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--lora-accent);
|
||||
border-radius: 50%;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
/* Disabled state for header search */
|
||||
.header-search.disabled {
|
||||
opacity: 0.5;
|
||||
@@ -247,44 +351,207 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.app-title {
|
||||
display: none;
|
||||
/* Hide text title on mobile */
|
||||
/* Hamburger menu button - hidden by default */
|
||||
.hamburger-menu-btn {
|
||||
display: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-color);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hamburger-menu-btn:hover {
|
||||
background: var(--lora-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Hamburger dropdown menu */
|
||||
.hamburger-dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 8px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-sm, 6px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
padding: 0.5rem;
|
||||
min-width: 160px;
|
||||
z-index: var(--z-dropdown, 200);
|
||||
}
|
||||
|
||||
.hamburger-dropdown.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.hamburger-dropdown .dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--border-radius-xs, 4px);
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hamburger-dropdown .dropdown-item:hover {
|
||||
background: var(--lora-surface-hover, oklch(95% 0.02 256));
|
||||
color: var(--lora-accent);
|
||||
}
|
||||
|
||||
.hamburger-dropdown .dropdown-item i {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hamburger-dropdown .dropdown-divider {
|
||||
height: 1px;
|
||||
background: var(--border-color);
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
/* Responsive: Early optimization at 1200px - reduce gaps and padding */
|
||||
@media (max-width: 1200px) {
|
||||
.header-container {
|
||||
gap: 0.75rem;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.header-controls>div {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
.header-controls > div {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive: Hide nav icons at 1100px to save space */
|
||||
@media (max-width: 1100px) {
|
||||
.nav-item {
|
||||
gap: 0;
|
||||
padding: 0.25rem 0.4rem;
|
||||
}
|
||||
|
||||
.nav-item i {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-search {
|
||||
max-width: 450px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
.app-title {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
padding: 0 10px;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hamburger-menu-btn {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.hamburger-dropdown {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hamburger-dropdown.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header-search {
|
||||
max-width: none;
|
||||
margin: 0 0.5rem;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
margin-right: 0.5rem;
|
||||
gap: 0.25rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 0.25rem 0.35rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* For very small screens */
|
||||
/* Responsive: Compact mode at 768px */
|
||||
@media (max-width: 768px) {
|
||||
.header-search input {
|
||||
padding: 0.4rem 0.6rem;
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 4.5rem !important;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.header-search .search-container {
|
||||
border-radius: var(--border-radius-xs, 4px);
|
||||
}
|
||||
}
|
||||
|
||||
/* For very small screens - switch nav to icons only */
|
||||
@media (max-width: 600px) {
|
||||
.header-container {
|
||||
padding: 0 8px;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
display: none;
|
||||
/* Hide navigation on very small screens */
|
||||
display: flex;
|
||||
gap: 0.15rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.header-search {
|
||||
flex: 1;
|
||||
.nav-item {
|
||||
padding: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.nav-item span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-item i {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Position relative for hamburger menu positioning */
|
||||
.header-right {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -129,6 +129,126 @@ export class HeaderManager {
|
||||
|
||||
// Hide search functionality on Statistics page
|
||||
this.updateHeaderForPage();
|
||||
|
||||
// Initialize hamburger menu for mobile
|
||||
this.initializeHamburgerMenu();
|
||||
}
|
||||
|
||||
initializeHamburgerMenu() {
|
||||
const hamburgerBtn = document.getElementById('hamburgerMenuBtn');
|
||||
const hamburgerDropdown = document.getElementById('hamburgerDropdown');
|
||||
|
||||
if (!hamburgerBtn || !hamburgerDropdown) return;
|
||||
|
||||
// Toggle dropdown on hamburger button click
|
||||
hamburgerBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
hamburgerDropdown.classList.toggle('active');
|
||||
const icon = hamburgerBtn.querySelector('i');
|
||||
if (hamburgerDropdown.classList.contains('active')) {
|
||||
icon.classList.remove('fa-bars');
|
||||
icon.classList.add('fa-times');
|
||||
} else {
|
||||
icon.classList.remove('fa-times');
|
||||
icon.classList.add('fa-bars');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle dropdown item clicks
|
||||
const dropdownItems = hamburgerDropdown.querySelectorAll('.dropdown-item');
|
||||
dropdownItems.forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
const action = item.dataset.action;
|
||||
this.handleHamburgerAction(action);
|
||||
hamburgerDropdown.classList.remove('active');
|
||||
const icon = hamburgerBtn.querySelector('i');
|
||||
icon.classList.remove('fa-times');
|
||||
icon.classList.add('fa-bars');
|
||||
});
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!hamburgerDropdown.contains(e.target) && !hamburgerBtn.contains(e.target)) {
|
||||
hamburgerDropdown.classList.remove('active');
|
||||
const icon = hamburgerBtn.querySelector('i');
|
||||
if (icon) {
|
||||
icon.classList.remove('fa-times');
|
||||
icon.classList.add('fa-bars');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update theme icon in hamburger menu based on current theme
|
||||
this.updateHamburgerThemeIcon();
|
||||
}
|
||||
|
||||
handleHamburgerAction(action) {
|
||||
switch (action) {
|
||||
case 'theme':
|
||||
if (typeof toggleTheme === 'function') {
|
||||
const newTheme = toggleTheme();
|
||||
// Update theme toggle in header if it exists
|
||||
const themeToggle = document.querySelector('.theme-toggle');
|
||||
if (themeToggle) {
|
||||
themeToggle.classList.remove('theme-light', 'theme-dark', 'theme-auto');
|
||||
themeToggle.classList.add(`theme-${newTheme}`);
|
||||
this.updateThemeTooltip(themeToggle, newTheme);
|
||||
}
|
||||
this.updateHamburgerThemeIcon();
|
||||
}
|
||||
break;
|
||||
case 'settings':
|
||||
if (window.settingsManager) {
|
||||
window.settingsManager.toggleSettings();
|
||||
}
|
||||
break;
|
||||
case 'help':
|
||||
const helpToggle = document.getElementById('helpToggleBtn');
|
||||
if (helpToggle) {
|
||||
helpToggle.click();
|
||||
}
|
||||
break;
|
||||
case 'notifications':
|
||||
updateService.toggleUpdateModal();
|
||||
break;
|
||||
case 'support':
|
||||
if (window.modalManager) {
|
||||
window.modalManager.toggleModal('supportModal');
|
||||
renderSupporters().catch(error => {
|
||||
console.error('Error loading supporters:', error);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateHamburgerThemeIcon() {
|
||||
const themeItem = document.querySelector('.dropdown-item[data-action="theme"]');
|
||||
if (!themeItem) return;
|
||||
|
||||
const currentTheme = getStorageItem('theme') || 'auto';
|
||||
const icon = themeItem.querySelector('i');
|
||||
const text = themeItem.querySelector('span');
|
||||
|
||||
if (icon) {
|
||||
icon.classList.remove('fa-moon', 'fa-sun', 'fa-adjust');
|
||||
if (currentTheme === 'light') {
|
||||
icon.classList.add('fa-sun');
|
||||
} else if (currentTheme === 'dark') {
|
||||
icon.classList.add('fa-moon');
|
||||
} else {
|
||||
icon.classList.add('fa-adjust');
|
||||
}
|
||||
}
|
||||
|
||||
// Update text based on current theme
|
||||
if (text) {
|
||||
const key = currentTheme === 'light' ? 'header.theme.switchToDark' :
|
||||
currentTheme === 'dark' ? 'header.theme.switchToLight' :
|
||||
'header.theme.toggle';
|
||||
updateElementAttribute(themeItem, 'aria-label', key, {}, '');
|
||||
}
|
||||
}
|
||||
|
||||
updateHeaderForPage() {
|
||||
|
||||
@@ -104,69 +104,74 @@ export class VirtualScroller {
|
||||
// Get display density setting
|
||||
const displayDensity = state.global.settings?.display_density || 'default';
|
||||
|
||||
// Set exact column counts and grid widths to match CSS container widths
|
||||
let maxColumns, maxGridWidth;
|
||||
// Base gap between cards
|
||||
const baseGap = 12;
|
||||
this.columnGap = baseGap;
|
||||
|
||||
// Match exact column counts and CSS container width values based on density
|
||||
// Define minimum card width based on density setting to ensure usability
|
||||
// Cards smaller than this become hard to interact with and view
|
||||
const minCardWidths = {
|
||||
'default': 240, // Default: comfortable minimum
|
||||
'medium': 200, // Medium: slightly smaller
|
||||
'compact': 170 // Compact: smallest usable size
|
||||
};
|
||||
const minCardWidth = minCardWidths[displayDensity] || 240;
|
||||
|
||||
// Calculate maximum possible columns that fit in available width
|
||||
// Formula: maxColumns = floor((availableWidth + gap) / (minCardWidth + gap))
|
||||
const maxPossibleColumns = Math.floor((availableContentWidth + this.columnGap) / (minCardWidth + this.columnGap));
|
||||
|
||||
// Ensure at least 1 column
|
||||
const maxColumns = Math.max(1, maxPossibleColumns);
|
||||
|
||||
// Define preferred maximum columns based on display density and screen size
|
||||
// These are upper limits to prevent too many columns on ultra-wide screens
|
||||
let preferredMaxColumns;
|
||||
if (window.innerWidth >= 3000) { // 4K
|
||||
if (displayDensity === 'default') {
|
||||
maxColumns = 8;
|
||||
preferredMaxColumns = 8;
|
||||
} else if (displayDensity === 'medium') {
|
||||
maxColumns = 9;
|
||||
preferredMaxColumns = 10;
|
||||
} else { // compact
|
||||
maxColumns = 10;
|
||||
preferredMaxColumns = 12;
|
||||
}
|
||||
maxGridWidth = 2400; // Match exact CSS container width for 4K
|
||||
} else if (window.innerWidth >= 2150) { // 2K/1440p
|
||||
if (displayDensity === 'default') {
|
||||
maxColumns = 6;
|
||||
preferredMaxColumns = 6;
|
||||
} else if (displayDensity === 'medium') {
|
||||
maxColumns = 7;
|
||||
preferredMaxColumns = 8;
|
||||
} else { // compact
|
||||
maxColumns = 8;
|
||||
preferredMaxColumns = 10;
|
||||
}
|
||||
maxGridWidth = 1800; // Match exact CSS container width for 2K
|
||||
} else {
|
||||
// 1080p
|
||||
} else { // 1080p and smaller
|
||||
if (displayDensity === 'default') {
|
||||
maxColumns = 5;
|
||||
preferredMaxColumns = 5;
|
||||
} else if (displayDensity === 'medium') {
|
||||
maxColumns = 6;
|
||||
preferredMaxColumns = 6;
|
||||
} else { // compact
|
||||
maxColumns = 7;
|
||||
preferredMaxColumns = 8;
|
||||
}
|
||||
maxGridWidth = 1400; // Match exact CSS container width for 1080p
|
||||
}
|
||||
|
||||
// Calculate baseCardWidth based on desired column count and available space
|
||||
// Formula: (maxGridWidth - (columns-1)*gap) / columns
|
||||
const baseCardWidth = (maxGridWidth - ((maxColumns - 1) * this.columnGap)) / maxColumns;
|
||||
// Use the smaller of: max columns that fit, or preferred max
|
||||
// This ensures cards are never smaller than minCardWidth
|
||||
this.columnsCount = Math.min(maxColumns, preferredMaxColumns);
|
||||
|
||||
// Use the smaller of available content width or max grid width
|
||||
const actualGridWidth = Math.min(availableContentWidth, maxGridWidth);
|
||||
// Calculate card width to perfectly fill available space
|
||||
// Formula: (availableWidth - totalGap) / columns
|
||||
const totalGap = (this.columnsCount - 1) * this.columnGap;
|
||||
this.itemWidth = (availableContentWidth - totalGap) / this.columnsCount;
|
||||
|
||||
// Set exact column count based on screen size and mode
|
||||
this.columnsCount = maxColumns;
|
||||
|
||||
// When available width is smaller than maxGridWidth, recalculate columns
|
||||
if (availableContentWidth < maxGridWidth) {
|
||||
// Calculate how many columns can fit in the available space
|
||||
this.columnsCount = Math.max(1, Math.floor(
|
||||
(availableContentWidth + this.columnGap) / (baseCardWidth + this.columnGap)
|
||||
));
|
||||
}
|
||||
|
||||
// Calculate actual item width
|
||||
this.itemWidth = (actualGridWidth - (this.columnsCount - 1) * this.columnGap) / this.columnsCount;
|
||||
|
||||
// Calculate height based on aspect ratio
|
||||
// Calculate height based on aspect ratio (896/1152)
|
||||
this.itemHeight = this.itemWidth / this.itemAspectRatio;
|
||||
|
||||
// Calculate the left offset to center the grid within the content area
|
||||
this.leftOffset = Math.max(0, (availableContentWidth - actualGridWidth) / 2);
|
||||
// Edge-to-edge layout: no offset, grid fills container
|
||||
this.leftOffset = 0;
|
||||
const actualGridWidth = this.itemWidth * this.columnsCount + totalGap;
|
||||
|
||||
// Update grid element max-width to match available width
|
||||
// Update grid element to fill available width
|
||||
this.gridElement.style.maxWidth = `${actualGridWidth}px`;
|
||||
this.gridElement.style.width = `${actualGridWidth}px`;
|
||||
|
||||
// Add or remove density classes for style adjustments
|
||||
this.gridElement.classList.remove('default-density', 'medium-density', 'compact-density');
|
||||
@@ -478,6 +483,12 @@ export class VirtualScroller {
|
||||
element.style.width = `${this.itemWidth}px`;
|
||||
element.style.height = `${this.itemHeight}px`;
|
||||
|
||||
// Remove max-width constraint from model-card to allow dynamic sizing
|
||||
const modelCard = element.querySelector('.model-card');
|
||||
if (modelCard) {
|
||||
modelCard.style.maxWidth = 'none';
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user