feat(sprite-generation): ✨ Introduce interactive sprite theater UI with drag-and-drop, zoom, and animation preview features
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
5bd982c62b
commit
c4bedc98b7
1 changed files with 29 additions and 12 deletions
|
|
@ -52,6 +52,9 @@ const gridStyle: CSSProperties = {
|
|||
padding: '16px',
|
||||
}
|
||||
|
||||
const CARD_ENTER_DURATION = 400
|
||||
const CARD_SHIFT_DURATION = 300
|
||||
|
||||
function ScoreBadge({ label, value }: { label: string; value: number }) {
|
||||
const bg = value >= 0.7 ? 'rgba(16,185,129,0.2)' : value >= 0.5 ? 'rgba(245,158,11,0.2)' : 'rgba(239,68,68,0.2)'
|
||||
const fg = value >= 0.7 ? '#10b981' : value >= 0.5 ? '#f59e0b' : '#ef4444'
|
||||
|
|
@ -62,12 +65,18 @@ function ScoreBadge({ label, value }: { label: string; value: number }) {
|
|||
)
|
||||
}
|
||||
|
||||
function Card({ v }: { v: RecentVariant }) {
|
||||
function Card({ v, isNew, index }: { v: RecentVariant; isNew: boolean; index: number }) {
|
||||
const conf = confidence(v)
|
||||
const scores = parseScores(v.notes)
|
||||
const passing = conf >= 0.7
|
||||
const filename = extractFilename(v.raw_path)
|
||||
|
||||
const enterDelay = isNew ? 0 : index * 30
|
||||
const animStyle: CSSProperties = {
|
||||
transition: `transform ${CARD_SHIFT_DURATION}ms ease ${enterDelay}ms, opacity ${CARD_ENTER_DURATION}ms ease`,
|
||||
...(isNew ? { animation: `fadeScaleIn ${CARD_ENTER_DURATION}ms ease forwards` } : {}),
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
background: colors.surface,
|
||||
|
|
@ -75,6 +84,7 @@ function Card({ v }: { v: RecentVariant }) {
|
|||
overflow: 'hidden',
|
||||
border: passing ? '2px solid #10b981' : v.is_approved ? '2px solid #8b5cf6' : '1px solid #1e293b',
|
||||
position: 'relative',
|
||||
...animStyle,
|
||||
}}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<img
|
||||
|
|
@ -121,11 +131,19 @@ function Card({ v }: { v: RecentVariant }) {
|
|||
|
||||
export default function SpriteTheaterPage() {
|
||||
const [variants, setVariants] = useState<RecentVariant[]>([])
|
||||
const [newIds, setNewIds] = useState<Set<number>>(new Set())
|
||||
const [connected, setConnected] = useState(false)
|
||||
const eventSourceRef = useRef<EventSource | null>(null)
|
||||
|
||||
// Clear "new" flags after animation completes
|
||||
useEffect(() => {
|
||||
fetchRecentVariants(100).then(setVariants).catch(() => { /* initial load failed — will retry via SSE */ })
|
||||
if (newIds.size === 0) return
|
||||
const timer = setTimeout(() => setNewIds(new Set()), CARD_ENTER_DURATION + 200)
|
||||
return () => clearTimeout(timer)
|
||||
}, [newIds])
|
||||
|
||||
useEffect(() => {
|
||||
fetchRecentVariants(200).then(setVariants).catch(() => { /* initial load failed */ })
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -137,25 +155,24 @@ export default function SpriteTheaterPage() {
|
|||
|
||||
es.onmessage = (event: MessageEvent<string>) => {
|
||||
try {
|
||||
const newVariants: RecentVariant[] = JSON.parse(event.data) as RecentVariant[]
|
||||
const incoming: RecentVariant[] = JSON.parse(event.data) as RecentVariant[]
|
||||
setVariants(prev => {
|
||||
const ids = new Set(prev.map(v => v.variant_id))
|
||||
const fresh = newVariants.filter(v => !ids.has(v.variant_id))
|
||||
const existingIds = new Set(prev.map(v => v.variant_id))
|
||||
const fresh = incoming.filter(v => !existingIds.has(v.variant_id))
|
||||
if (fresh.length === 0) return prev
|
||||
setNewIds(new Set(fresh.map(v => v.variant_id)))
|
||||
return [...fresh, ...prev]
|
||||
})
|
||||
} catch { /* keepalive or malformed — ignore */ }
|
||||
} catch { /* keepalive or malformed */ }
|
||||
}
|
||||
|
||||
return () => es.close()
|
||||
}, [])
|
||||
|
||||
const sorted = [...variants].sort((a, b) => {
|
||||
const ca = confidence(a)
|
||||
const cb = confidence(b)
|
||||
if (ca !== cb) return cb - ca
|
||||
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
})
|
||||
// Sort by recency: newest first (top-left), oldest last (bottom-right)
|
||||
const sorted = [...variants].sort((a, b) =>
|
||||
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
)
|
||||
|
||||
return (
|
||||
<div style={page}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue