fix: refine reference frame previews

This commit is contained in:
2026-05-19 19:31:45 +08:00
parent b9bf50f851
commit a5979bb0d7
4 changed files with 41 additions and 14 deletions

View File

@@ -3226,7 +3226,11 @@ function SourceSubjectPipeline({
label={`参考帧 ${String(index + 1).padStart(2, "0")}`}
meta={`${frame.timestamp.toFixed(1)}s`}
className="h-24"
objectFit="contain"
objectFit="cover"
previewObjectFit="contain"
previewPlacement="left"
previewMaxWidth={320}
previewClassName="p-2"
selected={selected || conversionFrameIndices.includes(frame.index)}
title={`${selected ? "已选 · 点击取消" : "点击选择"} · 拖到转换层生成主体套图`}
onClick={() => onToggleFrame(frame.index)}
@@ -3283,7 +3287,7 @@ function SourceSubjectPipeline({
<div className="mb-2 rounded-md border border-[#d6b36a]/18 bg-[#d6b36a]/[0.06] px-2.5 py-2 text-[10px] leading-snug text-white/62">
1-2
</div>
<div className="mb-2 flex flex-col gap-1.5">
<div className="mb-2 flex max-h-[224px] flex-col gap-1.5 overflow-y-auto pr-0.5 2xl:max-h-[286px]">
{conversionFrames.map((frame, index) => (
<div key={frame.index} className="relative">
<MediaAssetTile
@@ -3292,7 +3296,7 @@ function SourceSubjectPipeline({
label={`转换参考 ${index + 1}`}
meta={`${frame.timestamp.toFixed(1)}s`}
className="h-24"
objectFit="contain"
objectFit="cover"
disablePreview
topLeft={<span className="rounded bg-black/72 px-1 font-mono text-[9px] text-white/70">{String(index + 1).padStart(2, "0")}</span>}
/>

View File

@@ -14,6 +14,8 @@ type MediaAssetAction = {
tone?: "neutral" | "cyan" | "rose"
}
type MediaAssetPreviewPlacement = "auto" | "left" | "right"
type MediaAssetTileProps = {
kind?: "image" | "video"
src?: string
@@ -29,6 +31,8 @@ type MediaAssetTileProps = {
objectFit?: "contain" | "cover"
previewObjectFit?: "contain" | "cover"
previewClassName?: string
previewPlacement?: MediaAssetPreviewPlacement
previewMaxWidth?: number
selected?: boolean
disabled?: boolean
busy?: boolean
@@ -55,15 +59,21 @@ function mediaObjectClass(fit: "contain" | "cover") {
return fit === "cover" ? "object-cover" : "object-contain"
}
function previewPosition(event: ReactMouseEvent<HTMLElement>) {
function previewPosition(event: ReactMouseEvent<HTMLElement>, placement: MediaAssetPreviewPlacement, maxWidth: number) {
const margin = 16
const previewWidth = Math.min(520, window.innerWidth - margin * 2)
const previewWidth = Math.min(maxWidth, window.innerWidth - margin * 2)
const previewHeight = Math.min(760, window.innerHeight - margin * 2)
let left = event.clientX + 18
let left = placement === "left" ? event.clientX - previewWidth - 18 : event.clientX + 18
let top = event.clientY + 18
if (left + previewWidth > window.innerWidth - margin) left = event.clientX - previewWidth - 18
if (placement === "auto" && left + previewWidth > window.innerWidth - margin) left = event.clientX - previewWidth - 18
if (placement === "right" && left + previewWidth > window.innerWidth - margin) left = window.innerWidth - previewWidth - margin
if (placement === "left" && left < margin) left = margin
if (top + previewHeight > window.innerHeight - margin) top = window.innerHeight - previewHeight - margin
return { left: Math.max(margin, left), top: Math.max(margin, top) }
return {
left: Math.max(margin, Math.min(left, window.innerWidth - previewWidth - margin)),
top: Math.max(margin, top),
width: previewWidth,
}
}
export function MediaAssetTile({
@@ -81,6 +91,8 @@ export function MediaAssetTile({
objectFit = "contain",
previewObjectFit,
previewClassName = "",
previewPlacement = "auto",
previewMaxWidth = 520,
selected = false,
disabled = false,
busy = false,
@@ -96,7 +108,7 @@ export function MediaAssetTile({
actions = [],
disablePreview = false,
}: MediaAssetTileProps) {
const [position, setPosition] = useState<{ left: number; top: number } | null>(null)
const [position, setPosition] = useState<{ left: number; top: number; width: number } | null>(null)
const mediaSrc = src || poster || ""
const canPreview = !!mediaSrc && !disablePreview
const fit = mediaObjectClass(objectFit)
@@ -104,7 +116,7 @@ export function MediaAssetTile({
const updatePreview = (event: ReactMouseEvent<HTMLElement>) => {
if (!canPreview) return
setPosition(previewPosition(event))
setPosition(previewPosition(event, previewPlacement, previewMaxWidth))
}
const media = kind === "video" && src ? (
@@ -136,7 +148,7 @@ export function MediaAssetTile({
? createPortal(
<div
className={`pointer-events-none fixed z-[10000] w-[min(520px,calc(100vw-32px))] rounded-xl border border-white/15 bg-black/94 p-3 shadow-[0_28px_80px_rgba(0,0,0,0.72)] ${previewClassName}`}
style={{ left: position.left, top: position.top }}
style={{ left: position.left, top: position.top, width: position.width }}
>
<div className="flex max-h-[min(76vh,720px)] items-center justify-center overflow-hidden rounded-lg bg-black">
{kind === "video" && src ? (