mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-10-05 09:35:31 -03:00
feat(ui): add optional vertical list layout for model modal showcase
Bring back the pre-v1.2.2 classic vertical list for example images as a
persisted showcase_layout setting (gallery stays the default), switchable
from both a Settings select and an in-modal segmented toggle.
- Revive the vertical list renderer (adapted from 4a6042d0) as
VerticalListView.js, reusing the shared MediaUtils/MetadataPanel
infrastructure; legacy CSS scoped under .showcase-vertical
- Dispatch showcase rendering on state.settings.showcase_layout;
collapsed bar, empty/filtered states and import flow stay shared
- Keep the back-to-top button available in vertical mode (the
showcase-expanded class that hides it now only applies to the gallery,
whose thumbnail strip occupies that corner)
- Persist the setting via DEFAULT_SETTINGS + DEFAULT_SETTINGS_BASE and a
new select under Settings > Layout Settings
- i18n: 6 new keys translated in all 9 locales; terminology recorded in
docs/i18n-translation-guidelines.md
Closes #1136
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* VerticalListView.js
|
||||
* Classic vertical example list for the model modal showcase: every example
|
||||
* rendered full-width and stacked, each with its own metadata panel and media
|
||||
* controls. Revived from the pre-gallery implementation (removed in v1.2.2),
|
||||
* adapted to the current shared showcase infrastructure (MediaRenderers,
|
||||
* MediaUtils) — no legacy scroll/metrics machinery.
|
||||
*/
|
||||
import {
|
||||
initLazyLoading,
|
||||
initNsfwBlurHandlers,
|
||||
initMetadataPanelHandlers,
|
||||
initMediaControlHandlers,
|
||||
positionAllMediaControls
|
||||
} from './MediaUtils.js';
|
||||
import { renderShowcaseMediaItem, findLocalFile } from './MediaRenderers.js';
|
||||
import { getShowcaseUrl } from '../../../utils/civitaiUtils.js';
|
||||
import { openMediaViewer } from '../MediaViewer.js';
|
||||
|
||||
/**
|
||||
* Render the vertical list: all examples stacked in a column
|
||||
* @param {Array} images - Filtered images/videos to show
|
||||
* @param {Array} exampleFiles - Local example files
|
||||
* @returns {string} HTML content
|
||||
*/
|
||||
export function renderVerticalList(images, exampleFiles = []) {
|
||||
return `
|
||||
<div class="vertical-list-container">
|
||||
${images.map((img, index) => renderVerticalMediaItem(img, index, exampleFiles)).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single vertical-list item. The wrapper is sized by the
|
||||
* padding-bottom aspect-ratio hack so the column layout is stable before any
|
||||
* media loads (and lazy loading has a box to intersect).
|
||||
* @param {Object} img - Image/video metadata
|
||||
* @param {number} index - Index in the array
|
||||
* @param {Array} exampleFiles - Local files
|
||||
* @returns {string} HTML for the media item
|
||||
*/
|
||||
function renderVerticalMediaItem(img, index, exampleFiles) {
|
||||
return renderShowcaseMediaItem(img, index, exampleFiles, {
|
||||
heightPercent: computeHeightPercent(img),
|
||||
fullQuality: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the wrapper height as a padding-bottom percent of the modal width,
|
||||
* clamped so very tall or very wide media still gets a reasonable box
|
||||
* (defensive 4:3 fallback for missing dimensions prevents NaN layout)
|
||||
* @param {Object} img - Image/video metadata
|
||||
* @returns {number} padding-bottom percent
|
||||
*/
|
||||
function computeHeightPercent(img) {
|
||||
const safeW = img.width || 4;
|
||||
const safeH = img.height || 3;
|
||||
const aspectRatio = (safeH / safeW) * 100;
|
||||
const containerWidth = 800; // modal content maximum width
|
||||
const minHeightPercent = 40;
|
||||
const maxHeightPercent = (window.innerHeight * 0.6 / containerWidth) * 100;
|
||||
return Math.max(minHeightPercent, Math.min(maxHeightPercent, aspectRatio));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the item list for the full-size media viewer from the rendered items
|
||||
* @param {Array} images - Rendered images/videos
|
||||
* @param {Array} exampleFiles - Local example files
|
||||
* @returns {Array<{url: string, type: string}>}
|
||||
*/
|
||||
function buildViewerItems(images, exampleFiles) {
|
||||
return images.map((img, index) => {
|
||||
const localFile = findLocalFile(img, index, exampleFiles);
|
||||
const originalRemoteUrl = img.url || '';
|
||||
const isVideo = localFile ? localFile.is_video :
|
||||
originalRemoteUrl.endsWith('.mp4') || originalRemoteUrl.endsWith('.webm');
|
||||
return {
|
||||
url: localFile?.path || getShowcaseUrl(originalRemoteUrl, isVideo ? 'video' : 'image'),
|
||||
type: isVideo ? 'video' : 'image'
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all vertical-list interactions: lazy loading, NSFW blur,
|
||||
* per-item metadata panels and media controls, click-to-view
|
||||
* @param {HTMLElement} container - The .showcase-vertical root element
|
||||
* @param {Array} images - Rendered images/videos (aligned with DOM order)
|
||||
* @param {Array} exampleFiles - Local example files
|
||||
*/
|
||||
export function initVerticalList(container, images, exampleFiles = []) {
|
||||
if (!container) return;
|
||||
|
||||
initLazyLoading(container);
|
||||
initNsfwBlurHandlers(container);
|
||||
initMetadataPanelHandlers(container);
|
||||
initMediaControlHandlers(container);
|
||||
positionAllMediaControls(container);
|
||||
|
||||
// Click-to-view: open the full-size media viewer at the clicked item
|
||||
const viewerItems = buildViewerItems(images, exampleFiles);
|
||||
container.querySelectorAll('.vertical-list-container .media-wrapper').forEach((wrapper, index) => {
|
||||
const mediaEl = wrapper.querySelector('img, video');
|
||||
if (!mediaEl) return;
|
||||
mediaEl.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
openMediaViewer(viewerItems, index);
|
||||
});
|
||||
});
|
||||
|
||||
// Reposition controls once media dimensions are known
|
||||
container.querySelectorAll('.vertical-list-container img, .vertical-list-container video').forEach(media => {
|
||||
media.addEventListener('load', () => positionAllMediaControls(container));
|
||||
if (media.tagName === 'VIDEO') {
|
||||
media.addEventListener('loadedmetadata', () => positionAllMediaControls(container));
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user