(null)
const onResizeMove = React.useCallback(
(ev: PointerEvent) => {
const s = resizingRef.current
if (!s) return
const dx = ev.clientX - s.sx
const dy = ev.clientY - s.sy
const ratio = s.ratio
const fromW = s.dir.includes('w')
const fromE = s.dir.includes('e')
const fromN = s.dir.includes('n')
const fromS = s.dir.includes('s')
let w = s.start.w
let h = s.start.h
let x = s.start.x
let y = s.start.y
const { w: vw, h: vh } = getViewport()
const EDGE_SNAP = 24
const startRight = s.start.x + s.start.w
const startBottom = s.start.y + s.start.h
const anchoredRight = Math.abs(vw - MARGIN - startRight) <= EDGE_SNAP
const anchoredBottom = Math.abs(vh - MARGIN - startBottom) <= EDGE_SNAP
const fitFromW = (newW: number) => {
newW = Math.max(MIN_W, newW)
let newH = newW / ratio
if (newH < MIN_H) {
newH = MIN_H
newW = newH * ratio
}
return { newW, newH }
}
const fitFromH = (newH: number) => {
newH = Math.max(MIN_H, newH)
let newW = newH * ratio
if (newW < MIN_W) {
newW = MIN_W
newH = newW / ratio
}
return { newW, newH }
}
const isCorner = (fromE || fromW) && (fromN || fromS)
if (isCorner) {
const useWidth = Math.abs(dx) >= Math.abs(dy)
if (useWidth) {
const rawW = fromE ? s.start.w + dx : s.start.w - dx
const { newW, newH } = fitFromW(rawW)
w = newW
h = newH
} else {
const rawH = fromS ? s.start.h + dy : s.start.h - dy
const { newW, newH } = fitFromH(rawH)
w = newW
h = newH
}
if (fromW) x = s.start.x + (s.start.w - w)
if (fromN) y = s.start.y + (s.start.h - h)
} else if (fromE || fromW) {
const rawW = fromE ? s.start.w + dx : s.start.w - dx
const { newW, newH } = fitFromW(rawW)
w = newW
h = newH
if (fromW) x = s.start.x + (s.start.w - w)
y = anchoredBottom ? s.start.y + (s.start.h - h) : s.start.y
} else if (fromN || fromS) {
const rawH = fromS ? s.start.h + dy : s.start.h - dy
const { newW, newH } = fitFromH(rawH)
w = newW
h = newH
if (fromN) y = s.start.y + (s.start.h - h)
if (anchoredRight) x = s.start.x + (s.start.w - w)
else x = s.start.x
}
const next = clampRect({ x, y, w, h }, ratio)
pendingRectRef.current = next
if (resizeRafRef.current == null) {
resizeRafRef.current = requestAnimationFrame(() => {
resizeRafRef.current = null
const r = pendingRectRef.current
if (r) setWin(r)
})
}
},
[clampRect]
)
const endResize = React.useCallback(() => {
if (!resizingRef.current) return
setIsResizing(false)
if (resizeRafRef.current != null) {
cancelAnimationFrame(resizeRafRef.current)
resizeRafRef.current = null
}
resizingRef.current = null
window.removeEventListener('pointermove', onResizeMove)
window.removeEventListener('pointerup', endResize)
saveRect(winRef.current)
}, [onResizeMove, saveRect])
const beginResize = React.useCallback(
(dir: ResizeDir) => (e: React.PointerEvent) => {
if (!miniDesktop) return
if (e.button !== 0) return
e.preventDefault()
e.stopPropagation()
const start = winRef.current
resizingRef.current = { dir, sx: e.clientX, sy: e.clientY, start, ratio: start.w / start.h }
setIsResizing(true)
window.addEventListener('pointermove', onResizeMove)
window.addEventListener('pointerup', endResize)
},
[miniDesktop, onResizeMove, endResize]
)
const [canHover, setCanHover] = React.useState(false)
const [chromeHover, setChromeHover] = React.useState(false)
React.useEffect(() => {
const mq = window.matchMedia?.('(hover: hover) and (pointer: fine)')
const update = () => setCanHover(Boolean(mq?.matches))
update()
mq?.addEventListener?.('change', update)
return () => mq?.removeEventListener?.('change', update)
}, [])
const dragUiActive = miniDesktop && (chromeHover || isDragging || isResizing)
const [stopPending, setStopPending] = React.useState(false)
React.useEffect(() => {
if (job.status !== 'running') setStopPending(false)
}, [job.id, job.status])
if (!mounted || !portalTarget) return null
const overlayBtn =
'inline-flex items-center justify-center rounded-md p-2 transition ' +
'bg-white/75 text-gray-900 ring-1 ring-black/10 hover:bg-white/90 active:scale-[0.98] ' +
'dark:bg-black/45 dark:text-white dark:ring-white/10 dark:hover:bg-black/60 ' +
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500'
const phaseRaw = String((job as any).phase ?? '')
const phase = phaseRaw.toLowerCase()
const isStoppingLike = phase === 'stopping' || phase === 'remuxing' || phase === 'moving'
const stopDisabled = !onStopJob || !isRunning || isStoppingLike || stopPending
const footerRight = (
{isRunning ? (
<>
onToggleWatch(j) : undefined}
onToggleFavorite={onToggleFavorite ? (j) => onToggleFavorite(j) : undefined}
onToggleLike={onToggleLike ? (j) => onToggleLike(j) : undefined}
order={['watch', 'favorite', 'like', 'details']}
className="gap-1 min-w-0 flex-1"
/>
>
) : (
onToggleWatch(j) : undefined}
onToggleFavorite={onToggleFavorite ? (j) => onToggleFavorite(j) : undefined}
onToggleLike={onToggleLike ? (j) => onToggleLike(j) : undefined}
onToggleHot={
onToggleHot
? async (j) => {
releaseMedia()
await new Promise((r) => setTimeout(r, 150))
await onToggleHot(j)
await new Promise((r) => setTimeout(r, 0))
const p = playerRef.current
if (p && !(p as any).isDisposed?.()) {
const ret = p.play?.()
if (ret && typeof (ret as any).catch === 'function') {
;(ret as Promise).catch(() => {})
}
}
}
: undefined
}
onKeep={
onKeep
? async (j) => {
releaseMedia()
onClose()
await new Promise((r) => setTimeout(r, 150))
await onKeep(j)
}
: undefined
}
onDelete={
onDelete
? async (j) => {
releaseMedia()
onClose()
await new Promise((r) => setTimeout(r, 150))
await onDelete(j)
}
: undefined
}
order={['watch', 'favorite', 'like', 'hot', 'keep', 'delete', 'details']}
className="gap-1 min-w-0 flex-1"
/>
)}
)
const fullSize = expanded || miniDesktop
const liveBottom = `env(safe-area-inset-bottom)`
const vjsBottom = `calc(${controlBarH}px + env(safe-area-inset-bottom))`
const overlayBottom = isRunning ? liveBottom : vjsBottom
const metaBottom = isRunning
? `calc(8px + env(safe-area-inset-bottom))`
: `calc(${controlBarH + 8}px + env(safe-area-inset-bottom))`
const topOverlayTop = miniDesktop ? 'top-4' : 'top-2'
const showSideInfo = expanded && isDesktop
const videoChrome = (
{
if (!miniDesktop || !canHover) return
setChromeHover(true)
}}
onMouseLeave={() => {
if (!miniDesktop || !canHover) return
setChromeHover(false)
}}
>
{isRunning ? (
) : (
)}
{/* ✅ Top overlay */}
{showSideInfo ? null : footerRight}
{miniDesktop ? (
) : null}
{/* Bottom overlay: Gradient */}
{model}
{file || title}
{isHot || isHotFile ? (
HOT
) : null}
{resolutionLabel !== '—' ? (
{resolutionLabel}
) : null}
{!isRunning ? (
{runtimeLabel}
) : null}
{sizeLabel !== '—' ? {sizeLabel} : null}
)
const sidePanel = (
{/* eslint-disable-next-line @next/next/no-img-element */}

{
if (previewSrc !== previewB) setPreviewSrc(previewB)
}}
/>
{isRunning ? (
) : null}
onToggleWatch(j) : undefined}
onToggleFavorite={onToggleFavorite ? (j) => onToggleFavorite(j) : undefined}
onToggleLike={onToggleLike ? (j) => onToggleLike(j) : undefined}
onToggleHot={
onToggleHot
? async (j) => {
releaseMedia()
await new Promise((r) => setTimeout(r, 150))
await onToggleHot(j)
await new Promise((r) => setTimeout(r, 0))
const p = playerRef.current
if (p && !(p as any).isDisposed?.()) {
const ret = p.play?.()
if (ret && typeof (ret as any).catch === 'function') {
;(ret as Promise).catch(() => {})
}
}
}
: undefined
}
onKeep={
onKeep
? async (j) => {
releaseMedia()
onClose()
await new Promise((r) => setTimeout(r, 150))
await onKeep(j)
}
: undefined
}
onDelete={
onDelete
? async (j) => {
releaseMedia()
onClose()
await new Promise((r) => setTimeout(r, 150))
await onDelete(j)
}
: undefined
}
order={isRunning ? ['watch', 'favorite', 'like', 'details'] : ['watch', 'favorite', 'like', 'hot', 'details', 'keep', 'delete']}
className="flex items-center justify-start gap-1"
/>
Status
{job.status}
Auflösung
{resolutionLabel}
FPS
{fpsLabel}
Laufzeit
{runtimeLabel}
Größe
{sizeLabel}
Datum
{dateLabel}
{tags.length ? (
{tags.map((t) => (
{t}
))}
) : (
—
)}
)
const cardEl = (
{showSideInfo ? sidePanel : null}
{videoChrome}
)
const { w: vw, h: vh, ox, oy, bottomInset } = getViewport()
const expandedRect = {
left: ox + 16,
top: oy + 16,
width: Math.max(0, vw - 32),
height: Math.max(0, vh - 32),
}
const wrapStyle = expanded
? expandedRect
: miniDesktop
? { left: win.x, top: win.y, width: win.w, height: win.h }
: undefined
return createPortal(
<>
{expanded || miniDesktop ? (
{cardEl}
{miniDesktop ? (
) : null}
) : (
{cardEl}
)}
>,
portalTarget
)
}