fix: preserve result thumbnail aspect ratios

This commit is contained in:
2026-05-19 20:35:16 +08:00
parent ece4db338e
commit 6c3f5eda0a
2 changed files with 23 additions and 5 deletions

View File

@@ -11,7 +11,9 @@ type PreviewState = {
function parseRatio(aspectRatio?: string) { function parseRatio(aspectRatio?: string) {
if (!aspectRatio || aspectRatio === 'long') return aspectRatio === 'long' ? 1 / 3 : 1; if (!aspectRatio || aspectRatio === 'long') return aspectRatio === 'long' ? 1 / 3 : 1;
const [w, h] = aspectRatio.split(':').map(Number); const [w, h] = aspectRatio.includes('/')
? aspectRatio.split('/').map(part => Number(part.trim()))
: aspectRatio.split(':').map(Number);
return w && h ? w / h : 1; return w && h ? w / h : 1;
} }
@@ -42,11 +44,13 @@ export function HoverImagePreview({
alt, alt,
imageClassName, imageClassName,
aspectRatio, aspectRatio,
onImageLoad,
}: { }: {
src: string; src: string;
alt: string; alt: string;
imageClassName?: string; imageClassName?: string;
aspectRatio?: string; aspectRatio?: string;
onImageLoad?: (image: HTMLImageElement) => void;
}) { }) {
const [preview, setPreview] = useState<PreviewState | null>(null); const [preview, setPreview] = useState<PreviewState | null>(null);
@@ -61,6 +65,7 @@ export function HoverImagePreview({
setPreview(nextPreviewState(event, aspectRatio)); setPreview(nextPreviewState(event, aspectRatio));
}} }}
onPointerLeave={() => setPreview(null)} onPointerLeave={() => setPreview(null)}
onLoad={event => onImageLoad?.(event.currentTarget)}
/> />
{preview && ( {preview && (
<div <div

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import type { GenImage } from '@/lib/types'; import type { GenImage } from '@/lib/types';
import { HoverImagePreview } from './HoverImagePreview'; import { HoverImagePreview } from './HoverImagePreview';
@@ -10,6 +10,8 @@ export type ResultGridProps = {
}; };
export default function ResultGrid({ images, onAction }: ResultGridProps) { export default function ResultGrid({ images, onAction }: ResultGridProps) {
const [ratios, setRatios] = useState<Record<string, string>>({});
useEffect(() => { useEffect(() => {
function handler(e: KeyboardEvent) { function handler(e: KeyboardEvent) {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
@@ -27,7 +29,7 @@ export default function ResultGrid({ images, onAction }: ResultGridProps) {
return () => window.removeEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler);
}, [images, onAction]); }, [images, onAction]);
const cols = images.length <= 4 ? 'grid-cols-2' : images.length <= 9 ? 'grid-cols-3' : 'grid-cols-4'; const cols = images.length === 1 ? 'grid-cols-[minmax(220px,520px)]' : images.length <= 4 ? 'grid-cols-2' : images.length <= 9 ? 'grid-cols-3' : 'grid-cols-4';
const selectedCount = images.filter(i => i.status === 'selected').length; const selectedCount = images.filter(i => i.status === 'selected').length;
return ( return (
@@ -51,13 +53,24 @@ export default function ResultGrid({ images, onAction }: ResultGridProps) {
</div> </div>
</div> </div>
<div className={`grid ${cols} gap-3`}> <div className={`grid ${cols} items-start gap-3`}>
{images.map((img, i) => ( {images.map((img, i) => (
<div <div
key={img.id} key={img.id}
className={`tile group ${img.status === 'selected' ? 'tile-selected' : ''} ${img.status === 'rejected' ? 'tile-rejected' : ''}`} className={`tile group ${img.status === 'selected' ? 'tile-selected' : ''} ${img.status === 'rejected' ? 'tile-rejected' : ''}`}
style={{ aspectRatio: ratios[img.id] ?? '1 / 1' }}
> >
<HoverImagePreview src={img.url} alt={`gen ${i + 1}`} imageClassName="w-full h-full object-contain bg-white" /> <HoverImagePreview
src={img.url}
alt={`gen ${i + 1}`}
aspectRatio={ratios[img.id]}
imageClassName="w-full h-full object-contain bg-white"
onImageLoad={image => {
if (!image.naturalWidth || !image.naturalHeight) return;
const next = `${image.naturalWidth} / ${image.naturalHeight}`;
setRatios(prev => prev[img.id] === next ? prev : { ...prev, [img.id]: next });
}}
/>
<div className="tile-keynum">{i + 1}</div> <div className="tile-keynum">{i + 1}</div>
{img.status === 'selected' && ( {img.status === 'selected' && (