ui(gui): 💄 Improve sprite stream UI by updating App and SpriteStream components, API interactions, and global styles for better functionality and user experience
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
24bb015e96
commit
c27abb5ef9
5 changed files with 46 additions and 148 deletions
|
|
@ -17,6 +17,9 @@ function DashboardOrTheater(): ReactElement {
|
|||
}
|
||||
|
||||
export function App(): ReactElement {
|
||||
const [params] = useSearchParams()
|
||||
const isTheater = params.get('spriteTheater') === 'true'
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -27,38 +30,40 @@ export function App(): ReactElement {
|
|||
}}
|
||||
>
|
||||
<SpriteStream />
|
||||
<nav
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0 24px',
|
||||
height: 48,
|
||||
background: colors.surface,
|
||||
borderBottom: `1px solid ${colors.accent}`,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to="/"
|
||||
{!isTheater && (
|
||||
<nav
|
||||
style={{
|
||||
color: colors.highlight,
|
||||
textDecoration: 'none',
|
||||
fontSize: 15,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.3px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0 24px',
|
||||
height: 48,
|
||||
background: colors.surface,
|
||||
borderBottom: `1px solid ${colors.accent}`,
|
||||
}}
|
||||
>
|
||||
Sprite Review
|
||||
</Link>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Link to="/?spriteTheater=true" style={{ color: colors.muted, textDecoration: 'none', fontSize: 13 }}>
|
||||
Theater
|
||||
<Link
|
||||
to="/"
|
||||
style={{
|
||||
color: colors.highlight,
|
||||
textDecoration: 'none',
|
||||
fontSize: 15,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.3px',
|
||||
}}
|
||||
>
|
||||
Sprite Review
|
||||
</Link>
|
||||
<Link to="/review" style={{ color: colors.muted, textDecoration: 'none', fontSize: 13 }}>
|
||||
Review
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Link to="/?spriteTheater=true" style={{ color: colors.muted, textDecoration: 'none', fontSize: 13 }}>
|
||||
Theater
|
||||
</Link>
|
||||
<Link to="/review" style={{ color: colors.muted, textDecoration: 'none', fontSize: 13 }}>
|
||||
Review
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
<Routes>
|
||||
<Route path="/" element={<DashboardOrTheater />} />
|
||||
<Route path="/theater" element={<SpriteTheaterPage />} />
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { colors } from './pages/theme'
|
|||
const MAX_SPRITES = 60
|
||||
const STREAM_HEIGHT = 110
|
||||
const SPRITE_SIZE = 80
|
||||
const THEATER_SPRITE_SIZE = 120
|
||||
const CARD_GAP = 16
|
||||
const CARD_TOTAL = SPRITE_SIZE + CARD_GAP
|
||||
const SSE_RETRY_DELAYS = [5000, 10000, 20000, 30000]
|
||||
|
|
@ -309,110 +308,6 @@ function ControlButton({
|
|||
)
|
||||
}
|
||||
|
||||
function TheaterMode({
|
||||
sprites,
|
||||
newIds,
|
||||
scoredIds,
|
||||
onNavigate,
|
||||
onClose,
|
||||
}: {
|
||||
sprites: RecentVariant[]
|
||||
newIds: Set<number>
|
||||
scoredIds: Set<number>
|
||||
onNavigate: (id: string) => void
|
||||
onClose: () => void
|
||||
}): ReactNode {
|
||||
useEffect((): (() => void) => {
|
||||
const handler = (e: KeyboardEvent): void => {
|
||||
if (e.key === 'Escape') onClose()
|
||||
}
|
||||
window.addEventListener('keydown', handler)
|
||||
return (): void => window.removeEventListener('keydown', handler)
|
||||
}, [onClose])
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
zIndex: 900,
|
||||
background: colors.bg + 'f8',
|
||||
backdropFilter: 'blur(8px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '12px 24px',
|
||||
borderBottom: `1px solid ${colors.accent}44`,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: colors.text, fontSize: 14, fontWeight: 600 }}>
|
||||
Sprite Theater
|
||||
<span style={{ color: colors.muted, fontWeight: 400, marginLeft: 8, fontSize: 12 }}>
|
||||
{sprites.length} sprites
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: `1px solid ${colors.accent}`,
|
||||
color: colors.muted,
|
||||
cursor: 'pointer',
|
||||
fontSize: 12,
|
||||
padding: '4px 14px',
|
||||
borderRadius: 6,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
onMouseEnter={(e): void => {
|
||||
e.currentTarget.style.color = colors.text
|
||||
e.currentTarget.style.borderColor = colors.muted
|
||||
}}
|
||||
onMouseLeave={(e): void => {
|
||||
e.currentTarget.style.color = colors.muted
|
||||
e.currentTarget.style.borderColor = colors.accent
|
||||
}}
|
||||
>
|
||||
ESC to close
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: '24px 32px' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 16,
|
||||
justifyContent: 'flex-start',
|
||||
alignContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
{sprites.map((variant, i) => (
|
||||
<div
|
||||
key={variant.variant_id}
|
||||
style={{ animation: `theaterFadeIn 0.4s ease-out ${i * 0.03}s both` }}
|
||||
>
|
||||
<SpriteCard
|
||||
variant={variant}
|
||||
isNew={newIds.has(variant.variant_id)}
|
||||
justScored={scoredIds.has(variant.variant_id)}
|
||||
onClick={(): void => onNavigate(variant.sprite_id)}
|
||||
size={THEATER_SPRITE_SIZE}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SpriteStream(): ReactNode {
|
||||
const navigate = useNavigate()
|
||||
|
|
@ -642,22 +537,10 @@ export function SpriteStream(): ReactNode {
|
|||
}
|
||||
`
|
||||
|
||||
// When theater mode is active, SpriteTheaterPage handles the full view.
|
||||
// SpriteStream hides itself to avoid rendering on top.
|
||||
if (theater) {
|
||||
return (
|
||||
<>
|
||||
<style>{keyframeStyles}</style>
|
||||
<TheaterMode
|
||||
sprites={[...sprites].reverse()}
|
||||
newIds={newIds}
|
||||
scoredIds={scoredIds}
|
||||
onNavigate={(id): void => {
|
||||
toggleTheater(false)
|
||||
navigate(`/sprite/${id}`)
|
||||
}}
|
||||
onClose={(): void => toggleTheater(false)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
// Sprites are newest-first. Reverse so oldest is left, newest is right.
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@ export async function skipSprite(spriteId: string): Promise<void> {
|
|||
return requestVoid(`${API_BASE}/sprites/${spriteId}/skip`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export async function rejectVariant(variantId: number): Promise<void> {
|
||||
return requestVoid(`${API_BASE}/variants/${variantId}/reject`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export async function regenerateSprite(spriteId: string, prompt?: string, dimensionId?: number): Promise<void> {
|
||||
const body: Record<string, unknown> = {}
|
||||
if (prompt !== undefined) body.prompt = prompt
|
||||
|
|
|
|||
5
tools/sprite-generation/gui/src/global.css
Normal file
5
tools/sprite-generation/gui/src/global.css
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #0a0a14;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import './global.css'
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue