Standardize film types/arguments

This commit is contained in:
Sam Becker 2025-03-30 00:51:14 -05:00
parent 4441ab2b72
commit 0dfc12d06e
54 changed files with 350 additions and 470 deletions

View File

@ -1,11 +1,10 @@
/* eslint-disable max-len */
import { import {
getEscapePath, getEscapePath,
getPathComponents, getPathComponents,
isPathCamera, isPathCamera,
isPathCameraPhoto, isPathCameraPhoto,
isPathFilmSimulation, isPathFilm,
isPathFilmSimulationPhoto, isPathFilmPhoto,
isPathFocalLength, isPathFocalLength,
isPathFocalLengthPhoto, isPathFocalLengthPhoto,
isPathPhoto, isPathPhoto,
@ -20,34 +19,34 @@ const TAG = 'tag-name';
const CAMERA_MAKE = 'fujifilm'; const CAMERA_MAKE = 'fujifilm';
const CAMERA_MODEL = 'x-t1'; const CAMERA_MODEL = 'x-t1';
const CAMERA_OBJECT = { make: CAMERA_MAKE, model: CAMERA_MODEL }; const CAMERA_OBJECT = { make: CAMERA_MAKE, model: CAMERA_MODEL };
const FILM_SIMULATION = 'acros'; const FILM = 'acros';
const FOCAL_LENGTH = 90; const FOCAL_LENGTH = 90;
const FOCAL_LENGTH_STRING = `${FOCAL_LENGTH}mm`; const FOCAL_LENGTH_STRING = `${FOCAL_LENGTH}mm`;
const PATH_ROOT = '/'; const PATH_ROOT = '/';
const PATH_GRID = '/grid'; const PATH_GRID = '/grid';
const PATH_FEED = '/feed'; const PATH_FEED = '/feed';
const PATH_ADMIN = '/admin/photos'; const PATH_ADMIN = '/admin/photos';
const PATH_OG = '/og'; const PATH_OG = '/og';
const PATH_OG_ALL = `${PATH_OG}/all`; const PATH_OG_ALL = `${PATH_OG}/all`;
const PATH_OG_SAMPLE = `${PATH_OG}/sample`; const PATH_OG_SAMPLE = `${PATH_OG}/sample`;
const PATH_PHOTO = `/p/${PHOTO_ID}`; const PATH_PHOTO = `/p/${PHOTO_ID}`;
const PATH_TAG = `/tag/${TAG}`; const PATH_TAG = `/tag/${TAG}`;
const PATH_TAG_PHOTO = `${PATH_TAG}/${PHOTO_ID}`; const PATH_TAG_PHOTO = `${PATH_TAG}/${PHOTO_ID}`;
const PATH_TAG_HIDDEN = `/tag/${TAG_HIDDEN}`; const PATH_TAG_HIDDEN = `/tag/${TAG_HIDDEN}`;
const PATH_TAG_HIDDEN_PHOTO = `${PATH_TAG_HIDDEN}/${PHOTO_ID}`; const PATH_TAG_HIDDEN_PHOTO = `${PATH_TAG_HIDDEN}/${PHOTO_ID}`;
const PATH_CAMERA = `/shot-on/${CAMERA_MAKE}/${CAMERA_MODEL}`; const PATH_CAMERA = `/shot-on/${CAMERA_MAKE}/${CAMERA_MODEL}`;
const PATH_CAMERA_PHOTO = `${PATH_CAMERA}/${PHOTO_ID}`; const PATH_CAMERA_PHOTO = `${PATH_CAMERA}/${PHOTO_ID}`;
const PATH_FILM_SIMULATION = `/film/${FILM_SIMULATION}`; const PATH_FILM = `/film/${FILM}`;
const PATH_FILM_SIMULATION_PHOTO = `${PATH_FILM_SIMULATION}/${PHOTO_ID}`; const PATH_FILM_PHOTO = `${PATH_FILM}/${PHOTO_ID}`;
const PATH_FOCAL_LENGTH = `/focal/${FOCAL_LENGTH_STRING}`; const PATH_FOCAL_LENGTH = `/focal/${FOCAL_LENGTH_STRING}`;
const PATH_FOCAL_LENGTH_PHOTO = `${PATH_FOCAL_LENGTH}/${PHOTO_ID}`; const PATH_FOCAL_LENGTH_PHOTO = `${PATH_FOCAL_LENGTH}/${PHOTO_ID}`;
describe('Paths', () => { describe('Paths', () => {
it('can be protected', () => { it('can be protected', () => {
@ -57,7 +56,7 @@ describe('Paths', () => {
expect(isPathProtected(PATH_TAG)).toBe(false); expect(isPathProtected(PATH_TAG)).toBe(false);
expect(isPathProtected(PATH_TAG_PHOTO)).toBe(false); expect(isPathProtected(PATH_TAG_PHOTO)).toBe(false);
expect(isPathProtected(PATH_CAMERA)).toBe(false); expect(isPathProtected(PATH_CAMERA)).toBe(false);
expect(isPathProtected(PATH_FILM_SIMULATION)).toBe(false); expect(isPathProtected(PATH_FILM)).toBe(false);
// Private // Private
expect(isPathProtected(PATH_ADMIN)).toBe(true); expect(isPathProtected(PATH_ADMIN)).toBe(true);
expect(isPathProtected(PATH_OG)).toBe(true); expect(isPathProtected(PATH_OG)).toBe(true);
@ -73,13 +72,13 @@ describe('Paths', () => {
expect(isPathTagPhoto(PATH_TAG_PHOTO)).toBe(true); expect(isPathTagPhoto(PATH_TAG_PHOTO)).toBe(true);
expect(isPathCamera(PATH_CAMERA)).toBe(true); expect(isPathCamera(PATH_CAMERA)).toBe(true);
expect(isPathCameraPhoto(PATH_CAMERA_PHOTO)).toBe(true); expect(isPathCameraPhoto(PATH_CAMERA_PHOTO)).toBe(true);
expect(isPathFilmSimulation(PATH_FILM_SIMULATION)).toBe(true); expect(isPathFilm(PATH_FILM)).toBe(true);
expect(isPathFilmSimulationPhoto(PATH_FILM_SIMULATION_PHOTO)).toBe(true); expect(isPathFilmPhoto(PATH_FILM_PHOTO)).toBe(true);
expect(isPathFocalLength(PATH_FOCAL_LENGTH)).toBe(true); expect(isPathFocalLength(PATH_FOCAL_LENGTH)).toBe(true);
expect(isPathFocalLengthPhoto(PATH_FOCAL_LENGTH_PHOTO)).toBe(true); expect(isPathFocalLengthPhoto(PATH_FOCAL_LENGTH_PHOTO)).toBe(true);
// Negative // Negative
expect(isPathFocalLength(PATH_FILM_SIMULATION)).toBe(false); expect(isPathFocalLength(PATH_FILM)).toBe(false);
expect(isPathFocalLengthPhoto(PATH_FILM_SIMULATION_PHOTO)).toBe(false); expect(isPathFocalLengthPhoto(PATH_FILM_PHOTO)).toBe(false);
}); });
it('can be parsed', () => { it('can be parsed', () => {
// Core // Core
@ -103,13 +102,13 @@ describe('Paths', () => {
photoId: PHOTO_ID, photoId: PHOTO_ID,
camera: CAMERA_OBJECT, camera: CAMERA_OBJECT,
}); });
// Film Simulation // Film
expect(getPathComponents(PATH_FILM_SIMULATION)).toEqual({ expect(getPathComponents(PATH_FILM)).toEqual({
simulation: FILM_SIMULATION, film: FILM,
}); });
expect(getPathComponents(PATH_FILM_SIMULATION_PHOTO)).toEqual({ expect(getPathComponents(PATH_FILM_PHOTO)).toEqual({
photoId: PHOTO_ID, photoId: PHOTO_ID,
simulation: FILM_SIMULATION, film: FILM,
}); });
// Focal Length // Focal Length
expect(getPathComponents(PATH_FOCAL_LENGTH)).toEqual({ expect(getPathComponents(PATH_FOCAL_LENGTH)).toEqual({
@ -134,9 +133,9 @@ describe('Paths', () => {
// Camera // Camera
expect(getEscapePath(PATH_CAMERA)).toEqual(PATH_ROOT); expect(getEscapePath(PATH_CAMERA)).toEqual(PATH_ROOT);
expect(getEscapePath(PATH_CAMERA_PHOTO)).toEqual(PATH_CAMERA); expect(getEscapePath(PATH_CAMERA_PHOTO)).toEqual(PATH_CAMERA);
// Film Simulation // Film
expect(getEscapePath(PATH_FILM_SIMULATION)).toEqual(PATH_ROOT); expect(getEscapePath(PATH_FILM)).toEqual(PATH_ROOT);
expect(getEscapePath(PATH_FILM_SIMULATION_PHOTO)).toEqual(PATH_FILM_SIMULATION); expect(getEscapePath(PATH_FILM_PHOTO)).toEqual(PATH_FILM);
// Focal Length // Focal Length
expect(getEscapePath(PATH_FOCAL_LENGTH)).toEqual(PATH_ROOT); expect(getEscapePath(PATH_FOCAL_LENGTH)).toEqual(PATH_ROOT);
expect(getEscapePath(PATH_FOCAL_LENGTH_PHOTO)).toEqual(PATH_FOCAL_LENGTH); expect(getEscapePath(PATH_FOCAL_LENGTH_PHOTO)).toEqual(PATH_FOCAL_LENGTH);

View File

@ -129,7 +129,7 @@ export default function ComponentsPage() {
</div> </div>
<div> <div>
<EntityLink <EntityLink
icon={<PhotoFilmIcon simulation="astia" />} icon={<PhotoFilmIcon film="astia" />}
label="Astia/Soft" label="Astia/Soft"
type="icon-last" type="icon-last"
iconWide iconWide
@ -149,7 +149,7 @@ export default function ComponentsPage() {
</div> </div>
<div> <div>
<EntityLink <EntityLink
icon={<PhotoFilmIcon simulation="astia" />} icon={<PhotoFilmIcon film="astia" />}
label="Astia/Soft" label="Astia/Soft"
type="icon-last" type="icon-last"
iconWide iconWide
@ -184,7 +184,7 @@ export default function ComponentsPage() {
</div> </div>
<div> <div>
<EntityLink <EntityLink
icon={<PhotoFilmIcon simulation="astia" />} icon={<PhotoFilmIcon film="astia" />}
label="Astia/Soft" label="Astia/Soft"
type="icon-last" type="icon-last"
iconWide iconWide

View File

@ -32,7 +32,7 @@ export default async function RecipePageEdit({
const { const {
recipeData, recipeData,
filmSimulation, film,
} = getPhotoWithRecipeFromPhotos(photos) ?? {}; } = getPhotoWithRecipeFromPhotos(photos) ?? {};
if (count === 0) { redirect(PATH_ADMIN); } if (count === 0) { redirect(PATH_ADMIN); }
@ -42,11 +42,11 @@ export default async function RecipePageEdit({
backPath={PATH_ADMIN_RECIPES} backPath={PATH_ADMIN_RECIPES}
backLabel="Recipes" backLabel="Recipes"
breadcrumb={<AdminRecipeBadge {...{ recipe, count, hideBadge: true }} />} breadcrumb={<AdminRecipeBadge {...{ recipe, count, hideBadge: true }} />}
accessory={recipeData && filmSimulation && accessory={recipeData && film &&
<AdminShowRecipeButton <AdminShowRecipeButton
title={recipe} title={recipe}
recipe={recipeData} recipe={recipeData}
simulation={filmSimulation} film={film}
/> />
} }
> >

View File

@ -49,10 +49,10 @@ export default async function UploadPage({ params }: Params) {
] = await Promise.all([ ] = await Promise.all([
getUniqueTagsCached(), getUniqueTagsCached(),
getUniqueRecipesCached(), getUniqueRecipesCached(),
formDataFromExif?.recipeData && formDataFromExif.filmSimulation formDataFromExif?.recipeData && formDataFromExif.film
? getRecipeTitleForData( ? getRecipeTitleForData(
formDataFromExif.recipeData, formDataFromExif.recipeData,
formDataFromExif.filmSimulation as FilmSimulation, formDataFromExif.film as FilmSimulation,
) )
: undefined, : undefined,
]); ]);

View File

@ -28,7 +28,7 @@ export default function FilmPage() {
Film Simulation: Film Simulation:
</div> </div>
<PhotoFilm <PhotoFilm
simulation={FILM_SIMULATION_FORM_INPUT_OPTIONS[index].value} film={FILM_SIMULATION_FORM_INPUT_OPTIONS[index].value}
type="icon-first" type="icon-first"
/> />
<div className="mt-4 text-dim relative"> <div className="mt-4 text-dim relative">

View File

@ -9,7 +9,7 @@ export default function FilmPage() {
{FILM_SIMULATION_FORM_INPUT_OPTIONS.map(({ value }) => {FILM_SIMULATION_FORM_INPUT_OPTIONS.map(({ value }) =>
<div key={value}> <div key={value}>
<PhotoFilm <PhotoFilm
simulation={value} film={value}
type="icon-first" type="icon-first"
/> />
</div>)} </div>)}

View File

@ -20,20 +20,20 @@ import { cache } from 'react';
const getPhotosNearIdCachedCached = cache(( const getPhotosNearIdCachedCached = cache((
photoId: string, photoId: string,
simulation: FilmSimulation, film: FilmSimulation,
) => ) =>
getPhotosNearIdCached( getPhotosNearIdCached(
photoId, photoId,
{ film: simulation, limit: RELATED_GRID_PHOTOS_TO_SHOW + 2 }, { film, limit: RELATED_GRID_PHOTOS_TO_SHOW + 2 },
)); ));
interface PhotoFilmSimulationProps { interface PhotoFilmProps {
params: Promise<{ photoId: string, film: FilmSimulation }> params: Promise<{ photoId: string, film: FilmSimulation }>
} }
export async function generateMetadata({ export async function generateMetadata({
params, params,
}: PhotoFilmSimulationProps): Promise<Metadata> { }: PhotoFilmProps): Promise<Metadata> {
const { photoId, film } = await params; const { photoId, film } = await params;
const { photo } = await getPhotosNearIdCachedCached(photoId, film); const { photo } = await getPhotosNearIdCachedCached(photoId, film);
@ -65,7 +65,7 @@ export async function generateMetadata({
export default async function PhotoFilmPage({ export default async function PhotoFilmPage({
params, params,
}: PhotoFilmSimulationProps) { }: PhotoFilmProps) {
const { photoId, film } = await params; const { photoId, film } = await params;
const { photo, photos, photosGrid, indexNumber } = const { photo, photos, photosGrid, indexNumber } =

View File

@ -9,14 +9,14 @@ import { FilmSimulation } from '@/film';
import { getIBMPlexMono } from '@/app/font'; import { getIBMPlexMono } from '@/app/font';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
import { getUniqueFilmSimulations } from '@/photo/db/query'; import { getUniqueFilms } from '@/photo/db/query';
import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static';
export const generateStaticParams = staticallyGenerateCategoryIfConfigured( export const generateStaticParams = staticallyGenerateCategoryIfConfigured(
'films', 'films',
'image', 'image',
getUniqueFilmSimulations, getUniqueFilms,
simulations => simulations.map(({ film: simulation }) => ({ simulation })), films => films.map(({ film }) => ({ film })),
); );
export async function GET( export async function GET(
@ -42,7 +42,7 @@ export async function GET(
return new ImageResponse( return new ImageResponse(
<FilmImageResponse {...{ <FilmImageResponse {...{
simulation: film, film,
photos, photos,
width, width,
height, height,

View File

@ -1,38 +1,38 @@
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
import { getUniqueFilmSimulations } from '@/photo/db/query'; import { getUniqueFilms } from '@/photo/db/query';
import { FilmSimulation, generateMetaForFilmSimulation } from '@/film'; import { FilmSimulation, generateMetaForFilm } from '@/film';
import FilmOverview from '@/film/FilmOverview'; import FilmOverview from '@/film/FilmOverview';
import { getPhotosFilmSimulationDataCached } from '@/film/data'; import { getPhotosFilmDataCached } from '@/film/data';
import { Metadata } from 'next/types'; import { Metadata } from 'next/types';
import { cache } from 'react'; import { cache } from 'react';
import { PATH_ROOT } from '@/app/paths'; import { PATH_ROOT } from '@/app/paths';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static';
const getPhotosFilmSimulationDataCachedCached = const getPhotosFilmDataCachedCached =
cache(getPhotosFilmSimulationDataCached); cache(getPhotosFilmDataCached);
export const generateStaticParams = staticallyGenerateCategoryIfConfigured( export const generateStaticParams = staticallyGenerateCategoryIfConfigured(
'films', 'films',
'page', 'page',
getUniqueFilmSimulations, getUniqueFilms,
films => films.map(({ film }) => ({ film })), films => films.map(({ film }) => ({ film })),
); );
interface FilmSimulationProps { interface FilmProps {
params: Promise<{ film: FilmSimulation }> params: Promise<{ film: FilmSimulation }>
} }
export async function generateMetadata({ export async function generateMetadata({
params, params,
}: FilmSimulationProps): Promise<Metadata> { }: FilmProps): Promise<Metadata> {
const { film } = await params; const { film } = await params;
const [ const [
photos, photos,
{ count, dateRange }, { count, dateRange },
] = await getPhotosFilmSimulationDataCachedCached({ ] = await getPhotosFilmDataCachedCached({
simulation: film, film,
limit: INFINITE_SCROLL_GRID_INITIAL, limit: INFINITE_SCROLL_GRID_INITIAL,
}); });
@ -43,7 +43,7 @@ export async function generateMetadata({
title, title,
description, description,
images, images,
} = generateMetaForFilmSimulation(film, photos, count, dateRange); } = generateMetaForFilm(film, photos, count, dateRange);
return { return {
title, title,
@ -64,14 +64,14 @@ export async function generateMetadata({
export default async function FilmPage({ export default async function FilmPage({
params, params,
}: FilmSimulationProps) { }: FilmProps) {
const { film } = await params; const { film } = await params;
const [ const [
photos, photos,
{ count, dateRange }, { count, dateRange },
] = await getPhotosFilmSimulationDataCachedCached({ ] = await getPhotosFilmDataCachedCached({
simulation: film, film,
limit: INFINITE_SCROLL_GRID_INITIAL, limit: INFINITE_SCROLL_GRID_INITIAL,
}); });
@ -79,7 +79,7 @@ export default async function FilmPage({
return ( return (
<FilmOverview {...{ <FilmOverview {...{
simulation: film, film,
photos, photos,
count, count,
dateRange, dateRange,

View File

@ -29,7 +29,7 @@ export default async function GridPage() {
lenses, lenses,
tags, tags,
recipes, recipes,
simulations, films,
focalLengths, focalLengths,
] = await Promise.all([ ] = await Promise.all([
getPhotosCached() getPhotosCached()
@ -49,7 +49,7 @@ export default async function GridPage() {
cameras, cameras,
lenses, lenses,
tags, tags,
films: simulations, films,
recipes, recipes,
focalLengths, focalLengths,
}} }}

View File

@ -9,7 +9,7 @@ import TagOGTile from '@/tag/TagOGTile';
const tag = 'cicadas'; const tag = 'cicadas';
const camera = { make: 'Fujifilm', model: 'X-T5' }; const camera = { make: 'Fujifilm', model: 'X-T5' };
const cameraIcon = { make: 'Apple', model: 'iPhone 13 Pro' }; const cameraIcon = { make: 'Apple', model: 'iPhone 13 Pro' };
const simulation = 'acros'; const film = 'acros';
const focal = 90; const focal = 90;
export default async function OGOverviewPage() { export default async function OGOverviewPage() {
@ -19,7 +19,7 @@ export default async function OGOverviewPage() {
photosTag, photosTag,
photosFavs, photosFavs,
photosCamera, photosCamera,
photosSimulation, photosFilm,
photosFocal, photosFocal,
] = await Promise.all([ ] = await Promise.all([
getPhotosCached({ limit: 1 }).then(photos => photos[0]) getPhotosCached({ limit: 1 }).then(photos => photos[0])
@ -32,7 +32,7 @@ export default async function OGOverviewPage() {
.catch(() => []), .catch(() => []),
getPhotosCached({ limit: 1, camera }) getPhotosCached({ limit: 1, camera })
.catch(() => []), .catch(() => []),
getPhotosCached({ limit: 1, film: simulation }) getPhotosCached({ limit: 1, film })
.catch(() => []), .catch(() => []),
getPhotosCached({ limit: 1, focal }) getPhotosCached({ limit: 1, focal })
.catch(() => []), .catch(() => []),
@ -45,7 +45,7 @@ export default async function OGOverviewPage() {
<TagOGTile tag={tag} photos={photosTag} /> <TagOGTile tag={tag} photos={photosTag} />
<TagOGTile tag={TAG_FAVS} photos={photosFavs} /> <TagOGTile tag={TAG_FAVS} photos={photosFavs} />
<CameraOGTile camera={camera} photos={photosCamera} /> <CameraOGTile camera={camera} photos={photosCamera} />
<FilmOGTile simulation={simulation} photos={photosSimulation} /> <FilmOGTile film={film} photos={photosFilm} />
<FocalLengthOGTile focal={focal} photos={photosFocal} /> <FocalLengthOGTile focal={focal} photos={photosFocal} />
</div> </div>
); );

View File

@ -35,7 +35,7 @@ export default async function HomePage() {
lenses, lenses,
tags, tags,
recipes, recipes,
simulations, films,
focalLengths, focalLengths,
] = await Promise.all([ ] = await Promise.all([
getPhotosCached() getPhotosCached()
@ -59,7 +59,7 @@ export default async function HomePage() {
lenses, lenses,
tags, tags,
recipes, recipes,
films: simulations, films,
focalLengths, focalLengths,
}} }}
/> />

View File

@ -9,11 +9,11 @@ import { TbChecklist } from 'react-icons/tb';
export default function AdminShowRecipeButton({ export default function AdminShowRecipeButton({
title, title,
recipe, recipe,
simulation, film,
}: { }: {
title: string title: string
recipe: FujifilmRecipe recipe: FujifilmRecipe
simulation: FujifilmSimulation film: FujifilmSimulation
}) { }) {
const { setRecipeModalProps } = useAppState(); const { setRecipeModalProps } = useAppState();
@ -26,7 +26,7 @@ export default function AdminShowRecipeButton({
onClick={() => setRecipeModalProps?.({ onClick={() => setRecipeModalProps?.({
title, title,
recipe, recipe,
simulation, film,
})} })}
> >
Preview Preview

View File

@ -1,7 +1,7 @@
import { import {
getPhotosMeta, getPhotosMeta,
getUniqueCameras, getUniqueCameras,
getUniqueFilmSimulations, getUniqueFilms,
getUniqueFocalLengths, getUniqueFocalLengths,
getUniqueLenses, getUniqueLenses,
getUniqueRecipes, getUniqueRecipes,
@ -34,7 +34,7 @@ export default async function AdminAppInsights() {
lenses, lenses,
tags, tags,
recipes, recipes,
filmSimulations, films,
focalLengths, focalLengths,
] = await Promise.all([ ] = await Promise.all([
getPhotosMeta({ hidden: 'include' }), getPhotosMeta({ hidden: 'include' }),
@ -46,7 +46,7 @@ export default async function AdminAppInsights() {
getUniqueLenses(), getUniqueLenses(),
getUniqueTags(), getUniqueTags(),
getUniqueRecipes(), getUniqueRecipes(),
getUniqueFilmSimulations(), getUniqueFilms(),
getUniqueFocalLengths(), getUniqueFocalLengths(),
]); ]);
@ -94,7 +94,7 @@ export default async function AdminAppInsights() {
lensesCount: lenses.length, lensesCount: lenses.length,
tagsCount: tags.length, tagsCount: tags.length,
recipesCount: recipes.length, recipesCount: recipes.length,
filmSimulationsCount: filmSimulations.length, filmsCount: films.length,
focalLengthsCount: focalLengths.length, focalLengthsCount: focalLengths.length,
dateRange, dateRange,
}} }}

View File

@ -96,7 +96,7 @@ export default function AdminAppInsightsClient({
lensesCount, lensesCount,
tagsCount, tagsCount,
recipesCount, recipesCount,
filmSimulationsCount, filmsCount,
focalLengthsCount, focalLengthsCount,
dateRange, dateRange,
}, },
@ -487,11 +487,11 @@ export default function AdminAppInsightsClient({
/> />
: null; : null;
case 'films': case 'films':
return filmSimulationsCount > 0 return filmsCount > 0
? <ScoreCardRow ? <ScoreCardRow
key={category} key={category}
icon={<IconFilm size={15} />} icon={<IconFilm size={15} />}
content={pluralize(filmSimulationsCount, 'film simulation')} content={pluralize(filmsCount, 'film')}
/> />
: null; : null;
case 'focal-lengths': case 'focal-lengths':

View File

@ -53,7 +53,7 @@ export interface PhotoStats {
lensesCount: number lensesCount: number
tagsCount: number tagsCount: number
recipesCount: number recipesCount: number
filmSimulationsCount: number filmsCount: number
focalLengthsCount: number focalLengthsCount: number
dateRange?: PhotoDateRange dateRange?: PhotoDateRange
} }

View File

@ -249,7 +249,7 @@ export const SHOW_TAGS =
CATEGORY_VISIBILITY.includes('tags'); CATEGORY_VISIBILITY.includes('tags');
export const SHOW_RECIPES = export const SHOW_RECIPES =
CATEGORY_VISIBILITY.includes('recipes'); CATEGORY_VISIBILITY.includes('recipes');
export const SHOW_FILM_SIMULATIONS = export const SHOW_FILMS =
CATEGORY_VISIBILITY.includes('films'); CATEGORY_VISIBILITY.includes('films');
export const SHOW_FOCAL_LENGTHS = export const SHOW_FOCAL_LENGTHS =
CATEGORY_VISIBILITY.includes('focal-lengths'); CATEGORY_VISIBILITY.includes('focal-lengths');

View File

@ -26,7 +26,7 @@ export const PREFIX_CAMERA = '/shot-on';
export const PREFIX_LENS = '/lens'; export const PREFIX_LENS = '/lens';
export const PREFIX_TAG = '/tag'; export const PREFIX_TAG = '/tag';
export const PREFIX_RECIPE = '/recipe'; export const PREFIX_RECIPE = '/recipe';
export const PREFIX_FILM_SIMULATION = '/film'; export const PREFIX_FILM = '/film';
export const PREFIX_FOCAL_LENGTH = '/focal'; export const PREFIX_FOCAL_LENGTH = '/focal';
// Dynamic paths // Dynamic paths
@ -34,8 +34,7 @@ const PATH_PHOTO_DYNAMIC = `${PREFIX_PHOTO}/[photoId]`;
const PATH_CAMERA_DYNAMIC = `${PREFIX_CAMERA}/[make]/[model]`; const PATH_CAMERA_DYNAMIC = `${PREFIX_CAMERA}/[make]/[model]`;
const PATH_LENS_DYNAMIC = `${PREFIX_LENS}/[make]/[model]`; const PATH_LENS_DYNAMIC = `${PREFIX_LENS}/[make]/[model]`;
const PATH_TAG_DYNAMIC = `${PREFIX_TAG}/[tag]`; const PATH_TAG_DYNAMIC = `${PREFIX_TAG}/[tag]`;
// eslint-disable-next-line max-len const PATH_FILM_DYNAMIC = `${PREFIX_FILM}/[film]`;
const PATH_FILM_SIMULATION_DYNAMIC = `${PREFIX_FILM_SIMULATION}/[simulation]`;
const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`; const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`;
const PATH_RECIPE_DYNAMIC = `${PREFIX_RECIPE}/[recipe]`; const PATH_RECIPE_DYNAMIC = `${PREFIX_RECIPE}/[recipe]`;
@ -86,7 +85,7 @@ export const PATHS_TO_CACHE = [
PATH_CAMERA_DYNAMIC, PATH_CAMERA_DYNAMIC,
PATH_LENS_DYNAMIC, PATH_LENS_DYNAMIC,
PATH_TAG_DYNAMIC, PATH_TAG_DYNAMIC,
PATH_FILM_SIMULATION_DYNAMIC, PATH_FILM_DYNAMIC,
PATH_FOCAL_LENGTH_DYNAMIC, PATH_FOCAL_LENGTH_DYNAMIC,
PATH_RECIPE_DYNAMIC, PATH_RECIPE_DYNAMIC,
...PATHS_ADMIN, ...PATHS_ADMIN,
@ -121,7 +120,7 @@ export const pathForPhoto = ({
camera, camera,
lens, lens,
tag, tag,
film: simulation, film,
focal, focal,
recipe, recipe,
}: PhotoPathParams) => { }: PhotoPathParams) => {
@ -135,8 +134,8 @@ export const pathForPhoto = ({
prefix = pathForLens(lens); prefix = pathForLens(lens);
} else if (tag) { } else if (tag) {
prefix = pathForTag(tag); prefix = pathForTag(tag);
} else if (simulation) { } else if (film) {
prefix = pathForFilmSimulation(simulation); prefix = pathForFilm(film);
} else if (recipe) { } else if (recipe) {
prefix = pathForRecipe(recipe); prefix = pathForRecipe(recipe);
} else if (focal) { } else if (focal) {
@ -152,8 +151,8 @@ export const pathForTag = (tag: string) =>
export const pathForCamera = ({ make, model }: Camera) => export const pathForCamera = ({ make, model }: Camera) =>
`${PREFIX_CAMERA}/${parameterize(make)}/${parameterize(model)}`; `${PREFIX_CAMERA}/${parameterize(make)}/${parameterize(model)}`;
export const pathForFilmSimulation = (simulation: FilmSimulation) => export const pathForFilm = (film: FilmSimulation) =>
`${PREFIX_FILM_SIMULATION}/${simulation}`; `${PREFIX_FILM}/${film}`;
export const pathForLens = ({ make, model }: Lens) => export const pathForLens = ({ make, model }: Lens) =>
make make
@ -178,8 +177,8 @@ export const absolutePathForCamera= (camera: Camera) =>
export const absolutePathForLens= (lens: Lens) => export const absolutePathForLens= (lens: Lens) =>
`${BASE_URL}${pathForLens(lens)}`; `${BASE_URL}${pathForLens(lens)}`;
export const absolutePathForFilmSimulation = (simulation: FilmSimulation) => export const absolutePathForFilm = (film: FilmSimulation) =>
`${BASE_URL}${pathForFilmSimulation(simulation)}`; `${BASE_URL}${pathForFilm(film)}`;
export const absolutePathForRecipe = (recipe: string) => export const absolutePathForRecipe = (recipe: string) =>
`${BASE_URL}${pathForRecipe(recipe)}`; `${BASE_URL}${pathForRecipe(recipe)}`;
@ -199,9 +198,8 @@ export const absolutePathForCameraImage= (camera: Camera) =>
export const absolutePathForLensImage= (lens: Lens) => export const absolutePathForLensImage= (lens: Lens) =>
`${absolutePathForLens(lens)}/image`; `${absolutePathForLens(lens)}/image`;
export const absolutePathForFilmSimulationImage = export const absolutePathForFilmImage = (film: FilmSimulation) =>
(simulation: FilmSimulation) => `${absolutePathForFilm(film)}/image`;
`${absolutePathForFilmSimulation(simulation)}/image`;
export const absolutePathForRecipeImage = (recipe: string) => export const absolutePathForRecipeImage = (recipe: string) =>
`${absolutePathForRecipe(recipe)}/image`; `${absolutePathForRecipe(recipe)}/image`;
@ -230,13 +228,13 @@ export const isPathCamera = (pathname = '') =>
export const isPathCameraPhoto = (pathname = '') => export const isPathCameraPhoto = (pathname = '') =>
new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/[^/]+/?$`).test(pathname); new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/[^/]+/?$`).test(pathname);
// film/[simulation] // film/[film]
export const isPathFilmSimulation = (pathname = '') => export const isPathFilm = (pathname = '') =>
new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/?$`).test(pathname); new RegExp(`^${PREFIX_FILM}/[^/]+/?$`).test(pathname);
// film/[simulation]/[photoId] // film/[film]/[photoId]
export const isPathFilmSimulationPhoto = (pathname = '') => export const isPathFilmPhoto = (pathname = '') =>
new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/[^/]+/?$`).test(pathname); new RegExp(`^${PREFIX_FILM}/[^/]+/[^/]+/?$`).test(pathname);
// focal/[focal] // focal/[focal]
export const isPathFocalLength = (pathname = '') => export const isPathFocalLength = (pathname = '') =>
@ -299,8 +297,8 @@ export const getPathComponents = (pathname = ''): {
new RegExp(`^${PREFIX_TAG}/[^/]+/([^/]+)`))?.[1]; new RegExp(`^${PREFIX_TAG}/[^/]+/([^/]+)`))?.[1];
const photoIdFromCamera = pathname.match( const photoIdFromCamera = pathname.match(
new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/([^/]+)`))?.[1]; new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/([^/]+)`))?.[1];
const photoIdFromFilmSimulation = pathname.match( const photoIdFromFilm = pathname.match(
new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/([^/]+)`))?.[1]; new RegExp(`^${PREFIX_FILM}/[^/]+/([^/]+)`))?.[1];
const photoIdFromFocalLength = pathname.match( const photoIdFromFocalLength = pathname.match(
new RegExp(`^${PREFIX_FOCAL_LENGTH}/[0-9]+mm/([^/]+)`))?.[1]; new RegExp(`^${PREFIX_FOCAL_LENGTH}/[0-9]+mm/([^/]+)`))?.[1];
const tag = pathname.match( const tag = pathname.match(
@ -309,8 +307,8 @@ export const getPathComponents = (pathname = ''): {
new RegExp(`^${PREFIX_CAMERA}/([^/]+)`))?.[1]; new RegExp(`^${PREFIX_CAMERA}/([^/]+)`))?.[1];
const cameraModel = pathname.match( const cameraModel = pathname.match(
new RegExp(`^${PREFIX_CAMERA}/[^/]+/([^/]+)`))?.[1]; new RegExp(`^${PREFIX_CAMERA}/[^/]+/([^/]+)`))?.[1];
const simulation = pathname.match( const film = pathname.match(
new RegExp(`^${PREFIX_FILM_SIMULATION}/([^/]+)`))?.[1] as FilmSimulation; new RegExp(`^${PREFIX_FILM}/([^/]+)`))?.[1] as FilmSimulation;
const focalString = pathname.match( const focalString = pathname.match(
new RegExp(`^${PREFIX_FOCAL_LENGTH}/([0-9]+)mm`))?.[1]; new RegExp(`^${PREFIX_FOCAL_LENGTH}/([0-9]+)mm`))?.[1];
@ -325,12 +323,12 @@ export const getPathComponents = (pathname = ''): {
photoIdFromPhoto || photoIdFromPhoto ||
photoIdFromTag || photoIdFromTag ||
photoIdFromCamera || photoIdFromCamera ||
photoIdFromFilmSimulation || photoIdFromFilm ||
photoIdFromFocalLength photoIdFromFocalLength
), ),
tag, tag,
camera, camera,
film: simulation, film,
focal, focal,
}; };
}; };
@ -340,7 +338,7 @@ export const getEscapePath = (pathname?: string) => {
photoId, photoId,
tag, tag,
camera, camera,
film: simulation, film,
focal, focal,
} = getPathComponents(pathname); } = getPathComponents(pathname);
@ -348,7 +346,7 @@ export const getEscapePath = (pathname?: string) => {
(photoId && isPathPhoto(pathname)) || (photoId && isPathPhoto(pathname)) ||
(tag && isPathTag(pathname)) || (tag && isPathTag(pathname)) ||
(camera && isPathCamera(pathname)) || (camera && isPathCamera(pathname)) ||
(simulation && isPathFilmSimulation(pathname)) || (film && isPathFilm(pathname)) ||
(focal && isPathFocalLength(pathname)) (focal && isPathFocalLength(pathname))
) { ) {
return PATH_ROOT; return PATH_ROOT;
@ -356,8 +354,8 @@ export const getEscapePath = (pathname?: string) => {
return pathForTag(tag); return pathForTag(tag);
} else if (camera && isPathCameraPhoto(pathname)) { } else if (camera && isPathCameraPhoto(pathname)) {
return pathForCamera(camera); return pathForCamera(camera);
} else if (simulation && isPathFilmSimulationPhoto(pathname)) { } else if (film && isPathFilmPhoto(pathname)) {
return pathForFilmSimulation(simulation); return pathForFilm(film);
} else if (focal && isPathFocalLengthPhoto(pathname)) { } else if (focal && isPathFocalLengthPhoto(pathname)) {
return pathForFocalLength(focal); return pathForFocalLength(focal);
} }

View File

@ -1,13 +1,13 @@
import { import {
getUniqueCameras, getUniqueCameras,
getUniqueFilmSimulations, getUniqueFilms,
getUniqueFocalLengths, getUniqueFocalLengths,
getUniqueLenses, getUniqueLenses,
getUniqueRecipes, getUniqueRecipes,
getUniqueTags, getUniqueTags,
} from '@/photo/db/query'; } from '@/photo/db/query';
import { import {
SHOW_FILM_SIMULATIONS, SHOW_FILMS,
SHOW_FOCAL_LENGTHS, SHOW_FOCAL_LENGTHS,
SHOW_LENSES, SHOW_LENSES,
SHOW_RECIPES, SHOW_RECIPES,
@ -40,8 +40,8 @@ export const getDataForCategories = () => [
.then(sortCategoriesByCount) .then(sortCategoriesByCount)
.catch(() => []) .catch(() => [])
: [], : [],
SHOW_FILM_SIMULATIONS SHOW_FILMS
? getUniqueFilmSimulations() ? getUniqueFilms()
.then(sortCategoriesByCount) .then(sortCategoriesByCount)
.catch(() => []) .catch(() => [])
: [], : [],
@ -58,7 +58,7 @@ export const getCountsForCategories = async () => {
lenses, lenses,
tags, tags,
recipes, recipes,
filmSimulations, films,
focalLengths, focalLengths,
] = await Promise.all(getDataForCategories()); ] = await Promise.all(getDataForCategories());
@ -79,8 +79,8 @@ export const getCountsForCategories = async () => {
acc[recipe.recipe] = recipe.count; acc[recipe.recipe] = recipe.count;
return acc; return acc;
}, {} as Record<string, number>), }, {} as Record<string, number>),
filmSimulations: filmSimulations.reduce((acc, filmSimulation) => { films: films.reduce((acc, film) => {
acc[filmSimulation.film] = filmSimulation.count; acc[film.film] = film.count;
return acc; return acc;
}, {} as Record<string, number>), }, {} as Record<string, number>),
focalLengths: focalLengths.reduce((acc, focalLength) => { focalLengths: focalLengths.reduce((acc, focalLength) => {

View File

@ -1,7 +1,7 @@
import { Photo } from '../photo'; import { Photo } from '../photo';
import { Camera, Cameras } from '@/camera'; import { Camera, Cameras } from '@/camera';
import { PhotoDateRange } from '../photo'; import { PhotoDateRange } from '../photo';
import { FilmSimulation, FilmSimulations } from '@/film'; import { FilmSimulation, Films } from '@/film';
import { Lens, Lenses } from '@/lens'; import { Lens, Lenses } from '@/lens';
import { Tags } from '@/tag'; import { Tags } from '@/tag';
import { FocalLengths } from '@/focal'; import { FocalLengths } from '@/focal';
@ -48,7 +48,7 @@ export interface PhotoSetCategories {
lenses: Lenses lenses: Lenses
tags: Tags tags: Tags
recipes: Recipes recipes: Recipes
films: FilmSimulations films: Films
focalLengths: FocalLengths focalLengths: FocalLengths
} }

View File

@ -4,6 +4,7 @@ import { Camera } from '@/camera';
import { Lens } from '@/lens'; import { Lens } from '@/lens';
import { useAppState } from '@/state/AppState'; import { useAppState } from '@/state/AppState';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { FujifilmSimulation } from '@/platforms/fujifilm/simulation';
export default function useCategoryCounts() { export default function useCategoryCounts() {
const { categoriesWithCounts } = useAppState(); const { categoriesWithCounts } = useAppState();
@ -28,9 +29,9 @@ export default function useCategoryCounts() {
return recipeCounts[recipe]; return recipeCounts[recipe];
}, [categoriesWithCounts]); }, [categoriesWithCounts]);
const getFilmSimulationCount = useCallback((simulation: string) => { const getFilmCount = useCallback((film: FujifilmSimulation) => {
const filmSimulationCounts = categoriesWithCounts?.filmSimulations ?? {}; const filmCounts = categoriesWithCounts?.films ?? {};
return filmSimulationCounts[simulation]; return filmCounts[film];
}, [categoriesWithCounts]); }, [categoriesWithCounts]);
const getFocalLengthCount = useCallback((focalLength: number) => { const getFocalLengthCount = useCallback((focalLength: number) => {
@ -43,7 +44,7 @@ export default function useCategoryCounts() {
getLensCount, getLensCount,
getTagCount, getTagCount,
getRecipeCount, getRecipeCount,
getFilmSimulationCount, getFilmCount,
getFocalLengthCount, getFocalLengthCount,
}; };
} }

View File

@ -10,7 +10,7 @@ export default function useCategoryCountsForPhoto(photo: Photo) {
getLensCount, getLensCount,
getTagCount, getTagCount,
getRecipeCount, getRecipeCount,
getFilmSimulationCount, getFilmCount,
getFocalLengthCount, getFocalLengthCount,
} = useCategoryCounts(); } = useCategoryCounts();
@ -25,21 +25,20 @@ export default function useCategoryCountsForPhoto(photo: Photo) {
return acc; return acc;
}, {} as Record<string, number>), }, {} as Record<string, number>),
recipeCount: photo.recipeTitle ? getRecipeCount(photo.recipeTitle) : 0, recipeCount: photo.recipeTitle ? getRecipeCount(photo.recipeTitle) : 0,
simulationCount: filmCount: photo.film ? getFilmCount(photo.film) : 0,
photo.filmSimulation ? getFilmSimulationCount(photo.filmSimulation) : 0,
focalCount: photo.focalLength ? getFocalLengthCount(photo.focalLength) : 0, focalCount: photo.focalLength ? getFocalLengthCount(photo.focalLength) : 0,
}), [ }), [
getCameraCount, getCameraCount,
getLensCount, getLensCount,
getRecipeCount, getRecipeCount,
getFilmSimulationCount, getFilmCount,
getFocalLengthCount, getFocalLengthCount,
getTagCount, getTagCount,
camera, camera,
lens, lens,
photo.tags, photo.tags,
photo.recipeTitle, photo.recipeTitle,
photo.filmSimulation, photo.film,
photo.focalLength, photo.focalLength,
]); ]);

View File

@ -25,7 +25,7 @@ import {
PATH_ROOT, PATH_ROOT,
PATH_SIGN_IN, PATH_SIGN_IN,
pathForCamera, pathForCamera,
pathForFilmSimulation, pathForFilm,
pathForFocalLength, pathForFocalLength,
pathForLens, pathForLens,
pathForPhoto, pathForPhoto,
@ -333,13 +333,13 @@ export default function CommandKClient({
})), })),
}; };
case 'films': return { case 'films': return {
heading: 'Film Simulations', heading: 'Films',
accessory: <IconFilm size={14} />, accessory: <IconFilm size={14} />,
items: films.map(({ film, count }) => ({ items: films.map(({ film, count }) => ({
label: labelForFilm(film).medium, label: labelForFilm(film).medium,
annotation: formatCount(count), annotation: formatCount(count),
annotationAria: formatCountDescriptive(count), annotationAria: formatCountDescriptive(count),
path: pathForFilmSimulation(film), path: pathForFilm(film),
})), })),
}; };
case 'focal-lengths': return { case 'focal-lengths': return {

View File

@ -1,17 +1,17 @@
import { Photo, PhotoDateRange } from '@/photo'; import { Photo, PhotoDateRange } from '@/photo';
import { FilmSimulation, descriptionForFilmSimulationPhotos } from '.'; import { FilmSimulation, descriptionForFilmPhotos } from '.';
import PhotoHeader from '@/photo/PhotoHeader'; import PhotoHeader from '@/photo/PhotoHeader';
import PhotoFilm from '@/film/PhotoFilm'; import PhotoFilm from '@/film/PhotoFilm';
export default function FilmHeader({ export default function FilmHeader({
simulation, film,
photos, photos,
selectedPhoto, selectedPhoto,
indexNumber, indexNumber,
count, count,
dateRange, dateRange,
}: { }: {
simulation: FilmSimulation film: FilmSimulation
photos: Photo[] photos: Photo[]
selectedPhoto?: Photo selectedPhoto?: Photo
indexNumber?: number indexNumber?: number
@ -20,9 +20,9 @@ export default function FilmHeader({
}) { }) {
return ( return (
<PhotoHeader <PhotoHeader
film={simulation} film={film}
entity={<PhotoFilm {...{ simulation }} />} entity={<PhotoFilm {...{ film }} />}
entityDescription={descriptionForFilmSimulationPhotos( entityDescription={descriptionForFilmPhotos(
photos, undefined, count, dateRange)} photos, undefined, count, dateRange)}
photos={photos} photos={photos}
selectedPhoto={selectedPhoto} selectedPhoto={selectedPhoto}

View File

@ -1,19 +1,19 @@
import { Photo, PhotoDateRange } from '@/photo'; import { Photo, PhotoDateRange } from '@/photo';
import { import {
absolutePathForFilmSimulationImage, absolutePathForFilmImage,
pathForFilmSimulation, pathForFilm,
} from '@/app/paths'; } from '@/app/paths';
import OGTile from '@/components/OGTile'; import OGTile from '@/components/OGTile';
import { import {
FilmSimulation, FilmSimulation,
descriptionForFilmSimulationPhotos, descriptionForFilmPhotos,
titleForFilmSimulation, titleForFilm,
} from '.'; } from '.';
export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed'; export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed';
export default function FilmOGTile({ export default function FilmOGTile({
simulation, film,
photos, photos,
loadingState: loadingStateExternal, loadingState: loadingStateExternal,
riseOnHover, riseOnHover,
@ -23,7 +23,7 @@ export default function FilmOGTile({
count, count,
dateRange, dateRange,
}: { }: {
simulation: FilmSimulation film: FilmSimulation
photos: Photo[] photos: Photo[]
loadingState?: OGLoadingState loadingState?: OGLoadingState
onLoad?: () => void onLoad?: () => void
@ -35,11 +35,11 @@ export default function FilmOGTile({
}) { }) {
return ( return (
<OGTile {...{ <OGTile {...{
title: titleForFilmSimulation(simulation, photos, count), title: titleForFilm(film, photos, count),
description: description:
descriptionForFilmSimulationPhotos(photos, true, count, dateRange), descriptionForFilmPhotos(photos, true, count, dateRange),
path: pathForFilmSimulation(simulation), path: pathForFilm(film),
pathImageAbsolute: absolutePathForFilmSimulationImage(simulation), pathImageAbsolute: absolutePathForFilmImage(film),
loadingState: loadingStateExternal, loadingState: loadingStateExternal,
onLoad, onLoad,
onFail, onFail,

View File

@ -4,13 +4,13 @@ import { FilmSimulation } from '.';
import PhotoGridContainer from '@/photo/PhotoGridContainer'; import PhotoGridContainer from '@/photo/PhotoGridContainer';
export default function FilmOverview({ export default function FilmOverview({
simulation, film,
photos, photos,
count, count,
dateRange, dateRange,
animateOnFirstLoadOnly, animateOnFirstLoadOnly,
}: { }: {
simulation: FilmSimulation, film: FilmSimulation,
photos: Photo[], photos: Photo[],
count: number, count: number,
dateRange?: PhotoDateRange, dateRange?: PhotoDateRange,
@ -18,12 +18,12 @@ export default function FilmOverview({
}) { }) {
return ( return (
<PhotoGridContainer {...{ <PhotoGridContainer {...{
cacheKey: `simulation-${simulation}`, cacheKey: `film-${film}`,
photos, photos,
count, count,
film: simulation, film,
header: <FilmHeader {...{ header: <FilmHeader {...{
simulation, film,
photos, photos,
count, count,
dateRange, dateRange,

View File

@ -1,23 +1,23 @@
import { absolutePathForFilmSimulation } from '@/app/paths'; import { absolutePathForFilm } from '@/app/paths';
import { PhotoSetAttributes } from '../category'; import { PhotoSetAttributes } from '../category';
import ShareModal from '@/share/ShareModal'; import ShareModal from '@/share/ShareModal';
import FilmOGTile from './FilmOGTile'; import FilmOGTile from './FilmOGTile';
import { FilmSimulation, shareTextForFilmSimulation } from '.'; import { FilmSimulation, shareTextForFilm } from '.';
export default function FilmShareModal({ export default function FilmShareModal({
simulation, film,
photos, photos,
count, count,
dateRange, dateRange,
}: { }: {
simulation: FilmSimulation film: FilmSimulation
} & PhotoSetAttributes) { } & PhotoSetAttributes) {
return ( return (
<ShareModal <ShareModal
pathShare={absolutePathForFilmSimulation(simulation)} pathShare={absolutePathForFilm(film)}
socialText={shareTextForFilmSimulation(simulation)} socialText={shareTextForFilm(film)}
> >
<FilmOGTile {...{ simulation, photos, count, dateRange }} /> <FilmOGTile {...{ film, photos, count, dateRange }} />
</ShareModal> </ShareModal>
); );
}; };

View File

@ -1,6 +1,6 @@
import { labelForFilm } from '@/platforms/fujifilm/simulation'; import { labelForFilm } from '@/platforms/fujifilm/simulation';
import PhotoFilmIcon from './PhotoFilmIcon'; import PhotoFilmIcon from './PhotoFilmIcon';
import { pathForFilmSimulation } from '@/app/paths'; import { pathForFilm } from '@/app/paths';
import { FilmSimulation } from '.'; import { FilmSimulation } from '.';
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import EntityLink, { import EntityLink, {
@ -9,27 +9,27 @@ import EntityLink, {
import clsx from 'clsx/lite'; import clsx from 'clsx/lite';
export default function PhotoFilm({ export default function PhotoFilm({
simulation, film,
type = 'icon-last', type = 'icon-last',
badged = true, badged = true,
contrast = 'low', contrast = 'low',
countOnHover, countOnHover,
...props ...props
}: { }: {
simulation: FilmSimulation film: FilmSimulation
countOnHover?: number countOnHover?: number
recipe?: FujifilmRecipe recipe?: FujifilmRecipe
} & EntityLinkExternalProps) { } & EntityLinkExternalProps) {
const { small, medium, large } = labelForFilm(simulation); const { small, medium, large } = labelForFilm(film);
return ( return (
<EntityLink <EntityLink
{...props} {...props}
label={medium} label={medium}
labelSmall={small} labelSmall={small}
href={pathForFilmSimulation(simulation)} href={pathForFilm(film)}
icon={<PhotoFilmIcon icon={<PhotoFilmIcon
simulation={simulation} film={film}
className={clsx( className={clsx(
contrast === 'frosted' && 'text-black', contrast === 'frosted' && 'text-black',
type === 'icon-only' type === 'icon-only'
@ -37,7 +37,7 @@ export default function PhotoFilm({
: 'translate-y-[-1px]', : 'translate-y-[-1px]',
)} )}
/>} />}
title={`Film Simulation: ${large}`} title={`Film: ${large}`}
type={type} type={type}
badged={badged} badged={badged}
contrast={contrast} contrast={contrast}

View File

@ -7,12 +7,12 @@ const INTRINSIC_WIDTH = 28;
const INTRINSIC_HEIGHT = 16; const INTRINSIC_HEIGHT = 16;
export default function PhotoFilmIcon({ export default function PhotoFilmIcon({
simulation, film,
height = INTRINSIC_HEIGHT, height = INTRINSIC_HEIGHT,
className, className,
style, style,
}: { }: {
simulation?: FilmSimulation film?: FilmSimulation
height?: number height?: number
className?: string className?: string
style?: CSSProperties style?: CSSProperties
@ -21,9 +21,9 @@ export default function PhotoFilmIcon({
<svg <svg
className={className} className={className}
style={style} style={style}
aria-description={simulation aria-description={film
? labelForFilm(simulation).large ? labelForFilm(film).large
: 'Film Simulation'} : 'Film'}
width={INTRINSIC_WIDTH * height / INTRINSIC_HEIGHT} width={INTRINSIC_WIDTH * height / INTRINSIC_HEIGHT}
height={height} height={height}
viewBox="0 0 28 16" viewBox="0 0 28 16"
@ -33,7 +33,7 @@ export default function PhotoFilmIcon({
{(() => { {(() => {
// Self-calling switch function and non-fragment groups // Self-calling switch function and non-fragment groups
// necessary for ImageResponse compatibility // necessary for ImageResponse compatibility
switch (simulation) { switch (film) {
case 'monochrome': return <g> case 'monochrome': return <g>
<path fillRule="evenodd" clipRule="evenodd" d="M16.25 14H22.5C22.6381 14 22.75 13.8881 22.75 13.75V10.5202C22.75 10.4539 22.7763 10.3903 22.8232 10.3434L25.677 7.48989C25.7238 7.44301 25.7502 7.37942 25.7502 7.31311V4.25C25.7502 4.11193 25.6383 4 25.5002 4H16.25C16.1119 4 16 4.11194 16 4.25002L16.0002 6.49998C16.0002 6.63806 15.8882 6.75 15.7502 6.75H14.7502C14.6121 6.75 14.5002 6.86192 14.5002 6.99999L14.5 11C14.5 11.1381 14.6119 11.25 14.75 11.25H15.75C15.8881 11.25 16 11.3619 16 11.5V13.75C16 13.8881 16.1119 14 16.25 14ZM18.75 5H17V6.5H18.75V5ZM17 11.5H18.75V13H17V11.5ZM21.75 5H20V6.5H21.75V5ZM20 11.5H21.75V13H20V11.5ZM24.75 5H23V6.5H24.75V5Z" fill="currentColor"/> <path fillRule="evenodd" clipRule="evenodd" d="M16.25 14H22.5C22.6381 14 22.75 13.8881 22.75 13.75V10.5202C22.75 10.4539 22.7763 10.3903 22.8232 10.3434L25.677 7.48989C25.7238 7.44301 25.7502 7.37942 25.7502 7.31311V4.25C25.7502 4.11193 25.6383 4 25.5002 4H16.25C16.1119 4 16 4.11194 16 4.25002L16.0002 6.49998C16.0002 6.63806 15.8882 6.75 15.7502 6.75H14.7502C14.6121 6.75 14.5002 6.86192 14.5002 6.99999L14.5 11C14.5 11.1381 14.6119 11.25 14.75 11.25H15.75C15.8881 11.25 16 11.3619 16 11.5V13.75C16 13.8881 16.1119 14 16.25 14ZM18.75 5H17V6.5H18.75V5ZM17 11.5H18.75V13H17V11.5ZM21.75 5H20V6.5H21.75V5ZM20 11.5H21.75V13H20V11.5ZM24.75 5H23V6.5H24.75V5Z" fill="currentColor"/>
<path fillRule="evenodd" clipRule="evenodd" d="M5.25 3.49999L2 3.49999C1.86193 3.49999 1.75 3.61192 1.75 3.74999V5.24998C1.75 5.38806 1.86193 5.49998 2 5.49998H3.00001C3.13808 5.49998 3.25001 5.61191 3.25001 5.74999L3.25 12.25C3.25 12.3881 3.13807 12.5 3 12.5H2C1.86193 12.5 1.75 12.6119 1.75 12.75V14.25C1.75 14.3881 1.86193 14.5 2 14.5H14.25C14.3881 14.5 14.5 14.3881 14.5 14.25V12.75C14.5 12.6119 14.3881 12.5 14.25 12.5H13.25C13.1119 12.5 13 12.3881 13 12.25L13 5.75C13 5.61193 13.112 5.5 13.25 5.5H14.25C14.3881 5.5 14.5 5.38807 14.5 5.25V3.74999C14.5 3.61192 14.3881 3.49999 14.25 3.49999L11 3.49999C10.8619 3.49998 10.75 3.38806 10.75 3.24999V1.74998C10.75 1.61191 10.6381 1.49998 10.5 1.49998H5.75C5.61193 1.49998 5.5 1.61191 5.5 1.74998V3.24999C5.5 3.38806 5.38807 3.49998 5.25 3.49999ZM9.03232 4.7985H5.38982V13H9.36132C9.65899 13 9.92532 12.9412 10.1603 12.8237C10.3953 12.6984 10.5951 12.53 10.7596 12.3185C10.9241 12.107 11.0494 11.8602 11.1356 11.5782C11.2217 11.2884 11.2648 10.9829 11.2648 10.6617C11.2648 10.3328 11.2139 10.0547 11.1121 9.8275C11.0102 9.5925 10.8771 9.4045 10.7126 9.2635C10.5481 9.11467 10.364 9.00892 10.1603 8.94625C9.96449 8.88358 9.77257 8.85225 9.58457 8.85225V8.66425C9.7569 8.65642 9.92532 8.62508 10.0898 8.57025C10.2543 8.50758 10.3992 8.41358 10.5246 8.28825C10.6577 8.15508 10.7635 7.97883 10.8418 7.7595C10.928 7.53233 10.9711 7.24642 10.9711 6.90175C10.9711 6.26725 10.8105 5.75808 10.4893 5.37425C10.176 4.99042 9.69032 4.7985 9.03232 4.7985ZM8.89132 11.5782H7.07007V9.55725H8.89132C9.07932 9.55725 9.22815 9.61208 9.33782 9.72175C9.45532 9.83142 9.51407 10.0155 9.51407 10.274V10.8615C9.51407 11.12 9.45532 11.3041 9.33782 11.4137C9.22815 11.5234 9.07932 11.5782 8.89132 11.5782ZM8.62107 8.17075H7.07007V6.22025H8.62107C8.8169 6.22025 8.96965 6.27508 9.07932 6.38475C9.18899 6.49442 9.24382 6.67458 9.24382 6.92525V7.46575C9.24382 7.71642 9.18899 7.89658 9.07932 8.00625C8.96965 8.11592 8.8169 8.17075 8.62107 8.17075Z" fill="currentColor"/> <path fillRule="evenodd" clipRule="evenodd" d="M5.25 3.49999L2 3.49999C1.86193 3.49999 1.75 3.61192 1.75 3.74999V5.24998C1.75 5.38806 1.86193 5.49998 2 5.49998H3.00001C3.13808 5.49998 3.25001 5.61191 3.25001 5.74999L3.25 12.25C3.25 12.3881 3.13807 12.5 3 12.5H2C1.86193 12.5 1.75 12.6119 1.75 12.75V14.25C1.75 14.3881 1.86193 14.5 2 14.5H14.25C14.3881 14.5 14.5 14.3881 14.5 14.25V12.75C14.5 12.6119 14.3881 12.5 14.25 12.5H13.25C13.1119 12.5 13 12.3881 13 12.25L13 5.75C13 5.61193 13.112 5.5 13.25 5.5H14.25C14.3881 5.5 14.5 5.38807 14.5 5.25V3.74999C14.5 3.61192 14.3881 3.49999 14.25 3.49999L11 3.49999C10.8619 3.49998 10.75 3.38806 10.75 3.24999V1.74998C10.75 1.61191 10.6381 1.49998 10.5 1.49998H5.75C5.61193 1.49998 5.5 1.61191 5.5 1.74998V3.24999C5.5 3.38806 5.38807 3.49998 5.25 3.49999ZM9.03232 4.7985H5.38982V13H9.36132C9.65899 13 9.92532 12.9412 10.1603 12.8237C10.3953 12.6984 10.5951 12.53 10.7596 12.3185C10.9241 12.107 11.0494 11.8602 11.1356 11.5782C11.2217 11.2884 11.2648 10.9829 11.2648 10.6617C11.2648 10.3328 11.2139 10.0547 11.1121 9.8275C11.0102 9.5925 10.8771 9.4045 10.7126 9.2635C10.5481 9.11467 10.364 9.00892 10.1603 8.94625C9.96449 8.88358 9.77257 8.85225 9.58457 8.85225V8.66425C9.7569 8.65642 9.92532 8.62508 10.0898 8.57025C10.2543 8.50758 10.3992 8.41358 10.5246 8.28825C10.6577 8.15508 10.7635 7.97883 10.8418 7.7595C10.928 7.53233 10.9711 7.24642 10.9711 6.90175C10.9711 6.26725 10.8105 5.75808 10.4893 5.37425C10.176 4.99042 9.69032 4.7985 9.03232 4.7985ZM8.89132 11.5782H7.07007V9.55725H8.89132C9.07932 9.55725 9.22815 9.61208 9.33782 9.72175C9.45532 9.83142 9.51407 10.0155 9.51407 10.274V10.8615C9.51407 11.12 9.45532 11.3041 9.33782 11.4137C9.22815 11.5234 9.07932 11.5782 8.89132 11.5782ZM8.62107 8.17075H7.07007V6.22025H8.62107C8.8169 6.22025 8.96965 6.27508 9.07932 6.38475C9.18899 6.49442 9.24382 6.67458 9.24382 6.92525V7.46575C9.24382 7.71642 9.18899 7.89658 9.07932 8.00625C8.96965 8.11592 8.8169 8.17075 8.62107 8.17075Z" fill="currentColor"/>

View File

@ -4,14 +4,14 @@ import {
} from '@/photo/cache'; } from '@/photo/cache';
import { FilmSimulation } from '.'; import { FilmSimulation } from '.';
export const getPhotosFilmSimulationDataCached = ({ export const getPhotosFilmDataCached = ({
simulation, film,
limit, limit,
}: { }: {
simulation: FilmSimulation, film: FilmSimulation,
limit?: number, limit?: number,
}) => }) =>
Promise.all([ Promise.all([
getPhotosCached({ film: simulation, limit }), getPhotosCached({ film, limit }),
getPhotosMetaCached({ film: simulation }), getPhotosMetaCached({ film }),
]); ]);

View File

@ -5,8 +5,8 @@ import {
photoQuantityText, photoQuantityText,
} from '@/photo'; } from '@/photo';
import { import {
absolutePathForFilmSimulation, absolutePathForFilm,
absolutePathForFilmSimulationImage, absolutePathForFilmImage,
} from '@/app/paths'; } from '@/app/paths';
import { import {
FujifilmSimulation, FujifilmSimulation,
@ -15,27 +15,27 @@ import {
export type FilmSimulation = FujifilmSimulation; export type FilmSimulation = FujifilmSimulation;
export type FilmSimulationWithCount = { export type FilmWithCount = {
film: FilmSimulation film: FilmSimulation
count: number count: number
} }
export type FilmSimulations = FilmSimulationWithCount[] export type Films = FilmWithCount[]
export const sortFilms = ( export const sortFilms = (
films: FilmSimulations, films: Films,
) => films.sort(sortFilmsWithCount); ) => films.sort(sortFilmsWithCount);
export const sortFilmsWithCount = ( export const sortFilmsWithCount = (
a: FilmSimulationWithCount, a: FilmWithCount,
b: FilmSimulationWithCount, b: FilmWithCount,
) => { ) => {
const aLabel = labelForFilm(a.film).large; const aLabel = labelForFilm(a.film).large;
const bLabel = labelForFilm(b.film).large; const bLabel = labelForFilm(b.film).large;
return aLabel.localeCompare(bLabel); return aLabel.localeCompare(bLabel);
}; };
export const titleForFilmSimulation = ( export const titleForFilm = (
film: FilmSimulation, film: FilmSimulation,
photos: Photo[], photos: Photo[],
explicitCount?: number, explicitCount?: number,
@ -44,12 +44,12 @@ export const titleForFilmSimulation = (
photoQuantityText(explicitCount ?? photos.length), photoQuantityText(explicitCount ?? photos.length),
].join(' '); ].join(' ');
export const shareTextForFilmSimulation = ( export const shareTextForFilm = (
film: FilmSimulation, film: FilmSimulation,
) => ) =>
`Photos shot on Fujifilm ${labelForFilm(film).large}`; `Photos shot on Fujifilm ${labelForFilm(film).large}`;
export const descriptionForFilmSimulationPhotos = ( export const descriptionForFilmPhotos = (
photos: Photo[], photos: Photo[],
dateBased?: boolean, dateBased?: boolean,
explicitCount?: number, explicitCount?: number,
@ -63,22 +63,22 @@ export const descriptionForFilmSimulationPhotos = (
explicitDateRange, explicitDateRange,
); );
export const generateMetaForFilmSimulation = ( export const generateMetaForFilm = (
film: FilmSimulation, film: FilmSimulation,
photos: Photo[], photos: Photo[],
explicitCount?: number, explicitCount?: number,
explicitDateRange?: PhotoDateRange, explicitDateRange?: PhotoDateRange,
) => ({ ) => ({
url: absolutePathForFilmSimulation(film), url: absolutePathForFilm(film),
title: titleForFilmSimulation(film, photos, explicitCount), title: titleForFilm(film, photos, explicitCount),
description: descriptionForFilmSimulationPhotos( description: descriptionForFilmPhotos(
photos, photos,
true, true,
explicitCount, explicitCount,
explicitDateRange, explicitDateRange,
), ),
images: absolutePathForFilmSimulationImage(film), images: absolutePathForFilmImage(film),
}); });
export const photoHasFilmSimulationData = (photo: Photo) => export const photoHasFilmData = (photo: Photo) =>
Boolean(photo.filmSimulation); Boolean(photo.film);

View File

@ -11,13 +11,13 @@ import { FilmSimulation } from '@/film';
import { NextImageSize } from '@/platforms/next-image'; import { NextImageSize } from '@/platforms/next-image';
export default function FilmImageResponse({ export default function FilmImageResponse({
simulation, film,
photos, photos,
width, width,
height, height,
fontFamily, fontFamily,
}: { }: {
simulation: FilmSimulation, film: FilmSimulation,
photos: Photo[] photos: Photo[]
width: NextImageSize width: NextImageSize
height: number height: number
@ -37,11 +37,11 @@ export default function FilmImageResponse({
height, height,
fontFamily, fontFamily,
icon: <PhotoFilmIcon icon: <PhotoFilmIcon
simulation={simulation} film={film}
height={height * .081} height={height * .081}
style={{ transform: `translateY(${height * .001}px)`}} style={{ transform: `translateY(${height * .001}px)`}}
/>, />,
title: labelForFilm(simulation).medium.toLocaleUpperCase(), title: labelForFilm(film).medium.toLocaleUpperCase(),
}} /> }} />
</ImageContainer> </ImageContainer>
); );

View File

@ -27,13 +27,13 @@ export default function RecipeImageResponse({
}) { }) {
const { const {
recipeData, recipeData,
filmSimulation, film,
} = getPhotoWithRecipeFromPhotos(photos) ?? {}; } = getPhotoWithRecipeFromPhotos(photos) ?? {};
let recipeLines = recipeData && filmSimulation let recipeLines = recipeData && film
? generateRecipeText({ ? generateRecipeText({
recipe: recipeData, recipe: recipeData,
simulation: filmSimulation, film,
}, true) }, true)
: []; : [];
@ -109,10 +109,10 @@ export default function RecipeImageResponse({
flexGrow: 1, flexGrow: 1,
}}> }}>
{text} {text}
{isStringFilmSimulation(text) && filmSimulation && {isStringFilmSimulation(text) && film &&
<div tw="flex"> <div tw="flex">
<PhotoFilmIcon <PhotoFilmIcon
simulation={filmSimulation} film={film}
height={height * .06} height={height * .06}
style={{ transform: `translateY(${-height * .001}px)`}} style={{ transform: `translateY(${-height * .001}px)`}}
/> />

View File

@ -29,10 +29,12 @@ export default function InfinitePhotoScroll({
initialOffset, initialOffset,
itemsPerPage, itemsPerPage,
sortBy, sortBy,
tag,
camera, camera,
lens, lens,
film: simulation, tag,
recipe,
film,
focal,
wrapMoreButtonInGrid, wrapMoreButtonInGrid,
useCachedPhotos = true, useCachedPhotos = true,
includeHiddenPhotos, includeHiddenPhotos,
@ -73,7 +75,9 @@ export default function InfinitePhotoScroll({
camera, camera,
lens, lens,
tag, tag,
film: simulation, recipe,
film,
focal,
}, warmOnly) }, warmOnly)
, [ , [
useCachedPhotos, useCachedPhotos,
@ -84,7 +88,9 @@ export default function InfinitePhotoScroll({
camera, camera,
lens, lens,
tag, tag,
simulation, recipe,
film,
focal,
]); ]);
const { data, isLoading, isValidating, error, mutate, size, setSize } = const { data, isLoading, isValidating, error, mutate, size, setSize } =

View File

@ -22,7 +22,7 @@ export default function PhotoDetailPage({
tag, tag,
camera, camera,
lens, lens,
film: simulation, film,
recipe, recipe,
focal, focal,
indexNumber, indexNumber,
@ -77,9 +77,9 @@ export default function PhotoDetailPage({
count={count} count={count}
dateRange={dateRange} dateRange={dateRange}
/>; />;
} else if (simulation) { } else if (film) {
customHeader = <FilmHeader customHeader = <FilmHeader
simulation={simulation} film={film}
photos={photos} photos={photos}
selectedPhoto={photo} selectedPhoto={photo}
indexNumber={indexNumber} indexNumber={indexNumber}
@ -129,13 +129,13 @@ export default function PhotoDetailPage({
showTitleAsH1 showTitleAsH1
showCamera={!camera} showCamera={!camera}
showLens={!lens} showLens={!lens}
showSimulation={!simulation} showFilm={!film}
showRecipe={!recipe} showRecipe={!recipe}
shouldShare={shouldShare} shouldShare={shouldShare}
shouldShareCamera={camera !== undefined} shouldShareCamera={camera !== undefined}
shouldShareLens={lens !== undefined} shouldShareLens={lens !== undefined}
shouldShareTag={tag !== undefined} shouldShareTag={tag !== undefined}
shouldShareSimulation={simulation !== undefined} shouldShareFilm={film !== undefined}
shouldShareRecipe={recipe !== undefined} shouldShareRecipe={recipe !== undefined}
shouldShareFocalLength={focal !== undefined} shouldShareFocalLength={focal !== undefined}
includeFavoriteInAdminMenu={includeFavoriteInAdminMenu} includeFavoriteInAdminMenu={includeFavoriteInAdminMenu}
@ -149,7 +149,7 @@ export default function PhotoDetailPage({
selectedPhoto={photo} selectedPhoto={photo}
tag={tag} tag={tag}
camera={camera} camera={camera}
film={simulation} film={film}
focal={focal} focal={focal}
animateOnFirstLoadOnly animateOnFirstLoadOnly
/>} />}

View File

@ -48,7 +48,7 @@ export default function PhotoGridSidebar({
cameras, cameras,
lenses, lenses,
tags, tags,
films: simulations, films,
recipes, recipes,
focalLengths, focalLengths,
} = categories; } = categories;
@ -192,17 +192,17 @@ export default function PhotoGridSidebar({
/> />
: null; : null;
const filmsContent = simulations.length > 0 const filmsContent = films.length > 0
? <HeaderList ? <HeaderList
key="films" key="films"
title="Films" title="Films"
icon={<IconFilm size={15} />} icon={<IconFilm size={15} />}
maxItems={maxItemsPerCategory} maxItems={maxItemsPerCategory}
items={simulations items={films
.map(({ film: simulation, count }) => .map(({ film, count }) =>
<PhotoFilm <PhotoFilm
key={simulation} key={film}
simulation={simulation} film={film}
countOnHover={count} countOnHover={count}
type="text-only" type="text-only"
prefetch={false} prefetch={false}

View File

@ -6,7 +6,7 @@ import {
doesPhotoNeedBlurCompatibility, doesPhotoNeedBlurCompatibility,
shouldShowCameraDataForPhoto, shouldShowCameraDataForPhoto,
shouldShowExifDataForPhoto, shouldShowExifDataForPhoto,
shouldShowFilmSimulationDataForPhoto, shouldShowFilmDataForPhoto,
shouldShowLensDataForPhoto, shouldShowLensDataForPhoto,
shouldShowRecipeDataForPhoto, shouldShowRecipeDataForPhoto,
titleForPhoto, titleForPhoto,
@ -65,7 +65,7 @@ export default function PhotoLarge({
showTitleAsH1, showTitleAsH1,
showCamera = true, showCamera = true,
showLens = true, showLens = true,
showSimulation = true, showFilm = true,
showRecipe = true, showRecipe = true,
showZoomControls: showZoomControlsProp = true, showZoomControls: showZoomControlsProp = true,
shouldZoomOnFKeydown = true, shouldZoomOnFKeydown = true,
@ -73,7 +73,7 @@ export default function PhotoLarge({
shouldShareCamera, shouldShareCamera,
shouldShareLens, shouldShareLens,
shouldShareTag, shouldShareTag,
shouldShareSimulation, shouldShareFilm,
shouldShareRecipe, shouldShareRecipe,
shouldShareFocalLength, shouldShareFocalLength,
includeFavoriteInAdminMenu, includeFavoriteInAdminMenu,
@ -90,7 +90,7 @@ export default function PhotoLarge({
showTitleAsH1?: boolean showTitleAsH1?: boolean
showCamera?: boolean showCamera?: boolean
showLens?: boolean showLens?: boolean
showSimulation?: boolean showFilm?: boolean
showRecipe?: boolean showRecipe?: boolean
showZoomControls?: boolean showZoomControls?: boolean
shouldZoomOnFKeydown?: boolean shouldZoomOnFKeydown?: boolean
@ -98,7 +98,7 @@ export default function PhotoLarge({
shouldShareCamera?: boolean shouldShareCamera?: boolean
shouldShareLens?: boolean shouldShareLens?: boolean
shouldShareTag?: boolean shouldShareTag?: boolean
shouldShareSimulation?: boolean shouldShareFilm?: boolean
shouldShareRecipe?: boolean shouldShareRecipe?: boolean
shouldShareFocalLength?: boolean shouldShareFocalLength?: boolean
includeFavoriteInAdminMenu?: boolean includeFavoriteInAdminMenu?: boolean
@ -120,7 +120,7 @@ export default function PhotoLarge({
lensCount, lensCount,
tagCounts, tagCounts,
recipeCount, recipeCount,
simulationCount, filmCount,
} = useCategoryCountsForPhoto(photo); } = useCategoryCountsForPhoto(photo);
const showZoomControls = showZoomControlsProp && areZoomControlsShown; const showZoomControls = showZoomControlsProp && areZoomControlsShown;
@ -150,8 +150,7 @@ export default function PhotoLarge({
const showTagsContent = tags.length > 0; const showTagsContent = tags.length > 0;
const showRecipeContent = showRecipe && shouldShowRecipeDataForPhoto(photo); const showRecipeContent = showRecipe && shouldShowRecipeDataForPhoto(photo);
const showRecipeButton = shouldShowRecipeDataForPhoto(photo); const showRecipeButton = shouldShowRecipeDataForPhoto(photo);
const showSimulationContent = showSimulation && const showFilmContent = showFilm && shouldShowFilmDataForPhoto(photo);
shouldShowFilmSimulationDataForPhoto(photo);
useVisible({ ref, onVisible }); useVisible({ ref, onVisible });
@ -168,7 +167,7 @@ export default function PhotoLarge({
showLensContent || showLensContent ||
showTagsContent || showTagsContent ||
showRecipeContent || showRecipeContent ||
showSimulationContent || showFilmContent ||
showExifContent; showExifContent;
const hasNonDateContent = const hasNonDateContent =
@ -226,12 +225,12 @@ export default function PhotoLarge({
<AnimatePresence> <AnimatePresence>
{(shouldShowRecipeOverlay || shouldDebugRecipeOverlays) && {(shouldShowRecipeOverlay || shouldDebugRecipeOverlays) &&
photo.recipeData && photo.recipeData &&
photo.filmSimulation && photo.film &&
<PhotoRecipeOverlay <PhotoRecipeOverlay
ref={refRecipe} ref={refRecipe}
title={photo.recipeTitle} title={photo.recipeTitle}
recipe={photo.recipeData} recipe={photo.recipeData}
simulation={photo.filmSimulation} film={photo.film}
iso={photo.isoFormatted} iso={photo.isoFormatted}
exposure={photo.exposureCompensationFormatted} exposure={photo.exposureCompensationFormatted}
onClose={hideRecipeOverlay} onClose={hideRecipeOverlay}
@ -391,13 +390,13 @@ export default function PhotoLarge({
<li>{photo.isoFormatted}</li> <li>{photo.isoFormatted}</li>
<li>{photo.exposureCompensationFormatted ?? '0ev'}</li> <li>{photo.exposureCompensationFormatted ?? '0ev'}</li>
</ul> </ul>
{(showRecipeButton || showSimulationContent) && {(showRecipeButton || showFilmContent) &&
<div className="flex items-center gap-2 *:w-auto"> <div className="flex items-center gap-2 *:w-auto">
{showSimulationContent && photo.filmSimulation && {showFilmContent && photo.film &&
<PhotoFilm <PhotoFilm
simulation={photo.filmSimulation} film={photo.film}
prefetch={prefetchRelatedLinks} prefetch={prefetchRelatedLinks}
countOnHover={simulationCount} countOnHover={filmCount}
/>} />}
{showRecipeButton && {showRecipeButton &&
<Tooltip content="Fujifilm Recipe"> <Tooltip content="Fujifilm Recipe">
@ -415,7 +414,7 @@ export default function PhotoLarge({
'px-[4px] py-[2.5px] my-[-3px]', 'px-[4px] py-[2.5px] my-[-3px]',
'translate-y-[2px]', 'translate-y-[2px]',
'hover:bg-dim active:bg-main', 'hover:bg-dim active:bg-main',
!showSimulation && 'translate-x-[-2px]', !showFilm && 'translate-x-[-2px]',
)}> )}>
{shouldShowRecipeOverlay {shouldShowRecipeOverlay
? <IoCloseSharp size={15} /> ? <IoCloseSharp size={15} />
@ -464,8 +463,8 @@ export default function PhotoLarge({
tag={shouldShareTag ? primaryTag : undefined} tag={shouldShareTag ? primaryTag : undefined}
camera={shouldShareCamera ? camera : undefined} camera={shouldShareCamera ? camera : undefined}
lens={shouldShareLens ? lens : undefined} lens={shouldShareLens ? lens : undefined}
film={shouldShareSimulation film={shouldShareFilm
? photo.filmSimulation ? photo.film
: undefined} : undefined}
recipe={shouldShareRecipe recipe={shouldShareRecipe
? recipeTitle ? recipeTitle

View File

@ -315,13 +315,13 @@ export const renamePhotoTagGloballyAction = async (formData: FormData) =>
export const getPhotosNeedingRecipeTitleCountAction = async ( export const getPhotosNeedingRecipeTitleCountAction = async (
recipeData: string, recipeData: string,
simulation: FilmSimulation, film: FilmSimulation,
photoIdToExclude?: string, photoIdToExclude?: string,
) => ) =>
runAuthenticatedAdminServerAction(async () => runAuthenticatedAdminServerAction(async () =>
await getPhotosNeedingRecipeTitleCount( await getPhotosNeedingRecipeTitleCount(
recipeData, recipeData,
simulation, film,
photoIdToExclude, photoIdToExclude,
), ),
); );

View File

@ -10,7 +10,7 @@ import {
getUniqueCameras, getUniqueCameras,
getUniqueTags, getUniqueTags,
getUniqueTagsHidden, getUniqueTagsHidden,
getUniqueFilmSimulations, getUniqueFilms,
getPhotosNearId, getPhotosNearId,
getPhotosMostRecentUpdate, getPhotosMostRecentUpdate,
getPhotosMeta, getPhotosMeta,
@ -29,7 +29,7 @@ import {
PATH_GRID, PATH_GRID,
PATH_ROOT, PATH_ROOT,
PREFIX_CAMERA, PREFIX_CAMERA,
PREFIX_FILM_SIMULATION, PREFIX_FILM,
PREFIX_FOCAL_LENGTH, PREFIX_FOCAL_LENGTH,
PREFIX_LENS, PREFIX_LENS,
PREFIX_RECIPE, PREFIX_RECIPE,
@ -45,7 +45,7 @@ const KEY_PHOTO = 'photo';
const KEY_CAMERAS = 'cameras'; const KEY_CAMERAS = 'cameras';
const KEY_LENSES = 'lenses'; const KEY_LENSES = 'lenses';
const KEY_TAGS = 'tags'; const KEY_TAGS = 'tags';
const KEY_FILM_SIMULATIONS = 'film-simulations'; const KEY_FILMS = 'films';
const KEY_RECIPES = 'recipes'; const KEY_RECIPES = 'recipes';
const KEY_FOCAL_LENGTHS = 'focal-lengths'; const KEY_FOCAL_LENGTHS = 'focal-lengths';
// Type keys // Type keys
@ -109,8 +109,8 @@ export const revalidateCamerasKey = () =>
export const revalidateLensesKey = () => export const revalidateLensesKey = () =>
revalidateTag(KEY_LENSES); revalidateTag(KEY_LENSES);
export const revalidateFilmSimulationsKey = () => export const revalidateFilmsKey = () =>
revalidateTag(KEY_FILM_SIMULATIONS); revalidateTag(KEY_FILMS);
export const revalidateFocalLengthsKey = () => export const revalidateFocalLengthsKey = () =>
revalidateTag(KEY_FOCAL_LENGTHS); revalidateTag(KEY_FOCAL_LENGTHS);
@ -120,7 +120,7 @@ export const revalidateAllKeys = () => {
revalidateTagsKey(); revalidateTagsKey();
revalidateCamerasKey(); revalidateCamerasKey();
revalidateLensesKey(); revalidateLensesKey();
revalidateFilmSimulationsKey(); revalidateFilmsKey();
revalidateRecipesKey(); revalidateRecipesKey();
revalidateFocalLengthsKey(); revalidateFocalLengthsKey();
}; };
@ -140,7 +140,7 @@ export const revalidatePhoto = (photoId: string) => {
revalidateTagsKey(); revalidateTagsKey();
revalidateCamerasKey(); revalidateCamerasKey();
revalidateLensesKey(); revalidateLensesKey();
revalidateFilmSimulationsKey(); revalidateFilmsKey();
revalidateRecipesKey(); revalidateRecipesKey();
revalidateFocalLengthsKey(); revalidateFocalLengthsKey();
// Paths // Paths
@ -151,7 +151,7 @@ export const revalidatePhoto = (photoId: string) => {
revalidatePath(PREFIX_TAG, 'layout'); revalidatePath(PREFIX_TAG, 'layout');
revalidatePath(PREFIX_CAMERA, 'layout'); revalidatePath(PREFIX_CAMERA, 'layout');
revalidatePath(PREFIX_LENS, 'layout'); revalidatePath(PREFIX_LENS, 'layout');
revalidatePath(PREFIX_FILM_SIMULATION, 'layout'); revalidatePath(PREFIX_FILM, 'layout');
revalidatePath(PREFIX_RECIPE, 'layout'); revalidatePath(PREFIX_RECIPE, 'layout');
revalidatePath(PREFIX_FOCAL_LENGTH, 'layout'); revalidatePath(PREFIX_FOCAL_LENGTH, 'layout');
revalidatePath(PATH_ADMIN, 'layout'); revalidatePath(PATH_ADMIN, 'layout');
@ -231,10 +231,10 @@ export const getUniqueLensesCached =
[KEY_PHOTOS, KEY_LENSES], [KEY_PHOTOS, KEY_LENSES],
); );
export const getUniqueFilmSimulationsCached = export const getUniqueFilmsCached =
unstable_cache( unstable_cache(
getUniqueFilmSimulations, getUniqueFilms,
[KEY_PHOTOS, KEY_FILM_SIMULATIONS], [KEY_PHOTOS, KEY_FILMS],
); );
export const getUniqueRecipesCached = export const getUniqueRecipesCached =

View File

@ -47,7 +47,7 @@ export const getWheresFromOptions = (
tag, tag,
camera, camera,
lens, lens,
film: simulation, film,
recipe, recipe,
focal, focal,
} = options; } = options;
@ -108,9 +108,9 @@ export const getWheresFromOptions = (
wheres.push(`$${valuesIndex++}=ANY(tags)`); wheres.push(`$${valuesIndex++}=ANY(tags)`);
wheresValues.push(tag); wheresValues.push(tag);
} }
if (simulation) { if (film) {
wheres.push(`film_simulation=$${valuesIndex++}`); wheres.push(`film=$${valuesIndex++}`);
wheresValues.push(simulation); wheresValues.push(film);
} }
if (recipe) { if (recipe) {
wheres.push(`recipe_title=$${valuesIndex++}`); wheres.push(`recipe_title=$${valuesIndex++}`);

View File

@ -50,6 +50,27 @@ export const MIGRATIONS: Migration[] = [{
ALTER TABLE photos ALTER TABLE photos
ADD COLUMN IF NOT EXISTS recipe_title VARCHAR(255) ADD COLUMN IF NOT EXISTS recipe_title VARCHAR(255)
`, `,
}, {
label: '05: Universal Film',
fields: ['film'],
run: () => sql`
DO $$
BEGIN
IF EXISTS(
SELECT 1
FROM information_schema.columns
WHERE table_name='photos'
AND column_name='film_simulation'
)
THEN
ALTER TABLE photos
RENAME COLUMN film_simulation TO film;
ELSE
ALTER TABLE photos
ADD COLUMN IF NOT EXISTS film VARCHAR(255);
END IF;
END $$;
`,
}]; }];
export const migrationForError = (e: any) => export const migrationForError = (e: any) =>

View File

@ -13,7 +13,7 @@ import {
} from '@/photo'; } from '@/photo';
import { Cameras, createCameraKey } from '@/camera'; import { Cameras, createCameraKey } from '@/camera';
import { Tags } from '@/tag'; import { Tags } from '@/tag';
import { FilmSimulation, FilmSimulations } from '@/film'; import { FilmSimulation, Films } from '@/film';
import { ADMIN_SQL_DEBUG_ENABLED } from '@/app/config'; import { ADMIN_SQL_DEBUG_ENABLED } from '@/app/config';
import { import {
GetPhotosOptions, GetPhotosOptions,
@ -53,7 +53,7 @@ const createPhotosTable = () =>
location_name VARCHAR(255), location_name VARCHAR(255),
latitude DOUBLE PRECISION, latitude DOUBLE PRECISION,
longitude DOUBLE PRECISION, longitude DOUBLE PRECISION,
film_simulation VARCHAR(255), film VARCHAR(255),
recipe_title VARCHAR(255), recipe_title VARCHAR(255),
recipe_data JSONB, recipe_data JSONB,
priority_order REAL, priority_order REAL,
@ -160,7 +160,7 @@ export const insertPhoto = (photo: PhotoDbInsert) =>
location_name, location_name,
latitude, latitude,
longitude, longitude,
film_simulation, film,
recipe_title, recipe_title,
recipe_data, recipe_data,
priority_order, priority_order,
@ -191,7 +191,7 @@ export const insertPhoto = (photo: PhotoDbInsert) =>
${photo.locationName}, ${photo.locationName},
${photo.latitude}, ${photo.latitude},
${photo.longitude}, ${photo.longitude},
${photo.filmSimulation}, ${photo.film},
${photo.recipeTitle}, ${photo.recipeTitle},
${photo.recipeData}, ${photo.recipeData},
${photo.priorityOrder}, ${photo.priorityOrder},
@ -225,7 +225,7 @@ export const updatePhoto = (photo: PhotoDbInsert) =>
location_name=${photo.locationName}, location_name=${photo.locationName},
latitude=${photo.latitude}, latitude=${photo.latitude},
longitude=${photo.longitude}, longitude=${photo.longitude},
film_simulation=${photo.filmSimulation}, film=${photo.film},
recipe_title=${photo.recipeTitle}, recipe_title=${photo.recipeTitle},
recipe_data=${photo.recipeData}, recipe_data=${photo.recipeData},
priority_order=${photo.priorityOrder || null}, priority_order=${photo.priorityOrder || null},
@ -367,14 +367,14 @@ export const getUniqueRecipes = async () =>
export const getRecipeTitleForData = async ( export const getRecipeTitleForData = async (
data: string | object, data: string | object,
simulation: FilmSimulation, film: FilmSimulation,
) => ) =>
// Includes legacy check on pre-stringified JSON // Includes legacy check on pre-stringified JSON
safelyQueryPhotos(() => sql` safelyQueryPhotos(() => sql`
SELECT recipe_title FROM photos SELECT recipe_title FROM photos
WHERE hidden IS NOT TRUE WHERE hidden IS NOT TRUE
AND recipe_data=${typeof data === 'string' ? data : JSON.stringify(data)} AND recipe_data=${typeof data === 'string' ? data : JSON.stringify(data)}
AND film_simulation=${simulation} AND film=${film}
LIMIT 1 LIMIT 1
` `
.then(({ rows }) => rows[0]?.recipe_title as string | undefined) .then(({ rows }) => rows[0]?.recipe_title as string | undefined)
@ -382,7 +382,7 @@ export const getRecipeTitleForData = async (
export const getPhotosNeedingRecipeTitleCount = async ( export const getPhotosNeedingRecipeTitleCount = async (
data: string, data: string,
simulation: FilmSimulation, film: FilmSimulation,
photoIdToExclude?: string, photoIdToExclude?: string,
) => ) =>
safelyQueryPhotos(() => sql` safelyQueryPhotos(() => sql`
@ -390,7 +390,7 @@ export const getPhotosNeedingRecipeTitleCount = async (
FROM photos FROM photos
WHERE recipe_title IS NULL WHERE recipe_title IS NULL
AND recipe_data=${data} AND recipe_data=${data}
AND film_simulation=${simulation} AND film=${film}
AND id <> ${photoIdToExclude} AND id <> ${photoIdToExclude}
`.then(({ rows }) => parseInt(rows[0].count, 10)) `.then(({ rows }) => parseInt(rows[0].count, 10))
, 'getPhotosNeedingRecipeTitleCount'); , 'getPhotosNeedingRecipeTitleCount');
@ -398,29 +398,29 @@ export const getPhotosNeedingRecipeTitleCount = async (
export const updateAllMatchingRecipeTitles = ( export const updateAllMatchingRecipeTitles = (
title: string, title: string,
data: string, data: string,
simulation: FilmSimulation, film: FilmSimulation,
) => ) =>
safelyQueryPhotos(() => sql` safelyQueryPhotos(() => sql`
UPDATE photos UPDATE photos
SET recipe_title=${title} SET recipe_title=${title}
WHERE recipe_title IS NULL WHERE recipe_title IS NULL
AND recipe_data=${data} AND recipe_data=${data}
AND film_simulation=${simulation} AND film=${film}
`, 'updateAllMatchingRecipeTitles'); `, 'updateAllMatchingRecipeTitles');
export const getUniqueFilmSimulations = async () => export const getUniqueFilms = async () =>
safelyQueryPhotos(() => sql` safelyQueryPhotos(() => sql`
SELECT DISTINCT film_simulation, COUNT(*) SELECT DISTINCT film, COUNT(*)
FROM photos FROM photos
WHERE hidden IS NOT TRUE AND film_simulation IS NOT NULL WHERE hidden IS NOT TRUE AND film IS NOT NULL
GROUP BY film_simulation GROUP BY film
ORDER BY film_simulation ASC ORDER BY film ASC
`.then(({ rows }): FilmSimulations => rows `.then(({ rows }): Films => rows
.map(({ film_simulation, count }) => ({ .map(({ film, count }) => ({
film: film_simulation as FilmSimulation, film: film as FilmSimulation,
count: parseInt(count, 10), count: parseInt(count, 10),
}))) })))
, 'getUniqueFilmSimulations'); , 'getUniqueFilms');
export const getUniqueFocalLengths = async () => export const getUniqueFocalLengths = async () =>
safelyQueryPhotos(() => sql` safelyQueryPhotos(() => sql`

View File

@ -8,7 +8,7 @@ export default function ApplyRecipeTitleGloballyCheckbox({
recipeTitle, recipeTitle,
hasRecipeTitleChanged, hasRecipeTitleChanged,
recipeData, recipeData,
simulation, film,
onMatchResults, onMatchResults,
...props ...props
}: ComponentProps<typeof FieldSetWithStatus> & { }: ComponentProps<typeof FieldSetWithStatus> & {
@ -16,7 +16,7 @@ export default function ApplyRecipeTitleGloballyCheckbox({
recipeTitle?: string recipeTitle?: string
hasRecipeTitleChanged?: boolean hasRecipeTitleChanged?: boolean
recipeData?: string recipeData?: string
simulation?: FilmSimulation film?: FilmSimulation
onMatchResults: (didFindMatchingPhotos: boolean) => void onMatchResults: (didFindMatchingPhotos: boolean) => void
}) { }) {
const [matchingPhotosCount, setMatchingPhotosCount] = useState<number>(); const [matchingPhotosCount, setMatchingPhotosCount] = useState<number>();
@ -24,14 +24,14 @@ export default function ApplyRecipeTitleGloballyCheckbox({
const loading = matchingPhotosCount === undefined; const loading = matchingPhotosCount === undefined;
useEffect(() => { useEffect(() => {
if (recipeTitle && hasRecipeTitleChanged && recipeData && simulation) { if (recipeTitle && hasRecipeTitleChanged && recipeData && film) {
setMatchingPhotosCount(undefined); setMatchingPhotosCount(undefined);
getPhotosNeedingRecipeTitleCountAction(recipeData, simulation, photoId) getPhotosNeedingRecipeTitleCountAction(recipeData, film, photoId)
.then(setMatchingPhotosCount); .then(setMatchingPhotosCount);
} else { } else {
setMatchingPhotosCount(0); setMatchingPhotosCount(0);
} }
}, [recipeTitle, hasRecipeTitleChanged, recipeData, simulation, photoId]); }, [recipeTitle, hasRecipeTitleChanged, recipeData, film, photoId]);
useEffect(() => { useEffect(() => {
onMatchResults((matchingPhotosCount ?? 0) > 0); onMatchResults((matchingPhotosCount ?? 0) > 0);

View File

@ -414,7 +414,7 @@ export default function PhotoForm({
hasRecipeTitleChanged={ hasRecipeTitleChanged={
changedFormKeys.includes('recipeTitle')} changedFormKeys.includes('recipeTitle')}
recipeData={formData.recipeData} recipeData={formData.recipeData}
simulation={formData.filmSimulation as FilmSimulation} film={formData.film as FilmSimulation}
onMatchResults={onMatchResults} onMatchResults={onMatchResults}
{...fieldProps} {...fieldProps}
/>; />;

View File

@ -119,8 +119,8 @@ const FORM_METADATA = (
aspectRatio: { label: 'aspect ratio', readOnly: true }, aspectRatio: { label: 'aspect ratio', readOnly: true },
make: { label: 'camera make' }, make: { label: 'camera make' },
model: { label: 'camera model' }, model: { label: 'camera model' },
filmSimulation: { film: {
label: 'fujifilm simulation', label: 'film',
selectOptions: FILM_SIMULATION_FORM_INPUT_OPTIONS, selectOptions: FILM_SIMULATION_FORM_INPUT_OPTIONS,
selectOptionsDefaultLabel: 'Unknown', selectOptionsDefaultLabel: 'Unknown',
shouldHide: ({ make }) => make !== MAKE_FUJIFILM, shouldHide: ({ make }) => make !== MAKE_FUJIFILM,
@ -274,7 +274,7 @@ export const convertPhotoToFormData = (photo: Photo): PhotoFormData => {
export const convertExifToFormData = ( export const convertExifToFormData = (
data: ExifData, data: ExifData,
filmSimulation?: FilmSimulation, film?: FilmSimulation,
recipeData?: FujifilmRecipe, recipeData?: FujifilmRecipe,
): Omit< ): Omit<
Record<keyof PhotoExif, string | undefined>, Record<keyof PhotoExif, string | undefined>,
@ -298,7 +298,7 @@ export const convertExifToFormData = (
!GEO_PRIVACY_ENABLED ? data.tags?.GPSLatitude?.toString() : undefined, !GEO_PRIVACY_ENABLED ? data.tags?.GPSLatitude?.toString() : undefined,
longitude: longitude:
!GEO_PRIVACY_ENABLED ? data.tags?.GPSLongitude?.toString() : undefined, !GEO_PRIVACY_ENABLED ? data.tags?.GPSLongitude?.toString() : undefined,
filmSimulation, film,
recipeData: JSON.stringify(recipeData), recipeData: JSON.stringify(recipeData),
...data.tags?.DateTimeOriginal && { ...data.tags?.DateTimeOriginal && {
takenAt: convertTimestampWithOffsetToPostgresString( takenAt: convertTimestampWithOffsetToPostgresString(
@ -345,7 +345,7 @@ export const convertFormDataToPhotoDbInsert = (
return { return {
...(photoForm as PhotoFormData & { ...(photoForm as PhotoFormData & {
filmSimulation?: FilmSimulation film?: FilmSimulation
recipeData?: FujifilmRecipe recipeData?: FujifilmRecipe
}), }),
...!photoForm.id && { id: generateNanoid() }, ...!photoForm.id && { id: generateNanoid() },

View File

@ -1,11 +1,11 @@
import { formatFocalLength } from '@/focal'; import { formatFocalLength } from '@/focal';
import { getNextImageUrlForRequest } from '@/platforms/next-image'; import { getNextImageUrlForRequest } from '@/platforms/next-image';
import { FilmSimulation, photoHasFilmSimulationData } from '@/film'; import { FilmSimulation, photoHasFilmData } from '@/film';
import { import {
HIGH_DENSITY_GRID, HIGH_DENSITY_GRID,
IS_PREVIEW, IS_PREVIEW,
SHOW_EXIF_DATA, SHOW_EXIF_DATA,
SHOW_FILM_SIMULATIONS, SHOW_FILMS,
SHOW_LENSES, SHOW_LENSES,
SHOW_RECIPES, SHOW_RECIPES,
} from '@/app/config'; } from '@/app/config';
@ -65,7 +65,7 @@ export interface PhotoExif {
exposureCompensation?: number exposureCompensation?: number
latitude?: number latitude?: number
longitude?: number longitude?: number
filmSimulation?: FilmSimulation film?: FilmSimulation
recipeData?: string recipeData?: string
takenAt?: string takenAt?: string
takenAtNaive?: string takenAtNaive?: string
@ -329,10 +329,10 @@ export const shouldShowRecipeDataForPhoto = (photo: Photo) =>
SHOW_RECIPES && SHOW_RECIPES &&
photoHasRecipeData(photo); photoHasRecipeData(photo);
export const shouldShowFilmSimulationDataForPhoto = (photo: Photo) => export const shouldShowFilmDataForPhoto = (photo: Photo) =>
SHOW_EXIF_DATA && SHOW_EXIF_DATA &&
SHOW_FILM_SIMULATIONS && SHOW_FILMS &&
photoHasFilmSimulationData(photo); photoHasFilmData(photo);
export const shouldShowExifDataForPhoto = (photo: Photo) => export const shouldShowExifDataForPhoto = (photo: Photo) =>
SHOW_EXIF_DATA && photoHasExifData(photo); SHOW_EXIF_DATA && photoHasExifData(photo);

View File

@ -58,7 +58,7 @@ export const extractImageDataFromBlobPath = async (
const extension = getExtensionFromStorageUrl(url); const extension = getExtensionFromStorageUrl(url);
let exifData: ExifData | undefined; let exifData: ExifData | undefined;
let filmSimulation: FilmSimulation | undefined; let film: FilmSimulation | undefined;
let recipe: FujifilmRecipe | undefined; let recipe: FujifilmRecipe | undefined;
let blurData: string | undefined; let blurData: string | undefined;
let imageResizedBase64: string | undefined; let imageResizedBase64: string | undefined;
@ -89,7 +89,7 @@ export const extractImageDataFromBlobPath = async (
const exifDataBinary = parser.parse(); const exifDataBinary = parser.parse();
const makerNote = exifDataBinary.tags?.MakerNote; const makerNote = exifDataBinary.tags?.MakerNote;
if (Buffer.isBuffer(makerNote)) { if (Buffer.isBuffer(makerNote)) {
filmSimulation = getFujifilmSimulationFromMakerNote(makerNote); film = getFujifilmSimulationFromMakerNote(makerNote);
recipe = getFujifilmRecipeFromMakerNote(makerNote); recipe = getFujifilmRecipeFromMakerNote(makerNote);
} }
} }
@ -124,7 +124,7 @@ export const extractImageDataFromBlobPath = async (
url, url,
}, },
...generateBlurData && { blurData }, ...generateBlurData && { blurData },
...convertExifToFormData(exifData, filmSimulation, recipe), ...convertExifToFormData(exifData, film, recipe),
}, },
}, },
imageResizedBase64, imageResizedBase64,
@ -206,10 +206,10 @@ export const convertFormDataToPhotoDbInsertAndLookupRecipeTitle =
Promise<ReturnType<typeof convertFormDataToPhotoDbInsert>> => { Promise<ReturnType<typeof convertFormDataToPhotoDbInsert>> => {
const photo = convertFormDataToPhotoDbInsert(...args); const photo = convertFormDataToPhotoDbInsert(...args);
if (photo.recipeData && !photo.recipeTitle && photo.filmSimulation) { if (photo.recipeData && !photo.recipeTitle && photo.film) {
const recipeTitle = await getRecipeTitleForData( const recipeTitle = await getRecipeTitleForData(
photo.recipeData, photo.recipeData,
photo.filmSimulation, photo.film,
); );
if (recipeTitle) { if (recipeTitle) {
photo.recipeTitle = recipeTitle; photo.recipeTitle = recipeTitle;
@ -229,12 +229,12 @@ export const propagateRecipeTitleIfNecessary = async (
formData.get('recipeTitle') && formData.get('recipeTitle') &&
photo.recipeTitle && photo.recipeTitle &&
photo.recipeData && photo.recipeData &&
photo.filmSimulation photo.film
) { ) {
await updateAllMatchingRecipeTitles( await updateAllMatchingRecipeTitles(
photo.recipeTitle, photo.recipeTitle,
photo.recipeData, photo.recipeData,
photo.filmSimulation, photo.film,
); );
} }
}; };

View File

@ -222,8 +222,8 @@ const ALL_POSSIBLE_FILM_SIMULATION_LABELS = Object
large.toLocaleLowerCase(), large.toLocaleLowerCase(),
]); ]);
export const isStringFilmSimulation = (simulation: string) => export const isStringFilmSimulation = (film: string) =>
ALL_POSSIBLE_FILM_SIMULATION_LABELS.includes(simulation.toLocaleLowerCase()); ALL_POSSIBLE_FILM_SIMULATION_LABELS.includes(film.toLocaleLowerCase());
export const labelForFilm = (film: FujifilmSimulation) => export const labelForFilm = (film: FujifilmSimulation) =>
FILM_SIMULATION_LABELS[film]; FILM_SIMULATION_LABELS[film];

View File

@ -1,143 +0,0 @@
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { FilmSimulation } from '@/film';
import PhotoFilm from '@/film/PhotoFilm';
import clsx from 'clsx/lite';
import { ReactNode, RefObject } from 'react';
import { addSign, formatWhiteBalance } from '.';
export default function PhotoRecipeOGTile({
recipe,
simulation,
iso,
exposure,
}: {
ref?: RefObject<HTMLDivElement | null>
recipe: FujifilmRecipe
simulation: FilmSimulation
iso?: string
exposure?: string
}) {
const {
dynamicRange,
whiteBalance,
highISONoiseReduction,
noiseReductionBasic,
highlight,
shadow,
color,
sharpness,
clarity,
colorChromeEffect,
colorChromeFXBlue,
grainEffect,
bwAdjustment,
bwMagentaGreen,
} = recipe;
const whiteBalanceTypeFormatted = formatWhiteBalance(recipe);
const renderRow = (children: ReactNode, className?: string) =>
<div className={clsx(
'flex gap-2 *:w-full *:grow',
className,
)}>
{children}
</div>;
const renderDataSquare = (
value: ReactNode,
label?: string,
className?: string,
) => (
<div className={clsx(
'flex flex-col items-center justify-center gap-0.5 rounded-md min-w-0',
'rounded-md border',
'border-transparent',
'bg-neutral-100/60',
label && 'p-1',
className,
)}>
<div className="flex truncate max-w-full tracking-wide text-lg">
{typeof value === 'number' ? addSign(value) : value}
</div>
{label && <div className={clsx(
'text-[11px] leading-none tracking-wide font-medium text-black/50',
'uppercase',
)}>
{label}
</div>}
</div>
);
return (
<div
className={clsx(
'flex z-10',
'w-[37rem] p-10 aspect-video',
'text-[13.5px] text-black',
'bg-white/50',
'backdrop-blur-xl saturate-[300%]',
)}
>
<div className="flex flex-col gap-2 w-full">
{renderRow(<>
<div className={clsx(
'flex',
'text-lg leading-none text-black truncate',
)}>
KODAK PORTRA 500
</div>
<PhotoFilm
contrast="frosted"
simulation={simulation}
className="w-auto! grow-0!"
/>
</>, 'flex items-center gap-4')}
{renderRow(<>
{renderDataSquare(`DR${dynamicRange.development}`)}
{renderDataSquare(iso)}
{renderDataSquare(exposure ?? '0ev')}
</>)}
{renderRow(<>
{renderDataSquare(
whiteBalanceTypeFormatted.toUpperCase(),
`R${addSign(whiteBalance?.red)} / B${addSign(whiteBalance?.blue)}`,
)}
{renderDataSquare(
highISONoiseReduction ?? noiseReductionBasic ?? 'OFF',
'ISO NR',
)}
{renderDataSquare(highlight, 'Highlight')}
{renderDataSquare(shadow, 'Shadow')}
</>)}
{renderRow(<>
{renderDataSquare(color, 'Color')}
{renderDataSquare(sharpness, 'Sharpness')}
{renderDataSquare(clarity, 'Clarity')}
</>)}
{renderRow(<>
{renderDataSquare(
colorChromeEffect?.toLocaleUpperCase() ?? 'N/A',
'Color Chrome',
)}
{renderDataSquare(
colorChromeFXBlue?.toLocaleUpperCase() ?? 'N/A',
'FX Blue',
)}
</>)}
{renderRow(<>
{renderDataSquare(
grainEffect.roughness.toLocaleUpperCase(),
grainEffect.size === 'large'
? 'Large Grain'
: grainEffect.size === 'small'
? 'Small Grain'
: 'Grain',
)}
{renderDataSquare(bwAdjustment ?? 0, 'BW ADJ')}
{renderDataSquare(bwMagentaGreen ?? 0, 'BW M/G')}
</>)}
</div>
</div>
);
}

View File

@ -25,7 +25,7 @@ export default function PhotoRecipeOverlay({
ref, ref,
title, title,
recipe, recipe,
simulation, film,
onClose, onClose,
}: RecipeProps & { }: RecipeProps & {
ref?: RefObject<HTMLDivElement | null> ref?: RefObject<HTMLDivElement | null>
@ -122,7 +122,7 @@ export default function PhotoRecipeOverlay({
label={`${title label={`${title
? `${formatRecipe(title).toLocaleUpperCase()} recipe` ? `${formatRecipe(title).toLocaleUpperCase()} recipe`
: 'Recipe'}`} : 'Recipe'}`}
text={generateRecipeText({ title, recipe, simulation }).join('\n')} text={generateRecipeText({ title, recipe, film }).join('\n')}
iconSize={17} iconSize={17}
className={clsx( className={clsx(
'translate-y-[0.5px]', 'translate-y-[0.5px]',
@ -147,10 +147,10 @@ export default function PhotoRecipeOverlay({
<div className="col-span-8"> <div className="col-span-8">
{renderDataSquare( {renderDataSquare(
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
{labelForFilm(simulation).medium.toLocaleUpperCase()} {labelForFilm(film).medium.toLocaleUpperCase()}
<PhotoFilm <PhotoFilm
contrast="frosted" contrast="frosted"
simulation={simulation} film={film}
type="icon-only" type="icon-only"
className="opacity-80 translate-y-[-0.5px]" className="opacity-80 translate-y-[-0.5px]"
/> />

View File

@ -32,11 +32,11 @@ export default function RecipeHeader({
contrast="high" contrast="high"
recipeOnClick={() => ( recipeOnClick={() => (
photo?.recipeData && photo?.recipeData &&
photo?.filmSimulation photo?.film
) ? setRecipeModalProps?.({ ) ? setRecipeModalProps?.({
title: photo.recipeTitle, title: photo.recipeTitle,
recipe: photo.recipeData, recipe: photo.recipeData,
simulation: photo.filmSimulation, film: photo.film,
iso: photo.isoFormatted, iso: photo.isoFormatted,
exposure: photo.exposureTimeFormatted, exposure: photo.exposureTimeFormatted,
}) })

View File

@ -20,7 +20,7 @@ export type Recipes = RecipeWithCount[]
export interface RecipeProps { export interface RecipeProps {
title?: string title?: string
recipe: FujifilmRecipe recipe: FujifilmRecipe
simulation: FilmSimulation film: FilmSimulation
iso?: string iso?: string
exposure?: string exposure?: string
} }
@ -57,12 +57,12 @@ export const descriptionForRecipePhotos = (
export const generateRecipeText = ({ export const generateRecipeText = ({
title, title,
recipe, recipe,
simulation, film,
}: RecipeProps, }: RecipeProps,
abbreviate?: boolean, abbreviate?: boolean,
) => { ) => {
const lines = [ const lines = [
`${labelForFilm(simulation).small.toLocaleUpperCase()}`, `${labelForFilm(film).small.toLocaleUpperCase()}`,
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
`${formatWhiteBalance(recipe).toLocaleUpperCase()} ${formatWhiteBalanceColor(recipe)}`, `${formatWhiteBalance(recipe).toLocaleUpperCase()} ${formatWhiteBalanceColor(recipe)}`,
]; ];
@ -133,7 +133,7 @@ export const generateMetaForRecipe = (
}); });
const photoHasRecipe = (photo?: Photo) => const photoHasRecipe = (photo?: Photo) =>
photo?.filmSimulation && photo?.recipeData; photo?.film && photo?.recipeData;
export const getPhotoWithRecipeFromPhotos = ( export const getPhotoWithRecipeFromPhotos = (
photos: Photo[], photos: Photo[],

View File

@ -20,7 +20,7 @@ export default function ShareModals() {
camera, camera,
lens, lens,
tag, tag,
film: simulation, film,
recipe, recipe,
focal, focal,
} = shareModalProps; } = shareModalProps;
@ -30,7 +30,7 @@ export default function ShareModals() {
photo, photo,
tag, tag,
camera, camera,
film: simulation, film,
recipe, recipe,
focal, focal,
}} />; }} />;
@ -42,8 +42,8 @@ export default function ShareModals() {
return <CameraShareModal {...{ camera, ...attributes }} />; return <CameraShareModal {...{ camera, ...attributes }} />;
} else if (lens) { } else if (lens) {
return <LensShareModal {...{ lens, ...attributes }} />; return <LensShareModal {...{ lens, ...attributes }} />;
} else if (simulation) { } else if (film) {
return <FilmShareModal {...{ simulation, ...attributes }} />; return <FilmShareModal {...{ film, ...attributes }} />;
} else if (recipe) { } else if (recipe) {
return <RecipeShareModal {...{ recipe, ...attributes }} />; return <RecipeShareModal {...{ recipe, ...attributes }} />;
} else if (focal !== undefined) { } else if (focal !== undefined) {

View File

@ -2,7 +2,7 @@ import { Photo } from '@/photo';
import { PhotoSetAttributes, PhotoSetCategory } from '@/category'; import { PhotoSetAttributes, PhotoSetCategory } from '@/category';
import { import {
absolutePathForCameraImage, absolutePathForCameraImage,
absolutePathForFilmSimulationImage, absolutePathForFilmImage,
absolutePathForFocalLengthImage, absolutePathForFocalLengthImage,
absolutePathForLensImage, absolutePathForLensImage,
absolutePathForPhotoImage, absolutePathForPhotoImage,
@ -21,7 +21,7 @@ export const getSharePathFromShareModalProps = ({
lens, lens,
tag, tag,
recipe, recipe,
film: simulation, film,
focal, focal,
}: ShareModalProps) => { }: ShareModalProps) => {
if (photo) { if (photo) {
@ -34,8 +34,8 @@ export const getSharePathFromShareModalProps = ({
return absolutePathForTagImage(tag); return absolutePathForTagImage(tag);
} else if (recipe) { } else if (recipe) {
return absolutePathForRecipeImage(recipe); return absolutePathForRecipeImage(recipe);
} else if (simulation) { } else if (film) {
return absolutePathForFilmSimulationImage(simulation); return absolutePathForFilmImage(film);
} else if (focal) { } else if (focal) {
return absolutePathForFocalLengthImage(focal); return absolutePathForFocalLengthImage(focal);
} }