Skip to content

Commit df6d81b

Browse files
feat: style tweaks
1 parent 25fef62 commit df6d81b

16 files changed

+231
-239
lines changed

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"devDependencies": {
6666
"@arianrhodsandlot/eslint-config": "0.18.4",
6767
"@eslint/config-inspector": "1.0.2",
68-
"@iconify/json": "2.2.315",
68+
"@iconify/json": "2.2.316",
6969
"@iconify/tailwind4": "1.0.6",
7070
"@tailwindcss/vite": "4.0.12",
7171
"@tsconfig/vite-react": "3.4.0",
@@ -79,7 +79,7 @@
7979
"drizzle-kit": "0.30.5",
8080
"eslint": "9.22.0",
8181
"libretrodb": "1.0.0",
82-
"lint-staged": "15.4.3",
82+
"lint-staged": "15.5.0",
8383
"miniflare": "3.20250310.0",
8484
"nanoid": "5.1.3",
8585
"react-server-dom-webpack": "19.0.0",
@@ -90,7 +90,7 @@
9090
"vite-plugin-cjs-interop": "2.1.6",
9191
"vite-tsconfig-paths": "5.1.4",
9292
"wrangler": "3.114.1",
93-
"zx": "8.4.0"
93+
"zx": "8.4.1"
9494
},
9595
"packageManager": "[email protected]",
9696
"pnpm": {

pnpm-lock.yaml

+119-169
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/controllers/get-states.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { and, eq } from 'drizzle-orm'
1+
import { and, desc, eq } from 'drizzle-orm'
22
import { getContextData } from 'waku/middleware/context'
33
import { stateTable } from '../databases/library/schema.ts'
44

@@ -13,6 +13,6 @@ export async function getStates({ rom, type }: { rom: string; type?: 'auto' | 'm
1313
conditions.push(eq(stateTable.type, type))
1414
}
1515
const where = and(...conditions)
16-
const results = await db.library.select().from(stateTable).where(where)
16+
const results = await db.library.select().from(stateTable).where(where).orderBy(desc(stateTable.created_at))
1717
return results
1818
}

src/pages/library/components/app-layout.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export default function AppLayout({
2828
return (
2929
<ServerDataContextProvider value={value}>
3030
<div className='flex h-screen bg-[var(--theme)]'>
31-
<aside className='flex w-64 shrink-0 flex-col'>
31+
<aside className='ml-4 flex w-64 shrink-0 flex-col'>
3232
<div className='flex items-center justify-center gap-2 pb-4 pt-2 font-bold text-white'>
3333
<img alt='logo' height='32' src='/assets/logo/logo-192x192.png' width='32' />
3434
RetroAssembly
@@ -38,10 +38,10 @@ export default function AppLayout({
3838
</ScrollArea>
3939
</aside>
4040

41-
<div className='flex h-full flex-1'>
42-
<div className='relative my-4 mr-4 flex flex-1 overflow-hidden rounded bg-zinc-50 shadow-[0_0_12px] shadow-black/10'>
43-
<MainScrollArea className='z-1 relative flex-1 p-4' key={req.url.href} size='2'>
44-
<main className='min-h-full'>{children}</main>
41+
<div className='m-4 flex min-w-0 flex-1'>
42+
<div className='relative flex flex-1 overflow-hidden rounded bg-zinc-50 shadow-[0_0_12px] shadow-black/10'>
43+
<MainScrollArea className='z-1 relative flex flex-1' key={req.url.href} size='2'>
44+
<main className='min-h-full w-full p-4'>{children}</main>
4545
</MainScrollArea>
4646
{append}
4747
</div>

src/pages/library/components/game-entry.tsx

+15-18
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
'use client'
2+
import { Skeleton } from '@radix-ui/themes'
23
import clsx from 'clsx'
3-
import { useState } from 'react'
44
import { Link } from 'waku'
5-
import { getPlatformGameIcon, getRomGoodcodes, getRomLibretroThumbnail } from '@/utils/rom.ts'
5+
import { getRomGoodcodes } from '@/utils/rom.ts'
6+
import { useRomCover } from '../hooks/use-rom-cover.ts'
67
import { GameEntryContextMenu } from './game-entry-context-menu.tsx'
78
import { GameTitle } from './game-title.tsx'
89

910
export function GameEntry({ rom }) {
1011
const goodcodes = getRomGoodcodes(rom)
11-
const [cover, setCover] = useState(() => getRomLibretroThumbnail(rom))
12-
13-
function handleError() {
14-
const platformCover = getPlatformGameIcon(rom.platform)
15-
if (cover !== platformCover) {
16-
setCover(getPlatformGameIcon(rom.platform))
17-
}
18-
}
19-
12+
const { data: cover, isLoading } = useRomCover(rom)
2013
return (
2114
<GameEntryContextMenu rom={rom}>
2215
<div className='relative'>
@@ -35,13 +28,17 @@ export function GameEntry({ rom }) {
3528
}
3629
>
3730
<div className='flex aspect-square size-full items-center justify-center'>
38-
<img
39-
alt={goodcodes.rom}
40-
className={clsx('max-w-4/5 max-h-full rounded object-contain drop-shadow-lg')}
41-
loading='lazy'
42-
onError={handleError}
43-
src={cover}
44-
/>
31+
{isLoading ? <Skeleton className='!size-4/5' loading /> : null}
32+
{cover?.src ? (
33+
<img
34+
alt={goodcodes.rom}
35+
className={clsx('max-w-4/5 max-h-full rounded object-contain drop-shadow-lg', {
36+
'bg-[var(--gray-a3)]': cover.type === 'rom',
37+
})}
38+
loading='lazy'
39+
src={cover.src}
40+
/>
41+
) : null}
4542
</div>
4643

4744
<GameTitle rom={rom} />

src/pages/library/components/game-list-pagination.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export function GameListPagination({ pagination }: { pagination: RomsPagination
1111
}
1212

1313
return (
14-
<ul className='mt-8 flex flex-wrap justify-center gap-2 px-10'>
15-
{range(1, pages + 1).map((page) => (
14+
<ul className='flex flex-wrap justify-center gap-2 px-10'>
15+
{range(1, pages).map((page) => (
1616
<li key={page}>
1717
<Button asChild size='3' variant={current === page ? 'solid' : 'soft'}>
1818
{current === page ? (

src/pages/library/components/sidebar-link.tsx

+1-6
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@ import { Link } from 'waku/router/client'
66
export function SidebarLink({ active = false, children, href }) {
77
return (
88
<Button asChild size='3' variant={active ? 'ghost' : 'solid'}>
9-
<Link
10-
className={clsx('!mx-2 !my-0 !h-auto !px-4 !py-2.5', { '!bg-white': active })}
11-
scroll
12-
to={href}
13-
unstable_pending={<div className='z-1 absolute top-0 size-full bg-white' />}
14-
>
9+
<Link className={clsx('!m-0 !h-auto !px-4 !py-2.5', { '!bg-white': active })} scroll to={href}>
1510
<div className='flex h-auto w-full justify-start gap-2 font-semibold'>{children}</div>
1611
</Link>
1712
</Button>

src/pages/library/components/sidebar-links.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ export function SidebarLinks({ platform }: { platform?: string }) {
2929
</div>
3030

3131
<div className='mt-4'>
32-
<h3 className='px-4 text-white/60'>Platforms</h3>
32+
<h3 className='flex items-center gap-2 px-4 text-sm text-white/50'>
33+
<span className='icon-[mdi--gamepad-classic] size-5' />
34+
Platforms
35+
</h3>
3336

3437
<div className='mt-2 flex flex-col gap-y-2'>
3538
{platformLinks.map(({ href, icon, name, text }) => (
3639
<SidebarLink active={platform === name} href={href} key={text}>
37-
{icon ? <img alt='icon' className='shrink-0' height='20' src={icon} width='20' /> : null}
40+
{icon ? <img alt='icon' className='size-5' src={icon} /> : null}
3841
{text}
3942
</SidebarLink>
4043
))}
+23-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
11
'use client'
2-
import ky from 'ky'
32
import useSWRImmutable from 'swr/immutable'
43
import { getPlatformGameIcon, getRomLibretroThumbnail } from '@/utils/rom.ts'
54

5+
const validImages = new Set<string>([])
6+
const invalidImages = new Set<string>([])
7+
8+
function imageLoaded(src) {
9+
const img = new Image()
10+
img.src = src
11+
return new Promise<void>((resolve, reject) => {
12+
img.addEventListener('load', () => resolve())
13+
img.addEventListener('error', (error) => reject(error))
14+
})
15+
}
16+
617
export function useRomCover(rom) {
718
const romCover = getRomLibretroThumbnail(rom)
819
const platformCover = getPlatformGameIcon(rom.platform)
920

1021
return useSWRImmutable([romCover, platformCover], async () => {
11-
if (romCover) {
22+
if (romCover && !invalidImages.has(romCover)) {
23+
if (validImages.has(romCover)) {
24+
return { src: romCover, type: 'rom' }
25+
}
26+
1227
try {
13-
await ky.head(romCover)
28+
await imageLoaded(romCover)
29+
validImages.add(romCover)
1430
return { src: romCover, type: 'rom' }
15-
} catch {}
31+
} catch {
32+
invalidImages.add(romCover)
33+
}
1634
}
35+
validImages.add(platformCover)
1736
return { src: platformCover, type: 'platform' }
1837
})
1938
}

src/pages/library/platform/rom/components/game-overlay/game-overlay-button.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ export function GameOverlayButton({
99
}: { children: ReactNode; isLoading?: boolean; onClick?: any }) {
1010
return (
1111
<Button
12-
className='[--accent-a11:white] [--accent-a3:rgba(255,255,255,.3)] [--accent-a4:rgba(255,255,255,.3)] [--accent-a5:rgba(255,255,255,.2)]'
12+
className='[--accent-a11:white] [--accent-a3:rgba(0,0,0,.3)] [--accent-a4:rgba(0,0,0,.3)] [--accent-a5:rgba(0,0,0,.2)] [--accent-a8:white] [--gray-a3:rgba(0,0,0,.2)] [--gray-a8:white]'
1313
disabled={isLoading}
1414
onClick={onClick}
1515
radius='full'
1616
size='4'
1717
type='button'
18-
variant='soft'
18+
variant='outline'
1919
>
2020
{children}
2121
</Button>

src/pages/library/platform/rom/components/game-overlay/game-overlay-buttons.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { GameOverlayButton } from './game-overlay-button.tsx'
77
export function GameOverlayButtons() {
88
const { emulator, exit } = useEmulator()
99
const { saveState } = useGameStates()
10-
const { setIsPending, toggle } = useGameOverlay()
10+
const { isPending, setIsPending, toggle } = useGameOverlay()
1111

1212
function handleClickResume() {
1313
emulator?.resume()
@@ -46,28 +46,28 @@ export function GameOverlayButtons() {
4646

4747
return (
4848
<>
49-
<GameOverlayButton onClick={handleClickResume}>
49+
<GameOverlayButton isLoading={isPending} onClick={handleClickResume}>
5050
<span className='icon-[material-symbols--resume] size-5' />
5151
Resume
5252
</GameOverlayButton>
5353

54-
<GameOverlayButton onClick={handleClickSaveState}>
54+
<GameOverlayButton isLoading={isPending} onClick={handleClickSaveState}>
5555
<span className='icon-[mdi--content-save] size-5' />
5656
Save State
5757
</GameOverlayButton>
5858

5959
<div className='flex-1' />
60-
<GameOverlayButton onClick={handleClickRestart}>
60+
<GameOverlayButton isLoading={isPending} onClick={handleClickRestart}>
6161
<span className='icon-[mdi--restart] size-5' />
6262
Restart
6363
</GameOverlayButton>
6464

65-
<GameOverlayButton onClick={handleClickExit}>
65+
<GameOverlayButton isLoading={isPending} onClick={handleClickExit}>
6666
<span className='icon-[mdi--exit-to-app] size-5' />
6767
Exit
6868
</GameOverlayButton>
6969

70-
<GameOverlayButton onClick={handleClickSaveExit}>
70+
<GameOverlayButton isLoading={isPending} onClick={handleClickSaveExit}>
7171
<span className='icon-[mdi--location-exit] size-5' />
7272
Save & Exit
7373
</GameOverlayButton>

src/pages/library/platform/rom/components/game-overlay/game-overlay.tsx

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
'use client'
22
import { useKeyboardEvent } from '@react-hookz/web'
33
import { AnimatePresence, motion } from 'motion/react'
4+
import { useRomCover } from '@/pages/library/hooks/use-rom-cover.ts'
5+
import { getRomGoodcodes } from '@/utils/rom.ts'
46
import { useEmulator } from '../../hooks/use-emulator.ts'
57
import { useGameOverlay } from '../../hooks/use-game-overlay.ts'
68
import { useGameStates } from '../../hooks/use-game-states.ts'
@@ -12,6 +14,9 @@ export function GameOverlay({ rom }) {
1214
const { emulator } = useEmulator()
1315
const { reloadStates } = useGameStates()
1416

17+
const goodcodes = getRomGoodcodes(rom)
18+
const { data: cover } = useRomCover(rom)
19+
1520
useKeyboardEvent(true, (event) => {
1621
const isEscapeKey = event.key === 'Escape'
1722
const status = emulator?.getStatus()
@@ -41,7 +46,14 @@ export function GameOverlay({ rom }) {
4146
>
4247
<div className='bg-linear-to-b to-text-transparent h-32 w-full from-black' />
4348
<div className='w-6xl mx-auto flex flex-1 flex-col gap-8'>
44-
<div className='text-5xl'>{rom.file_name}</div>
49+
<div className='flex items-center gap-4'>
50+
<div className='size-30 shrink-0'>
51+
{cover ? (
52+
<img alt={goodcodes.rom} className='size-30 object-contain object-center' src={cover.src} />
53+
) : null}
54+
</div>
55+
<div className='text-3xl font-semibold'>{goodcodes.rom}</div>
56+
</div>
4557
<div className='flex gap-8'>
4658
<GameOverlayButtons />
4759
</div>

src/pages/library/platform/rom/components/game-overlay/game-state.tsx

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Badge } from '@radix-ui/themes'
2+
import { clsx } from 'clsx'
13
import ky from 'ky'
24
import useSWRMutation from 'swr/mutation'
35
import { humanizeDate } from '@/utils/misc.ts'
@@ -8,7 +10,7 @@ export function GameState({ state }) {
810
const { setIsPending, toggle } = useGameOverlay()
911
const { emulator } = useEmulator()
1012

11-
const { trigger: loadState } = useSWRMutation(
13+
const { isMutating, trigger: loadState } = useSWRMutation(
1214
`/api/v1/state/${state.id}/content`,
1315
async (url) => {
1416
if (emulator) {
@@ -31,21 +33,37 @@ export function GameState({ state }) {
3133

3234
return (
3335
<button
34-
className='flex h-36 w-48 shrink-0 flex-col overflow-hidden rounded border-4 border-white bg-white shadow'
36+
className={clsx(
37+
'flex h-36 w-48 shrink-0 flex-col overflow-hidden rounded border-4 border-white bg-white shadow',
38+
{ 'cursor-default': isMutating },
39+
)}
40+
disabled={isMutating}
3541
key={state.id}
3642
onClick={handleClick}
3743
type='button'
3844
>
3945
<div className='relative flex-1 bg-black'>
40-
<img alt={state.id} className='absolute size-full object-contain' src={`/api/v1/state/${state.id}/thumbnail`} />
46+
<img
47+
alt={state.id}
48+
className={clsx('absolute size-full object-contain', { 'opacity-80': isMutating })}
49+
src={`/api/v1/state/${state.id}/thumbnail`}
50+
/>
4151

4252
{state.type === 'auto' ? (
4353
<div className='absolute bottom-0 right-0 rounded-tl bg-black/50 px-3 py-1 text-xs font-semibold text-white'>
4454
Auto Saved
4555
</div>
4656
) : null}
4757
</div>
48-
<div className='py-1 text-xs text-zinc-600'>Saved at {humanizeDate(state.created_at)}</div>
58+
<div className='flex h-5 items-center justify-center text-xs text-zinc-600'>
59+
{isMutating ? (
60+
<span className='icon-[svg-spinners--180-ring] block size-3 text-[var(--theme)]' />
61+
) : (
62+
<div>
63+
Saved at <Badge>{humanizeDate(state.created_at)}</Badge>
64+
</div>
65+
)}
66+
</div>
4967
</button>
5068
)
5169
}

src/pages/library/platform/rom/page.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ export async function RomPage({ fileName, id, platform }) {
2727
serverData={{ rom }}
2828
sidebar={<SidebarLinks platform={rom.platform} />}
2929
>
30+
<title>{`${goodcodes.rom} - RetroAssembly`}</title>
3031
<div className='flex gap-4'>
31-
<title>{`${goodcodes.rom} - RetroAssembly`}</title>
32-
3332
<div>
3433
<GameCover rom={rom} />
3534
</div>

src/pages/tsconfig.json

-13
This file was deleted.

src/styles/globals.css

+12
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,15 @@ button {
2929
button[disabled] {
3030
cursor: not-allowed;
3131
}
32+
33+
.radix-themes {
34+
--cursor-button: pointer;
35+
--cursor-checkbox: pointer;
36+
--cursor-disabled: default;
37+
--cursor-link: pointer;
38+
--cursor-menu-item: pointer;
39+
--cursor-radio: pointer;
40+
--cursor-slider-thumb: grab;
41+
--cursor-slider-thumb-active: grabbing;
42+
--cursor-switch: pointer;
43+
}

0 commit comments

Comments
 (0)