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 { FilmSimulation } from '@/simulation'; import { labelForFilmSimulation } from '@/platforms/fujifilm/simulation'; export type RecipeWithCount = { recipe: string count: number } export type Recipes = RecipeWithCount[] export interface RecipeProps { title?: string recipe: FujifilmRecipe simulation: FilmSimulation 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, recipe, simulation, }: RecipeProps, abbreviate?: boolean, ) => { const lines = [ `${labelForFilmSimulation(simulation).small.toLocaleUpperCase()}`, // eslint-disable-next-line max-len `${formatWhiteBalance(recipe).toLocaleUpperCase()} ${formatWhiteBalanceColor(recipe)}`, ]; if (abbreviate) { // eslint-disable-next-line max-len lines.push(`DR${recipe.dynamicRange.development} NR${formatNoiseReduction(recipe)}`); } else { lines.push( `DYNAMIC RANGE ${recipe.dynamicRange.development}`, `NOISE REDUCTION ${formatNoiseReduction(recipe)}`, ); } if (recipe.highlight || recipe.shadow) { lines.push(abbreviate ? `HIGH${addSign(recipe.highlight)} SHAD${addSign(recipe.shadow)}` // eslint-disable-next-line max-len : `HIGHLIGHT ${addSign(recipe.highlight)} SHADOW ${addSign(recipe.shadow)}`, ); } lines.push(abbreviate // eslint-disable-next-line max-len ? `COL${addSign(recipe.color)} SHARP${addSign(recipe.sharpness)} CLAR${addSign(recipe.clarity)}` // eslint-disable-next-line max-len : `COLOR ${addSign(recipe.color)} SHARPEN ${addSign(recipe.sharpness)} CLARITY ${addSign(recipe.clarity)}`, ); if (recipe.colorChromeEffect) { lines.push(abbreviate ? `CHROME ${recipe.colorChromeEffect.toLocaleUpperCase()}` : `COLOR CHROME ${recipe.colorChromeEffect.toLocaleUpperCase()}`, ); } if (recipe.colorChromeFXBlue) { lines.push(abbreviate ? `FX BLUE ${recipe.colorChromeFXBlue.toLocaleUpperCase()}` : `CHROME FX BLUE ${recipe.colorChromeFXBlue.toLocaleUpperCase()}`, ); } if (recipe.grainEffect.roughness !== 'off') { lines.push(`GRAIN ${formatGrain(recipe, abbreviate)}`); } if (recipe.bwAdjustment || recipe.bwMagentaGreen) { lines.push(abbreviate // eslint-disable-next-line max-len ? `BW ADJ${addSign(recipe.bwAdjustment)} M/G${addSign(recipe.bwMagentaGreen)}` // eslint-disable-next-line max-len : `BW ADJUSTMENT ${addSign(recipe.bwAdjustment)} MAGENTA/GREEN ${addSign(recipe.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?.filmSimulation && 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';