189 lines
5.3 KiB
TypeScript
189 lines
5.3 KiB
TypeScript
import { absolutePathForRecipe, absolutePathForRecipeImage } from '@/app/paths';
|
||
import { descriptionForPhotoSet, Photo, photoQuantityText } from '@/photo';
|
||
import { PhotoDateRange } from '@/photo';
|
||
import {
|
||
capitalizeWords,
|
||
formatCount,
|
||
formatCountDescriptive,
|
||
} from '@/utility/string';
|
||
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
||
import { labelForFilm } from '@/film';
|
||
|
||
export type RecipeWithCount = {
|
||
recipe: string
|
||
count: number
|
||
}
|
||
|
||
export type Recipes = RecipeWithCount[]
|
||
|
||
export interface RecipeProps {
|
||
title?: string
|
||
data: FujifilmRecipe
|
||
film: string
|
||
iso?: string
|
||
exposure?: string
|
||
}
|
||
|
||
export const formatRecipe = (recipe?: string) =>
|
||
capitalizeWords(recipe?.replaceAll('-', ' '));
|
||
|
||
export const titleForRecipe = (
|
||
recipe: string,
|
||
photos:Photo[] = [],
|
||
explicitCount?: number,
|
||
) => [
|
||
`Recipe: ${formatRecipe(recipe)}`,
|
||
photoQuantityText(explicitCount ?? photos.length),
|
||
].join(' ');
|
||
|
||
export const shareTextForRecipe = (recipe: string) =>
|
||
`${formatRecipe(recipe)} recipe photos`;
|
||
|
||
export const descriptionForRecipePhotos = (
|
||
photos: Photo[] = [],
|
||
dateBased?: boolean,
|
||
explicitCount?: number,
|
||
explicitDateRange?: PhotoDateRange,
|
||
) =>
|
||
descriptionForPhotoSet(
|
||
photos,
|
||
undefined,
|
||
dateBased,
|
||
explicitCount,
|
||
explicitDateRange,
|
||
);
|
||
|
||
export const generateRecipeText = ({
|
||
title,
|
||
data,
|
||
film,
|
||
}: RecipeProps,
|
||
abbreviate?: boolean,
|
||
) => {
|
||
const lines = [
|
||
`${labelForFilm(film).small.toLocaleUpperCase()}`,
|
||
// eslint-disable-next-line max-len
|
||
`${formatWhiteBalance(data).toLocaleUpperCase()} ${formatWhiteBalanceColor(data)}`,
|
||
];
|
||
|
||
if (abbreviate) {
|
||
// eslint-disable-next-line max-len
|
||
lines.push(`DR${data.dynamicRange.development} NR${formatNoiseReduction(data)}`);
|
||
} else {
|
||
lines.push(
|
||
`DYNAMIC RANGE ${data.dynamicRange.development}`,
|
||
`NOISE REDUCTION ${formatNoiseReduction(data)}`,
|
||
);
|
||
}
|
||
|
||
if (data.highlight || data.shadow) {
|
||
lines.push(abbreviate
|
||
? `HIGH${addSign(data.highlight)} SHAD${addSign(data.shadow)}`
|
||
: `HIGHLIGHT ${addSign(data.highlight)} SHADOW ${addSign(data.shadow)}`,
|
||
);
|
||
}
|
||
lines.push(abbreviate
|
||
// eslint-disable-next-line max-len
|
||
? `COL${addSign(data.color)} SHARP${addSign(data.sharpness)} CLAR${addSign(data.clarity)}`
|
||
// eslint-disable-next-line max-len
|
||
: `COLOR ${addSign(data.color)} SHARPEN ${addSign(data.sharpness)} CLARITY ${addSign(data.clarity)}`,
|
||
);
|
||
if (data.colorChromeEffect) {
|
||
lines.push(abbreviate
|
||
? `CHROME ${data.colorChromeEffect.toLocaleUpperCase()}`
|
||
: `COLOR CHROME ${data.colorChromeEffect.toLocaleUpperCase()}`,
|
||
);
|
||
}
|
||
if (data.colorChromeFXBlue) {
|
||
lines.push(abbreviate
|
||
? `FX BLUE ${data.colorChromeFXBlue.toLocaleUpperCase()}`
|
||
: `CHROME FX BLUE ${data.colorChromeFXBlue.toLocaleUpperCase()}`,
|
||
);
|
||
}
|
||
if (data.grainEffect.roughness !== 'off') {
|
||
lines.push(`GRAIN ${formatGrain(data, abbreviate)}`);
|
||
}
|
||
if (data.bwAdjustment || data.bwMagentaGreen) {
|
||
lines.push(abbreviate
|
||
? `BW ADJ${addSign(data.bwAdjustment)} M/G${addSign(data.bwMagentaGreen)}`
|
||
// eslint-disable-next-line max-len
|
||
: `BW ADJUSTMENT ${addSign(data.bwAdjustment)} MAGENTA/GREEN ${addSign(data.bwMagentaGreen)}`,
|
||
);
|
||
}
|
||
|
||
return title
|
||
? [formatRecipe(title).toLocaleUpperCase(),'–', ...lines]
|
||
: lines;
|
||
};
|
||
|
||
export const generateMetaForRecipe = (
|
||
recipe: string,
|
||
photos: Photo[],
|
||
explicitCount?: number,
|
||
explicitDateRange?: PhotoDateRange,
|
||
) => ({
|
||
url: absolutePathForRecipe(recipe),
|
||
title: titleForRecipe(recipe, photos, explicitCount),
|
||
description:
|
||
descriptionForRecipePhotos(photos, true, explicitCount, explicitDateRange),
|
||
images: absolutePathForRecipeImage(recipe),
|
||
});
|
||
|
||
const photoHasRecipe = (photo?: Photo) =>
|
||
photo?.film && photo?.recipeData;
|
||
|
||
export const getPhotoWithRecipeFromPhotos = (
|
||
photos: Photo[],
|
||
preferredPhoto?: Photo,
|
||
) =>
|
||
photoHasRecipe(preferredPhoto)
|
||
? preferredPhoto
|
||
: photos.find(photoHasRecipe);
|
||
|
||
export const sortRecipes = (recipes: Recipes = []) =>
|
||
recipes.sort((a, b) => a.recipe.localeCompare(b.recipe));
|
||
|
||
export const convertRecipesForForm = (recipes: Recipes = []) =>
|
||
sortRecipes(recipes)
|
||
.map(({ recipe, count }) => ({
|
||
value: recipe,
|
||
annotation: formatCount(count),
|
||
annotationAria: formatCountDescriptive(count),
|
||
}));
|
||
|
||
export const addSign = (value = 0) => value < 0 ? value : `+${value}`;
|
||
|
||
export const formatWhiteBalance = ({ whiteBalance }: FujifilmRecipe) =>
|
||
whiteBalance.type === 'kelvin' && whiteBalance.colorTemperature
|
||
? `${whiteBalance.colorTemperature}K`
|
||
: whiteBalance.type
|
||
.replace(/auto./i, '')
|
||
.replaceAll('-', ' ');
|
||
|
||
export const formatWhiteBalanceColor = ({
|
||
whiteBalance: { red, blue },
|
||
}: FujifilmRecipe) =>
|
||
(red || blue)
|
||
? `R${addSign(red)}/B${addSign(blue)}`
|
||
: '';
|
||
|
||
export const formatGrain = (
|
||
{ grainEffect }: FujifilmRecipe,
|
||
abbreviate?: boolean,
|
||
) => {
|
||
const size = abbreviate
|
||
? grainEffect.size === 'small' ? 'SM' : 'LG'
|
||
: grainEffect.size;
|
||
return grainEffect.roughness === 'off'
|
||
? 'OFF'
|
||
: `${grainEffect.roughness}/${size}`.toLocaleUpperCase();
|
||
};
|
||
|
||
export const formatNoiseReduction = ({
|
||
highISONoiseReduction,
|
||
noiseReductionBasic,
|
||
}: FujifilmRecipe) =>
|
||
highISONoiseReduction
|
||
? addSign(highISONoiseReduction)
|
||
: noiseReductionBasic?.toLocaleUpperCase() ?? 'OFF';
|