2024-06-15 14:09:31 +02:00

435 lines
12 KiB
TypeScript

import { convertAudioToEndpointAudio } from "../collections/Audios/endpoints/getByID";
import { convertImageToEndpointImage } from "../collections/Images/endpoints/getByID";
import { convertRecorderToEndpointRecorderPreview } from "../collections/Recorders/endpoints/getByID";
import { convertVideoToEndpointVideo } from "../collections/Videos/endpoints/getByID";
import {
AttributeTypes,
RichTextBreakBlock,
RichTextContent,
RichTextSectionBlock,
RichTextUploadNode,
isBlockNodeBreakBlock,
isBlockNodeSectionBlock,
isNodeBlockNode,
isNodeUploadNode,
isUploadNodeAudioNode,
isUploadNodeImageNode,
isUploadNodeVideoNode,
} from "../constants";
import {
EndpointAttribute,
EndpointCredit,
EndpointPayloadImage,
EndpointRole,
EndpointScanImage,
EndpointSource,
EndpointSourcePreview,
EndpointTag,
PayloadImage,
} from "../sdk";
import {
Audio,
Collectible,
Credits,
CreditsRole,
Folder,
Image,
Language,
MediaThumbnail,
NumberBlock,
Scan,
Tag,
TagsBlock,
TextBlock,
Video,
} from "../types/collections";
import {
isAudio,
isDefined,
isEmpty,
isImage,
isNotEmpty,
isPayloadArrayType,
isPayloadImage,
isPayloadType,
isPublished,
isVideo,
} from "./asserts";
const convertTagToEndpointTag = ({ id, slug, page, translations }: Tag): EndpointTag => ({
id,
slug,
...(page && isPayloadType(page) ? { page: { slug: page.slug } } : {}),
translations: translations.map(({ language, name }) => ({
language: isPayloadType(language) ? language.id : language,
name,
})),
});
export const convertRTCToEndpointRTC = (
{ root: { children, ...others } }: RichTextContent,
parentPrefix = ""
): RichTextContent => {
let index = 0;
return {
root: {
...others,
children: children.map((node) => {
if (isNodeBlockNode(node)) {
// Add anchor hash on section block (TOC)
if (isBlockNodeSectionBlock(node)) {
index++;
const anchorHash = `${parentPrefix}${index}.`;
const newNode: RichTextSectionBlock = {
...node,
fields: {
...node.fields,
content: convertRTCToEndpointRTC(node.fields.content, anchorHash),
},
anchorHash,
};
return newNode;
// Add anchor hash on section block (TOC)
} else if (isBlockNodeBreakBlock(node)) {
index++;
const anchorHash = `${parentPrefix}${index}.`;
const newNode: RichTextBreakBlock = {
...node,
anchorHash,
};
return newNode;
}
} else if (isNodeUploadNode(node)) {
const errorUploadNode: RichTextUploadNode = {
type: "upload",
relationTo: "error",
version: 1,
};
if (isUploadNodeImageNode(node)) {
const value = node.value as unknown as Image | string;
if (!isImage(value)) return errorUploadNode;
return {
...node,
value: convertImageToEndpointImage(value),
};
} else if (isUploadNodeAudioNode(node)) {
const value = node.value as unknown as Audio | string;
if (!isAudio(value)) return errorUploadNode;
return {
...node,
value: convertAudioToEndpointAudio(value),
};
} else if (isUploadNodeVideoNode(node)) {
const value = node.value as unknown as Video | string;
if (!isVideo(value)) return errorUploadNode;
return {
...node,
value: convertVideoToEndpointVideo(value),
};
}
}
return node;
}),
},
};
};
// TODO: Handle URL sources
export const convertSourceToEndpointSource = ({
collectibles,
folders,
gallery,
scans,
}: {
collectibles?: (string | Collectible)[] | null | undefined;
scans?: (string | Collectible)[] | null | undefined;
gallery?: (string | Collectible)[] | null | undefined;
folders?: (string | Folder)[] | null | undefined;
}): EndpointSource[] => {
const result: EndpointSource[] = [];
const convertFolderToEndpointSourcePreview = ({
id,
slug,
translations,
}: Folder): EndpointSourcePreview => ({
id,
slug,
translations: translations.map(({ language, name }) => ({
language: isPayloadType(language) ? language.id : language,
title: name,
})),
});
const convertCollectibleToEndpointSourcePreview = ({
id,
slug,
translations,
}: Collectible): EndpointSourcePreview => ({
id,
slug,
translations: translations.map(({ language, title, pretitle, subtitle }) => ({
language: isPayloadType(language) ? language.id : language,
title,
...(isNotEmpty(pretitle) ? { pretitle } : {}),
...(isNotEmpty(subtitle) ? { subtitle } : {}),
})),
});
if (collectibles && isPayloadArrayType(collectibles)) {
collectibles.filter(isPublished).forEach((collectible) => {
result.push({
type: "collectible",
collectible: convertCollectibleToEndpointSourcePreview(collectible),
});
});
}
if (scans && isPayloadArrayType(scans)) {
scans.filter(isPublished).forEach((collectible) => {
result.push({
type: "scans",
collectible: convertCollectibleToEndpointSourcePreview(collectible),
});
});
}
if (gallery && isPayloadArrayType(gallery)) {
gallery.filter(isPublished).forEach((collectible) => {
result.push({
type: "gallery",
collectible: convertCollectibleToEndpointSourcePreview(collectible),
});
});
}
if (folders && isPayloadArrayType(folders)) {
folders.forEach((folder) => {
result.push({
type: "folder",
folder: convertFolderToEndpointSourcePreview(folder),
});
});
}
return result;
};
export const getDomainFromUrl = (url: string): string => {
const urlObject = new URL(url);
let domain = urlObject.hostname;
if (domain.startsWith("www.")) {
domain = domain.substring("www.".length);
}
return domain;
};
export const getLanguageId = (language: string | Language) =>
typeof language === "object" ? language.id : language;
const convertRoleToEndpointRole = ({ id, icon, translations }: CreditsRole): EndpointRole => ({
id,
icon: icon ?? "material-symbols:person",
translations: translations.map(({ language, name }) => ({
language: getLanguageId(language),
name,
})),
});
export const convertCreditsToEndpointCredits = (credits?: Credits | null): EndpointCredit[] =>
credits?.flatMap<EndpointCredit>(({ recorders, role }) => {
if (!isPayloadArrayType(recorders) || !isPayloadType(role)) return [];
return [
{
role: convertRoleToEndpointRole(role),
recorders: recorders.map(convertRecorderToEndpointRecorderPreview),
},
];
}) ?? [];
export const convertAttributesToEndpointAttributes = (
attributes: (TagsBlock | NumberBlock | TextBlock)[] | null | undefined
): EndpointAttribute[] =>
attributes?.map(convertAttributeToEndpointAttribute).filter(isDefined) ?? [];
const convertAttributeToEndpointAttribute = (
attribute: TagsBlock | NumberBlock | TextBlock
): EndpointAttribute | undefined => {
switch (attribute.blockType) {
case "numberBlock": {
const { name, number } = attribute;
if (!isPayloadType(name)) return;
const { id, slug, icon, translations } = name;
return {
id,
slug,
icon: icon ?? "material-symbols:category",
translations: translations.map(({ language, name }) => ({
language: isPayloadType(language) ? language.id : language,
name,
})),
type: AttributeTypes.Number,
value: number,
};
}
case "textBlock": {
const { name, text } = attribute;
if (!isPayloadType(name)) return;
if (isEmpty(text)) return;
const { id, slug, icon, translations } = name;
return {
id,
slug,
icon: icon ?? "material-symbols:category",
translations: translations.map(({ language, name }) => ({
language: isPayloadType(language) ? language.id : language,
name,
})),
type: AttributeTypes.Text,
value: text,
};
}
case "tagsBlock": {
const { name, tags } = attribute;
if (!isPayloadType(name)) return;
if (!isPayloadArrayType(tags)) return;
if (tags.length === 0) return;
const { id, slug, icon, translations } = name;
return {
id,
slug,
icon: icon ?? "material-symbols:category",
translations: translations.map(({ language, name }) => ({
language: isPayloadType(language) ? language.id : language,
name,
})),
type: AttributeTypes.Tags,
value: tags.map(convertTagToEndpointTag),
};
}
}
};
type Nullable<T> = { [P in keyof T]?: T[P] | undefined | null };
export const convertSizesToPayloadImages = (
sizes: (Nullable<PayloadImage> | undefined)[],
targetSizes: number[]
): PayloadImage[] => {
const processedSizes = sizes.filter(isPayloadImage);
const images: PayloadImage[] = [];
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 imagesAtTargetSize = processedSizes
.filter(({ width }) => width > min && width <= max)
.sort((a, b) => a.filesize - b.filesize);
const smallestImage = imagesAtTargetSize[0];
if (!smallestImage) continue;
images.push(smallestImage);
}
return images;
};
export const convertScanToEndpointScanImage = (
{ id, url, width, height, mimeType, filename, filesize, sizes }: Scan & PayloadImage,
index: string
): EndpointScanImage => ({
id,
index,
url,
width,
height,
filename,
filesize,
mimeType,
sizes: convertSizesToPayloadImages(
[
sizes?.["200w"],
sizes?.["320w"],
sizes?.["480w"],
sizes?.["800w"],
{ url, width, height, filename, filesize, mimeType },
],
[200, 320, 480, 800]
),
});
export const convertImageToEndpointPayloadImage = ({
url,
width,
height,
mimeType,
filename,
filesize,
id,
sizes,
}: Image & PayloadImage): EndpointPayloadImage => ({
filename,
filesize,
height,
id,
mimeType,
sizes: convertSizesToPayloadImages(
[
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]
),
url,
width,
...(isPayloadImage(sizes?.og) ? { openGraph: sizes.og } : {}),
});
export const convertMediaThumbnailToEndpointPayloadImage = ({
id,
url,
width,
height,
mimeType,
filename,
filesize,
sizes,
}: MediaThumbnail & PayloadImage): EndpointPayloadImage => ({
id,
url,
width,
height,
filename,
filesize,
mimeType,
sizes: convertSizesToPayloadImages(
[
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]
),
...(isPayloadImage(sizes?.og) ? { openGraph: sizes.og } : {}),
});