Cleanup. Fix simple linter error.

This commit is contained in:
Yura Dupyn 2026-05-15 00:51:24 +02:00
parent ca1a2bf1ca
commit 9138a187a9
2 changed files with 32 additions and 36 deletions

View file

@ -34,6 +34,7 @@ export default tseslint.config(
"error", "error",
{ prefer: "type-imports", fixStyle: "inline-type-imports" }, { prefer: "type-imports", fixStyle: "inline-type-imports" },
], ],
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/no-unused-vars": [ "@typescript-eslint/no-unused-vars": [
"error", "error",
{ {

View file

@ -10,24 +10,12 @@ import {
DialogContent, DialogContent,
} from "@mui/material" } from "@mui/material"
// TODO: Use Material UI later.
// TODO: Improve error-handling. Introduce some proper server response. // TODO: Improve error-handling. Introduce some proper server response.
// For better type-error messages when you have inexhaustive switch cases.
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`)
}
// === Image === // === Image ===
type ImageId = string type ImageId = string
type ImageRef = string type ImageRef = string
type Image = {
id: ImageId
dimension: Dimension
source: ImageRef
}
// === Dimension === // === Dimension ===
type Dimension = { type Dimension = {
@ -35,8 +23,11 @@ type Dimension = {
height: number height: number
} }
const Dimension = { const Dimension = {
medium: { width: 180, height: 180 } as Dimension, medium: { width: 180, height: 180 },
big: { width: 720, height: 720 } as Dimension, big: { width: 720, height: 720 },
} satisfies {
medium: Dimension
big: Dimension
} }
// === Page === // === Page ===
@ -69,7 +60,7 @@ const Page = {
}, },
// for hashing in maps to avoid identity problems // for hashing in maps to avoid identity problems
key(page: Page): PageKey { key(page: Page): PageKey {
return `${page.page}:${page.limit}` return `${String(page.page)}:${String(page.limit)}`
}, },
isFirst(page: Page): boolean { isFirst(page: Page): boolean {
return page.page === FIRST_PAGE return page.page === FIRST_PAGE
@ -88,9 +79,11 @@ const picsumApiImageSchema = z.object({
type PicsumApiImage = z.infer<typeof picsumApiImageSchema> type PicsumApiImage = z.infer<typeof picsumApiImageSchema>
async function getPicsumImages({ page, limit }: Page): Promise<PicsumApiImage[]> { async function getPicsumImages({ page, limit }: Page): Promise<PicsumApiImage[]> {
const response = await fetch(`https://picsum.photos/v2/list?limit=${limit}&page=${page}`) const response = await fetch(
`https://picsum.photos/v2/list?limit=${String(limit)}&page=${String(page)}`,
)
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to fetch images: ${response.status}`) throw new Error(`Failed to fetch images: ${String(response.status)}`)
} }
const json: unknown = await response.json() const json: unknown = await response.json()
@ -99,16 +92,6 @@ async function getPicsumImages({ page, limit }: Page): Promise<PicsumApiImage[]>
return data return data
} }
// TODO: We don't really need this.
async function getImages(page: Page): Promise<Image[]> {
const data = await getPicsumImages(page)
return data.map(({ id, download_url, width, height }) => ({
id: id as ImageId,
dimension: { width, height },
source: download_url as ImageRef,
}))
}
async function getImageIds(page: Page): Promise<ImageId[]> { async function getImageIds(page: Page): Promise<ImageId[]> {
const data = await getPicsumImages(page) const data = await getPicsumImages(page)
return data.map(({ id }) => id) return data.map(({ id }) => id)
@ -116,7 +99,7 @@ async function getImageIds(page: Page): Promise<ImageId[]> {
// Use this for `<img src=... />` // Use this for `<img src=... />`
function getImageSource(id: ImageId, dimension: Dimension): ImageRef { function getImageSource(id: ImageId, dimension: Dimension): ImageRef {
return `https://picsum.photos/id/${id}/${dimension.width}/${dimension.height}` return `https://picsum.photos/id/${id}/${String(dimension.width)}/${String(dimension.height)}`
} }
// === Generic Remote Data === // === Generic Remote Data ===
@ -218,7 +201,7 @@ function useApp(): [State, Dispatch] {
return { ...state, selectedImage: undefined } return { ...state, selectedImage: undefined }
} }
default: default:
return assertNever(msg) return msg satisfies never
} }
} }
@ -254,10 +237,10 @@ function imageGridStyle(dimension: Dimension) {
return { return {
display: "grid", display: "grid",
gridTemplateColumns: { gridTemplateColumns: {
xs: `repeat(2, ${dimension.width}px)`, xs: `repeat(2, ${String(dimension.width)}px)`,
md: `repeat(5, ${dimension.width}px)`, md: `repeat(5, ${String(dimension.width)}px)`,
}, },
gridAutoRows: `${dimension.height}px`, gridAutoRows: `${String(dimension.height)}px`,
gap: 2, gap: 2,
} }
} }
@ -287,7 +270,9 @@ function Images({ images }: { images: ImageId[] }) {
<Box <Box
component="img" component="img"
src={getImageSource(imageId, Dimension.medium)} src={getImageSource(imageId, Dimension.medium)}
onClick={() => dispatch({ tag: "imageClicked", imageId })} onClick={() => {
dispatch({ tag: "imageClicked", imageId })
}}
sx={{ sx={{
width: Dimension.medium.width, width: Dimension.medium.width,
height: Dimension.medium.height, height: Dimension.medium.height,
@ -307,7 +292,9 @@ function ImageModal({ selectedImage }: { selectedImage: ImageId | undefined }) {
return ( return (
<Dialog <Dialog
open={selectedImage !== undefined} open={selectedImage !== undefined}
onClose={() => dispatch({ tag: "modalCloseButtonClicked" })} onClose={() => {
dispatch({ tag: "modalCloseButtonClicked" })
}}
maxWidth={false} maxWidth={false}
> >
<DialogContent sx={{ p: 0, overflow: "hidden" }}> <DialogContent sx={{ p: 0, overflow: "hidden" }}>
@ -359,7 +346,9 @@ export default function App() {
}} }}
> >
<Button <Button
onClick={() => dispatch({ tag: "previousButtonClicked" })} onClick={() => {
dispatch({ tag: "previousButtonClicked" })
}}
disabled={Page.isFirst(state.page)} disabled={Page.isFirst(state.page)}
> >
prev prev
@ -374,7 +363,13 @@ export default function App() {
> >
{state.page.page} {state.page.page}
</Typography> </Typography>
<Button onClick={() => dispatch({ tag: "nextButtonClicked" })}>next</Button> <Button
onClick={() => {
dispatch({ tag: "nextButtonClicked" })
}}
>
next
</Button>
</Box> </Box>
</Box> </Box>