Introduce Picturarium config.

This commit is contained in:
Yura Dupyn 2026-05-15 11:36:50 +02:00
parent c3c629943c
commit 65fe6a9a82

View file

@ -10,6 +10,57 @@ import { Remote } from "../remote"
// I also like to keep views/states/messages/initialization/effects/update of one component in one single (even if giant) file (or a structure like `Component/...` + `Component.tsx`) // I also like to keep views/states/messages/initialization/effects/update of one component in one single (even if giant) file (or a structure like `Component/...` + `Component.tsx`)
// - Others school of thought are to grind all of these into fine dust and scatter the files all over the codebase (all state definitions in one giant folder, all messages/updates in another). I prefer not doing that. It violates locality too much for me, and I constantly have to jump between files "far-away". // - Others school of thought are to grind all of these into fine dust and scatter the files all over the codebase (all state definitions in one giant folder, all messages/updates in another). I prefer not doing that. It violates locality too much for me, and I constantly have to jump between files "far-away".
// === Config ===
// prefered number of cols/rows. The page size (how many images are on a page) is calculated as `columns * rows`
type Config = {
pageSize: number
mobileColumns: number
mobileRows: number
desktopColumns: number
desktopRows: number
}
const CONFIG = {
pageSize: 10,
mobileColumns: 2,
mobileRows: 5,
desktopColumns: 5,
desktopRows: 2,
} as const satisfies Config
// const CONFIG = {
// pageSize: 12,
// mobileColumns: 2,
// mobileRows: 6,
// desktopColumns: 4,
// desktopRows: 3,
// } as const satisfies Config
// Could do comptime assert: `size = mobile product`, and `size = desktop product`, but this is pretty heavy in TS.
assertValidConfig(CONFIG)
function assertValidConfig(config: Config): void {
const mobilePageSize = config.mobileColumns * config.mobileRows
const desktopPageSize = config.desktopColumns * config.desktopRows
if (mobilePageSize !== config.pageSize) {
throw new Error(
`Invalid config: mobile grid defines ${String(mobilePageSize)} images, but pageSize is ${String(config.pageSize)}`,
)
}
if (desktopPageSize !== config.pageSize) {
throw new Error(
`Invalid config: desktop grid defines ${String(desktopPageSize)} images, but pageSize is ${String(config.pageSize)}`,
)
}
}
// === Image === // === Image ===
type ImageId = string type ImageId = string
type ImageRef = string type ImageRef = string
@ -28,45 +79,44 @@ const Dimension = {
} }
// === Page === // === Page ===
type PageNumber = number type PageIndex = number
type Page = { type Page = {
page: PageNumber index: PageIndex
limit: number // page size size: number
} }
type PageKey = string type PageKey = string
const LIMIT = 10 const FIRST_PAGE: PageIndex = 1
const FIRST_PAGE: PageNumber = 1
const Page = { const Page = {
init(): Page { init(): Page {
return { page: FIRST_PAGE, limit: LIMIT } return { index: FIRST_PAGE, size: CONFIG.pageSize }
}, },
next(page: Page): Page { next(page: Page): Page {
return { ...page, page: page.page + 1 } return { ...page, index: page.index + 1 }
}, },
previous(page: Page): Page { previous(page: Page): Page {
// Could do // Could do
// if (page.page == FIRST_PAGE) { return page } // if (page.page == FIRST_PAGE) { return page }
// this preserves identity of object, so is nicer for `useEffect`, but it's waaaay to subtle. So I'm not relying on that. // this preserves identity of object, so is nicer for `useEffect`, but it's waaaay to subtle. So I'm not relying on that.
return { ...page, page: Math.max(FIRST_PAGE, page.page - 1) } return { ...page, index: Math.max(FIRST_PAGE, page.index - 1) }
}, },
eq(page0: Page, page1: Page): boolean { eq(page0: Page, page1: Page): boolean {
return page0.page === page1.page && page0.limit === page1.limit return page0.index === page1.index && page0.size === page1.size
}, },
// 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 `${String(page.page)}:${String(page.limit)}` return `${String(page.index)}:${String(page.size)}`
}, },
isFirst(page: Page): boolean { isFirst(page: Page): boolean {
return page.page === FIRST_PAGE return page.index === FIRST_PAGE
}, },
} }
// === api === // === api ===
async function getImageIds(page: Page): Promise<Result<ImageId[], RequestError>> { async function getImageIds(page: Page): Promise<Result<ImageId[], RequestError>> {
const result = await getPicsumImages(page) const result = await getPicsumImages({ page: page.index, limit: page.size })
return Result.map(result, (data) => data.map(({ id }) => id)) return Result.map(result, (data) => data.map(({ id }) => id))
} }
@ -233,8 +283,8 @@ function imageGridStyle(dimension: Dimension) {
return { return {
display: "grid", display: "grid",
gridTemplateColumns: { gridTemplateColumns: {
xs: `repeat(2, ${String(dimension.width)}px)`, xs: `repeat(${String(CONFIG.mobileColumns)}, ${String(dimension.width)}px)`,
md: `repeat(5, ${String(dimension.width)}px)`, md: `repeat(${String(CONFIG.desktopColumns)}, ${String(dimension.width)}px)`,
}, },
gridAutoRows: `${String(dimension.height)}px`, gridAutoRows: `${String(dimension.height)}px`,
gap: 2, gap: 2,
@ -243,7 +293,7 @@ function imageGridStyle(dimension: Dimension) {
// 2x5 on mobile, 5x2 on desktop (assuming 10 images per page) // 2x5 on mobile, 5x2 on desktop (assuming 10 images per page)
function ImagesSkeleton({ visible = true }: { visible?: boolean }) { function ImagesSkeleton({ visible = true }: { visible?: boolean }) {
const images = Array.from({ length: 10 }) const images = Array.from({ length: CONFIG.pageSize })
return ( return (
<Box sx={imageGridStyle(Dimension.medium)}> <Box sx={imageGridStyle(Dimension.medium)}>
{images.map((_, index) => {images.map((_, index) =>
@ -396,7 +446,7 @@ export default function Picturarium() {
color: "oklch(65% 0.02 260)", color: "oklch(65% 0.02 260)",
}} }}
> >
{state.page.page} {state.page.index}
</Typography> </Typography>
<Button <Button
onClick={() => { onClick={() => {