/** * Names the widths the site's own images come in. Every image the API serves * (avatars, covers, thumbnails, attachments, proxied images) takes `?w=` and * answers a copy scaled down to it, so a card or an avatar doesn't load the * upload at full size. The frontend builds those URLs from this list * (`imageSrcSet` in `src/utils/shared/urls.ts`) and the server makes no other * width (`serveImage` in `api/lib/upload.ts`), which bounds what it keeps. * * @module */ /** * The widths, in pixels, smallest first: an avatar on a dense screen, a small * cover, a card on a phone, and the widest feed card on a dense screen. */ export const IMAGE_WIDTHS = [128, 320, 640, 960] as const; /** One of {@link IMAGE_WIDTHS}. */ export type ImageWidth = typeof IMAGE_WIDTHS[number]; /** The largest of {@link IMAGE_WIDTHS}, which a thumbnail never exceeds. */ export const MAX_IMAGE_WIDTH: ImageWidth = IMAGE_WIDTHS[IMAGE_WIDTHS.length - 1]; /** * Returns the narrowest of {@link IMAGE_WIDTHS} that covers `width` pixels, or * the widest when none does. */ export function snapImageWidth(width: number): ImageWidth { return IMAGE_WIDTHS.find((w) => w >= width) ?? MAX_IMAGE_WIDTH; } /** * Returns the width a `?w=` value asks for, snapped to {@link IMAGE_WIDTHS}, * or null when it is absent or not a positive number, which asks for the image * as it is. */ export function parseImageWidth(raw: string | null): ImageWidth | null { if (raw === null || !/^\d{1,5}$/.test(raw)) return null; const width = Number(raw); return width > 0 ? snapImageWidth(width) : null; }