164 lines
4.4 KiB
TypeScript
164 lines
4.4 KiB
TypeScript
import {
|
|
getNextImageUrlForRequest,
|
|
NextImageSize,
|
|
} from '@/platforms/next-image';
|
|
import {
|
|
generateFileNameWithId,
|
|
getFileNamePartsFromStorageUrl,
|
|
getSignedUrlForUrl,
|
|
getStorageUrlsForPrefix,
|
|
uploadFileFromClient,
|
|
} from '@/platforms/storage';
|
|
import { Photo } from '..';
|
|
|
|
const PREFIX_PHOTO = 'photo';
|
|
const PREFIX_UPLOAD = 'upload';
|
|
|
|
const EXTENSION_DEFAULT = 'jpg';
|
|
const EXTENSION_OPTIMIZED = 'jpg';
|
|
|
|
// For the time being, make compatible with `next/image` sizes
|
|
const OPTIMIZED_FILE_SIZES = [{
|
|
suffix: 'sm',
|
|
size: 200,
|
|
quality: 90,
|
|
}, {
|
|
suffix: 'md',
|
|
size: 640,
|
|
quality: 90,
|
|
}, {
|
|
suffix: 'lg',
|
|
size: 1080,
|
|
quality: 80,
|
|
}] as const satisfies {
|
|
suffix: string
|
|
size: NextImageSize
|
|
quality: number
|
|
}[];
|
|
|
|
type OptimizedSuffix = (typeof OPTIMIZED_FILE_SIZES)[number]['suffix'];
|
|
|
|
const OPTIMIZED_SUFFIX_DEFAULT: OptimizedSuffix = 'md';
|
|
|
|
const getOptimizedFileName = ({
|
|
fileNameBase,
|
|
suffix,
|
|
}: {
|
|
fileNameBase: string
|
|
suffix: OptimizedSuffix
|
|
}) =>
|
|
`${fileNameBase}-${suffix}.${EXTENSION_OPTIMIZED}`;
|
|
|
|
const getOptimizedUrl =({
|
|
urlBase,
|
|
fileNameBase,
|
|
suffix,
|
|
}: {
|
|
urlBase: string
|
|
fileNameBase: string
|
|
suffix: OptimizedSuffix
|
|
}) =>
|
|
`${urlBase}/${getOptimizedFileName({ fileNameBase, suffix })}`;
|
|
|
|
export const getOptimizedPhotoFileMeta = (fileNameBase: string) =>
|
|
OPTIMIZED_FILE_SIZES.map(({ suffix, ...rest }) => ({
|
|
...rest,
|
|
fileName: getOptimizedFileName({ fileNameBase, suffix }),
|
|
}));
|
|
|
|
export const getOptimizedUrlsFromPhotoUrl = (url: string) => {
|
|
const { urlBase, fileNameBase } = getFileNamePartsFromStorageUrl(url);
|
|
return getOptimizedPhotoFileMeta(fileNameBase).map(({ fileName }) =>
|
|
`${urlBase}/${fileName}`);
|
|
};
|
|
|
|
export const generateRandomFileNameForPhoto = () =>
|
|
generateFileNameWithId(PREFIX_PHOTO);
|
|
|
|
export const getStorageUploadUrls = () =>
|
|
getStorageUrlsForPrefix(`${PREFIX_UPLOAD}-`);
|
|
|
|
export const getStoragePhotoUrls = () =>
|
|
getStorageUrlsForPrefix(`${PREFIX_PHOTO}-`);
|
|
|
|
export const uploadTempPhotoFromClient = (
|
|
file: File | Blob,
|
|
extension = EXTENSION_DEFAULT,
|
|
) =>
|
|
uploadFileFromClient(file, PREFIX_UPLOAD, extension);
|
|
|
|
export const uploadPhotoFromClient = (
|
|
file: File | Blob,
|
|
extension = EXTENSION_DEFAULT,
|
|
) =>
|
|
uploadFileFromClient(file, PREFIX_PHOTO, extension);
|
|
|
|
const getSuffixFromNextImageSize = (nextSize: NextImageSize) =>
|
|
OPTIMIZED_FILE_SIZES.find(({ size }) => size === nextSize)?.suffix
|
|
?? OPTIMIZED_SUFFIX_DEFAULT;
|
|
|
|
export const getOptimizedPhotoUrl = (
|
|
args: Parameters<typeof getNextImageUrlForRequest>[0] & {
|
|
compatibilityMode?: boolean
|
|
},
|
|
) => {
|
|
const { compatibilityMode = true } = args;
|
|
const suffix = getSuffixFromNextImageSize(args.size);
|
|
const {
|
|
urlBase,
|
|
fileNameBase,
|
|
} = getFileNamePartsFromStorageUrl(args.imageUrl);
|
|
return compatibilityMode
|
|
? getNextImageUrlForRequest(args)
|
|
: getOptimizedUrl({ urlBase, fileNameBase, suffix });
|
|
};
|
|
|
|
// Generate small, low-bandwidth images for quick manipulations such as
|
|
// generating blur data or image thumbnails for AI text generation
|
|
export const getOptimizedPhotoUrlForManipulation = (
|
|
imageUrl: string,
|
|
addBypassSecret: boolean,
|
|
compatibilityMode?: boolean,
|
|
) =>
|
|
getOptimizedPhotoUrl({
|
|
imageUrl,
|
|
size: 640,
|
|
addBypassSecret,
|
|
compatibilityMode,
|
|
});
|
|
|
|
const getTestOptimizedPhotoUrl = (url: string) => {
|
|
const { urlBase, fileNameBase } = getFileNamePartsFromStorageUrl(url);
|
|
return getOptimizedUrl({
|
|
urlBase,
|
|
fileNameBase,
|
|
suffix: 'sm',
|
|
});
|
|
};
|
|
|
|
export const doesPhotoUrlHaveOptimizedFiles = async (url: string) =>
|
|
fetch(getTestOptimizedPhotoUrl(url)).then(res => res.ok);
|
|
|
|
export const doAllPhotosHaveOptimizedFiles = async (photos: Photo[]) =>
|
|
Promise.all(photos.map(async ({ url }) => fetch(
|
|
await getSignedUrlForUrl(getTestOptimizedPhotoUrl(url), 'GET'),
|
|
)))
|
|
.then(urls => urls.every(url => url.ok))
|
|
.catch(() => false);
|
|
|
|
export const getStorageUrlsForPhoto = async ({ url }: Photo) => {
|
|
const getSortScoreForUrl = (url: string) => {
|
|
const { fileNameBase } = getFileNamePartsFromStorageUrl(url);
|
|
if (fileNameBase.endsWith('-sm')) { return 1; }
|
|
if (fileNameBase.endsWith('-md')) { return 2; }
|
|
if (fileNameBase.endsWith('-lg')) { return 3; }
|
|
return 0;
|
|
};
|
|
|
|
const { fileNameBase } = getFileNamePartsFromStorageUrl(url);
|
|
|
|
return getStorageUrlsForPrefix(fileNameBase).then(urls =>
|
|
urls.sort((a, b) => getSortScoreForUrl(a.url) - getSortScoreForUrl(b.url)),
|
|
);
|
|
};
|