Files
20260512-skg-tk/web/components/lightbox.tsx
2026-05-12 20:04:48 +08:00

98 lines
3.8 KiB
TypeScript

"use client"
import { useEffect } from "react"
import { X, ChevronLeft, ChevronRight, Check } from "lucide-react"
import { frameUrl, type KeyFrame } from "@/lib/api"
interface Props {
jobId: string
frames: KeyFrame[]
activeIndex: number | null
selected: Set<number>
onClose: () => void
onChange: (idx: number) => void
onToggleSelect: (idx: number) => void
}
export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect }: Props) {
useEffect(() => {
if (activeIndex === null) return
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose()
if (e.key === "ArrowLeft" && activeIndex > 0) onChange(activeIndex - 1)
if (e.key === "ArrowRight" && activeIndex < frames.length - 1) onChange(activeIndex + 1)
if (e.key === " " || e.key === "Enter") {
e.preventDefault()
onToggleSelect(activeIndex)
}
}
window.addEventListener("keydown", onKey)
return () => window.removeEventListener("keydown", onKey)
}, [activeIndex, frames.length, onClose, onChange, onToggleSelect])
if (activeIndex === null || !frames[activeIndex]) return null
const f = frames[activeIndex]
const isSelected = selected.has(f.index)
return (
<div
className="fixed inset-0 z-[100] bg-black/85 backdrop-blur-sm flex items-center justify-center"
onClick={onClose}
>
{/* 关闭 */}
<button
onClick={(e) => { e.stopPropagation(); onClose() }}
className="absolute top-5 right-5 h-10 w-10 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center"
aria-label="关闭"
>
<X className="h-5 w-5" />
</button>
{/* 左右切换 */}
{activeIndex > 0 && (
<button
onClick={(e) => { e.stopPropagation(); onChange(activeIndex - 1) }}
className="absolute left-5 h-12 w-12 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center"
>
<ChevronLeft className="h-6 w-6" />
</button>
)}
{activeIndex < frames.length - 1 && (
<button
onClick={(e) => { e.stopPropagation(); onChange(activeIndex + 1) }}
className="absolute right-5 h-12 w-12 rounded-full bg-white/10 hover:bg-white/20 text-white flex items-center justify-center"
>
<ChevronRight className="h-6 w-6" />
</button>
)}
{/* 大图 — 关键帧是静态素材(传给生图节点垫图用),不播放视频 */}
<div onClick={(e) => e.stopPropagation()} className="flex flex-col items-center gap-4 max-w-[92vw] max-h-[92vh]">
<img
src={frameUrl(jobId, f.index)}
alt={`frame ${f.index}`}
className="max-w-[88vw] max-h-[72vh] rounded-xl shadow-2xl object-contain"
/>
<div className="flex items-center gap-4 text-white">
<div className="font-mono text-sm tabular-nums">
{String(f.index + 1).padStart(2, "0")} / {String(frames.length).padStart(2, "0")}
<span className="mx-3 text-white/40">·</span>
<span className="text-white/70">{f.timestamp.toFixed(2)}s</span>
</div>
<button
onClick={() => onToggleSelect(f.index)}
className={`px-4 py-2 rounded-lg text-sm font-medium inline-flex items-center gap-2 transition ${
isSelected
? "bg-emerald-500 text-white hover:bg-emerald-400"
: "bg-white/10 text-white hover:bg-white/20"
}`}
>
<Check className="h-4 w-4" />
{isSelected ? "已选用(点取消)" : "选用此帧"}
</button>
</div>
<div className="text-[11px] text-white/40 font-mono">/ · Space · ESC </div>
</div>
</div>
)
}