File size: 4,999 Bytes
abcf568 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | import type { MouseEvent as ReactMouseEvent } from 'react'
import { useI18n } from '../../../i18n'
import type { StudioFileAttachment, StudioWorkResult } from '../../protocol/studio-agent-types'
import type { PlotPreviewVariant } from '../types'
interface PlotPreviewSurfaceProps {
attachment: StudioFileAttachment | null | undefined
previewSrc?: string
result: StudioWorkResult | null
canNavigate: boolean
currentIndex: number
total: number
variant: PlotPreviewVariant
onOpen: () => void
onContextMenu: (event: ReactMouseEvent<HTMLDivElement>) => void
onPrev: () => void
onNext: () => void
}
export function PlotPreviewSurface({
attachment,
previewSrc,
result,
canNavigate,
currentIndex,
total,
variant,
onOpen,
onContextMenu,
onPrev,
onNext,
}: PlotPreviewSurfaceProps) {
const { t } = useI18n()
const isMinimal = variant === 'pure-minimal-top'
if (attachment?.mimeType?.startsWith('image/') || isImagePath(attachment?.path)) {
return (
<div className="relative flex h-full w-full items-center justify-center overflow-visible">
{canNavigate && (
<>
<button
type="button"
onClick={onPrev}
className={`absolute left-2 top-1/2 z-10 -translate-y-1/2 font-mono text-sm transition sm:left-4 ${
isMinimal ? 'text-text-secondary/45 hover:text-text-primary dark:text-white/40 dark:hover:text-white' : 'text-text-secondary/70 hover:text-text-primary dark:text-white/55 dark:hover:text-white'
}`}
>
←
</button>
<button
type="button"
onClick={onNext}
className={`absolute right-2 top-1/2 z-10 -translate-y-1/2 font-mono text-sm transition sm:right-4 ${
isMinimal ? 'text-text-secondary/45 hover:text-text-primary dark:text-white/40 dark:hover:text-white' : 'text-text-secondary/70 hover:text-text-primary dark:text-white/55 dark:hover:text-white'
}`}
>
→
</button>
<div
className={`pointer-events-none absolute left-1/2 z-10 -translate-x-1/2 font-mono text-[10px] tracking-[0.24em] ${
isMinimal
? 'bottom-5 rounded-full border border-border/10 bg-bg-secondary/70 px-3 py-1 text-text-secondary/60 backdrop-blur-md dark:border-white/10 dark:bg-bg-secondary/74 dark:text-text-secondary/80'
: 'bottom-4 rounded-full border border-black/10 bg-white/70 px-3 py-1 text-text-primary/80 backdrop-blur-md dark:border-white/10 dark:bg-bg-secondary/82 dark:text-text-primary/90'
}`}
>
{String(currentIndex + 1).padStart(2, '0')} / {String(total).padStart(2, '0')}
</div>
</>
)}
<div
role="button"
tabIndex={0}
onClick={(event: ReactMouseEvent<HTMLDivElement>) => {
if (event.button !== 0) {
return
}
onOpen()
}}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
onOpen()
}
}}
onContextMenu={onContextMenu}
className={`flex h-full w-full cursor-zoom-in items-center justify-center animate-fade-in-soft ${
isMinimal ? 'px-4 py-3 sm:px-8 sm:py-6' : ''
}`}
title={t('image.openTitle')}
>
<div className={`relative flex h-full w-full items-center justify-center overflow-hidden transition-all duration-500 ${
isMinimal
? 'bg-transparent'
: 'bg-transparent'
}`}>
<img
src={previewSrc ?? attachment?.path}
alt={attachment?.name ?? t('studio.plot.previewAlt')}
className="relative z-[1] max-h-full max-w-full object-contain"
/>
</div>
</div>
</div>
)
}
if (result?.kind === 'failure-report') {
return (
<div className={`flex flex-col items-center justify-center ${isMinimal ? 'opacity-24' : 'opacity-30'}`}>
<div className={`font-medium uppercase tracking-widest ${isMinimal ? 'font-mono text-[11px] text-rose-600/60 dark:text-rose-300/55' : 'text-sm text-rose-600/70 dark:text-rose-300/65'}`}>
{t('studio.renderFailed')}
</div>
</div>
)
}
return (
<div className={`flex h-full w-full items-center justify-center ${isMinimal ? 'opacity-30 transition-opacity duration-500 hover:opacity-60' : 'opacity-22 dark:opacity-30'}`}>
<span className={`font-mono uppercase text-text-secondary/55 dark:text-white/40 ${isMinimal ? 'text-[12px] tracking-[0.34em]' : 'text-[11px] tracking-[0.28em]'}`}>
[ Canvas Area ]
</span>
</div>
)
}
function isImagePath(path?: string) {
return Boolean(path && /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(path))
}
|