Image sizes generation

This commit is contained in:
DrMint 2024-05-22 18:47:44 +02:00
parent ab2032ff0c
commit a4ad2d0f21
19 changed files with 575 additions and 116 deletions

View File

@ -7,6 +7,7 @@ import { isNotEmpty, isValidPayloadImage, isValidPayloadMedia } from "../../../u
import {
convertAttributesToEndpointAttributes,
convertCreditsToEndpointCredits,
convertMediaThumbnailToEndpointMediaThumbnail,
convertRTCToEndpointRTC,
getLanguageId,
} from "../../../utils/endpoints";
@ -77,6 +78,8 @@ export const convertAudioToEndpointAudio = ({
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [],
duration,
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
...(isValidPayloadImage(thumbnail)
? { thumbnail: convertMediaThumbnailToEndpointMediaThumbnail(thumbnail) }
: {}),
credits: convertCreditsToEndpointCredits(credits),
});

View File

@ -13,6 +13,7 @@ import {
} from "../../../utils/asserts";
import {
convertAttributesToEndpointAttributes,
convertScanToEndpointScanImage,
convertSourceToEndpointSource,
getDomainFromUrl,
} from "../../../utils/endpoints";
@ -147,29 +148,34 @@ const handleGallery = (gallery: Collectible["gallery"]): EndpointCollectible["ga
};
const handleScans = (scans: Collectible["scans"]): EndpointCollectible["scans"] => {
const result =
scans?.pages?.flatMap(({ image }) => {
if (!isValidPayloadImage(image)) return [];
return image;
}) ?? [];
if (!scans) return;
const totalCount =
Object.keys(scans?.cover ?? {}).length +
Object.keys(scans?.dustjacket ?? {}).length +
Object.keys(scans?.obi ?? {}).length +
result.length;
(scans.pages ?? []).length;
const result =
scans?.pages?.flatMap(({ image, page }) => {
if (!isValidPayloadImage(image)) return [];
return { image, index: page.toString() };
}) ?? [];
if (isValidPayloadImage(scans?.cover?.front)) {
result.push(scans.cover.front);
result.push({ image: scans.cover.front, index: "cover-front" });
}
if (isValidPayloadImage(scans?.dustjacket?.front)) {
result.push(scans.dustjacket.front);
result.push({ image: scans.dustjacket.front, index: "dustjacket-front" });
}
const thumbnail = result?.[0];
if (!thumbnail || !isValidPayloadImage(thumbnail)) return;
return { count: totalCount, thumbnail };
if (!thumbnail || !isValidPayloadImage(thumbnail.image)) return;
return {
count: totalCount,
thumbnail: convertScanToEndpointScanImage(thumbnail.image, thumbnail.index),
};
};
const handleContents = (contents: Collectible["contents"]): EndpointCollectible["contents"] => {

View File

@ -4,7 +4,10 @@ import { EndpointCollectibleScanPage } from "../../../sdk";
import { Collectible, Scan } from "../../../types/collections";
import { CollectionEndpoint } from "../../../types/payload";
import { isDefined, isNotEmpty, isPayloadType, isValidPayloadImage } from "../../../utils/asserts";
import { convertSourceToEndpointSource } from "../../../utils/endpoints";
import {
convertScanToEndpointScanImage,
convertSourceToEndpointSource,
} from "../../../utils/endpoints";
import { convertImageToEndpointImage } from "../../Images/endpoints/getByID";
export const getBySlugEndpointScanPage: CollectionEndpoint = {
@ -51,7 +54,7 @@ export const getBySlugEndpointScanPage: CollectionEndpoint = {
const nextIndex = getNextIndex(index, collectible.scans);
const scanPage: EndpointCollectibleScanPage = {
image: { ...image, index },
image: convertScanToEndpointScanImage(image, index),
parentPages: convertSourceToEndpointSource({ scans: [collectible] }),
slug,
...(isValidPayloadImage(collectible.thumbnail)

View File

@ -5,6 +5,7 @@ import { Collectible } from "../../../types/collections";
import { isNotEmpty, isPayloadType, isValidPayloadImage } from "../../../utils/asserts";
import {
convertCreditsToEndpointCredits,
convertScanToEndpointScanImage,
convertSourceToEndpointSource,
} from "../../../utils/endpoints";
import { convertImageToEndpointImage } from "../../Images/endpoints/getByID";
@ -51,7 +52,7 @@ const handleScans = ({
credits: convertCreditsToEndpointCredits(credits),
pages:
pages?.flatMap(({ image, page }) =>
isValidPayloadImage(image) ? { ...image, index: page.toString() } : []
isValidPayloadImage(image) ? convertScanToEndpointScanImage(image, page.toString()) : []
) ?? [],
...(coverEnabled && cover ? { cover: handleCover(cover) } : {}),
...(dustjacketEnabled && dustjacket ? { dustjacket: handleDustjacket(dustjacket) } : {}),
@ -69,25 +70,35 @@ const handleCover = ({
insideFront,
spine,
}: NonNullable<NonNullable<Collectible["scans"]>["cover"]>): EndpointCollectibleScans["cover"] => ({
...(isValidPayloadImage(back) ? { back: { ...back, index: "cover-back" } } : {}),
...(isValidPayloadImage(flapBack) ? { flapBack: { ...flapBack, index: "cover-flap-back" } } : {}),
...(isValidPayloadImage(flapFront)
? { flapFront: { ...flapFront, index: "cover-flap-front" } }
...(isValidPayloadImage(back)
? { back: convertScanToEndpointScanImage(back, "cover-back") }
: {}),
...(isValidPayloadImage(flapBack)
? { flapBack: convertScanToEndpointScanImage(flapBack, "cover-flap-back") }
: {}),
...(isValidPayloadImage(flapFront)
? { flapFront: convertScanToEndpointScanImage(flapFront, "cover-flap-front") }
: {}),
...(isValidPayloadImage(front)
? { front: convertScanToEndpointScanImage(front, "cover-front") }
: {}),
...(isValidPayloadImage(front) ? { front: { ...front, index: "cover-front" } } : {}),
...(isValidPayloadImage(insideBack)
? { insideBack: { ...insideBack, index: "cover-inside-back" } }
? { insideBack: convertScanToEndpointScanImage(insideBack, "cover-inside-back") }
: {}),
...(isValidPayloadImage(insideFlapBack)
? { insideFlapBack: { ...insideFlapBack, index: "cover-inside-flap-back" } }
? { insideFlapBack: convertScanToEndpointScanImage(insideFlapBack, "cover-inside-flap-back") }
: {}),
...(isValidPayloadImage(insideFlapFront)
? { insideFlapFront: { ...insideFlapFront, index: "cover-inside-flap-front" } }
? {
insideFlapFront: convertScanToEndpointScanImage(insideFlapFront, "cover-inside-flap-front"),
}
: {}),
...(isValidPayloadImage(insideFront)
? { insideFront: { ...insideFront, index: "cover-inside-front" } }
? { insideFront: convertScanToEndpointScanImage(insideFront, "cover-inside-front") }
: {}),
...(isValidPayloadImage(spine)
? { spine: convertScanToEndpointScanImage(spine, "cover-spine") }
: {}),
...(isValidPayloadImage(spine) ? { spine: { ...spine, index: "cover-spine" } } : {}),
});
const handleDustjacket = ({
@ -104,29 +115,45 @@ const handleDustjacket = ({
}: NonNullable<
NonNullable<Collectible["scans"]>["dustjacket"]
>): EndpointCollectibleScans["dustjacket"] => ({
...(isValidPayloadImage(back) ? { back: { ...back, index: "dustjacket-back" } } : {}),
...(isValidPayloadImage(back)
? { back: convertScanToEndpointScanImage(back, "dustjacket-back") }
: {}),
...(isValidPayloadImage(flapBack)
? { flapBack: { ...flapBack, index: "dustjacket-flap-back" } }
? { flapBack: convertScanToEndpointScanImage(flapBack, "dustjacket-flap-back") }
: {}),
...(isValidPayloadImage(flapFront)
? { flapFront: { ...flapFront, index: "dustjacket-flap-front" } }
? { flapFront: convertScanToEndpointScanImage(flapFront, "dustjacket-flap-front") }
: {}),
...(isValidPayloadImage(front)
? { front: convertScanToEndpointScanImage(front, "dustjacket-front") }
: {}),
...(isValidPayloadImage(front) ? { front: { ...front, index: "dustjacket-front" } } : {}),
...(isValidPayloadImage(insideBack)
? { insideBack: { ...insideBack, index: "dustjacket-inside-back" } }
? { insideBack: convertScanToEndpointScanImage(insideBack, "dustjacket-inside-back") }
: {}),
...(isValidPayloadImage(insideFlapBack)
? { insideFlapBack: { ...insideFlapBack, index: "dustjacket-inside-flap-back" } }
? {
insideFlapBack: convertScanToEndpointScanImage(
insideFlapBack,
"dustjacket-inside-flap-back"
),
}
: {}),
...(isValidPayloadImage(insideFlapFront)
? { insideFlapFront: { ...insideFlapFront, index: "dustjacket-inside-flap-front" } }
? {
insideFlapFront: convertScanToEndpointScanImage(
insideFlapFront,
"dustjacket-inside-flap-front"
),
}
: {}),
...(isValidPayloadImage(insideFront)
? { insideFront: { ...insideFront, index: "dustjacket-inside-front" } }
? { insideFront: convertScanToEndpointScanImage(insideFront, "dustjacket-inside-front") }
: {}),
...(isValidPayloadImage(spine)
? { spine: convertScanToEndpointScanImage(spine, "dustjacket-spine") }
: {}),
...(isValidPayloadImage(spine) ? { spine: { ...spine, index: "dustjacket-spine" } } : {}),
...(isValidPayloadImage(insideSpine)
? { insideSpine: { ...insideSpine, index: "dustjacket-inside-spine" } }
? { insideSpine: convertScanToEndpointScanImage(insideSpine, "dustjacket-inside-spine") }
: {}),
});
@ -142,26 +169,32 @@ const handleObi = ({
insideSpine,
spine,
}: NonNullable<NonNullable<Collectible["scans"]>["obi"]>): EndpointCollectibleScans["obi"] => ({
...(isValidPayloadImage(back) ? { back: { ...back, index: "obi-back" } } : {}),
...(isValidPayloadImage(flapBack) ? { flapBack: { ...flapBack, index: "obi-flap-back" } } : {}),
...(isValidPayloadImage(flapFront)
? { flapFront: { ...flapFront, index: "obi-flap-front" } }
...(isValidPayloadImage(back) ? { back: convertScanToEndpointScanImage(back, "obi-back") } : {}),
...(isValidPayloadImage(flapBack)
? { flapBack: convertScanToEndpointScanImage(flapBack, "obi-flap-back") }
: {}),
...(isValidPayloadImage(flapFront)
? { flapFront: convertScanToEndpointScanImage(flapFront, "obi-flap-front") }
: {}),
...(isValidPayloadImage(front)
? { front: convertScanToEndpointScanImage(front, "obi-front") }
: {}),
...(isValidPayloadImage(front) ? { front: { ...front, index: "obi-front" } } : {}),
...(isValidPayloadImage(insideBack)
? { insideBack: { ...insideBack, index: "obi-inside-back" } }
? { insideBack: convertScanToEndpointScanImage(insideBack, "obi-inside-back") }
: {}),
...(isValidPayloadImage(insideFlapBack)
? { insideFlapBack: { ...insideFlapBack, index: "obi-inside-flap-back" } }
? { insideFlapBack: convertScanToEndpointScanImage(insideFlapBack, "obi-inside-flap-back") }
: {}),
...(isValidPayloadImage(insideFlapFront)
? { insideFlapFront: { ...insideFlapFront, index: "obi-inside-flap-front" } }
? { insideFlapFront: convertScanToEndpointScanImage(insideFlapFront, "obi-inside-flap-front") }
: {}),
...(isValidPayloadImage(insideFront)
? { insideFront: { ...insideFront, index: "obi-inside-front" } }
? { insideFront: convertScanToEndpointScanImage(insideFront, "obi-inside-front") }
: {}),
...(isValidPayloadImage(spine)
? { spine: convertScanToEndpointScanImage(spine, "obi-spine") }
: {}),
...(isValidPayloadImage(spine) ? { spine: { ...spine, index: "obi-spine" } } : {}),
...(isValidPayloadImage(insideSpine)
? { insideSpine: { ...insideSpine, index: "obi-inside-spine" } }
? { insideSpine: convertScanToEndpointScanImage(insideSpine, "obi-inside-spine") }
: {}),
});

View File

@ -1,10 +1,15 @@
import { Collections } from "../../constants";
import { createImageSizesRegenerationEndpoint } from "../../endpoints/imageSizesRegenerationEndpoint";
import { attributesField } from "../../fields/attributesField/attributesField";
import { creditsField } from "../../fields/creditsField/creditsField";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { createEditor } from "../../utils/editor";
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
import {
buildImageCollectionConfig,
generateOpenGraphSize,
generateWebpSize,
} from "../../utils/imageCollectionConfig";
import { getByID } from "./endpoints/getByID";
const fields = {
@ -34,18 +39,17 @@ export const Images = buildImageCollectionConfig({
},
upload: {
imageSizes: [
{
name: "og",
height: 750,
width: 1125,
formatOptions: {
format: "jpg",
options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 60 },
},
},
generateOpenGraphSize(),
generateWebpSize(200, 60),
generateWebpSize(320, 60),
generateWebpSize(480, 70),
generateWebpSize(800, 70),
generateWebpSize(1280, 85),
generateWebpSize(1920, 85),
generateWebpSize(2560, 90),
],
},
endpoints: [getByID],
endpoints: [getByID, createImageSizesRegenerationEndpoint(Collections.Images)],
fields: [
translatedFields({
name: fields.translations,

View File

@ -8,6 +8,7 @@ import {
convertAttributesToEndpointAttributes,
convertCreditsToEndpointCredits,
convertRTCToEndpointRTC,
convertSizesToEndpointImageSize,
getLanguageId,
} from "../../../utils/endpoints";
@ -59,6 +60,7 @@ export const convertImageToEndpointImage = ({
filesize,
id,
credits,
sizes,
}: Image & PayloadImage): EndpointImage => ({
url,
width,
@ -79,4 +81,17 @@ export const convertImageToEndpointImage = ({
...(isNotEmpty(description) ? { description: convertRTCToEndpointRTC(description) } : {}),
})) ?? [],
credits: convertCreditsToEndpointCredits(credits),
sizes: convertSizesToEndpointImageSize(
[
sizes?.["200w"],
sizes?.["320w"],
sizes?.["480w"],
sizes?.["800w"],
sizes?.["1280w"],
sizes?.["1920w"],
sizes?.["2560w"],
{ url, width, height, filename, filesize, mimeType },
],
[200, 320, 480, 800, 1280, 1920, 2560]
),
});

View File

@ -1,6 +1,11 @@
import { shownOnlyToAdmin } from "../../accesses/collections/shownOnlyToAdmin";
import { Collections } from "../../constants";
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
import { createImageSizesRegenerationEndpoint } from "../../endpoints/imageSizesRegenerationEndpoint";
import {
buildImageCollectionConfig,
generateOpenGraphSize,
generateWebpSize,
} from "../../utils/imageCollectionConfig";
const fields = {
filename: "filename",
@ -16,17 +21,17 @@ export const MediaThumbnails = buildImageCollectionConfig({
plural: "Media Thumbnails",
},
admin: { defaultColumns: [fields.filename, fields.updatedAt], hidden: shownOnlyToAdmin },
endpoints: [createImageSizesRegenerationEndpoint(Collections.MediaThumbnails)],
upload: {
imageSizes: [
{
name: "og",
height: 750,
width: 1125,
formatOptions: {
format: "jpg",
options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 60 },
},
},
generateOpenGraphSize(),
generateWebpSize(200, 60),
generateWebpSize(320, 60),
generateWebpSize(480, 70),
generateWebpSize(800, 70),
generateWebpSize(1280, 85),
generateWebpSize(1920, 85),
generateWebpSize(2560, 90),
],
},
fields: [],

View File

@ -1,6 +1,7 @@
import { shownOnlyToAdmin } from "../../accesses/collections/shownOnlyToAdmin";
import { Collections } from "../../constants";
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
import { createImageSizesRegenerationEndpoint } from "../../endpoints/imageSizesRegenerationEndpoint";
import { buildImageCollectionConfig, generateWebpSize } from "../../utils/imageCollectionConfig";
const fields = {
filename: "filename",
@ -19,17 +20,13 @@ export const Scans = buildImageCollectionConfig({
defaultColumns: [fields.filename, fields.updatedAt],
hidden: shownOnlyToAdmin,
},
endpoints: [createImageSizesRegenerationEndpoint(Collections.Scans)],
upload: {
imageSizes: [
{
name: "og",
height: 750,
width: 1125,
formatOptions: {
format: "jpg",
options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 60 },
},
},
generateWebpSize(200, 60),
generateWebpSize(320, 60),
generateWebpSize(480, 70),
generateWebpSize(800, 70),
],
},
fields: [],

View File

@ -15,6 +15,7 @@ import {
import {
convertAttributesToEndpointAttributes,
convertCreditsToEndpointCredits,
convertMediaThumbnailToEndpointMediaThumbnail,
convertRTCToEndpointRTC,
getLanguageId,
} from "../../../utils/endpoints";
@ -88,7 +89,9 @@ export const convertVideoToEndpointVideo = ({
})) ?? [],
duration,
...(isValidPayloadImage(thumbnail) ? { thumbnail } : {}),
...(isValidPayloadImage(thumbnail)
? { thumbnail: convertMediaThumbnailToEndpointMediaThumbnail(thumbnail) }
: {}),
...(platformEnabled && isDefined(platform) && isPayloadType(platform.channel)
? {
platform: {

View File

@ -7,6 +7,9 @@ import { afterChangeWebhook } from "../../hooks/afterChangeWebhook";
import { getConfigEndpoint } from "./endpoints/getConfigEndpoint";
const fields = {
homeBackgroundImage: "homeBackgroundImage",
timelineBackgroundImage: "timelineBackgroundImage",
defaultOpenGraphImage: "defaultOpenGraphImage",
homeFolders: "homeFolders",
homeFoldersDarkThumbnail: "darkThumbnail",
homeFoldersLightThumbnail: "lightThumbnail",
@ -32,6 +35,26 @@ export const WebsiteConfig: GlobalConfig = {
afterChange: [afterChangeWebhook],
},
fields: [
rowField([
{
name: fields.homeBackgroundImage,
type: "upload",
relationTo: Collections.Images,
required: true,
},
{
name: fields.timelineBackgroundImage,
type: "upload",
relationTo: Collections.Images,
required: true,
},
{
name: fields.defaultOpenGraphImage,
type: "upload",
relationTo: Collections.Images,
required: true,
},
]),
{
name: fields.homeFolders,
admin: {

View File

@ -20,7 +20,13 @@ export const getConfigEndpoint: CollectionEndpoint = {
});
}
const { homeFolders, timeline } = await payload.findGlobal({
const {
homeFolders,
timeline,
defaultOpenGraphImage,
homeBackgroundImage,
timelineBackgroundImage,
} = await payload.findGlobal({
slug: Collections.WebsiteConfig,
});
@ -43,20 +49,28 @@ export const getConfigEndpoint: CollectionEndpoint = {
});
const endpointWebsiteConfig: EndpointWebsiteConfig = {
homeFolders:
homeFolders?.flatMap(({ folder, darkThumbnail, lightThumbnail }) => {
if (!isPayloadType(folder)) return [];
return {
...convertFolderToEndpointFolder(folder),
...(isValidPayloadImage(darkThumbnail)
? { darkThumbnail: convertImageToEndpointImage(darkThumbnail) }
: {}),
...(isValidPayloadImage(lightThumbnail)
? { lightThumbnail: convertImageToEndpointImage(lightThumbnail) }
: {}),
};
}) ?? [],
home: {
...(isValidPayloadImage(homeBackgroundImage)
? { backgroundImage: convertImageToEndpointImage(homeBackgroundImage) }
: {}),
folders:
homeFolders?.flatMap(({ folder, darkThumbnail, lightThumbnail }) => {
if (!isPayloadType(folder)) return [];
return {
...convertFolderToEndpointFolder(folder),
...(isValidPayloadImage(darkThumbnail)
? { darkThumbnail: convertImageToEndpointImage(darkThumbnail) }
: {}),
...(isValidPayloadImage(lightThumbnail)
? { lightThumbnail: convertImageToEndpointImage(lightThumbnail) }
: {}),
};
}) ?? [],
},
timeline: {
...(isValidPayloadImage(timelineBackgroundImage)
? { backgroundImage: convertImageToEndpointImage(timelineBackgroundImage) }
: {}),
breaks: timeline?.breaks ?? [],
eventCount,
eras:
@ -69,6 +83,9 @@ export const getConfigEndpoint: CollectionEndpoint = {
};
}) ?? [],
},
...(isValidPayloadImage(defaultOpenGraphImage)
? { defaultOpenGraphImage: convertImageToEndpointImage(defaultOpenGraphImage) }
: {}),
};
res.status(200).json(endpointWebsiteConfig);
},

View File

@ -0,0 +1,42 @@
import payload from "payload";
import { CollectionEndpoint } from "../types/payload";
export const createImageSizesRegenerationEndpoint = (
collection: "images" | "scans" | "media-thumbnails"
): CollectionEndpoint => ({
path: `/regenerate`,
method: "get",
handler: async (req, res) => {
if (!req.user) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
const result = await payload.find({
collection,
pagination: false,
});
for (const { id, filename } of result.docs) {
console.log("Handling", id);
if (!filename) {
throw new Error("No filename!");
}
await payload.update({
collection,
id,
filePath: `./uploads/${collection}/${filename}`,
data: {},
});
}
res.status(200).send({ message: `Regenerated sizes for ${result.docs.length} images!` });
},
});

View File

@ -1,22 +1,8 @@
import { Props } from "payload/components/views/Cell";
import React, { useEffect, useMemo, useState } from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { isUndefined } from "../../utils/asserts";
const Image = styled.img`
height: 3rem;
width: 3rem;
object-fit: contain;
transition: 0.2s transform;
transition-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
position: absolute;
transform: translateY(-50%) scale(1);
&:hover {
transform: translateY(-50%) scale(3);
}
`;
export const Cell = ({ cellData, field, rowData, collection }: Props): JSX.Element => {
const [imageURL, setImageURL] = useState<string>();
useEffect(() => {
@ -34,5 +20,13 @@ export const Cell = ({ cellData, field, rowData, collection }: Props): JSX.Eleme
[collection.slug, rowData.id]
);
return <Link to={link}>{imageURL ? <Image src={imageURL} /> : "<No Image>"}</Link>;
return (
<Link to={link}>
{imageURL ? (
<img className="thumbnail thumbnail--size-small file__thumbnail" src={imageURL} />
) : (
"<No Image>"
)}
</Link>
);
};

View File

@ -53,11 +53,15 @@ export type EndpointFolder = {
};
export type EndpointWebsiteConfig = {
homeFolders: (EndpointFolder & {
lightThumbnail?: EndpointImage;
darkThumbnail?: EndpointImage;
})[];
home: {
backgroundImage?: EndpointImage;
folders: (EndpointFolder & {
lightThumbnail?: EndpointImage;
darkThumbnail?: EndpointImage;
})[];
};
timeline: {
backgroundImage?: EndpointImage;
breaks: number[];
eventCount: number;
eras: {
@ -66,6 +70,7 @@ export type EndpointWebsiteConfig = {
name: string;
}[];
};
defaultOpenGraphImage?: EndpointImage;
};
export type EndpointRecorder = {
@ -176,7 +181,7 @@ export type EndpointCollectible = {
backgroundImage?: EndpointImage;
nature: CollectibleNature;
gallery?: { count: number; thumbnail: EndpointImage };
scans?: { count: number; thumbnail: PayloadImage };
scans?: { count: number; thumbnail: EndpointScanImage };
urls: { url: string; label: string }[];
price?: {
amount: number;
@ -341,6 +346,7 @@ export type EndpointCollectibleScanPage = {
export type EndpointScanImage = PayloadImage & {
index: string;
sizes: EndpointImageSize[];
};
export type TableOfContentEntry = {
@ -405,18 +411,26 @@ export type EndpointMedia = {
credits: EndpointCredit[];
};
export type EndpointImageSize = {
width: number;
height: number;
url: string;
wSize: number;
};
export type EndpointImage = EndpointMedia & {
width: number;
height: number;
sizes: EndpointImageSize[];
};
export type EndpointAudio = EndpointMedia & {
thumbnail?: PayloadImage;
thumbnail?: EndpointMediaThumbnail;
duration: number;
};
export type EndpointVideo = EndpointMedia & {
thumbnail?: PayloadImage;
thumbnail?: EndpointMediaThumbnail;
subtitles: {
language: string;
url: string;
@ -436,6 +450,10 @@ export type EndpointVideo = EndpointMedia & {
duration: number;
};
export type EndpointMediaThumbnail = PayloadImage & {
sizes: EndpointImageSize[];
};
export type PayloadMedia = {
url: string;
mimeType: string;

View File

@ -159,6 +159,62 @@ export interface Image {
filesize?: number | null;
filename?: string | null;
};
"200w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"320w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"480w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"800w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"1280w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"1920w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"2560w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
};
}
/**
@ -573,7 +629,31 @@ export interface Scan {
filesize?: number | null;
filename?: string | null;
};
og?: {
"200w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"320w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"480w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"800w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
@ -677,6 +757,62 @@ export interface MediaThumbnail {
filesize?: number | null;
filename?: string | null;
};
"200w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"320w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"480w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"800w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"1280w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"1920w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
"2560w"?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
};
}
/**
@ -923,6 +1059,9 @@ export interface PayloadMigration {
*/
export interface WebsiteConfig {
id: string;
homeBackgroundImage: string | Image;
timelineBackgroundImage: string | Image;
defaultOpenGraphImage: string | Image;
homeFolders?:
| {
lightThumbnail?: string | Image | null;

View File

@ -60,3 +60,32 @@ export const isPayloadArrayType = <T extends Object>(
export const isPublished = <T extends { _status?: ("draft" | "published") | null }>(
object: T
): boolean => object._status === "published";
export type ImageSize = {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
export type ValidImageSize = {
url: string;
width: number;
height: number;
mimeType: string;
filesize: number;
filename: string;
};
export const isValidImageSize = (size: ImageSize | undefined): size is ValidImageSize => {
if (isUndefined(size)) return false;
if (isUndefined(size.url)) return false;
if (isUndefined(size.width)) return false;
if (isUndefined(size.height)) return false;
if (isUndefined(size.mimeType)) return false;
if (isUndefined(size.filesize)) return false;
if (isUndefined(size.filename)) return false;
return true;
};

View File

@ -1,5 +1,5 @@
import { GeneratedTypes } from "payload";
import { CollectionConfig } from "payload/types";
import { Collections } from "../constants";
import { formatToPascalCase } from "./string";
type CollectionConfigWithPlugins = CollectionConfig;
@ -8,7 +8,7 @@ export type BuildCollectionConfig = Omit<
CollectionConfigWithPlugins,
"slug" | "typescript" | "labels"
> & {
slug: Collections;
slug: keyof GeneratedTypes["collections"];
labels: { singular: string; plural: string };
};

View File

@ -22,9 +22,13 @@ import {
import {
EndpointAttribute,
EndpointCredit,
EndpointImageSize,
EndpointMediaThumbnail,
EndpointRole,
EndpointScanImage,
EndpointSource,
EndpointTag,
PayloadImage,
} from "../sdk";
import {
Audio,
@ -34,18 +38,23 @@ import {
Folder,
Image,
Language,
MediaThumbnail,
NumberBlock,
Scan,
Tag,
TagsBlock,
TextBlock,
Video,
} from "../types/collections";
import {
ImageSize,
ValidImageSize,
isDefined,
isEmpty,
isPayloadArrayType,
isPayloadType,
isPublished,
isValidImageSize,
isValidPayloadImage,
isValidPayloadMedia,
} from "./asserts";
@ -272,3 +281,90 @@ const convertAttributeToEndpointAttribute = (
}
}
};
export const convertSizesToEndpointImageSize = (
sizes: (ImageSize | undefined)[],
targetSizes: number[]
): EndpointImageSize[] => {
if (!sizes) return [];
const processedSizes = sizes.filter(isValidImageSize);
const targetBins: { min: number; target: number; max: number; image: ValidImageSize }[] = [];
for (let index = 0; index < targetSizes.length; index++) {
const previous = targetSizes[index - 1];
const current = targetSizes[index]!;
const next = targetSizes[index + 1];
const min = previous ? previous + (current - previous) / 2 : 0;
const max = next ? current + (next - current) / 2 : Infinity;
const images = processedSizes
.filter(({ width }) => width > min && width <= max)
.sort((a, b) => a.filesize - b.filesize);
const smallestImage = images[0];
if (!smallestImage) continue;
targetBins.push({ min, target: current, max, image: smallestImage });
}
return targetBins.map(({ target, image: { height, width, url } }) => ({
width,
height,
url,
wSize: target,
}));
};
export const convertScanToEndpointScanImage = (
{ url, width, height, mimeType, filename, filesize, sizes }: Scan & PayloadImage,
index: string
): EndpointScanImage => ({
index,
url,
width,
height,
filename,
filesize,
mimeType,
sizes: convertSizesToEndpointImageSize(
[
sizes?.["200w"],
sizes?.["320w"],
sizes?.["480w"],
sizes?.["800w"],
{ url, width, height, filename, filesize, mimeType },
],
[200, 320, 480, 800]
),
});
export const convertMediaThumbnailToEndpointMediaThumbnail = ({
url,
width,
height,
mimeType,
filename,
filesize,
sizes,
}: MediaThumbnail & PayloadImage): EndpointMediaThumbnail => ({
url,
width,
height,
filename,
filesize,
mimeType,
sizes: convertSizesToEndpointImageSize(
[
sizes?.["200w"],
sizes?.["320w"],
sizes?.["480w"],
sizes?.["800w"],
sizes?.["1280w"],
sizes?.["1920w"],
sizes?.["2560w"],
{ url, width, height, filename, filesize, mimeType },
],
[200, 320, 480, 800, 1280, 1920, 2560]
),
});

View File

@ -49,3 +49,35 @@ export const buildImageCollectionConfig = ({
],
},
});
export const generateOpenGraphSize = (): ImageSize => ({
name: "og",
withoutEnlargement: true,
height: 1200,
width: 1200,
fit: "inside",
formatOptions: {
format: "jpg",
options: {
quality: 50,
optimizeScans: true,
quantizationTable: 2,
force: true,
},
},
});
export const generateWebpSize = (maxWidth: number, quality: number): ImageSize => ({
name: `${maxWidth}w`,
withoutEnlargement: true,
width: maxWidth,
fit: "inside",
formatOptions: {
format: "webp",
options: {
quality,
alphaQuality: quality,
force: true,
},
},
});