auto-save 2026-05-14 01:05 (+4, ~3)

This commit is contained in:
2026-05-14 01:06:06 +08:00
parent 302631939d
commit 3684917abe
7 changed files with 1021 additions and 33 deletions

View File

@@ -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>
)
})}