diff --git a/__tests__/path.test.ts b/__tests__/path.test.ts index 356e478e..9c8f8c56 100644 --- a/__tests__/path.test.ts +++ b/__tests__/path.test.ts @@ -1,11 +1,10 @@ -/* eslint-disable max-len */ import { getEscapePath, getPathComponents, isPathCamera, isPathCameraPhoto, - isPathFilmSimulation, - isPathFilmSimulationPhoto, + isPathFilm, + isPathFilmPhoto, isPathFocalLength, isPathFocalLengthPhoto, isPathPhoto, @@ -20,34 +19,34 @@ const TAG = 'tag-name'; const CAMERA_MAKE = 'fujifilm'; const CAMERA_MODEL = 'x-t1'; const CAMERA_OBJECT = { make: CAMERA_MAKE, model: CAMERA_MODEL }; -const FILM_SIMULATION = 'acros'; +const FILM = 'acros'; const FOCAL_LENGTH = 90; const FOCAL_LENGTH_STRING = `${FOCAL_LENGTH}mm`; -const PATH_ROOT = '/'; -const PATH_GRID = '/grid'; -const PATH_FEED = '/feed'; -const PATH_ADMIN = '/admin/photos'; -const PATH_OG = '/og'; -const PATH_OG_ALL = `${PATH_OG}/all`; -const PATH_OG_SAMPLE = `${PATH_OG}/sample`; +const PATH_ROOT = '/'; +const PATH_GRID = '/grid'; +const PATH_FEED = '/feed'; +const PATH_ADMIN = '/admin/photos'; +const PATH_OG = '/og'; +const PATH_OG_ALL = `${PATH_OG}/all`; +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_PHOTO = `${PATH_TAG}/${PHOTO_ID}`; +const PATH_TAG = `/tag/${TAG}`; +const PATH_TAG_PHOTO = `${PATH_TAG}/${PHOTO_ID}`; -const PATH_TAG_HIDDEN = `/tag/${TAG_HIDDEN}`; -const PATH_TAG_HIDDEN_PHOTO = `${PATH_TAG_HIDDEN}/${PHOTO_ID}`; +const PATH_TAG_HIDDEN = `/tag/${TAG_HIDDEN}`; +const PATH_TAG_HIDDEN_PHOTO = `${PATH_TAG_HIDDEN}/${PHOTO_ID}`; -const PATH_CAMERA = `/shot-on/${CAMERA_MAKE}/${CAMERA_MODEL}`; -const PATH_CAMERA_PHOTO = `${PATH_CAMERA}/${PHOTO_ID}`; +const PATH_CAMERA = `/shot-on/${CAMERA_MAKE}/${CAMERA_MODEL}`; +const PATH_CAMERA_PHOTO = `${PATH_CAMERA}/${PHOTO_ID}`; -const PATH_FILM_SIMULATION = `/film/${FILM_SIMULATION}`; -const PATH_FILM_SIMULATION_PHOTO = `${PATH_FILM_SIMULATION}/${PHOTO_ID}`; +const PATH_FILM = `/film/${FILM}`; +const PATH_FILM_PHOTO = `${PATH_FILM}/${PHOTO_ID}`; -const PATH_FOCAL_LENGTH = `/focal/${FOCAL_LENGTH_STRING}`; -const PATH_FOCAL_LENGTH_PHOTO = `${PATH_FOCAL_LENGTH}/${PHOTO_ID}`; +const PATH_FOCAL_LENGTH = `/focal/${FOCAL_LENGTH_STRING}`; +const PATH_FOCAL_LENGTH_PHOTO = `${PATH_FOCAL_LENGTH}/${PHOTO_ID}`; describe('Paths', () => { it('can be protected', () => { @@ -57,7 +56,7 @@ describe('Paths', () => { expect(isPathProtected(PATH_TAG)).toBe(false); expect(isPathProtected(PATH_TAG_PHOTO)).toBe(false); expect(isPathProtected(PATH_CAMERA)).toBe(false); - expect(isPathProtected(PATH_FILM_SIMULATION)).toBe(false); + expect(isPathProtected(PATH_FILM)).toBe(false); // Private expect(isPathProtected(PATH_ADMIN)).toBe(true); expect(isPathProtected(PATH_OG)).toBe(true); @@ -73,13 +72,13 @@ describe('Paths', () => { expect(isPathTagPhoto(PATH_TAG_PHOTO)).toBe(true); expect(isPathCamera(PATH_CAMERA)).toBe(true); expect(isPathCameraPhoto(PATH_CAMERA_PHOTO)).toBe(true); - expect(isPathFilmSimulation(PATH_FILM_SIMULATION)).toBe(true); - expect(isPathFilmSimulationPhoto(PATH_FILM_SIMULATION_PHOTO)).toBe(true); + expect(isPathFilm(PATH_FILM)).toBe(true); + expect(isPathFilmPhoto(PATH_FILM_PHOTO)).toBe(true); expect(isPathFocalLength(PATH_FOCAL_LENGTH)).toBe(true); expect(isPathFocalLengthPhoto(PATH_FOCAL_LENGTH_PHOTO)).toBe(true); // Negative - expect(isPathFocalLength(PATH_FILM_SIMULATION)).toBe(false); - expect(isPathFocalLengthPhoto(PATH_FILM_SIMULATION_PHOTO)).toBe(false); + expect(isPathFocalLength(PATH_FILM)).toBe(false); + expect(isPathFocalLengthPhoto(PATH_FILM_PHOTO)).toBe(false); }); it('can be parsed', () => { // Core @@ -103,13 +102,13 @@ describe('Paths', () => { photoId: PHOTO_ID, camera: CAMERA_OBJECT, }); - // Film Simulation - expect(getPathComponents(PATH_FILM_SIMULATION)).toEqual({ - simulation: FILM_SIMULATION, + // Film + expect(getPathComponents(PATH_FILM)).toEqual({ + film: FILM, }); - expect(getPathComponents(PATH_FILM_SIMULATION_PHOTO)).toEqual({ + expect(getPathComponents(PATH_FILM_PHOTO)).toEqual({ photoId: PHOTO_ID, - simulation: FILM_SIMULATION, + film: FILM, }); // Focal Length expect(getPathComponents(PATH_FOCAL_LENGTH)).toEqual({ @@ -134,9 +133,9 @@ describe('Paths', () => { // Camera expect(getEscapePath(PATH_CAMERA)).toEqual(PATH_ROOT); expect(getEscapePath(PATH_CAMERA_PHOTO)).toEqual(PATH_CAMERA); - // Film Simulation - expect(getEscapePath(PATH_FILM_SIMULATION)).toEqual(PATH_ROOT); - expect(getEscapePath(PATH_FILM_SIMULATION_PHOTO)).toEqual(PATH_FILM_SIMULATION); + // Film + expect(getEscapePath(PATH_FILM)).toEqual(PATH_ROOT); + expect(getEscapePath(PATH_FILM_PHOTO)).toEqual(PATH_FILM); // Focal Length expect(getEscapePath(PATH_FOCAL_LENGTH)).toEqual(PATH_ROOT); expect(getEscapePath(PATH_FOCAL_LENGTH_PHOTO)).toEqual(PATH_FOCAL_LENGTH); diff --git a/app/admin/baseline/page.tsx b/app/admin/baseline/page.tsx index 53a95c01..099c4dd6 100644 --- a/app/admin/baseline/page.tsx +++ b/app/admin/baseline/page.tsx @@ -7,7 +7,7 @@ import FieldSetWithStatus from '@/components/FieldSetWithStatus'; import AppGrid from '@/components/AppGrid'; import EntityLink from '@/components/primitives/EntityLink'; import LabeledIcon from '@/components/primitives/LabeledIcon'; -import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon'; +import PhotoFilmIcon from '@/film/PhotoFilmIcon'; import { useAppState } from '@/state/AppState'; import { clsx } from 'clsx/lite'; import { useEffect, useState } from 'react'; @@ -129,7 +129,7 @@ export default function ComponentsPage() {
} + icon={} label="Astia/Soft" type="icon-last" iconWide @@ -149,7 +149,7 @@ export default function ComponentsPage() {
} + icon={} label="Astia/Soft" type="icon-last" iconWide @@ -184,7 +184,7 @@ export default function ComponentsPage() {
} + icon={} label="Astia/Soft" type="icon-last" iconWide diff --git a/app/admin/recipes/[recipe]/edit/page.tsx b/app/admin/recipes/[recipe]/edit/page.tsx index 4c85519a..c9db0fbd 100644 --- a/app/admin/recipes/[recipe]/edit/page.tsx +++ b/app/admin/recipes/[recipe]/edit/page.tsx @@ -32,7 +32,7 @@ export default async function RecipePageEdit({ const { recipeData, - filmSimulation, + film, } = getPhotoWithRecipeFromPhotos(photos) ?? {}; if (count === 0) { redirect(PATH_ADMIN); } @@ -42,11 +42,11 @@ export default async function RecipePageEdit({ backPath={PATH_ADMIN_RECIPES} backLabel="Recipes" breadcrumb={} - accessory={recipeData && filmSimulation && + accessory={recipeData && film && } > diff --git a/app/admin/uploads/[uploadPath]/page.tsx b/app/admin/uploads/[uploadPath]/page.tsx index 7ed68f0c..d55b9912 100644 --- a/app/admin/uploads/[uploadPath]/page.tsx +++ b/app/admin/uploads/[uploadPath]/page.tsx @@ -10,7 +10,7 @@ import { } from '@/app/config'; import ErrorNote from '@/components/ErrorNote'; import { getRecipeTitleForData } from '@/photo/db/query'; -import { FilmSimulation } from '@/simulation'; +import { FilmSimulation } from '@/film'; export const maxDuration = 60; @@ -49,10 +49,10 @@ export default async function UploadPage({ params }: Params) { ] = await Promise.all([ getUniqueTagsCached(), getUniqueRecipesCached(), - formDataFromExif?.recipeData && formDataFromExif.filmSimulation + formDataFromExif?.recipeData && formDataFromExif.film ? getRecipeTitleForData( formDataFromExif.recipeData, - formDataFromExif.filmSimulation as FilmSimulation, + formDataFromExif.film as FilmSimulation, ) : undefined, ]); diff --git a/app/film-demo/animate/page.tsx b/app/film-demo/animate/page.tsx index 841140d9..0542cda2 100644 --- a/app/film-demo/animate/page.tsx +++ b/app/film-demo/animate/page.tsx @@ -5,7 +5,7 @@ import { clsx } from 'clsx/lite'; import { FILM_SIMULATION_FORM_INPUT_OPTIONS, } from '@/platforms/fujifilm/simulation'; -import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; +import PhotoFilm from '@/film/PhotoFilm'; import { useEffect, useState } from 'react'; export default function FilmPage() { @@ -27,8 +27,8 @@ export default function FilmPage() {
Film Simulation:
-
diff --git a/app/film-demo/page.tsx b/app/film-demo/page.tsx index cb5724c1..972e4143 100644 --- a/app/film-demo/page.tsx +++ b/app/film-demo/page.tsx @@ -1,15 +1,15 @@ import { FILM_SIMULATION_FORM_INPUT_OPTIONS, } from '@/platforms/fujifilm/simulation'; -import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; +import PhotoFilm from '@/film/PhotoFilm'; export default function FilmPage() { return (
{FILM_SIMULATION_FORM_INPUT_OPTIONS.map(({ value }) =>
-
)} diff --git a/app/film/[simulation]/[photoId]/page.tsx b/app/film/[film]/[photoId]/page.tsx similarity index 62% rename from app/film/[simulation]/[photoId]/page.tsx rename to app/film/[film]/[photoId]/page.tsx index 0566cd4f..f20b5942 100644 --- a/app/film/[simulation]/[photoId]/page.tsx +++ b/app/film/[film]/[photoId]/page.tsx @@ -11,7 +11,7 @@ import { absolutePathForPhotoImage, } from '@/app/paths'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; -import { FilmSimulation } from '@/simulation'; +import { FilmSimulation } from '@/film'; import { getPhotosMetaCached, getPhotosNearIdCached, @@ -20,30 +20,30 @@ import { cache } from 'react'; const getPhotosNearIdCachedCached = cache(( photoId: string, - simulation: FilmSimulation, + film: FilmSimulation, ) => getPhotosNearIdCached( photoId, - { simulation, limit: RELATED_GRID_PHOTOS_TO_SHOW + 2 }, + { film, limit: RELATED_GRID_PHOTOS_TO_SHOW + 2 }, )); -interface PhotoFilmSimulationProps { - params: Promise<{ photoId: string, simulation: FilmSimulation }> +interface PhotoFilmProps { + params: Promise<{ photoId: string, film: FilmSimulation }> } export async function generateMetadata({ params, -}: PhotoFilmSimulationProps): Promise { - const { photoId, simulation } = await params; +}: PhotoFilmProps): Promise { + const { photoId, film } = await params; - const { photo } = await getPhotosNearIdCachedCached(photoId, simulation); + const { photo } = await getPhotosNearIdCachedCached(photoId, film); if (!photo) { return {}; } const title = titleForPhoto(photo); const description = descriptionForPhoto(photo); const images = absolutePathForPhotoImage(photo); - const url = absolutePathForPhoto({ photo, simulation }); + const url = absolutePathForPhoto({ photo, film: film }); return { title, @@ -63,24 +63,24 @@ export async function generateMetadata({ }; } -export default async function PhotoFilmSimulationPage({ +export default async function PhotoFilmPage({ params, -}: PhotoFilmSimulationProps) { - const { photoId, simulation } = await params; +}: PhotoFilmProps) { + const { photoId, film } = await params; const { photo, photos, photosGrid, indexNumber } = - await getPhotosNearIdCachedCached(photoId, simulation); + await getPhotosNearIdCachedCached(photoId, film); if (!photo) { redirect(PATH_ROOT); } - const { count, dateRange } = await getPhotosMetaCached({ simulation }); + const { count, dateRange } = await getPhotosMetaCached({ film: film }); return ( simulations.map(({ simulation }) => ({ simulation })), + getUniqueFilms, + films => films.map(({ film }) => ({ film })), ); export async function GET( _: Request, - context: { params: Promise<{ simulation: FilmSimulation }> }, + context: { params: Promise<{ film: FilmSimulation }> }, ) { - const { simulation } = await context.params; + const { film } = await context.params; const [ photos, { fontFamily, fonts }, headers, ] = await Promise.all([ - getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY, simulation }), + getPhotosCached({ + limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY, + film: film, + }), getIBMPlexMono(), getImageResponseCacheControlHeaders(), ]); @@ -38,8 +41,8 @@ export async function GET( const { width, height } = IMAGE_OG_DIMENSION_SMALL; return new ImageResponse( - simulations.map(({ simulation }) => ({ simulation })), + getUniqueFilms, + films => films.map(({ film }) => ({ film })), ); -interface FilmSimulationProps { - params: Promise<{ simulation: FilmSimulation }> +interface FilmProps { + params: Promise<{ film: FilmSimulation }> } export async function generateMetadata({ params, -}: FilmSimulationProps): Promise { - const { simulation } = await params; +}: FilmProps): Promise { + const { film } = await params; const [ photos, { count, dateRange }, - ] = await getPhotosFilmSimulationDataCachedCached({ - simulation, + ] = await getPhotosFilmDataCachedCached({ + film, limit: INFINITE_SCROLL_GRID_INITIAL, }); @@ -43,7 +43,7 @@ export async function generateMetadata({ title, description, images, - } = generateMetaForFilmSimulation(simulation, photos, count, dateRange); + } = generateMetaForFilm(film, photos, count, dateRange); return { title, @@ -62,24 +62,24 @@ export async function generateMetadata({ }; } -export default async function FilmSimulationPage({ +export default async function FilmPage({ params, -}: FilmSimulationProps) { - const { simulation } = await params; +}: FilmProps) { + const { film } = await params; const [ photos, { count, dateRange }, - ] = await getPhotosFilmSimulationDataCachedCached({ - simulation, + ] = await getPhotosFilmDataCachedCached({ + film, limit: INFINITE_SCROLL_GRID_INITIAL, }); if (photos.length === 0) { redirect(PATH_ROOT); } return ( - photos[0]) @@ -32,7 +32,7 @@ export default async function OGOverviewPage() { .catch(() => []), getPhotosCached({ limit: 1, camera }) .catch(() => []), - getPhotosCached({ limit: 1, simulation }) + getPhotosCached({ limit: 1, film }) .catch(() => []), getPhotosCached({ limit: 1, focal }) .catch(() => []), @@ -45,7 +45,7 @@ export default async function OGOverviewPage() { - +
); diff --git a/app/page.tsx b/app/page.tsx index 39e9797d..f738529f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -35,7 +35,7 @@ export default async function HomePage() { lenses, tags, recipes, - simulations, + films, focalLengths, ] = await Promise.all([ getPhotosCached() @@ -59,7 +59,7 @@ export default async function HomePage() { lenses, tags, recipes, - simulations, + films, focalLengths, }} /> diff --git a/src/admin/AdminShowRecipeButton.tsx b/src/admin/AdminShowRecipeButton.tsx index 52c9f66d..bfe97689 100644 --- a/src/admin/AdminShowRecipeButton.tsx +++ b/src/admin/AdminShowRecipeButton.tsx @@ -9,11 +9,11 @@ import { TbChecklist } from 'react-icons/tb'; export default function AdminShowRecipeButton({ title, recipe, - simulation, + film, }: { title: string recipe: FujifilmRecipe - simulation: FujifilmSimulation + film: FujifilmSimulation }) { const { setRecipeModalProps } = useAppState(); @@ -26,7 +26,7 @@ export default function AdminShowRecipeButton({ onClick={() => setRecipeModalProps?.({ title, recipe, - simulation, + film, })} > Preview diff --git a/src/admin/insights/AdminAppInsights.tsx b/src/admin/insights/AdminAppInsights.tsx index ac11d13c..8595a9d0 100644 --- a/src/admin/insights/AdminAppInsights.tsx +++ b/src/admin/insights/AdminAppInsights.tsx @@ -1,7 +1,7 @@ import { getPhotosMeta, getUniqueCameras, - getUniqueFilmSimulations, + getUniqueFilms, getUniqueFocalLengths, getUniqueLenses, getUniqueRecipes, @@ -34,7 +34,7 @@ export default async function AdminAppInsights() { lenses, tags, recipes, - filmSimulations, + films, focalLengths, ] = await Promise.all([ getPhotosMeta({ hidden: 'include' }), @@ -46,7 +46,7 @@ export default async function AdminAppInsights() { getUniqueLenses(), getUniqueTags(), getUniqueRecipes(), - getUniqueFilmSimulations(), + getUniqueFilms(), getUniqueFocalLengths(), ]); @@ -94,7 +94,7 @@ export default async function AdminAppInsights() { lensesCount: lenses.length, tagsCount: tags.length, recipesCount: recipes.length, - filmSimulationsCount: filmSimulations.length, + filmsCount: films.length, focalLengthsCount: focalLengths.length, dateRange, }} diff --git a/src/admin/insights/AdminAppInsightsClient.tsx b/src/admin/insights/AdminAppInsightsClient.tsx index 4e2a7a99..da633400 100644 --- a/src/admin/insights/AdminAppInsightsClient.tsx +++ b/src/admin/insights/AdminAppInsightsClient.tsx @@ -41,7 +41,7 @@ import ScoreCardContainer from '@/components/ScoreCardContainer'; import IconLens from '@/components/icons/IconLens'; import IconCamera from '@/components/icons/IconCamera'; import IconRecipe from '@/components/icons/IconRecipe'; -import IconFilmSimulation from '@/components/icons/IconFilmSimulation'; +import IconFilm from '@/components/icons/IconFilm'; import IconFocalLength from '@/components/icons/IconFocalLength'; import IconTag from '@/components/icons/IconTag'; import IconPhoto from '@/components/icons/IconPhoto'; @@ -96,7 +96,7 @@ export default function AdminAppInsightsClient({ lensesCount, tagsCount, recipesCount, - filmSimulationsCount, + filmsCount, focalLengthsCount, dateRange, }, @@ -487,11 +487,11 @@ export default function AdminAppInsightsClient({ /> : null; case 'films': - return filmSimulationsCount > 0 + return filmsCount > 0 ? } - content={pluralize(filmSimulationsCount, 'film simulation')} + icon={} + content={pluralize(filmsCount, 'film')} /> : null; case 'focal-lengths': diff --git a/src/admin/insights/index.ts b/src/admin/insights/index.ts index 1a3edc9f..e6716a10 100644 --- a/src/admin/insights/index.ts +++ b/src/admin/insights/index.ts @@ -53,7 +53,7 @@ export interface PhotoStats { lensesCount: number tagsCount: number recipesCount: number - filmSimulationsCount: number + filmsCount: number focalLengthsCount: number dateRange?: PhotoDateRange } diff --git a/src/app/config.ts b/src/app/config.ts index e899ec7e..8ba47615 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -249,7 +249,7 @@ export const SHOW_TAGS = CATEGORY_VISIBILITY.includes('tags'); export const SHOW_RECIPES = CATEGORY_VISIBILITY.includes('recipes'); -export const SHOW_FILM_SIMULATIONS = +export const SHOW_FILMS = CATEGORY_VISIBILITY.includes('films'); export const SHOW_FOCAL_LENGTHS = CATEGORY_VISIBILITY.includes('focal-lengths'); diff --git a/src/app/paths.ts b/src/app/paths.ts index 5a42b3c5..311f1da8 100644 --- a/src/app/paths.ts +++ b/src/app/paths.ts @@ -2,7 +2,7 @@ import { Photo } from '@/photo'; import { PhotoSetCategory } from '@/category'; import { BASE_URL, GRID_HOMEPAGE_ENABLED } from './config'; import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; +import { FilmSimulation } from '@/film'; import { parameterize } from '@/utility/string'; import { TAG_HIDDEN } from '@/tag'; import { Lens } from '@/lens'; @@ -26,7 +26,7 @@ export const PREFIX_CAMERA = '/shot-on'; export const PREFIX_LENS = '/lens'; export const PREFIX_TAG = '/tag'; export const PREFIX_RECIPE = '/recipe'; -export const PREFIX_FILM_SIMULATION = '/film'; +export const PREFIX_FILM = '/film'; export const PREFIX_FOCAL_LENGTH = '/focal'; // Dynamic paths @@ -34,8 +34,7 @@ const PATH_PHOTO_DYNAMIC = `${PREFIX_PHOTO}/[photoId]`; const PATH_CAMERA_DYNAMIC = `${PREFIX_CAMERA}/[make]/[model]`; const PATH_LENS_DYNAMIC = `${PREFIX_LENS}/[make]/[model]`; const PATH_TAG_DYNAMIC = `${PREFIX_TAG}/[tag]`; -// eslint-disable-next-line max-len -const PATH_FILM_SIMULATION_DYNAMIC = `${PREFIX_FILM_SIMULATION}/[simulation]`; +const PATH_FILM_DYNAMIC = `${PREFIX_FILM}/[film]`; const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`; const PATH_RECIPE_DYNAMIC = `${PREFIX_RECIPE}/[recipe]`; @@ -86,7 +85,7 @@ export const PATHS_TO_CACHE = [ PATH_CAMERA_DYNAMIC, PATH_LENS_DYNAMIC, PATH_TAG_DYNAMIC, - PATH_FILM_SIMULATION_DYNAMIC, + PATH_FILM_DYNAMIC, PATH_FOCAL_LENGTH_DYNAMIC, PATH_RECIPE_DYNAMIC, ...PATHS_ADMIN, @@ -121,7 +120,7 @@ export const pathForPhoto = ({ camera, lens, tag, - simulation, + film, focal, recipe, }: PhotoPathParams) => { @@ -135,8 +134,8 @@ export const pathForPhoto = ({ prefix = pathForLens(lens); } else if (tag) { prefix = pathForTag(tag); - } else if (simulation) { - prefix = pathForFilmSimulation(simulation); + } else if (film) { + prefix = pathForFilm(film); } else if (recipe) { prefix = pathForRecipe(recipe); } else if (focal) { @@ -152,8 +151,8 @@ export const pathForTag = (tag: string) => export const pathForCamera = ({ make, model }: Camera) => `${PREFIX_CAMERA}/${parameterize(make)}/${parameterize(model)}`; -export const pathForFilmSimulation = (simulation: FilmSimulation) => - `${PREFIX_FILM_SIMULATION}/${simulation}`; +export const pathForFilm = (film: FilmSimulation) => + `${PREFIX_FILM}/${film}`; export const pathForLens = ({ make, model }: Lens) => make @@ -178,8 +177,8 @@ export const absolutePathForCamera= (camera: Camera) => export const absolutePathForLens= (lens: Lens) => `${BASE_URL}${pathForLens(lens)}`; -export const absolutePathForFilmSimulation = (simulation: FilmSimulation) => - `${BASE_URL}${pathForFilmSimulation(simulation)}`; +export const absolutePathForFilm = (film: FilmSimulation) => + `${BASE_URL}${pathForFilm(film)}`; export const absolutePathForRecipe = (recipe: string) => `${BASE_URL}${pathForRecipe(recipe)}`; @@ -199,9 +198,8 @@ export const absolutePathForCameraImage= (camera: Camera) => export const absolutePathForLensImage= (lens: Lens) => `${absolutePathForLens(lens)}/image`; -export const absolutePathForFilmSimulationImage = - (simulation: FilmSimulation) => - `${absolutePathForFilmSimulation(simulation)}/image`; +export const absolutePathForFilmImage = (film: FilmSimulation) => + `${absolutePathForFilm(film)}/image`; export const absolutePathForRecipeImage = (recipe: string) => `${absolutePathForRecipe(recipe)}/image`; @@ -230,13 +228,13 @@ export const isPathCamera = (pathname = '') => export const isPathCameraPhoto = (pathname = '') => new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/[^/]+/?$`).test(pathname); -// film/[simulation] -export const isPathFilmSimulation = (pathname = '') => - new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/?$`).test(pathname); +// film/[film] +export const isPathFilm = (pathname = '') => + new RegExp(`^${PREFIX_FILM}/[^/]+/?$`).test(pathname); -// film/[simulation]/[photoId] -export const isPathFilmSimulationPhoto = (pathname = '') => - new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/[^/]+/?$`).test(pathname); +// film/[film]/[photoId] +export const isPathFilmPhoto = (pathname = '') => + new RegExp(`^${PREFIX_FILM}/[^/]+/[^/]+/?$`).test(pathname); // focal/[focal] export const isPathFocalLength = (pathname = '') => @@ -299,8 +297,8 @@ export const getPathComponents = (pathname = ''): { new RegExp(`^${PREFIX_TAG}/[^/]+/([^/]+)`))?.[1]; const photoIdFromCamera = pathname.match( new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/([^/]+)`))?.[1]; - const photoIdFromFilmSimulation = pathname.match( - new RegExp(`^${PREFIX_FILM_SIMULATION}/[^/]+/([^/]+)`))?.[1]; + const photoIdFromFilm = pathname.match( + new RegExp(`^${PREFIX_FILM}/[^/]+/([^/]+)`))?.[1]; const photoIdFromFocalLength = pathname.match( new RegExp(`^${PREFIX_FOCAL_LENGTH}/[0-9]+mm/([^/]+)`))?.[1]; const tag = pathname.match( @@ -309,8 +307,8 @@ export const getPathComponents = (pathname = ''): { new RegExp(`^${PREFIX_CAMERA}/([^/]+)`))?.[1]; const cameraModel = pathname.match( new RegExp(`^${PREFIX_CAMERA}/[^/]+/([^/]+)`))?.[1]; - const simulation = pathname.match( - new RegExp(`^${PREFIX_FILM_SIMULATION}/([^/]+)`))?.[1] as FilmSimulation; + const film = pathname.match( + new RegExp(`^${PREFIX_FILM}/([^/]+)`))?.[1] as FilmSimulation; const focalString = pathname.match( new RegExp(`^${PREFIX_FOCAL_LENGTH}/([0-9]+)mm`))?.[1]; @@ -325,12 +323,12 @@ export const getPathComponents = (pathname = ''): { photoIdFromPhoto || photoIdFromTag || photoIdFromCamera || - photoIdFromFilmSimulation || + photoIdFromFilm || photoIdFromFocalLength ), tag, camera, - simulation, + film, focal, }; }; @@ -340,7 +338,7 @@ export const getEscapePath = (pathname?: string) => { photoId, tag, camera, - simulation, + film, focal, } = getPathComponents(pathname); @@ -348,7 +346,7 @@ export const getEscapePath = (pathname?: string) => { (photoId && isPathPhoto(pathname)) || (tag && isPathTag(pathname)) || (camera && isPathCamera(pathname)) || - (simulation && isPathFilmSimulation(pathname)) || + (film && isPathFilm(pathname)) || (focal && isPathFocalLength(pathname)) ) { return PATH_ROOT; @@ -356,8 +354,8 @@ export const getEscapePath = (pathname?: string) => { return pathForTag(tag); } else if (camera && isPathCameraPhoto(pathname)) { return pathForCamera(camera); - } else if (simulation && isPathFilmSimulationPhoto(pathname)) { - return pathForFilmSimulation(simulation); + } else if (film && isPathFilmPhoto(pathname)) { + return pathForFilm(film); } else if (focal && isPathFocalLengthPhoto(pathname)) { return pathForFocalLength(focal); } diff --git a/src/category/data.ts b/src/category/data.ts index 7485033c..f4b6e8f3 100644 --- a/src/category/data.ts +++ b/src/category/data.ts @@ -1,13 +1,13 @@ import { getUniqueCameras, - getUniqueFilmSimulations, + getUniqueFilms, getUniqueFocalLengths, getUniqueLenses, getUniqueRecipes, getUniqueTags, } from '@/photo/db/query'; import { - SHOW_FILM_SIMULATIONS, + SHOW_FILMS, SHOW_FOCAL_LENGTHS, SHOW_LENSES, SHOW_RECIPES, @@ -40,8 +40,8 @@ export const getDataForCategories = () => [ .then(sortCategoriesByCount) .catch(() => []) : [], - SHOW_FILM_SIMULATIONS - ? getUniqueFilmSimulations() + SHOW_FILMS + ? getUniqueFilms() .then(sortCategoriesByCount) .catch(() => []) : [], @@ -58,7 +58,7 @@ export const getCountsForCategories = async () => { lenses, tags, recipes, - filmSimulations, + films, focalLengths, ] = await Promise.all(getDataForCategories()); @@ -79,8 +79,8 @@ export const getCountsForCategories = async () => { acc[recipe.recipe] = recipe.count; return acc; }, {} as Record), - filmSimulations: filmSimulations.reduce((acc, filmSimulation) => { - acc[filmSimulation.simulation] = filmSimulation.count; + films: films.reduce((acc, film) => { + acc[film.film] = film.count; return acc; }, {} as Record), focalLengths: focalLengths.reduce((acc, focalLength) => { diff --git a/src/category/index.ts b/src/category/index.ts index acd4f40a..698330cc 100644 --- a/src/category/index.ts +++ b/src/category/index.ts @@ -1,7 +1,7 @@ import { Photo } from '../photo'; import { Camera, Cameras } from '@/camera'; import { PhotoDateRange } from '../photo'; -import { FilmSimulation, FilmSimulations } from '@/simulation'; +import { FilmSimulation, Films } from '@/film'; import { Lens, Lenses } from '@/lens'; import { Tags } from '@/tag'; import { FocalLengths } from '@/focal'; @@ -39,7 +39,7 @@ export interface PhotoSetCategory { lens?: Lens tag?: string recipe?: string - simulation?: FilmSimulation + film?: FilmSimulation focal?: number } @@ -48,7 +48,7 @@ export interface PhotoSetCategories { lenses: Lenses tags: Tags recipes: Recipes - simulations: FilmSimulations + films: Films focalLengths: FocalLengths } @@ -80,11 +80,9 @@ export const sortCategoriesByCount = ( const convertCategoryKeysToCategoryNames = (categoryKeys: CategoryKeys): (keyof PhotoSetCategories)[] => { return categoryKeys.map(key => { - return key === 'films' - ? 'simulations' - : key === 'focal-lengths' - ? 'focalLengths' - : key; + return key === 'focal-lengths' + ? 'focalLengths' + : key; }); }; diff --git a/src/category/useCategoryCounts.ts b/src/category/useCategoryCounts.ts index 75e9bbf6..1d5a02f1 100644 --- a/src/category/useCategoryCounts.ts +++ b/src/category/useCategoryCounts.ts @@ -4,6 +4,7 @@ import { Camera } from '@/camera'; import { Lens } from '@/lens'; import { useAppState } from '@/state/AppState'; import { useCallback } from 'react'; +import { FujifilmSimulation } from '@/platforms/fujifilm/simulation'; export default function useCategoryCounts() { const { categoriesWithCounts } = useAppState(); @@ -28,9 +29,9 @@ export default function useCategoryCounts() { return recipeCounts[recipe]; }, [categoriesWithCounts]); - const getFilmSimulationCount = useCallback((simulation: string) => { - const filmSimulationCounts = categoriesWithCounts?.filmSimulations ?? {}; - return filmSimulationCounts[simulation]; + const getFilmCount = useCallback((film: FujifilmSimulation) => { + const filmCounts = categoriesWithCounts?.films ?? {}; + return filmCounts[film]; }, [categoriesWithCounts]); const getFocalLengthCount = useCallback((focalLength: number) => { @@ -43,7 +44,7 @@ export default function useCategoryCounts() { getLensCount, getTagCount, getRecipeCount, - getFilmSimulationCount, + getFilmCount, getFocalLengthCount, }; } diff --git a/src/category/useCategoryCountsForPhoto.ts b/src/category/useCategoryCountsForPhoto.ts index 86b50620..b059ffdb 100644 --- a/src/category/useCategoryCountsForPhoto.ts +++ b/src/category/useCategoryCountsForPhoto.ts @@ -10,7 +10,7 @@ export default function useCategoryCountsForPhoto(photo: Photo) { getLensCount, getTagCount, getRecipeCount, - getFilmSimulationCount, + getFilmCount, getFocalLengthCount, } = useCategoryCounts(); @@ -25,21 +25,20 @@ export default function useCategoryCountsForPhoto(photo: Photo) { return acc; }, {} as Record), recipeCount: photo.recipeTitle ? getRecipeCount(photo.recipeTitle) : 0, - simulationCount: - photo.filmSimulation ? getFilmSimulationCount(photo.filmSimulation) : 0, + filmCount: photo.film ? getFilmCount(photo.film) : 0, focalCount: photo.focalLength ? getFocalLengthCount(photo.focalLength) : 0, }), [ getCameraCount, getLensCount, getRecipeCount, - getFilmSimulationCount, + getFilmCount, getFocalLengthCount, getTagCount, camera, lens, photo.tags, photo.recipeTitle, - photo.filmSimulation, + photo.film, photo.focalLength, ]); diff --git a/src/app/CommandK.tsx b/src/cmdk/CommandK.tsx similarity index 79% rename from src/app/CommandK.tsx rename to src/cmdk/CommandK.tsx index af1d9413..c1e7f01a 100644 --- a/src/app/CommandK.tsx +++ b/src/cmdk/CommandK.tsx @@ -1,7 +1,7 @@ -import CommandKClient from '@/components/cmdk/CommandKClient'; +import CommandKClient from './CommandKClient'; import { getPhotosMetaCached } from '@/photo/cache'; import { photoQuantityText } from '@/photo'; -import { ADMIN_DEBUG_TOOLS_ENABLED } from './config'; +import { ADMIN_DEBUG_TOOLS_ENABLED } from '../app/config'; import { getDataForCategories } from '@/category/data'; export default async function CommandK() { @@ -11,7 +11,7 @@ export default async function CommandK() { lenses, tags, recipes, - filmSimulations, + films, focalLengths, ] = await Promise.all([ getPhotosMetaCached() @@ -24,7 +24,7 @@ export default async function CommandK() { cameras={cameras} lenses={lenses} tags={tags} - simulations={filmSimulations} + films={films} recipes={recipes} focalLengths={focalLengths} showDebugTools={ADMIN_DEBUG_TOOLS_ENABLED} diff --git a/src/components/cmdk/CommandKClient.tsx b/src/cmdk/CommandKClient.tsx similarity index 95% rename from src/components/cmdk/CommandKClient.tsx rename to src/cmdk/CommandKClient.tsx index 7b3d86bb..aaddd55d 100644 --- a/src/components/cmdk/CommandKClient.tsx +++ b/src/cmdk/CommandKClient.tsx @@ -25,17 +25,17 @@ import { PATH_ROOT, PATH_SIGN_IN, pathForCamera, - pathForFilmSimulation, + pathForFilm, pathForFocalLength, pathForLens, pathForPhoto, pathForRecipe, pathForTag, -} from '../../app/paths'; -import Modal from '../Modal'; +} from '../app/paths'; +import Modal from '../components/Modal'; import { clsx } from 'clsx/lite'; import { useDebounce } from 'use-debounce'; -import Spinner from '../Spinner'; +import Spinner from '../components/Spinner'; import { usePathname, useRouter } from 'next/navigation'; import { useTheme } from 'next-themes'; import { BiDesktop, BiLockAlt, BiMoon, BiSun } from 'react-icons/bi'; @@ -59,20 +59,20 @@ import * as VisuallyHidden from '@radix-ui/react-visually-hidden'; import InsightsIndicatorDot from '@/admin/insights/InsightsIndicatorDot'; import { PhotoSetCategories } from '@/category'; import { formatCameraText } from '@/camera'; -import { labelForFilmSimulation } from '@/platforms/fujifilm/simulation'; +import { labelForFilm } from '@/platforms/fujifilm/simulation'; import { formatFocalLength } from '@/focal'; import { formatRecipe } from '@/recipe'; -import IconLens from '../icons/IconLens'; +import IconLens from '../components/icons/IconLens'; import { formatLensText } from '@/lens'; -import IconTag from '../icons/IconTag'; -import IconCamera from '../icons/IconCamera'; -import IconPhoto from '../icons/IconPhoto'; -import IconRecipe from '../icons/IconRecipe'; -import IconFocalLength from '../icons/IconFocalLength'; -import IconFilmSimulation from '../icons/IconFilmSimulation'; -import IconLock from '../icons/IconLock'; +import IconTag from '../components/icons/IconTag'; +import IconCamera from '../components/icons/IconCamera'; +import IconPhoto from '../components/icons/IconPhoto'; +import IconRecipe from '../components/icons/IconRecipe'; +import IconFocalLength from '../components/icons/IconFocalLength'; +import IconFilm from '../components/icons/IconFilm'; +import IconLock from '../components/icons/IconLock'; import useVisualViewportHeight from '@/utility/useVisualViewport'; -import useMaskedScroll from '../useMaskedScroll'; +import useMaskedScroll from '../components/useMaskedScroll'; const DIALOG_TITLE = 'Global Command-K Menu'; const DIALOG_DESCRIPTION = 'For searching photos, views, and settings'; @@ -112,7 +112,7 @@ export default function CommandKClient({ lenses, tags, recipes, - simulations, + films, focalLengths, showDebugTools, footer, @@ -333,13 +333,13 @@ export default function CommandKClient({ })), }; case 'films': return { - heading: 'Film Simulations', - accessory: , - items: simulations.map(({ simulation, count }) => ({ - label: labelForFilmSimulation(simulation).medium, + heading: 'Films', + accessory: , + items: films.map(({ film, count }) => ({ + label: labelForFilm(film).medium, annotation: formatCount(count), annotationAria: formatCountDescriptive(count), - path: pathForFilmSimulation(simulation), + path: pathForFilm(film), })), }; case 'focal-lengths': return { @@ -355,7 +355,7 @@ export default function CommandKClient({ } }) .filter(Boolean) as CommandKSection[] - , [tagsIncludingHidden, cameras, lenses, recipes, simulations, focalLengths]); + , [tagsIncludingHidden, cameras, lenses, recipes, films, focalLengths]); const clientSections: CommandKSection[] = [{ heading: 'Theme', diff --git a/src/components/cmdk/CommandKItem.tsx b/src/cmdk/CommandKItem.tsx similarity index 97% rename from src/components/cmdk/CommandKItem.tsx rename to src/cmdk/CommandKItem.tsx index 94d34455..907094c0 100644 --- a/src/components/cmdk/CommandKItem.tsx +++ b/src/cmdk/CommandKItem.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx/lite'; import { Command } from 'cmdk'; import { ReactNode } from 'react'; -import Spinner from '../Spinner'; +import Spinner from '../components/Spinner'; export default function CommandKItem({ label, diff --git a/src/components/icons/IconFilmSimulation.tsx b/src/components/icons/IconFilm.tsx similarity index 66% rename from src/components/icons/IconFilmSimulation.tsx rename to src/components/icons/IconFilm.tsx index 033f1571..9a322c4a 100644 --- a/src/components/icons/IconFilmSimulation.tsx +++ b/src/components/icons/IconFilm.tsx @@ -1,6 +1,6 @@ import { IconBaseProps } from 'react-icons'; import { IoFilmOutline } from 'react-icons/io5'; -export default function IconFilmSimulation(props: IconBaseProps) { +export default function IconFilm(props: IconBaseProps) { return ; } diff --git a/src/simulation/FilmSimulationHeader.tsx b/src/film/FilmHeader.tsx similarity index 58% rename from src/simulation/FilmSimulationHeader.tsx rename to src/film/FilmHeader.tsx index 57a20ac2..ee2b14f6 100644 --- a/src/simulation/FilmSimulationHeader.tsx +++ b/src/film/FilmHeader.tsx @@ -1,18 +1,17 @@ import { Photo, PhotoDateRange } from '@/photo'; -import { FilmSimulation, descriptionForFilmSimulationPhotos } from '.'; +import { FilmSimulation, descriptionForFilmPhotos } from '.'; import PhotoHeader from '@/photo/PhotoHeader'; -import PhotoFilmSimulation from - '@/simulation/PhotoFilmSimulation'; +import PhotoFilm from '@/film/PhotoFilm'; -export default function FilmSimulationHeader({ - simulation, +export default function FilmHeader({ + film, photos, selectedPhoto, indexNumber, count, dateRange, }: { - simulation: FilmSimulation + film: FilmSimulation photos: Photo[] selectedPhoto?: Photo indexNumber?: number @@ -21,9 +20,9 @@ export default function FilmSimulationHeader({ }) { return ( } - entityDescription={descriptionForFilmSimulationPhotos( + film={film} + entity={} + entityDescription={descriptionForFilmPhotos( photos, undefined, count, dateRange)} photos={photos} selectedPhoto={selectedPhoto} diff --git a/src/simulation/FilmSimulationOGTile.tsx b/src/film/FilmOGTile.tsx similarity index 60% rename from src/simulation/FilmSimulationOGTile.tsx rename to src/film/FilmOGTile.tsx index c2faf69b..19f7843a 100644 --- a/src/simulation/FilmSimulationOGTile.tsx +++ b/src/film/FilmOGTile.tsx @@ -1,19 +1,19 @@ import { Photo, PhotoDateRange } from '@/photo'; import { - absolutePathForFilmSimulationImage, - pathForFilmSimulation, + absolutePathForFilmImage, + pathForFilm, } from '@/app/paths'; import OGTile from '@/components/OGTile'; import { FilmSimulation, - descriptionForFilmSimulationPhotos, - titleForFilmSimulation, + descriptionForFilmPhotos, + titleForFilm, } from '.'; export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed'; -export default function FilmSimulationOGTile({ - simulation, +export default function FilmOGTile({ + film, photos, loadingState: loadingStateExternal, riseOnHover, @@ -23,7 +23,7 @@ export default function FilmSimulationOGTile({ count, dateRange, }: { - simulation: FilmSimulation + film: FilmSimulation photos: Photo[] loadingState?: OGLoadingState onLoad?: () => void @@ -35,11 +35,11 @@ export default function FilmSimulationOGTile({ }) { return ( + + + ); +}; diff --git a/src/simulation/PhotoFilmSimulation.tsx b/src/film/PhotoFilm.tsx similarity index 61% rename from src/simulation/PhotoFilmSimulation.tsx rename to src/film/PhotoFilm.tsx index 0d26d680..3b6136ac 100644 --- a/src/simulation/PhotoFilmSimulation.tsx +++ b/src/film/PhotoFilm.tsx @@ -1,6 +1,6 @@ -import { labelForFilmSimulation } from '@/platforms/fujifilm/simulation'; -import PhotoFilmSimulationIcon from './PhotoFilmSimulationIcon'; -import { pathForFilmSimulation } from '@/app/paths'; +import { labelForFilm } from '@/platforms/fujifilm/simulation'; +import PhotoFilmIcon from './PhotoFilmIcon'; +import { pathForFilm } from '@/app/paths'; import { FilmSimulation } from '.'; import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; import EntityLink, { @@ -8,28 +8,28 @@ import EntityLink, { } from '@/components/primitives/EntityLink'; import clsx from 'clsx/lite'; -export default function PhotoFilmSimulation({ - simulation, +export default function PhotoFilm({ + film, type = 'icon-last', badged = true, contrast = 'low', countOnHover, ...props }: { - simulation: FilmSimulation + film: FilmSimulation countOnHover?: number recipe?: FujifilmRecipe } & EntityLinkExternalProps) { - const { small, medium, large } = labelForFilmSimulation(simulation); + const { small, medium, large } = labelForFilm(film); return ( } - title={`Film Simulation: ${large}`} + title={`Film: ${large}`} type={type} badged={badged} contrast={contrast} diff --git a/src/simulation/PhotoFilmSimulationIcon.tsx b/src/film/PhotoFilmIcon.tsx similarity index 99% rename from src/simulation/PhotoFilmSimulationIcon.tsx rename to src/film/PhotoFilmIcon.tsx index 9c05d18d..c7db5dbf 100644 --- a/src/simulation/PhotoFilmSimulationIcon.tsx +++ b/src/film/PhotoFilmIcon.tsx @@ -1,18 +1,18 @@ /* eslint-disable max-len */ -import { labelForFilmSimulation } from '@/platforms/fujifilm/simulation'; +import { labelForFilm } from '@/platforms/fujifilm/simulation'; import { CSSProperties } from 'react'; import { FilmSimulation } from '.'; const INTRINSIC_WIDTH = 28; const INTRINSIC_HEIGHT = 16; -export default function PhotoFilmSimulationIcon({ - simulation, +export default function PhotoFilmIcon({ + film, height = INTRINSIC_HEIGHT, className, style, }: { - simulation?: FilmSimulation + film?: FilmSimulation height?: number className?: string style?: CSSProperties @@ -21,9 +21,9 @@ export default function PhotoFilmSimulationIcon({ { // Self-calling switch function and non-fragment groups // necessary for ImageResponse compatibility - switch (simulation) { + switch (film) { case 'monochrome': return diff --git a/src/film/data.ts b/src/film/data.ts new file mode 100644 index 00000000..227f5f9b --- /dev/null +++ b/src/film/data.ts @@ -0,0 +1,17 @@ +import { + getPhotosCached, + getPhotosMetaCached, +} from '@/photo/cache'; +import { FilmSimulation } from '.'; + +export const getPhotosFilmDataCached = ({ + film, + limit, +}: { + film: FilmSimulation, + limit?: number, +}) => + Promise.all([ + getPhotosCached({ film, limit }), + getPhotosMetaCached({ film }), + ]); diff --git a/src/film/index.ts b/src/film/index.ts new file mode 100644 index 00000000..b360e70f --- /dev/null +++ b/src/film/index.ts @@ -0,0 +1,84 @@ +import { + Photo, + PhotoDateRange, + descriptionForPhotoSet, + photoQuantityText, +} from '@/photo'; +import { + absolutePathForFilm, + absolutePathForFilmImage, +} from '@/app/paths'; +import { + FujifilmSimulation, + labelForFilm, +} from '@/platforms/fujifilm/simulation'; + +export type FilmSimulation = FujifilmSimulation; + +export type FilmWithCount = { + film: FilmSimulation + count: number +} + +export type Films = FilmWithCount[] + +export const sortFilms = ( + films: Films, +) => films.sort(sortFilmsWithCount); + +export const sortFilmsWithCount = ( + a: FilmWithCount, + b: FilmWithCount, +) => { + const aLabel = labelForFilm(a.film).large; + const bLabel = labelForFilm(b.film).large; + return aLabel.localeCompare(bLabel); +}; + +export const titleForFilm = ( + film: FilmSimulation, + photos: Photo[], + explicitCount?: number, +) => [ + labelForFilm(film).large, + photoQuantityText(explicitCount ?? photos.length), +].join(' '); + +export const shareTextForFilm = ( + film: FilmSimulation, +) => + `Photos shot on Fujifilm ${labelForFilm(film).large}`; + +export const descriptionForFilmPhotos = ( + photos: Photo[], + dateBased?: boolean, + explicitCount?: number, + explicitDateRange?: PhotoDateRange, +) => + descriptionForPhotoSet( + photos, + undefined, + dateBased, + explicitCount, + explicitDateRange, + ); + +export const generateMetaForFilm = ( + film: FilmSimulation, + photos: Photo[], + explicitCount?: number, + explicitDateRange?: PhotoDateRange, +) => ({ + url: absolutePathForFilm(film), + title: titleForFilm(film, photos, explicitCount), + description: descriptionForFilmPhotos( + photos, + true, + explicitCount, + explicitDateRange, + ), + images: absolutePathForFilmImage(film), +}); + +export const photoHasFilmData = (photo: Photo) => + Boolean(photo.film); diff --git a/src/image-response/FilmSimulationImageResponse.tsx b/src/image-response/FilmImageResponse.tsx similarity index 68% rename from src/image-response/FilmSimulationImageResponse.tsx rename to src/image-response/FilmImageResponse.tsx index b5bee5db..68bb0cf5 100644 --- a/src/image-response/FilmSimulationImageResponse.tsx +++ b/src/image-response/FilmImageResponse.tsx @@ -3,21 +3,21 @@ import ImageCaption from './components/ImageCaption'; import ImagePhotoGrid from './components/ImagePhotoGrid'; import ImageContainer from './components/ImageContainer'; import { - labelForFilmSimulation, + labelForFilm, } from '@/platforms/fujifilm/simulation'; -import PhotoFilmSimulationIcon from - '@/simulation/PhotoFilmSimulationIcon'; -import { FilmSimulation } from '@/simulation'; +import PhotoFilmIcon from + '@/film/PhotoFilmIcon'; +import { FilmSimulation } from '@/film'; import { NextImageSize } from '@/platforms/next-image'; -export default function FilmSimulationImageResponse({ - simulation, +export default function FilmImageResponse({ + film, photos, width, height, fontFamily, }: { - simulation: FilmSimulation, + film: FilmSimulation, photos: Photo[] width: NextImageSize height: number @@ -36,12 +36,12 @@ export default function FilmSimulationImageResponse({ width, height, fontFamily, - icon: , - title: labelForFilmSimulation(simulation).medium.toLocaleUpperCase(), + title: labelForFilm(film).medium.toLocaleUpperCase(), }} /> ); diff --git a/src/image-response/RecipeImageResponse.tsx b/src/image-response/RecipeImageResponse.tsx index 8c858426..f3caddf1 100644 --- a/src/image-response/RecipeImageResponse.tsx +++ b/src/image-response/RecipeImageResponse.tsx @@ -5,7 +5,7 @@ import ImageContainer from './components/ImageContainer'; import type { NextImageSize } from '@/platforms/next-image'; import { formatTag } from '@/tag'; import { generateRecipeText, getPhotoWithRecipeFromPhotos } from '@/recipe'; -import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon'; +import PhotoFilmIcon from '@/film/PhotoFilmIcon'; import { isStringFilmSimulation } from '@/platforms/fujifilm/simulation'; import IconRecipe from '@/components/icons/IconRecipe'; const MAX_RECIPE_LINES = 8; @@ -27,13 +27,13 @@ export default function RecipeImageResponse({ }) { const { recipeData, - filmSimulation, + film, } = getPhotoWithRecipeFromPhotos(photos) ?? {}; - let recipeLines = recipeData && filmSimulation + let recipeLines = recipeData && film ? generateRecipeText({ recipe: recipeData, - simulation: filmSimulation, + film, }, true) : []; @@ -109,10 +109,10 @@ export default function RecipeImageResponse({ flexGrow: 1, }}> {text} - {isStringFilmSimulation(text) && filmSimulation && + {isStringFilmSimulation(text) && film &&
- diff --git a/src/photo/InfinitePhotoScroll.tsx b/src/photo/InfinitePhotoScroll.tsx index b1a2a48e..0b2394b3 100644 --- a/src/photo/InfinitePhotoScroll.tsx +++ b/src/photo/InfinitePhotoScroll.tsx @@ -29,10 +29,12 @@ export default function InfinitePhotoScroll({ initialOffset, itemsPerPage, sortBy, - tag, camera, lens, - simulation, + tag, + recipe, + film, + focal, wrapMoreButtonInGrid, useCachedPhotos = true, includeHiddenPhotos, @@ -73,7 +75,9 @@ export default function InfinitePhotoScroll({ camera, lens, tag, - simulation, + recipe, + film, + focal, }, warmOnly) , [ useCachedPhotos, @@ -84,7 +88,9 @@ export default function InfinitePhotoScroll({ camera, lens, tag, - simulation, + recipe, + film, + focal, ]); const { data, isLoading, isValidating, error, mutate, size, setSize } = diff --git a/src/photo/PhotoDetailPage.tsx b/src/photo/PhotoDetailPage.tsx index 8ca24fce..f67c0414 100644 --- a/src/photo/PhotoDetailPage.tsx +++ b/src/photo/PhotoDetailPage.tsx @@ -6,7 +6,7 @@ import AppGrid from '@/components/AppGrid'; import PhotoGrid from './PhotoGrid'; import TagHeader from '@/tag/TagHeader'; import CameraHeader from '@/camera/CameraHeader'; -import FilmSimulationHeader from '@/simulation/FilmSimulationHeader'; +import FilmHeader from '@/film/FilmHeader'; import { TAG_HIDDEN } from '@/tag'; import HiddenHeader from '@/tag/HiddenHeader'; import FocalLengthHeader from '@/focal/FocalLengthHeader'; @@ -22,7 +22,7 @@ export default function PhotoDetailPage({ tag, camera, lens, - simulation, + film, recipe, focal, indexNumber, @@ -77,9 +77,9 @@ export default function PhotoDetailPage({ count={count} dateRange={dateRange} />; - } else if (simulation) { - customHeader = } diff --git a/src/photo/PhotoGridSidebar.tsx b/src/photo/PhotoGridSidebar.tsx index 54fe1b49..72116450 100644 --- a/src/photo/PhotoGridSidebar.tsx +++ b/src/photo/PhotoGridSidebar.tsx @@ -5,7 +5,7 @@ import HeaderList from '@/components/HeaderList'; import PhotoTag from '@/tag/PhotoTag'; import { PhotoDateRange, dateRangeForPhotos, photoQuantityText } from '.'; import { TAG_FAVS, TAG_HIDDEN, addHiddenToTags } from '@/tag'; -import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; +import PhotoFilm from '@/film/PhotoFilm'; import FavsTag from '../tag/FavsTag'; import { useAppState } from '@/state/AppState'; import { useMemo, useRef } from 'react'; @@ -20,7 +20,7 @@ import PhotoRecipe from '@/recipe/PhotoRecipe'; import IconCamera from '@/components/icons/IconCamera'; import IconRecipe from '@/components/icons/IconRecipe'; import IconTag from '@/components/icons/IconTag'; -import IconFilmSimulation from '@/components/icons/IconFilmSimulation'; +import IconFilm from '@/components/icons/IconFilm'; import IconLens from '@/components/icons/IconLens'; import PhotoLens from '@/lens/PhotoLens'; import IconFocalLength from '@/components/icons/IconFocalLength'; @@ -48,7 +48,7 @@ export default function PhotoGridSidebar({ cameras, lenses, tags, - simulations, + films, recipes, focalLengths, } = categories; @@ -192,17 +192,17 @@ export default function PhotoGridSidebar({ /> : null; - const filmsContent = simulations.length > 0 + const filmsContent = films.length > 0 ? } + icon={} maxItems={maxItemsPerCategory} - items={simulations - .map(({ simulation, count }) => - + 0; const showRecipeContent = showRecipe && shouldShowRecipeDataForPhoto(photo); const showRecipeButton = shouldShowRecipeDataForPhoto(photo); - const showSimulationContent = showSimulation && - shouldShowFilmSimulationDataForPhoto(photo); + const showFilmContent = showFilm && shouldShowFilmDataForPhoto(photo); useVisible({ ref, onVisible }); @@ -168,7 +167,7 @@ export default function PhotoLarge({ showLensContent || showTagsContent || showRecipeContent || - showSimulationContent || + showFilmContent || showExifContent; const hasNonDateContent = @@ -226,12 +225,12 @@ export default function PhotoLarge({ {(shouldShowRecipeOverlay || shouldDebugRecipeOverlays) && photo.recipeData && - photo.filmSimulation && + photo.film && {photo.isoFormatted}
  • {photo.exposureCompensationFormatted ?? '0ev'}
  • - {(showRecipeButton || showSimulationContent) && + {(showRecipeButton || showFilmContent) &&
    - {showSimulationContent && photo.filmSimulation && - } {showRecipeButton && @@ -415,7 +414,7 @@ export default function PhotoLarge({ 'px-[4px] py-[2.5px] my-[-3px]', 'translate-y-[2px]', 'hover:bg-dim active:bg-main', - !showSimulation && 'translate-x-[-2px]', + !showFilm && 'translate-x-[-2px]', )}> {shouldShowRecipeOverlay ? @@ -464,8 +463,8 @@ export default function PhotoLarge({ tag={shouldShareTag ? primaryTag : undefined} camera={shouldShareCamera ? camera : undefined} lens={shouldShareLens ? lens : undefined} - simulation={shouldShareSimulation - ? photo.filmSimulation + film={shouldShareFilm + ? photo.film : undefined} recipe={shouldShareRecipe ? recipeTitle diff --git a/src/photo/actions.ts b/src/photo/actions.ts index 507e165c..b3843331 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -60,7 +60,7 @@ import { convertUploadToPhoto } from './storage'; import { UrlAddStatus } from '@/admin/AdminUploadsClient'; import { convertStringToArray } from '@/utility/string'; import { after } from 'next/server'; -import { FilmSimulation } from '@/simulation'; +import { FilmSimulation } from '@/film'; // Private actions @@ -315,13 +315,13 @@ export const renamePhotoTagGloballyAction = async (formData: FormData) => export const getPhotosNeedingRecipeTitleCountAction = async ( recipeData: string, - simulation: FilmSimulation, + film: FilmSimulation, photoIdToExclude?: string, ) => runAuthenticatedAdminServerAction(async () => await getPhotosNeedingRecipeTitleCount( recipeData, - simulation, + film, photoIdToExclude, ), ); diff --git a/src/photo/cache.ts b/src/photo/cache.ts index e46ee873..98a49565 100644 --- a/src/photo/cache.ts +++ b/src/photo/cache.ts @@ -10,7 +10,7 @@ import { getUniqueCameras, getUniqueTags, getUniqueTagsHidden, - getUniqueFilmSimulations, + getUniqueFilms, getPhotosNearId, getPhotosMostRecentUpdate, getPhotosMeta, @@ -29,7 +29,7 @@ import { PATH_GRID, PATH_ROOT, PREFIX_CAMERA, - PREFIX_FILM_SIMULATION, + PREFIX_FILM, PREFIX_FOCAL_LENGTH, PREFIX_LENS, PREFIX_RECIPE, @@ -45,7 +45,7 @@ const KEY_PHOTO = 'photo'; const KEY_CAMERAS = 'cameras'; const KEY_LENSES = 'lenses'; const KEY_TAGS = 'tags'; -const KEY_FILM_SIMULATIONS = 'film-simulations'; +const KEY_FILMS = 'films'; const KEY_RECIPES = 'recipes'; const KEY_FOCAL_LENGTHS = 'focal-lengths'; // Type keys @@ -109,8 +109,8 @@ export const revalidateCamerasKey = () => export const revalidateLensesKey = () => revalidateTag(KEY_LENSES); -export const revalidateFilmSimulationsKey = () => - revalidateTag(KEY_FILM_SIMULATIONS); +export const revalidateFilmsKey = () => + revalidateTag(KEY_FILMS); export const revalidateFocalLengthsKey = () => revalidateTag(KEY_FOCAL_LENGTHS); @@ -120,7 +120,7 @@ export const revalidateAllKeys = () => { revalidateTagsKey(); revalidateCamerasKey(); revalidateLensesKey(); - revalidateFilmSimulationsKey(); + revalidateFilmsKey(); revalidateRecipesKey(); revalidateFocalLengthsKey(); }; @@ -140,7 +140,7 @@ export const revalidatePhoto = (photoId: string) => { revalidateTagsKey(); revalidateCamerasKey(); revalidateLensesKey(); - revalidateFilmSimulationsKey(); + revalidateFilmsKey(); revalidateRecipesKey(); revalidateFocalLengthsKey(); // Paths @@ -151,7 +151,7 @@ export const revalidatePhoto = (photoId: string) => { revalidatePath(PREFIX_TAG, 'layout'); revalidatePath(PREFIX_CAMERA, 'layout'); revalidatePath(PREFIX_LENS, 'layout'); - revalidatePath(PREFIX_FILM_SIMULATION, 'layout'); + revalidatePath(PREFIX_FILM, 'layout'); revalidatePath(PREFIX_RECIPE, 'layout'); revalidatePath(PREFIX_FOCAL_LENGTH, 'layout'); revalidatePath(PATH_ADMIN, 'layout'); @@ -231,10 +231,10 @@ export const getUniqueLensesCached = [KEY_PHOTOS, KEY_LENSES], ); -export const getUniqueFilmSimulationsCached = +export const getUniqueFilmsCached = unstable_cache( - getUniqueFilmSimulations, - [KEY_PHOTOS, KEY_FILM_SIMULATIONS], + getUniqueFilms, + [KEY_PHOTOS, KEY_FILMS], ); export const getUniqueRecipesCached = diff --git a/src/photo/db/index.ts b/src/photo/db/index.ts index cff5255a..f15d954c 100644 --- a/src/photo/db/index.ts +++ b/src/photo/db/index.ts @@ -47,7 +47,7 @@ export const getWheresFromOptions = ( tag, camera, lens, - simulation, + film, recipe, focal, } = options; @@ -108,9 +108,9 @@ export const getWheresFromOptions = ( wheres.push(`$${valuesIndex++}=ANY(tags)`); wheresValues.push(tag); } - if (simulation) { - wheres.push(`film_simulation=$${valuesIndex++}`); - wheresValues.push(simulation); + if (film) { + wheres.push(`film=$${valuesIndex++}`); + wheresValues.push(film); } if (recipe) { wheres.push(`recipe_title=$${valuesIndex++}`); diff --git a/src/photo/db/migration.ts b/src/photo/db/migration.ts index 8693e3f3..2dbeb765 100644 --- a/src/photo/db/migration.ts +++ b/src/photo/db/migration.ts @@ -50,6 +50,27 @@ export const MIGRATIONS: Migration[] = [{ ALTER TABLE photos 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) => diff --git a/src/photo/db/query.ts b/src/photo/db/query.ts index 35cffde1..9af7bf0c 100644 --- a/src/photo/db/query.ts +++ b/src/photo/db/query.ts @@ -13,7 +13,7 @@ import { } from '@/photo'; import { Cameras, createCameraKey } from '@/camera'; import { Tags } from '@/tag'; -import { FilmSimulation, FilmSimulations } from '@/simulation'; +import { FilmSimulation, Films } from '@/film'; import { ADMIN_SQL_DEBUG_ENABLED } from '@/app/config'; import { GetPhotosOptions, @@ -53,7 +53,7 @@ const createPhotosTable = () => location_name VARCHAR(255), latitude DOUBLE PRECISION, longitude DOUBLE PRECISION, - film_simulation VARCHAR(255), + film VARCHAR(255), recipe_title VARCHAR(255), recipe_data JSONB, priority_order REAL, @@ -160,7 +160,7 @@ export const insertPhoto = (photo: PhotoDbInsert) => location_name, latitude, longitude, - film_simulation, + film, recipe_title, recipe_data, priority_order, @@ -191,7 +191,7 @@ export const insertPhoto = (photo: PhotoDbInsert) => ${photo.locationName}, ${photo.latitude}, ${photo.longitude}, - ${photo.filmSimulation}, + ${photo.film}, ${photo.recipeTitle}, ${photo.recipeData}, ${photo.priorityOrder}, @@ -225,7 +225,7 @@ export const updatePhoto = (photo: PhotoDbInsert) => location_name=${photo.locationName}, latitude=${photo.latitude}, longitude=${photo.longitude}, - film_simulation=${photo.filmSimulation}, + film=${photo.film}, recipe_title=${photo.recipeTitle}, recipe_data=${photo.recipeData}, priority_order=${photo.priorityOrder || null}, @@ -367,14 +367,14 @@ export const getUniqueRecipes = async () => export const getRecipeTitleForData = async ( data: string | object, - simulation: FilmSimulation, + film: FilmSimulation, ) => // Includes legacy check on pre-stringified JSON safelyQueryPhotos(() => sql` SELECT recipe_title FROM photos WHERE hidden IS NOT TRUE AND recipe_data=${typeof data === 'string' ? data : JSON.stringify(data)} - AND film_simulation=${simulation} + AND film=${film} LIMIT 1 ` .then(({ rows }) => rows[0]?.recipe_title as string | undefined) @@ -382,7 +382,7 @@ export const getRecipeTitleForData = async ( export const getPhotosNeedingRecipeTitleCount = async ( data: string, - simulation: FilmSimulation, + film: FilmSimulation, photoIdToExclude?: string, ) => safelyQueryPhotos(() => sql` @@ -390,7 +390,7 @@ export const getPhotosNeedingRecipeTitleCount = async ( FROM photos WHERE recipe_title IS NULL AND recipe_data=${data} - AND film_simulation=${simulation} + AND film=${film} AND id <> ${photoIdToExclude} `.then(({ rows }) => parseInt(rows[0].count, 10)) , 'getPhotosNeedingRecipeTitleCount'); @@ -398,29 +398,29 @@ export const getPhotosNeedingRecipeTitleCount = async ( export const updateAllMatchingRecipeTitles = ( title: string, data: string, - simulation: FilmSimulation, + film: FilmSimulation, ) => safelyQueryPhotos(() => sql` UPDATE photos SET recipe_title=${title} WHERE recipe_title IS NULL AND recipe_data=${data} - AND film_simulation=${simulation} + AND film=${film} `, 'updateAllMatchingRecipeTitles'); -export const getUniqueFilmSimulations = async () => +export const getUniqueFilms = async () => safelyQueryPhotos(() => sql` - SELECT DISTINCT film_simulation, COUNT(*) + SELECT DISTINCT film, COUNT(*) FROM photos - WHERE hidden IS NOT TRUE AND film_simulation IS NOT NULL - GROUP BY film_simulation - ORDER BY film_simulation ASC - `.then(({ rows }): FilmSimulations => rows - .map(({ film_simulation, count }) => ({ - simulation: film_simulation as FilmSimulation, + WHERE hidden IS NOT TRUE AND film IS NOT NULL + GROUP BY film + ORDER BY film ASC + `.then(({ rows }): Films => rows + .map(({ film, count }) => ({ + film: film as FilmSimulation, count: parseInt(count, 10), }))) - , 'getUniqueFilmSimulations'); + , 'getUniqueFilms'); export const getUniqueFocalLengths = async () => safelyQueryPhotos(() => sql` diff --git a/src/photo/form/ApplyRecipesGloballyCheckbox.tsx b/src/photo/form/ApplyRecipesGloballyCheckbox.tsx index 889fb2d7..6bd5f7ce 100644 --- a/src/photo/form/ApplyRecipesGloballyCheckbox.tsx +++ b/src/photo/form/ApplyRecipesGloballyCheckbox.tsx @@ -1,14 +1,14 @@ import FieldSetWithStatus from '@/components/FieldSetWithStatus'; import { ComponentProps, useEffect, useState } from 'react'; import { getPhotosNeedingRecipeTitleCountAction } from '../actions'; -import { FilmSimulation } from '@/simulation'; +import { FilmSimulation } from '@/film'; export default function ApplyRecipeTitleGloballyCheckbox({ photoId, recipeTitle, hasRecipeTitleChanged, recipeData, - simulation, + film, onMatchResults, ...props }: ComponentProps & { @@ -16,7 +16,7 @@ export default function ApplyRecipeTitleGloballyCheckbox({ recipeTitle?: string hasRecipeTitleChanged?: boolean recipeData?: string - simulation?: FilmSimulation + film?: FilmSimulation onMatchResults: (didFindMatchingPhotos: boolean) => void }) { const [matchingPhotosCount, setMatchingPhotosCount] = useState(); @@ -24,14 +24,14 @@ export default function ApplyRecipeTitleGloballyCheckbox({ const loading = matchingPhotosCount === undefined; useEffect(() => { - if (recipeTitle && hasRecipeTitleChanged && recipeData && simulation) { + if (recipeTitle && hasRecipeTitleChanged && recipeData && film) { setMatchingPhotosCount(undefined); - getPhotosNeedingRecipeTitleCountAction(recipeData, simulation, photoId) + getPhotosNeedingRecipeTitleCountAction(recipeData, film, photoId) .then(setMatchingPhotosCount); } else { setMatchingPhotosCount(0); } - }, [recipeTitle, hasRecipeTitleChanged, recipeData, simulation, photoId]); + }, [recipeTitle, hasRecipeTitleChanged, recipeData, film, photoId]); useEffect(() => { onMatchResults((matchingPhotosCount ?? 0) > 0); diff --git a/src/photo/form/PhotoForm.tsx b/src/photo/form/PhotoForm.tsx index d66ea5e5..8ea391a0 100644 --- a/src/photo/form/PhotoForm.tsx +++ b/src/photo/form/PhotoForm.tsx @@ -41,7 +41,7 @@ import ErrorNote from '@/components/ErrorNote'; import { convertRecipesForForm, Recipes } from '@/recipe'; import deepEqual from 'fast-deep-equal/es6/react'; import ApplyRecipeTitleGloballyCheckbox from './ApplyRecipesGloballyCheckbox'; -import { FilmSimulation } from '@/simulation'; +import { FilmSimulation } from '@/film'; import IconFavs from '@/components/icons/IconFavs'; import IconHidden from '@/components/icons/IconHidden'; @@ -414,7 +414,7 @@ export default function PhotoForm({ hasRecipeTitleChanged={ changedFormKeys.includes('recipeTitle')} recipeData={formData.recipeData} - simulation={formData.filmSimulation as FilmSimulation} + film={formData.film as FilmSimulation} onMatchResults={onMatchResults} {...fieldProps} />; diff --git a/src/photo/form/index.ts b/src/photo/form/index.ts index d02bdd55..19cada9b 100644 --- a/src/photo/form/index.ts +++ b/src/photo/form/index.ts @@ -19,7 +19,7 @@ import { generateNanoid } from '@/utility/nanoid'; import { FILM_SIMULATION_FORM_INPUT_OPTIONS, } from '@/platforms/fujifilm/simulation'; -import { FilmSimulation } from '@/simulation'; +import { FilmSimulation } from '@/film'; import { GEO_PRIVACY_ENABLED } from '@/app/config'; import { TAG_FAVS, getValidationMessageForTags } from '@/tag'; import { MAKE_FUJIFILM } from '@/platforms/fujifilm'; @@ -119,8 +119,8 @@ const FORM_METADATA = ( aspectRatio: { label: 'aspect ratio', readOnly: true }, make: { label: 'camera make' }, model: { label: 'camera model' }, - filmSimulation: { - label: 'fujifilm simulation', + film: { + label: 'film', selectOptions: FILM_SIMULATION_FORM_INPUT_OPTIONS, selectOptionsDefaultLabel: 'Unknown', shouldHide: ({ make }) => make !== MAKE_FUJIFILM, @@ -274,7 +274,7 @@ export const convertPhotoToFormData = (photo: Photo): PhotoFormData => { export const convertExifToFormData = ( data: ExifData, - filmSimulation?: FilmSimulation, + film?: FilmSimulation, recipeData?: FujifilmRecipe, ): Omit< Record, @@ -298,7 +298,7 @@ export const convertExifToFormData = ( !GEO_PRIVACY_ENABLED ? data.tags?.GPSLatitude?.toString() : undefined, longitude: !GEO_PRIVACY_ENABLED ? data.tags?.GPSLongitude?.toString() : undefined, - filmSimulation, + film, recipeData: JSON.stringify(recipeData), ...data.tags?.DateTimeOriginal && { takenAt: convertTimestampWithOffsetToPostgresString( @@ -345,7 +345,7 @@ export const convertFormDataToPhotoDbInsert = ( return { ...(photoForm as PhotoFormData & { - filmSimulation?: FilmSimulation + film?: FilmSimulation recipeData?: FujifilmRecipe }), ...!photoForm.id && { id: generateNanoid() }, diff --git a/src/photo/index.ts b/src/photo/index.ts index 86bbb441..1b0d6a33 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -1,11 +1,11 @@ import { formatFocalLength } from '@/focal'; import { getNextImageUrlForRequest } from '@/platforms/next-image'; -import { FilmSimulation, photoHasFilmSimulationData } from '@/simulation'; +import { FilmSimulation, photoHasFilmData } from '@/film'; import { HIGH_DENSITY_GRID, IS_PREVIEW, SHOW_EXIF_DATA, - SHOW_FILM_SIMULATIONS, + SHOW_FILMS, SHOW_LENSES, SHOW_RECIPES, } from '@/app/config'; @@ -65,7 +65,7 @@ export interface PhotoExif { exposureCompensation?: number latitude?: number longitude?: number - filmSimulation?: FilmSimulation + film?: FilmSimulation recipeData?: string takenAt?: string takenAtNaive?: string @@ -329,10 +329,10 @@ export const shouldShowRecipeDataForPhoto = (photo: Photo) => SHOW_RECIPES && photoHasRecipeData(photo); -export const shouldShowFilmSimulationDataForPhoto = (photo: Photo) => +export const shouldShowFilmDataForPhoto = (photo: Photo) => SHOW_EXIF_DATA && - SHOW_FILM_SIMULATIONS && - photoHasFilmSimulationData(photo); + SHOW_FILMS && + photoHasFilmData(photo); export const shouldShowExifDataForPhoto = (photo: Photo) => SHOW_EXIF_DATA && photoHasExifData(photo); diff --git a/src/photo/server.ts b/src/photo/server.ts index 6a93ca0e..22ab8376 100644 --- a/src/photo/server.ts +++ b/src/photo/server.ts @@ -11,7 +11,7 @@ import { } from '@/platforms/fujifilm/simulation'; import { ExifData, ExifParserFactory } from 'ts-exif-parser'; import { PhotoFormData } from './form'; -import { FilmSimulation } from '@/simulation'; +import { FilmSimulation } from '@/film'; import sharp, { Sharp } from 'sharp'; import { GEO_PRIVACY_ENABLED, @@ -58,7 +58,7 @@ export const extractImageDataFromBlobPath = async ( const extension = getExtensionFromStorageUrl(url); let exifData: ExifData | undefined; - let filmSimulation: FilmSimulation | undefined; + let film: FilmSimulation | undefined; let recipe: FujifilmRecipe | undefined; let blurData: string | undefined; let imageResizedBase64: string | undefined; @@ -89,7 +89,7 @@ export const extractImageDataFromBlobPath = async ( const exifDataBinary = parser.parse(); const makerNote = exifDataBinary.tags?.MakerNote; if (Buffer.isBuffer(makerNote)) { - filmSimulation = getFujifilmSimulationFromMakerNote(makerNote); + film = getFujifilmSimulationFromMakerNote(makerNote); recipe = getFujifilmRecipeFromMakerNote(makerNote); } } @@ -124,7 +124,7 @@ export const extractImageDataFromBlobPath = async ( url, }, ...generateBlurData && { blurData }, - ...convertExifToFormData(exifData, filmSimulation, recipe), + ...convertExifToFormData(exifData, film, recipe), }, }, imageResizedBase64, @@ -206,10 +206,10 @@ export const convertFormDataToPhotoDbInsertAndLookupRecipeTitle = Promise> => { const photo = convertFormDataToPhotoDbInsert(...args); - if (photo.recipeData && !photo.recipeTitle && photo.filmSimulation) { + if (photo.recipeData && !photo.recipeTitle && photo.film) { const recipeTitle = await getRecipeTitleForData( photo.recipeData, - photo.filmSimulation, + photo.film, ); if (recipeTitle) { photo.recipeTitle = recipeTitle; @@ -229,12 +229,12 @@ export const propagateRecipeTitleIfNecessary = async ( formData.get('recipeTitle') && photo.recipeTitle && photo.recipeData && - photo.filmSimulation + photo.film ) { await updateAllMatchingRecipeTitles( photo.recipeTitle, photo.recipeData, - photo.filmSimulation, + photo.film, ); } }; diff --git a/src/platforms/fujifilm/simulation.ts b/src/platforms/fujifilm/simulation.ts index 5dce5078..df387d6c 100644 --- a/src/platforms/fujifilm/simulation.ts +++ b/src/platforms/fujifilm/simulation.ts @@ -222,11 +222,11 @@ const ALL_POSSIBLE_FILM_SIMULATION_LABELS = Object large.toLocaleLowerCase(), ]); -export const isStringFilmSimulation = (simulation: string) => - ALL_POSSIBLE_FILM_SIMULATION_LABELS.includes(simulation.toLocaleLowerCase()); +export const isStringFilmSimulation = (film: string) => + ALL_POSSIBLE_FILM_SIMULATION_LABELS.includes(film.toLocaleLowerCase()); -export const labelForFilmSimulation = (simulation: FujifilmSimulation) => - FILM_SIMULATION_LABELS[simulation]; +export const labelForFilm = (film: FujifilmSimulation) => + FILM_SIMULATION_LABELS[film]; export const getFujifilmSimulationFromMakerNote = ( bytes: Buffer, diff --git a/src/recipe/PhotoRecipeOGTile.tsx b/src/recipe/PhotoRecipeOGTile.tsx deleted file mode 100644 index 9e14cd7c..00000000 --- a/src/recipe/PhotoRecipeOGTile.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; -import { FilmSimulation } from '@/simulation'; -import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; -import clsx from 'clsx/lite'; -import { ReactNode, RefObject } from 'react'; -import { addSign, formatWhiteBalance } from '.'; - -export default function PhotoRecipeOGTile({ - recipe, - simulation, - iso, - exposure, -}: { - ref?: RefObject - 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) => -
    - {children} -
    ; - - const renderDataSquare = ( - value: ReactNode, - label?: string, - className?: string, - ) => ( -
    -
    - {typeof value === 'number' ? addSign(value) : value} -
    - {label &&
    - {label} -
    } -
    - ); - - return ( -
    -
    - {renderRow(<> -
    - KODAK PORTRA 500 -
    - - , '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')} - )} -
    -
    - ); -} diff --git a/src/recipe/PhotoRecipeOverlay.tsx b/src/recipe/PhotoRecipeOverlay.tsx index 36fd58f2..1a140874 100644 --- a/src/recipe/PhotoRecipeOverlay.tsx +++ b/src/recipe/PhotoRecipeOverlay.tsx @@ -1,7 +1,7 @@ 'use client'; import LoaderButton from '@/components/primitives/LoaderButton'; -import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; +import PhotoFilm from '@/film/PhotoFilm'; import clsx from 'clsx/lite'; import { ReactNode, RefObject } from 'react'; import { IoCloseCircle } from 'react-icons/io5'; @@ -15,7 +15,7 @@ import { generateRecipeText, RecipeProps, } from '.'; -import { labelForFilmSimulation } from '@/platforms/fujifilm/simulation'; +import { labelForFilm } from '@/platforms/fujifilm/simulation'; import { TbChecklist } from 'react-icons/tb'; import CopyButton from '@/components/CopyButton'; import { pathForRecipe } from '@/app/paths'; @@ -25,7 +25,7 @@ export default function PhotoRecipeOverlay({ ref, title, recipe, - simulation, + film, onClose, }: RecipeProps & { ref?: RefObject @@ -122,7 +122,7 @@ export default function PhotoRecipeOverlay({ label={`${title ? `${formatRecipe(title).toLocaleUpperCase()} recipe` : 'Recipe'}`} - text={generateRecipeText({ title, recipe, simulation }).join('\n')} + text={generateRecipeText({ title, recipe, film }).join('\n')} iconSize={17} className={clsx( 'translate-y-[0.5px]', @@ -147,10 +147,10 @@ export default function PhotoRecipeOverlay({
    {renderDataSquare(
    - {labelForFilmSimulation(simulation).medium.toLocaleUpperCase()} - diff --git a/src/recipe/RecipeHeader.tsx b/src/recipe/RecipeHeader.tsx index 451467c6..0613238a 100644 --- a/src/recipe/RecipeHeader.tsx +++ b/src/recipe/RecipeHeader.tsx @@ -32,11 +32,11 @@ export default function RecipeHeader({ contrast="high" recipeOnClick={() => ( photo?.recipeData && - photo?.filmSimulation + photo?.film ) ? setRecipeModalProps?.({ title: photo.recipeTitle, recipe: photo.recipeData, - simulation: photo.filmSimulation, + film: photo.film, iso: photo.isoFormatted, exposure: photo.exposureTimeFormatted, }) diff --git a/src/recipe/index.ts b/src/recipe/index.ts index 832ca371..0d6f102d 100644 --- a/src/recipe/index.ts +++ b/src/recipe/index.ts @@ -7,8 +7,8 @@ import { formatCountDescriptive, } from '@/utility/string'; import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; -import { FilmSimulation } from '@/simulation'; -import { labelForFilmSimulation } from '@/platforms/fujifilm/simulation'; +import { FilmSimulation } from '@/film'; +import { labelForFilm } from '@/platforms/fujifilm/simulation'; export type RecipeWithCount = { recipe: string @@ -20,7 +20,7 @@ export type Recipes = RecipeWithCount[] export interface RecipeProps { title?: string recipe: FujifilmRecipe - simulation: FilmSimulation + film: FilmSimulation iso?: string exposure?: string } @@ -57,12 +57,12 @@ export const descriptionForRecipePhotos = ( export const generateRecipeText = ({ title, recipe, - simulation, + film, }: RecipeProps, abbreviate?: boolean, ) => { const lines = [ - `${labelForFilmSimulation(simulation).small.toLocaleUpperCase()}`, + `${labelForFilm(film).small.toLocaleUpperCase()}`, // eslint-disable-next-line max-len `${formatWhiteBalance(recipe).toLocaleUpperCase()} ${formatWhiteBalanceColor(recipe)}`, ]; @@ -133,7 +133,7 @@ export const generateMetaForRecipe = ( }); const photoHasRecipe = (photo?: Photo) => - photo?.filmSimulation && photo?.recipeData; + photo?.film && photo?.recipeData; export const getPhotoWithRecipeFromPhotos = ( photos: Photo[], diff --git a/src/share/ShareModals.tsx b/src/share/ShareModals.tsx index 9e1ebd51..ddf26a95 100644 --- a/src/share/ShareModals.tsx +++ b/src/share/ShareModals.tsx @@ -3,7 +3,7 @@ import PhotoShareModal from '@/photo/PhotoShareModal'; import TagShareModal from '@/tag/TagShareModal'; import CameraShareModal from '@/camera/CameraShareModal'; -import FilmSimulationShareModal from '@/simulation/FilmSimulationShareModal'; +import FilmShareModal from '@/film/FilmShareModal'; import FocalLengthShareModal from '@/focal/FocalLengthShareModal'; import { useAppState } from '@/state/AppState'; import RecipeShareModal from '@/recipe/RecipeShareModal'; @@ -20,7 +20,7 @@ export default function ShareModals() { camera, lens, tag, - simulation, + film, recipe, focal, } = shareModalProps; @@ -30,7 +30,7 @@ export default function ShareModals() { photo, tag, camera, - simulation, + film, recipe, focal, }} />; @@ -42,8 +42,8 @@ export default function ShareModals() { return ; } else if (lens) { return ; - } else if (simulation) { - return ; + } else if (film) { + return ; } else if (recipe) { return ; } else if (focal !== undefined) { diff --git a/src/share/index.ts b/src/share/index.ts index 5e8165b0..701967e6 100644 --- a/src/share/index.ts +++ b/src/share/index.ts @@ -2,7 +2,7 @@ import { Photo } from '@/photo'; import { PhotoSetAttributes, PhotoSetCategory } from '@/category'; import { absolutePathForCameraImage, - absolutePathForFilmSimulationImage, + absolutePathForFilmImage, absolutePathForFocalLengthImage, absolutePathForLensImage, absolutePathForPhotoImage, @@ -21,7 +21,7 @@ export const getSharePathFromShareModalProps = ({ lens, tag, recipe, - simulation, + film, focal, }: ShareModalProps) => { if (photo) { @@ -34,8 +34,8 @@ export const getSharePathFromShareModalProps = ({ return absolutePathForTagImage(tag); } else if (recipe) { return absolutePathForRecipeImage(recipe); - } else if (simulation) { - return absolutePathForFilmSimulationImage(simulation); + } else if (film) { + return absolutePathForFilmImage(film); } else if (focal) { return absolutePathForFocalLengthImage(focal); } diff --git a/src/simulation/FilmSimulationShareModal.tsx b/src/simulation/FilmSimulationShareModal.tsx deleted file mode 100644 index b02c7723..00000000 --- a/src/simulation/FilmSimulationShareModal.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { absolutePathForFilmSimulation } from '@/app/paths'; -import { PhotoSetAttributes } from '../category'; -import ShareModal from '@/share/ShareModal'; -import FilmSimulationOGTile from './FilmSimulationOGTile'; -import { FilmSimulation, shareTextForFilmSimulation } from '.'; - -export default function FilmSimulationShareModal({ - simulation, - photos, - count, - dateRange, -}: { - simulation: FilmSimulation -} & PhotoSetAttributes) { - return ( - - - - ); -}; diff --git a/src/simulation/data.ts b/src/simulation/data.ts deleted file mode 100644 index a289fdef..00000000 --- a/src/simulation/data.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - getPhotosCached, - getPhotosMetaCached, -} from '@/photo/cache'; -import { FilmSimulation } from '.'; - -export const getPhotosFilmSimulationDataCached = ({ - simulation, - limit, -}: { - simulation: FilmSimulation, - limit?: number, -}) => - Promise.all([ - getPhotosCached({ simulation, limit }), - getPhotosMetaCached({ simulation }), - ]); diff --git a/src/simulation/index.ts b/src/simulation/index.ts deleted file mode 100644 index a1eb7876..00000000 --- a/src/simulation/index.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - Photo, - PhotoDateRange, - descriptionForPhotoSet, - photoQuantityText, -} from '@/photo'; -import { - absolutePathForFilmSimulation, - absolutePathForFilmSimulationImage, -} from '@/app/paths'; -import { - FujifilmSimulation, - labelForFilmSimulation, -} from '@/platforms/fujifilm/simulation'; - -export type FilmSimulation = FujifilmSimulation; - -export type FilmSimulationWithCount = { - simulation: FilmSimulation - count: number -} - -export type FilmSimulations = FilmSimulationWithCount[] - -export const sortFilmSimulations = ( - simulations: FilmSimulations, -) => simulations.sort(sortFilmSimulationsWithCount); - -export const sortFilmSimulationsWithCount = ( - a: FilmSimulationWithCount, - b: FilmSimulationWithCount, -) => { - const aLabel = labelForFilmSimulation(a.simulation).large; - const bLabel = labelForFilmSimulation(b.simulation).large; - return aLabel.localeCompare(bLabel); -}; - -export const titleForFilmSimulation = ( - simulation: FilmSimulation, - photos: Photo[], - explicitCount?: number, -) => [ - labelForFilmSimulation(simulation).large, - photoQuantityText(explicitCount ?? photos.length), -].join(' '); - -export const shareTextForFilmSimulation = ( - simulation: FilmSimulation, -) => - `Photos shot on Fujifilm ${labelForFilmSimulation(simulation).large}`; - -export const descriptionForFilmSimulationPhotos = ( - photos: Photo[], - dateBased?: boolean, - explicitCount?: number, - explicitDateRange?: PhotoDateRange, -) => - descriptionForPhotoSet( - photos, - undefined, - dateBased, - explicitCount, - explicitDateRange, - ); - -export const generateMetaForFilmSimulation = ( - simulation: FilmSimulation, - photos: Photo[], - explicitCount?: number, - explicitDateRange?: PhotoDateRange, -) => ({ - url: absolutePathForFilmSimulation(simulation), - title: titleForFilmSimulation(simulation, photos, explicitCount), - description: descriptionForFilmSimulationPhotos( - photos, - true, - explicitCount, - explicitDateRange, - ), - images: absolutePathForFilmSimulationImage(simulation), -}); - -export const photoHasFilmSimulationData = (photo: Photo) => - Boolean(photo.filmSimulation);