@lucidcms/core
Version:
The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.
1 lines • 13.2 kB
Source Map (JSON)
{"version":3,"file":"media.mjs","names":["formatter"],"sources":["../../../src/libs/formatters/media.ts"],"sourcesContent":["import type { BooleanInt } from \"../../libs/db/types.js\";\nimport type {\n\tMedia,\n\tMediaCropState,\n\tMediaFile,\n\tMediaFileMeta,\n\tMediaImageFile,\n\tMediaImageMeta,\n\tMediaOrigin,\n\tMediaPoster,\n\tProfilePicture,\n} from \"../../types/response.js\";\nimport { createMediaUrl } from \"../../utils/media/index.js\";\nimport type { MediaRef } from \"../collection/custom-fields/fields/media/types.js\";\nimport formatter from \"./index.js\";\n\ntype MediaTranslationProps = {\n\ttitle?: string | null;\n\talt: string | null;\n\tdescription?: string | null;\n\tsummary?: string | null;\n\tlocale_code: string | null;\n};\n\nexport interface MediaPosterPropsT {\n\tid: number;\n\tkey: string;\n\torigin: MediaOrigin;\n\ttype: string;\n\tmime_type: string;\n\tfile_extension: string;\n\tfile_name: string | null;\n\tfile_size: number;\n\twidth: number | null;\n\theight: number | null;\n\tfocal_x?: number | null;\n\tfocal_y?: number | null;\n\tcrop_x?: number | null;\n\tcrop_y?: number | null;\n\tcrop_width?: number | null;\n\tcrop_height?: number | null;\n\tcrop_rotation?: number | null;\n\tcrop_skew_x?: number | null;\n\tcrop_skew_y?: number | null;\n\tblur_hash: string | null;\n\taverage_color: string | null;\n\tbase64?: string | null;\n\tis_dark: BooleanInt | null;\n\tis_light: BooleanInt | null;\n\ttranslations?: MediaTranslationProps[];\n\tcrop?: MediaPosterPropsT[];\n}\n\nexport interface MediaPropsT extends MediaPosterPropsT {\n\tparent_media_id?: number | null;\n\trelation_type?: \"crop\" | \"poster\" | null;\n\te_tag: string | null;\n\tcreated_at: Date | string | null;\n\tupdated_at: Date | string | null;\n\tposter?: MediaPosterPropsT[];\n\tfolder_id: number | null;\n\tis_deleted: BooleanInt;\n\tis_deleted_at: Date | string | null;\n\tdeleted_by: number | null;\n\tpublic: BooleanInt;\n}\n\n/** Converts translation rows into a locale-keyed field map. */\nexport const objectifyTranslations = (\n\ttarget: \"title\" | \"alt\" | \"description\" | \"summary\",\n\ttranslations: MediaTranslationProps[],\n\tlocales: Array<string>,\n): Record<string, string> => {\n\treturn locales.reduce<Record<string, string>>(\n\t\t(acc, locale) => ({\n\t\t\t// biome-ignore lint/performance/noAccumulatingSpread: locale maps are small and this keeps the formatter readable\n\t\t\t...acc,\n\t\t\t[locale ?? \"\"]:\n\t\t\t\ttranslations.find((t) => t.locale_code === locale)?.[target] ?? \"\",\n\t\t}),\n\t\t{},\n\t);\n};\n\nexport const formatFocalPoint = (\n\tx: number | null | undefined,\n\ty: number | null | undefined,\n): MediaImageMeta[\"focalPoint\"] => {\n\tif (x === null || y === null || x === undefined || y === undefined) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tx: x / 10000,\n\t\ty: y / 10000,\n\t};\n};\n\n/** Formats metadata shared by every stored file. */\nconst formatFileMeta = (media: MediaPosterPropsT): MediaFileMeta => ({\n\tmimeType: media.mime_type,\n\textension: media.file_extension,\n\tfileSize: media.file_size,\n});\n\n/** Formats metadata that is only meaningful for image files. */\nconst formatImageMeta = (media: MediaPosterPropsT): MediaImageMeta => ({\n\t...formatFileMeta(media),\n\twidth: media.width,\n\theight: media.height,\n\tfocalPoint: formatFocalPoint(media.focal_x, media.focal_y),\n\tblurHash: media.blur_hash,\n\taverageColor: media.average_color,\n\tbase64: media.type === \"image\" ? (media.base64 ?? null) : null,\n\tisDark: formatter.formatBoolean(media.is_dark),\n\tisLight: formatter.formatBoolean(media.is_light),\n});\n\n/** Builds a public URL for a specific stored media row. */\nconst createFileUrl = (media: MediaPosterPropsT, host: string) =>\n\tcreateMediaUrl({\n\t\tkey: media.key,\n\t\thost,\n\t\tfileName: media.file_name,\n\t\textension: media.file_extension,\n\t});\n\n/** Formats and asserts the complete editor state stored on a crop row. */\nconst formatCropState = (media: MediaPosterPropsT): MediaCropState => {\n\tconst values = [\n\t\tmedia.crop_x,\n\t\tmedia.crop_y,\n\t\tmedia.crop_width,\n\t\tmedia.crop_height,\n\t\tmedia.crop_rotation,\n\t\tmedia.crop_skew_x,\n\t\tmedia.crop_skew_y,\n\t];\n\tif (values.some((value) => typeof value !== \"number\")) {\n\t\tthrow new TypeError(\"Active crop media has incomplete crop state\");\n\t}\n\n\treturn {\n\t\tx: media.crop_x as number,\n\t\ty: media.crop_y as number,\n\t\twidth: media.crop_width as number,\n\t\theight: media.crop_height as number,\n\t\trotation: media.crop_rotation as number,\n\t\tskewX: media.crop_skew_x as number,\n\t\tskewY: media.crop_skew_y as number,\n\t};\n};\n\n/** Formats the identifying fields shared by every file response. */\nconst formatFileIdentity = (\n\tmedia: MediaPosterPropsT,\n\thost: string,\n): Pick<MediaFile, \"key\" | \"url\" | \"fileName\"> => ({\n\tkey: media.key,\n\turl: createFileUrl(media, host),\n\tfileName: media.file_name,\n});\n\n/** Formats a non-image file without image-only presentation state. */\nconst formatFile = (media: MediaPosterPropsT, host: string): MediaFile => ({\n\t...formatFileIdentity(media, host),\n\tmeta: formatFileMeta(media),\n});\n\n/** Resolves an image's active crop and nests its original source when cropped. */\nconst formatImageFile = (\n\tmedia: MediaPosterPropsT,\n\thost: string,\n): MediaImageFile => {\n\tconst activeCrop = media.crop?.[0];\n\tif (!activeCrop) {\n\t\treturn {\n\t\t\t...formatFileIdentity(media, host),\n\t\t\tsourceType: \"original\",\n\t\t\tmeta: formatImageMeta(media),\n\t\t};\n\t}\n\n\treturn {\n\t\t...formatFileIdentity(activeCrop, host),\n\t\tsourceType: \"crop\",\n\t\tcrop: formatCropState(activeCrop),\n\t\tmeta: formatImageMeta(activeCrop),\n\t\toriginal: {\n\t\t\tkey: media.key,\n\t\t\turl: createFileUrl(media, host),\n\t\t\tmeta: formatImageMeta(media),\n\t\t},\n\t};\n};\n\n/** Formats one translated field for embedded media responses. */\nconst translationsFor = (\n\tmedia: MediaPosterPropsT,\n\tfield: \"title\" | \"alt\" | \"description\" | \"summary\",\n) =>\n\tmedia.translations?.map((translation) => ({\n\t\tvalue: translation[field] ?? null,\n\t\tlocaleCode: translation.locale_code,\n\t})) ?? [];\n\n/** Formats an image used as a user's profile picture. */\nconst formatProfilePicture = (props: {\n\tposter?: MediaPosterPropsT | null;\n\thost: string;\n}): ProfilePicture | null => {\n\tif (!props.poster) return null;\n\n\treturn {\n\t\tid: props.poster.id,\n\t\ttype: \"image\",\n\t\torigin: props.poster.origin,\n\t\ttitle: translationsFor(props.poster, \"title\"),\n\t\talt: translationsFor(props.poster, \"alt\"),\n\t\tfile: formatImageFile(props.poster, props.host),\n\t};\n};\n\n/** Formats an owned video poster with image-only fields. */\nconst formatPoster = (props: {\n\tposter?: MediaPosterPropsT | null;\n\thost: string;\n}): MediaPoster | null => {\n\tif (!props.poster) return null;\n\n\treturn {\n\t\tid: props.poster.id,\n\t\ttype: \"image\",\n\t\torigin: props.poster.origin,\n\t\talt: translationsFor(props.poster, \"alt\"),\n\t\tfile: formatImageFile(props.poster, props.host),\n\t};\n};\n\nconst formatSingle = (props: { media: MediaPropsT; host: string }): Media => {\n\tconst common = {\n\t\tfolderId: props.media.folder_id,\n\t\torigin: props.media.origin,\n\t\ttitle: translationsFor(props.media, \"title\"),\n\t};\n\tconst state = {\n\t\tpublic: formatter.formatBoolean(props.media.public),\n\t\tisDeleted: formatter.formatBoolean(props.media.is_deleted),\n\t\tisDeletedAt: formatter.formatDate(props.media.is_deleted_at),\n\t\tdeletedBy: props.media.deleted_by,\n\t\tcreatedAt: formatter.formatDate(props.media.created_at),\n\t\tupdatedAt: formatter.formatDate(props.media.updated_at),\n\t};\n\n\tswitch (props.media.type) {\n\t\tcase \"image\":\n\t\t\treturn {\n\t\t\t\tid: props.media.id,\n\t\t\t\ttype: \"image\",\n\t\t\t\t...common,\n\t\t\t\talt: translationsFor(props.media, \"alt\"),\n\t\t\t\tfile: formatImageFile(props.media, props.host),\n\t\t\t\t...state,\n\t\t\t};\n\t\tcase \"video\":\n\t\t\treturn {\n\t\t\t\tid: props.media.id,\n\t\t\t\ttype: \"video\",\n\t\t\t\t...common,\n\t\t\t\tdescription: translationsFor(props.media, \"description\"),\n\t\t\t\tfile: formatFile(props.media, props.host),\n\t\t\t\tposter: formatPoster({\n\t\t\t\t\tposter: props.media.poster?.[0],\n\t\t\t\t\thost: props.host,\n\t\t\t\t}),\n\t\t\t\t...state,\n\t\t\t};\n\t\tcase \"audio\":\n\t\t\treturn {\n\t\t\t\tid: props.media.id,\n\t\t\t\ttype: \"audio\",\n\t\t\t\t...common,\n\t\t\t\tdescription: translationsFor(props.media, \"description\"),\n\t\t\t\tfile: formatFile(props.media, props.host),\n\t\t\t\t...state,\n\t\t\t};\n\t\tcase \"document\":\n\t\t\treturn {\n\t\t\t\tid: props.media.id,\n\t\t\t\ttype: \"document\",\n\t\t\t\t...common,\n\t\t\t\tsummary: translationsFor(props.media, \"summary\"),\n\t\t\t\tfile: formatFile(props.media, props.host),\n\t\t\t\t...state,\n\t\t\t};\n\t\tcase \"archive\":\n\t\t\treturn {\n\t\t\t\tid: props.media.id,\n\t\t\t\ttype: \"archive\",\n\t\t\t\t...common,\n\t\t\t\tfile: formatFile(props.media, props.host),\n\t\t\t\t...state,\n\t\t\t};\n\t\tdefault:\n\t\t\treturn {\n\t\t\t\tid: props.media.id,\n\t\t\t\ttype: \"unknown\",\n\t\t\t\t...common,\n\t\t\t\tfile: formatFile(props.media, props.host),\n\t\t\t\t...state,\n\t\t\t};\n\t}\n};\n\n/** Formats a collection of media rows. */\nconst formatMultiple = (props: { media: MediaPropsT[]; host: string }) =>\n\tprops.media.map((media) => formatSingle({ media, host: props.host }));\n\nconst formatRef = (props: {\n\tmedia?: MediaPropsT | null;\n\thost: string;\n\tlocales: string[];\n}): MediaRef | null => {\n\tif (!props.media) return null;\n\treturn formatSingle({ media: props.media, host: props.host });\n};\n\nexport default {\n\tformatMultiple,\n\tformatSingle,\n\tformatProfilePicture,\n\tformatPoster,\n\tformatRef,\n\tformatFocalPoint,\n\tobjectifyTranslations,\n};\n"],"mappings":"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"}