auto-save 2026-05-13 15:28 (~4)

This commit is contained in:
2026-05-13 15:28:21 +08:00
parent de1254ff10
commit ad895f92e2
4 changed files with 44 additions and 47 deletions

View File

@@ -714,14 +714,14 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
<div className="absolute bottom-0 left-0 text-[8.5px] font-mono text-white bg-black/70 backdrop-blur px-1 rounded-tr">
#{ci + 1}
</div>
{/* 上推按钮:左上角 */}
{/* 上推按钮:常驻可见 */}
<button
onClick={(ev) => {
ev.preventDefault(); ev.stopPropagation()
const isLegacy = !(e.cutouts && e.cutouts.length > 0)
handlePushCutout(e.id, cid, `${e.name_zh} #${ci + 1}`, isLegacy)
}}
className="absolute left-0.5 top-0.5 h-4 w-4 rounded-sm bg-black/70 text-white/85 hover:bg-violet-500 hover:text-white inline-flex items-center justify-center opacity-0 group-hover/img:opacity-100 transition text-[11px] leading-none font-bold"
className="absolute left-0.5 top-0.5 h-4 w-4 rounded-sm bg-violet-500/90 text-white shadow hover:bg-violet-400 inline-flex items-center justify-center transition text-[11px] leading-none font-bold"
title="⬆ 上推到分镜头编排"
>

View File

@@ -409,7 +409,7 @@ export function KeyframeNode({ data, selected }: any) {
{f.timestamp.toFixed(1)}s
</div>
</button>
{/* 上推按钮:hover 时浮出 — 推送关键帧本身到分镜头编排 */}
{/* 上推按钮:常驻可见 — 推送关键帧本身到分镜头编排 */}
{d.onPushToStoryboard && (
<button
onClick={(e) => {
@@ -421,7 +421,7 @@ export function KeyframeNode({ data, selected }: any) {
})
}}
title="⬆ 上推到分镜头编排"
className="absolute top-1 left-1 h-5 w-5 rounded-full bg-black/70 backdrop-blur text-white/85 hover:bg-violet-500 hover:text-white inline-flex items-center justify-center opacity-0 group-hover:opacity-100 transition z-[70] text-[12px] leading-none font-bold"
className="absolute top-1 left-1 h-5 w-5 rounded-full bg-violet-500/90 backdrop-blur text-white shadow-md hover:bg-violet-400 hover:scale-110 inline-flex items-center justify-center transition z-[70] text-[12px] leading-none font-bold"
>
</button>
@@ -479,38 +479,31 @@ export function KeyframeNode({ data, selected }: any) {
)}
</NodeShell>
{/* Portal hover 预览 — 浮在缩略图上方(不挡其他界面) */}
{/* Portal hover 预览 — 固定视口右下角,不遮挡 */}
{mounted && hover && jobId && (() => {
const hf = frames.find((x) => x.index === hover.idx)
if (!hf) return null
// 大图最大尺寸(按视频比例算)
const vidAspect = d.job && d.job.height > 0 ? d.job.height / d.job.width : 16 / 9
const maxH = Math.min(window.innerHeight * 0.7, hover.rect.top - 16)
const maxW = Math.min(window.innerWidth * 0.6, 600)
let h = maxH, w = h / vidAspect
if (w > maxW) { w = maxW; h = w * vidAspect }
// 水平居中到缩略图clamp 在视口内
const centerX = hover.rect.left + hover.rect.width / 2
const left = Math.max(12, Math.min(window.innerWidth - w - 12, centerX - w / 2))
const w = 280
const h = w * vidAspect
return createPortal(
<div
className="fixed z-[120] pointer-events-none"
style={{
top: hover.rect.top - h - 12,
left,
right: 20,
bottom: 20,
animation: "drawer-in 0.18s cubic-bezier(0.32, 0.72, 0, 1)",
}}
>
<div className="rounded-2xl overflow-hidden border border-white/25 bg-black" style={{ boxShadow: "0 30px 80px -10px rgba(0,0,0,0.85), 0 0 0 1px rgba(255,255,255,0.06)" }}>
<div className="rounded-2xl overflow-hidden border border-orange-300/40 bg-black" style={{ boxShadow: "0 30px 80px -10px rgba(0,0,0,0.85), 0 0 0 1px rgba(255,255,255,0.06)" }}>
<img
src={effectiveFrameUrl(jobId, hf)}
alt={`preview ${hf.index}`}
className="block"
style={{ width: w, height: h, objectFit: "contain" }}
/>
<div className="flex items-center justify-between px-3 py-2 bg-black/70 backdrop-blur-md">
<span className="text-white text-[12.5px] font-medium"> {hf.index + 1} · {hf.timestamp.toFixed(2)}s</span>
<span className="text-white/60 text-[11px] font-mono"></span>
<div className="flex items-center justify-between px-3 py-1.5 bg-black/70 backdrop-blur-md">
<span className="text-white text-[12px] font-medium"> {hf.index + 1} · {hf.timestamp.toFixed(2)}s</span>
</div>
</div>
</div>,
@@ -674,7 +667,7 @@ export function ImageGenNode({ data, selected }: any) {
className="absolute inset-0 w-full h-full object-contain"
/>
</button>
{/* 上推按钮:hover 时浮出 */}
{/* 上推按钮:常驻可见 */}
{d.onPushToStoryboard && (
<button
onClick={(e) => {
@@ -688,7 +681,7 @@ export function ImageGenNode({ data, selected }: any) {
})
}}
title="⬆ 上推到分镜头编排"
className="absolute top-1 left-1 h-5 w-5 rounded-full bg-black/70 backdrop-blur text-white/85 hover:bg-violet-500 hover:text-white inline-flex items-center justify-center opacity-0 group-hover:opacity-100 transition z-[70] text-[12px] leading-none font-bold"
className="absolute top-1 left-1 h-5 w-5 rounded-full bg-violet-500/90 backdrop-blur text-white shadow-md hover:bg-violet-400 hover:scale-110 inline-flex items-center justify-center transition z-[70] text-[12px] leading-none font-bold"
>
</button>
@@ -724,38 +717,34 @@ export function ImageGenNode({ data, selected }: any) {
)}
</NodeShell>
{/* Portal hover 预览 — 浮在缩略图上方 */}
{/* Portal hover 预览 — 固定视口右下角,不遮挡 */}
{mounted && hover && job && (() => {
const [fi, ei] = hover.key.split("_")
const frameIdx = parseInt(fi, 10)
const p = elementCrops.find((x) => x.frameIdx === frameIdx && x.elementId === ei)
if (!p) return null
const vidAspect = job.height > 0 ? job.height / job.width : 16 / 9
const maxH = Math.min(window.innerHeight * 0.7, hover.rect.top - 16)
const maxW = Math.min(window.innerWidth * 0.6, 600)
let h = maxH, w = h / vidAspect
if (w > maxW) { w = maxW; h = w * vidAspect }
const centerX = hover.rect.left + hover.rect.width / 2
const left = Math.max(12, Math.min(window.innerWidth - w - 12, centerX - w / 2))
const w = 280
const h = w * vidAspect
return createPortal(
<div
className="fixed z-[120] pointer-events-none"
style={{
top: hover.rect.top - h - 12,
left,
right: 20,
bottom: 20,
animation: "drawer-in 0.18s cubic-bezier(0.32, 0.72, 0, 1)",
}}
>
<div className="rounded-2xl overflow-hidden border border-white/25 bg-black" style={{ boxShadow: "0 30px 80px -10px rgba(0,0,0,0.85), 0 0 0 1px rgba(255,255,255,0.06)" }}>
<div className="rounded-2xl overflow-hidden border border-violet-300/40 bg-white" style={{ boxShadow: "0 30px 80px -10px rgba(0,0,0,0.85), 0 0 0 1px rgba(255,255,255,0.06)" }}>
<img
src={p.src}
alt={`preview ${p.elementId}`}
className="block"
style={{ width: w, height: h, objectFit: "contain" }}
/>
<div className="flex items-center justify-between px-3 py-2 bg-black/70 backdrop-blur-md">
<span className="text-white text-[12.5px] font-medium">{p.name}</span>
<span className="text-white/60 text-[11px] font-mono"> {p.frameIdx + 1}</span>
<div className="flex items-center justify-between px-3 py-1.5 bg-black/70 backdrop-blur-md">
<span className="text-white text-[12px] font-medium">{p.name}</span>
<span className="text-white/60 text-[11px] font-mono"> {p.frameIdx + 1}</span>
</div>
</div>
</div>,

View File

@@ -213,34 +213,29 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame,
</div>
)}
{/* Hover 大图预览 · 浮在缩略图下方(不挡其他界面) */}
{/* Hover 预览 · 固定视口右下角,不遮挡 */}
{mounted && hover && (() => {
const vidAspect = job.height > 0 ? job.height / job.width : 16 / 9
const maxH = Math.min(window.innerHeight * 0.7, window.innerHeight - hover.rect.bottom - 16)
const maxW = Math.min(window.innerWidth * 0.6, 600)
let h = maxH, w = h / vidAspect
if (w > maxW) { w = maxW; h = w * vidAspect }
const centerX = hover.rect.left + hover.rect.width / 2
const left = Math.max(12, Math.min(window.innerWidth - w - 12, centerX - w / 2))
const w = 280
const h = w * vidAspect
return createPortal(
<div
className="fixed z-[120] pointer-events-none"
style={{
top: hover.rect.bottom + 8,
left,
right: 20,
bottom: 20,
animation: "drawer-in 0.18s cubic-bezier(0.32, 0.72, 0, 1)",
}}
>
<div className="rounded-2xl overflow-hidden border border-white/25 bg-black" style={{ boxShadow: "0 30px 80px -10px rgba(0,0,0,0.85), 0 0 0 1px rgba(255,255,255,0.06)" }}>
<div className="rounded-2xl overflow-hidden border border-violet-300/40 bg-black" style={{ boxShadow: "0 30px 80px -10px rgba(0,0,0,0.85), 0 0 0 1px rgba(255,255,255,0.06)" }}>
<img
src={effectiveFrameUrl(job.id, hover.frame)}
alt={`preview ${hover.frame.index}`}
className="block"
style={{ width: w, height: h, objectFit: "contain" }}
/>
<div className="flex items-center justify-between px-3 py-2 bg-black/70 backdrop-blur-md">
<span className="text-white text-[12.5px] font-medium"> {hover.seq} · {hover.frame.timestamp.toFixed(2)}s</span>
<span className="text-white/60 text-[11px] font-mono"></span>
<div className="flex items-center justify-between px-3 py-1.5 bg-black/70 backdrop-blur-md">
<span className="text-white text-[12px] font-medium"> {hover.seq} · {hover.frame.timestamp.toFixed(2)}s</span>
</div>
</div>
</div>,