Introduce Picturarium config.
This commit is contained in:
parent
c3c629943c
commit
65fe6a9a82
1 changed files with 66 additions and 16 deletions
|
|
@ -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={() => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue