import { ImageContent, ImageContentType, ImageContentView, isImageContent, } from "@prismicio/types-internal/lib/content" import type { Image } from "@prismicio/types-internal/lib/customtypes" import { DiffChange, DiffOperation, } from "@prismicio/types-internal/lib/customtypes/diff" import dataset from "../../dataset/images.json" import type { NestableMock, Patch } from "../../Mock" import type { MockConfig } from "../../MockConfig" import { randomInt, slug } from "../../utils" type ImageData = { width: number; height: number; url: string } type Size = Pick type ImageDataset = ReadonlyArray const defaultImages = dataset as ImageDataset /* Computes the best size and source rect for the image that satisfies the constraints. */ const fitImage = ( imageSize: Size, constraints?: | { width?: number | null | undefined height?: number | null | undefined } | undefined, ) => { const [constraintWidth, constraintHeight] = (() => { const { width, height } = constraints ?? {} // If we have both constraints, keep them. if (width && height) return [width, height] // If we only have a width constraint, calculate the missing height constraint based on the image ratio. if (width) return [width, Math.round(imageSize.height * (width / imageSize.width))] // If we only have a height constraint, calculate the missing width constraint based on the image ratio. if (height) return [Math.round(imageSize.width * (height / imageSize.height)), height] // If we have no constraints at all, then use the original size. return [imageSize.width, imageSize.height] })() // Computes which side is the most constraining so we can calculate the most fitting source rectangle. const scaleFactor = Math.max( constraintWidth / imageSize.width, constraintHeight / imageSize.height, ) const rectX = Math.round( (imageSize.width - constraintWidth / scaleFactor) / 2, ) const rectY = Math.round( (imageSize.height - constraintHeight / scaleFactor) / 2, ) const rectWidth = Math.round(constraintWidth / scaleFactor) const rectHeight = Math.round(constraintHeight / scaleFactor) return { width: constraintWidth, height: constraintHeight, rect: { x: rectX, y: rectY, width: rectWidth, height: rectHeight, }, } } const buildImageUrl = ( originUrl: string, rect: { x: number; y: number; width: number; height: number }, outputSize: Size, ) => { const url = new URL(originUrl) const { x, y, width, height } = rect url.searchParams.set("rect", [x, y, width, height].join(",")) url.searchParams.set("w", outputSize.width.toString()) url.searchParams.set("h", outputSize.height.toString()) return url.toString() } function generateImageView(img: { id: string constraints?: | { width?: number | null | undefined height?: number | null | undefined } | undefined images: ImageDataset }): ImageContentView { const [originUrl, originWidth, originHeight] = (() => { const randomImage = img.images[randomInt(0, img.images.length - 1)] const generatedUrl = randomImage?.url.split("?")[0] if (!generatedUrl) throw new Error("Something happened during Image generation.") return [generatedUrl, randomImage.width, randomImage.height] })() const { width, height, rect } = fitImage( { width: originWidth, height: originHeight }, img.constraints, ) const hasConstraints = Boolean( img.constraints?.width || img.constraints?.height, ) const url = hasConstraints ? buildImageUrl(originUrl, rect, { width, height }) : originUrl return { origin: { id: img.id, url: originUrl, width: originWidth, height: originHeight, }, url, width, height, edit: { zoom: 1, crop: { x: rect.x, y: rect.y, }, background: "transparent", }, alt: slug(), } } export function random( def: Image, images: ImageDataset = defaultImages, ): ImageContent { const mainView = generateImageView({ id: "main", constraints: def.config?.constraint, images, }) const views = def.config?.thumbnails?.map<[string, ImageContentView]>((t) => { return [ t.name, generateImageView({ id: t.name, constraints: { width: t.width, height: t.height }, images, }), ] }) const thumbnails = (() => { if (!views) return {} return { thumbnails: views.reduce((acc, [viewName, viewValue]) => { return { ...acc, [viewName]: viewValue } }, {}), } })() return { __TYPE__: ImageContentType, ...mainView, ...thumbnails, } } type ImageMockConfig = MockConfig export const ImageMock: NestableMock = { generate(def: Image, config?: ImageMockConfig): ImageContent { return random(def, config?.value && [config.value]) }, applyPatch(data: Patch): | { result: ImageContent | undefined } | undefined { if (data.diff.op === DiffOperation.Removed) return { result: undefined } if (data.diff.value.type === "Image") { const patched = this.patch( data.diff, isImageContent(data.content) ? data.content : undefined, ) return { result: patched } } return }, patch( diff: DiffChange, _content: ImageContent, ): ImageContent | undefined { switch (diff.op) { case DiffOperation.Removed: return case DiffOperation.Updated: case DiffOperation.Added: return this.generate(diff.value) } }, }