auto-save 2026-05-14 01:05 (+4, ~3)
This commit is contained in:
@@ -93,6 +93,7 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an
|
||||
const [videoT, setVideoT] = useState(0)
|
||||
const [addingFrame, setAddingFrame] = useState(false)
|
||||
const [videoExpanded, setVideoExpanded] = useState(false)
|
||||
const [pinnedPreviewJob, setPinnedPreviewJob] = useState<string | null>(null)
|
||||
const fileRef = useRef<HTMLInputElement>(null)
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const job = d.job
|
||||
@@ -140,10 +141,15 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (isActive && ready) setVideoExpanded(true)
|
||||
else d.onSwitchJob(j.id)
|
||||
// 单击:钉住 / 取消钉住大预览 + 切换 active(若需要)
|
||||
setPinnedPreviewJob((prev) => (prev === j.id ? null : j.id))
|
||||
if (!isActive && ready) d.onSwitchJob(j.id)
|
||||
}}
|
||||
title={ready ? `${j.width}×${j.height} · ${j.duration.toFixed(1)}s · ${isActive ? "点击展开" : "点击切换"}` : "下载中…"}
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (ready) setVideoExpanded(true)
|
||||
}}
|
||||
title={ready ? `${j.width}×${j.height} · ${j.duration.toFixed(1)}s · 单击钉住大预览 · 双击展开播放` : "下载中…"}
|
||||
className="absolute inset-0 w-full h-full overflow-hidden rounded-md"
|
||||
>
|
||||
{ready ? (
|
||||
@@ -172,6 +178,8 @@ export function InputNode({ data, selected }: NodeProps<{ data: NodeData }> | an
|
||||
label={`${j.width}×${j.height}`}
|
||||
caption={`${j.duration.toFixed(1)}s`}
|
||||
borderClass="border-violet-300/60"
|
||||
pinned={pinnedPreviewJob === j.id}
|
||||
onClose={() => setPinnedPreviewJob(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -378,6 +386,7 @@ export function KeyframeNode({ data, selected }: any) {
|
||||
const frames = d.job?.frames ?? []
|
||||
const jobId = d.job?.id
|
||||
const aspectStr = d.job && d.job.height > 0 ? `${d.job.width}/${d.job.height}` : "9/16"
|
||||
const [pinnedPreviewFrame, setPinnedPreviewFrame] = useState<number | null>(null)
|
||||
|
||||
return (
|
||||
<div className="relative" style={{ width: "100%", height: "100%" }}>
|
||||
@@ -407,9 +416,10 @@ export function KeyframeNode({ data, selected }: any) {
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setPinnedPreviewFrame((prev) => (prev === f.index ? null : f.index))
|
||||
;(d.onOpenFramePanel ?? d.onExpandFrame)(f.index)
|
||||
}}
|
||||
title={`第 ${f.index + 1} 张 · ${f.timestamp.toFixed(1)}s · hover 看大图 · 点击打开 / 找回详情面板`}
|
||||
title={`第 ${f.index + 1} 张 · ${f.timestamp.toFixed(1)}s · 单击钉住大预览 / 打开详情面板`}
|
||||
className="absolute inset-0 w-full h-full"
|
||||
>
|
||||
<img
|
||||
@@ -471,26 +481,15 @@ export function KeyframeNode({ data, selected }: any) {
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
)}
|
||||
{/* hover 预览 — absolute 浮在缩略图上方,跟着 ReactFlow 画布缩放平移 */}
|
||||
<div
|
||||
className="pointer-events-none absolute opacity-0 group-hover:opacity-100 scale-95 group-hover:scale-100 transition-all duration-150 z-[60]"
|
||||
style={{
|
||||
bottom: "calc(100% + 10px)",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
transformOrigin: "bottom center",
|
||||
}}
|
||||
>
|
||||
<div className="rounded-lg overflow-hidden border-2 border-orange-300/50 bg-black shadow-2xl" style={{ width: 280 }}>
|
||||
<div style={{ aspectRatio: aspectStr }}>
|
||||
<img src={effectiveFrameUrl(jobId, f)} alt="" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div className="px-2 py-1 bg-black/80 text-white text-[10.5px] flex items-center justify-between">
|
||||
<span>分镜 {f.index + 1}</span>
|
||||
<span className="text-white/60 font-mono">{f.timestamp.toFixed(2)}s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HoverPreview
|
||||
imgSrc={effectiveFrameUrl(jobId, f)}
|
||||
aspect={aspectStr}
|
||||
label={`分镜 ${f.index + 1}`}
|
||||
caption={`${f.timestamp.toFixed(2)}s`}
|
||||
borderClass="border-orange-300/50"
|
||||
pinned={pinnedPreviewFrame === f.index}
|
||||
onClose={() => setPinnedPreviewFrame(null)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user