auto-save 2026-05-13 15:44 (~2)

This commit is contained in:
2026-05-13 15:44:57 +08:00
parent 5c3da231e0
commit 7125f04832
2 changed files with 26 additions and 15 deletions

View File

@@ -1808,6 +1808,13 @@
"type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令claude · 2 项未提交变更 · 最近提交auto-save 2026-05-13 15:33 (~4)",
"files_changed": 2
},
{
"ts": "2026-05-13T15:39:25+08:00",
"type": "commit",
"message": "auto-save 2026-05-13 15:39 (~2)",
"hash": "5c3da23",
"files_changed": 2
}
]
}

View File

@@ -17,7 +17,7 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame,
const [collapsed, setCollapsed] = useState(false)
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
const [hover, setHover] = useState<{ frame: KeyFrame; seq: number; rect: DOMRect } | null>(null)
const [hover, setHover] = useState<{ src: string; topLabel: string; subLabel: string; rect: DOMRect } | null>(null)
const btnRefs = useRef<Record<number, HTMLButtonElement | null>>({})
if (!job) return null
@@ -105,7 +105,12 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame,
onClick={() => onFocusFrame(f.index)}
onMouseEnter={() => {
const el = btnRefs.current[f.index]
if (el) setHover({ frame: f, seq: i + 1, rect: el.getBoundingClientRect() })
if (el) setHover({
src: effectiveFrameUrl(job.id, f),
topLabel: `分镜 ${i + 1}`,
subLabel: `${f.timestamp.toFixed(2)}s`,
rect: el.getBoundingClientRect(),
})
}}
onMouseLeave={() => setHover(null)}
title={`分镜 ${i + 1} · ${f.timestamp.toFixed(2)}s${cleaned ? " · 已清洗" : ""} · ${elementCount}/${totalElCount} 元素 · 点击进入编排`}
@@ -213,18 +218,16 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame,
</div>
)}
{/* Hover 预览 · 浮在缩略图侧面(右侧优先 / 不够则左侧 */}
{/* Hover 预览 · 浮在缩略图正下方bar 在顶部,下方是 DAG 画布区 */}
{mounted && hover && (() => {
const vidAspect = job.height > 0 ? job.height / job.width : 16 / 9
const w = 320
const w = 280
const h = w * vidAspect
const gap = 16
let left = hover.rect.right + gap
if (left + w > window.innerWidth - 12) left = hover.rect.left - w - gap
if (left < 12) left = 12
let top = hover.rect.top + hover.rect.height / 2 - h / 2
if (top < 12) top = 12
if (top + h > window.innerHeight - 12) top = window.innerHeight - h - 12
const gap = 10
// 水平居中到缩略图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 top = hover.rect.bottom + gap
return createPortal(
<div
className="fixed z-[120] pointer-events-none"
@@ -233,15 +236,16 @@ export function StoryboardBar({ job, selectedFrames, focusedFrame, onFocusFrame,
animation: "drawer-in 0.18s cubic-bezier(0.32, 0.72, 0, 1)",
}}
>
<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)" }}>
<div className="rounded-lg overflow-hidden border-2 border-violet-300/50 bg-black shadow-2xl">
<img
src={effectiveFrameUrl(job.id, hover.frame)}
alt={`preview ${hover.frame.index}`}
className="block"
style={{ width: w, height: h, objectFit: "contain" }}
style={{ width: w, height: h, objectFit: "cover" }}
/>
<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 className="px-2 py-1 bg-black/80 text-white text-[10.5px] flex items-center justify-between">
<span> {hover.seq}</span>
<span className="text-white/60 font-mono">{hover.frame.timestamp.toFixed(2)}s</span>
</div>
</div>
</div>,