Generalize film type and labeling strategy

This commit is contained in:
Sam Becker 2025-04-01 21:08:36 -05:00
parent 087a5e223c
commit f9db50e41a
29 changed files with 92 additions and 95 deletions

View File

@ -14,7 +14,6 @@ import {
} from '@/app/config'; } from '@/app/config';
import ErrorNote from '@/components/ErrorNote'; import ErrorNote from '@/components/ErrorNote';
import { getRecipeTitleForData } from '@/photo/db/query'; import { getRecipeTitleForData } from '@/photo/db/query';
import { FilmSimulation } from '@/film';
export const maxDuration = 60; export const maxDuration = 60;
@ -58,7 +57,7 @@ export default async function UploadPage({ params }: Params) {
formDataFromExif?.recipeData && formDataFromExif.film formDataFromExif?.recipeData && formDataFromExif.film
? getRecipeTitleForData( ? getRecipeTitleForData(
formDataFromExif.recipeData, formDataFromExif.recipeData,
formDataFromExif.film as FilmSimulation, formDataFromExif.film,
) )
: undefined, : undefined,
]); ]);

View File

@ -3,7 +3,7 @@
import AppGrid from '@/components/AppGrid'; import AppGrid from '@/components/AppGrid';
import { clsx } from 'clsx/lite'; import { clsx } from 'clsx/lite';
import { import {
FILM_SIMULATION_FORM_INPUT_OPTIONS, FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS,
} from '@/platforms/fujifilm/simulation'; } from '@/platforms/fujifilm/simulation';
import PhotoFilm from '@/film/PhotoFilm'; import PhotoFilm from '@/film/PhotoFilm';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@ -13,7 +13,7 @@ export default function FilmPage() {
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
setIndex((index + 1) % FILM_SIMULATION_FORM_INPUT_OPTIONS.length); setIndex((index + 1) % FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS.length);
}, 200); }, 200);
return () => clearInterval(interval); return () => clearInterval(interval);
}); });
@ -28,7 +28,7 @@ export default function FilmPage() {
Film Simulation: Film Simulation:
</div> </div>
<PhotoFilm <PhotoFilm
film={FILM_SIMULATION_FORM_INPUT_OPTIONS[index].value} film={FUJIFILM_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

@ -1,12 +1,12 @@
import { import {
FILM_SIMULATION_FORM_INPUT_OPTIONS, FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS,
} from '@/platforms/fujifilm/simulation'; } from '@/platforms/fujifilm/simulation';
import PhotoFilm from '@/film/PhotoFilm'; import PhotoFilm from '@/film/PhotoFilm';
export default function FilmPage() { export default function FilmPage() {
return ( return (
<div className="space-y-1 my-12"> <div className="space-y-1 my-12">
{FILM_SIMULATION_FORM_INPUT_OPTIONS.map(({ value }) => {FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS.map(({ value }) =>
<div key={value}> <div key={value}>
<PhotoFilm <PhotoFilm
film={value} film={value}

View File

@ -11,7 +11,6 @@ import {
absolutePathForPhotoImage, absolutePathForPhotoImage,
} from '@/app/paths'; } from '@/app/paths';
import PhotoDetailPage from '@/photo/PhotoDetailPage'; import PhotoDetailPage from '@/photo/PhotoDetailPage';
import { FilmSimulation } from '@/film';
import { import {
getPhotosMetaCached, getPhotosMetaCached,
getPhotosNearIdCached, getPhotosNearIdCached,
@ -20,7 +19,7 @@ import { cache } from 'react';
const getPhotosNearIdCachedCached = cache(( const getPhotosNearIdCachedCached = cache((
photoId: string, photoId: string,
film: FilmSimulation, film: string,
) => ) =>
getPhotosNearIdCached( getPhotosNearIdCached(
photoId, photoId,
@ -28,7 +27,7 @@ const getPhotosNearIdCachedCached = cache((
)); ));
interface PhotoFilmProps { interface PhotoFilmProps {
params: Promise<{ photoId: string, film: FilmSimulation }> params: Promise<{ photoId: string, film: string }>
} }
export async function generateMetadata({ export async function generateMetadata({

View File

@ -5,7 +5,6 @@ import {
} from '@/image-response'; } from '@/image-response';
import FilmImageResponse from import FilmImageResponse from
'@/image-response/FilmImageResponse'; '@/image-response/FilmImageResponse';
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';
@ -21,7 +20,7 @@ export const generateStaticParams = staticallyGenerateCategoryIfConfigured(
export async function GET( export async function GET(
_: Request, _: Request,
context: { params: Promise<{ film: FilmSimulation }> }, context: { params: Promise<{ film: string }> },
) { ) {
const { film } = await context.params; const { film } = await context.params;

View File

@ -1,6 +1,6 @@
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
import { getUniqueFilms } from '@/photo/db/query'; import { getUniqueFilms } from '@/photo/db/query';
import { FilmSimulation, generateMetaForFilm } from '@/film'; import { generateMetaForFilm } from '@/film';
import FilmOverview from '@/film/FilmOverview'; import FilmOverview from '@/film/FilmOverview';
import { getPhotosFilmDataCached } from '@/film/data'; import { getPhotosFilmDataCached } from '@/film/data';
import { Metadata } from 'next/types'; import { Metadata } from 'next/types';
@ -20,7 +20,7 @@ export const generateStaticParams = staticallyGenerateCategoryIfConfigured(
); );
interface FilmProps { interface FilmProps {
params: Promise<{ film: FilmSimulation }> params: Promise<{ film: string }>
} }
export async function generateMetadata({ export async function generateMetadata({

View File

@ -2,7 +2,6 @@ import { Photo } from '@/photo';
import { PhotoSetCategory } from '@/category'; import { PhotoSetCategory } from '@/category';
import { BASE_URL, GRID_HOMEPAGE_ENABLED } from './config'; import { BASE_URL, GRID_HOMEPAGE_ENABLED } from './config';
import { Camera } from '@/camera'; import { Camera } from '@/camera';
import { FilmSimulation } from '@/film';
import { parameterize } from '@/utility/string'; import { parameterize } from '@/utility/string';
import { TAG_HIDDEN } from '@/tag'; import { TAG_HIDDEN } from '@/tag';
import { Lens } from '@/lens'; import { Lens } from '@/lens';
@ -151,7 +150,7 @@ 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 pathForFilm = (film: FilmSimulation) => export const pathForFilm = (film: string) =>
`${PREFIX_FILM}/${film}`; `${PREFIX_FILM}/${film}`;
export const pathForLens = ({ make, model }: Lens) => export const pathForLens = ({ make, model }: Lens) =>
@ -177,7 +176,7 @@ 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 absolutePathForFilm = (film: FilmSimulation) => export const absolutePathForFilm = (film: string) =>
`${BASE_URL}${pathForFilm(film)}`; `${BASE_URL}${pathForFilm(film)}`;
export const absolutePathForRecipe = (recipe: string) => export const absolutePathForRecipe = (recipe: string) =>
@ -198,7 +197,7 @@ export const absolutePathForCameraImage= (camera: Camera) =>
export const absolutePathForLensImage= (lens: Lens) => export const absolutePathForLensImage= (lens: Lens) =>
`${absolutePathForLens(lens)}/image`; `${absolutePathForLens(lens)}/image`;
export const absolutePathForFilmImage = (film: FilmSimulation) => export const absolutePathForFilmImage = (film: string) =>
`${absolutePathForFilm(film)}/image`; `${absolutePathForFilm(film)}/image`;
export const absolutePathForRecipeImage = (recipe: string) => export const absolutePathForRecipeImage = (recipe: string) =>
@ -308,7 +307,7 @@ export const getPathComponents = (pathname = ''): {
const cameraModel = pathname.match( const cameraModel = pathname.match(
new RegExp(`^${PREFIX_CAMERA}/[^/]+/([^/]+)`))?.[1]; new RegExp(`^${PREFIX_CAMERA}/[^/]+/([^/]+)`))?.[1];
const film = pathname.match( const film = pathname.match(
new RegExp(`^${PREFIX_FILM}/([^/]+)`))?.[1] as FilmSimulation; new RegExp(`^${PREFIX_FILM}/([^/]+)`))?.[1] as string;
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];

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, Films } from '@/film'; import { 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';
@ -39,7 +39,7 @@ export interface PhotoSetCategory {
lens?: Lens lens?: Lens
tag?: string tag?: string
recipe?: string recipe?: string
film?: FilmSimulation film?: string
focal?: number focal?: number
} }

View File

@ -59,7 +59,6 @@ import * as VisuallyHidden from '@radix-ui/react-visually-hidden';
import InsightsIndicatorDot from '@/admin/insights/InsightsIndicatorDot'; import InsightsIndicatorDot from '@/admin/insights/InsightsIndicatorDot';
import { PhotoSetCategories } from '@/category'; import { PhotoSetCategories } from '@/category';
import { formatCameraText } from '@/camera'; import { formatCameraText } from '@/camera';
import { labelForFilm } from '@/platforms/fujifilm/simulation';
import { formatFocalLength } from '@/focal'; import { formatFocalLength } from '@/focal';
import { formatRecipe } from '@/recipe'; import { formatRecipe } from '@/recipe';
import IconLens from '../components/icons/IconLens'; import IconLens from '../components/icons/IconLens';
@ -73,6 +72,7 @@ import IconFilm from '../components/icons/IconFilm';
import IconLock from '../components/icons/IconLock'; import IconLock from '../components/icons/IconLock';
import useVisualViewportHeight from '@/utility/useVisualViewport'; import useVisualViewportHeight from '@/utility/useVisualViewport';
import useMaskedScroll from '../components/useMaskedScroll'; import useMaskedScroll from '../components/useMaskedScroll';
import { labelForFilm } from '@/film';
const DIALOG_TITLE = 'Global Command-K Menu'; const DIALOG_TITLE = 'Global Command-K Menu';
const DIALOG_DESCRIPTION = 'For searching photos, views, and settings'; const DIALOG_DESCRIPTION = 'For searching photos, views, and settings';

View File

@ -1,5 +1,5 @@
import { Photo, PhotoDateRange } from '@/photo'; import { Photo, PhotoDateRange } from '@/photo';
import { FilmSimulation, descriptionForFilmPhotos } from '.'; import { descriptionForFilmPhotos } from '.';
import PhotoHeader from '@/photo/PhotoHeader'; import PhotoHeader from '@/photo/PhotoHeader';
import PhotoFilm from '@/film/PhotoFilm'; import PhotoFilm from '@/film/PhotoFilm';
@ -11,7 +11,7 @@ export default function FilmHeader({
count, count,
dateRange, dateRange,
}: { }: {
film: FilmSimulation film: string
photos: Photo[] photos: Photo[]
selectedPhoto?: Photo selectedPhoto?: Photo
indexNumber?: number indexNumber?: number

View File

@ -4,11 +4,7 @@ import {
pathForFilm, pathForFilm,
} from '@/app/paths'; } from '@/app/paths';
import OGTile from '@/components/OGTile'; import OGTile from '@/components/OGTile';
import { import { descriptionForFilmPhotos, titleForFilm } from '.';
FilmSimulation,
descriptionForFilmPhotos,
titleForFilm,
} from '.';
export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed'; export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed';
@ -23,7 +19,7 @@ export default function FilmOGTile({
count, count,
dateRange, dateRange,
}: { }: {
film: FilmSimulation film: string
photos: Photo[] photos: Photo[]
loadingState?: OGLoadingState loadingState?: OGLoadingState
onLoad?: () => void onLoad?: () => void

View File

@ -1,6 +1,5 @@
import { Photo, PhotoDateRange } from '@/photo'; import { Photo, PhotoDateRange } from '@/photo';
import FilmHeader from './FilmHeader'; import FilmHeader from './FilmHeader';
import { FilmSimulation } from '.';
import PhotoGridContainer from '@/photo/PhotoGridContainer'; import PhotoGridContainer from '@/photo/PhotoGridContainer';
export default function FilmOverview({ export default function FilmOverview({
@ -10,7 +9,7 @@ export default function FilmOverview({
dateRange, dateRange,
animateOnFirstLoadOnly, animateOnFirstLoadOnly,
}: { }: {
film: FilmSimulation, film: string,
photos: Photo[], photos: Photo[],
count: number, count: number,
dateRange?: PhotoDateRange, dateRange?: PhotoDateRange,

View File

@ -2,7 +2,7 @@ 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, shareTextForFilm } from '.'; import { shareTextForFilm } from '.';
export default function FilmShareModal({ export default function FilmShareModal({
film, film,
@ -10,7 +10,7 @@ export default function FilmShareModal({
count, count,
dateRange, dateRange,
}: { }: {
film: FilmSimulation film: string
} & PhotoSetAttributes) { } & PhotoSetAttributes) {
return ( return (
<ShareModal <ShareModal

View File

@ -1,12 +1,11 @@
import { labelForFilm } from '@/platforms/fujifilm/simulation';
import PhotoFilmIcon from './PhotoFilmIcon'; import PhotoFilmIcon from './PhotoFilmIcon';
import { pathForFilm } from '@/app/paths'; import { pathForFilm } from '@/app/paths';
import { FilmSimulation } from '.';
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import EntityLink, { import EntityLink, {
EntityLinkExternalProps, EntityLinkExternalProps,
} from '@/components/primitives/EntityLink'; } from '@/components/primitives/EntityLink';
import clsx from 'clsx/lite'; import clsx from 'clsx/lite';
import { labelForFilm } from '.';
export default function PhotoFilm({ export default function PhotoFilm({
film, film,
@ -16,7 +15,7 @@ export default function PhotoFilm({
countOnHover, countOnHover,
...props ...props
}: { }: {
film: FilmSimulation film: string
countOnHover?: number countOnHover?: number
recipe?: FujifilmRecipe recipe?: FujifilmRecipe
} & EntityLinkExternalProps) { } & EntityLinkExternalProps) {

View File

@ -1,7 +1,6 @@
/* eslint-disable max-len */ /* eslint-disable max-len */
import { labelForFilm } from '@/platforms/fujifilm/simulation';
import { CSSProperties, useMemo } from 'react'; import { CSSProperties, useMemo } from 'react';
import { FilmSimulation } from '.'; import { labelForFilm } from '.';
const INTRINSIC_WIDTH = 28; const INTRINSIC_WIDTH = 28;
const INTRINSIC_WIDTH_FALLBACK = 16; const INTRINSIC_WIDTH_FALLBACK = 16;
@ -17,7 +16,7 @@ export default function PhotoFilmIcon({
className, className,
style, style,
}: { }: {
film?: FilmSimulation film?: string
height?: number height?: number
className?: string className?: string
style?: CSSProperties style?: CSSProperties

View File

@ -2,13 +2,12 @@ import {
getPhotosCached, getPhotosCached,
getPhotosMetaCached, getPhotosMetaCached,
} from '@/photo/cache'; } from '@/photo/cache';
import { FilmSimulation } from '.';
export const getPhotosFilmDataCached = ({ export const getPhotosFilmDataCached = ({
film, film,
limit, limit,
}: { }: {
film: FilmSimulation, film: string,
limit?: number, limit?: number,
}) => }) =>
Promise.all([ Promise.all([

View File

@ -9,24 +9,36 @@ import {
absolutePathForFilmImage, absolutePathForFilmImage,
} from '@/app/paths'; } from '@/app/paths';
import { import {
FILM_SIMULATION_FORM_INPUT_OPTIONS, FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS,
FujifilmSimulation, labelForFujifilmSimulation,
labelForFilm,
} from '@/platforms/fujifilm/simulation'; } from '@/platforms/fujifilm/simulation';
import { formatCount } from '@/utility/string'; import { deparameterize, formatCount } from '@/utility/string';
import { formatCountDescriptive } from '@/utility/string'; import { formatCountDescriptive } from '@/utility/string';
import { AnnotatedTag } from '@/photo/form'; import { AnnotatedTag } from '@/photo/form';
import PhotoFilmIcon from './PhotoFilmIcon'; import PhotoFilmIcon from './PhotoFilmIcon';
export type FilmSimulation = FujifilmSimulation;
export type FilmWithCount = { export type FilmWithCount = {
film: FilmSimulation film: string
count: number count: number
} }
export type Films = FilmWithCount[] export type Films = FilmWithCount[]
export const labelForFilm = (film: string) => {
// Use Fujifilm simulation text when recognized
const simulationLabel = labelForFujifilmSimulation(film as any);
if (simulationLabel) {
return simulationLabel;
} else {
const filmFormatted = deparameterize(film);
return {
small: filmFormatted,
medium: filmFormatted,
large: filmFormatted,
};
}
};
export const sortFilms = ( export const sortFilms = (
films: Films, films: Films,
) => films.sort(sortFilmsWithCount); ) => films.sort(sortFilmsWithCount);
@ -41,7 +53,7 @@ export const sortFilmsWithCount = (
}; };
export const titleForFilm = ( export const titleForFilm = (
film: FilmSimulation, film: string,
photos: Photo[], photos: Photo[],
explicitCount?: number, explicitCount?: number,
) => [ ) => [
@ -50,9 +62,9 @@ export const titleForFilm = (
].join(' '); ].join(' ');
export const shareTextForFilm = ( export const shareTextForFilm = (
film: FilmSimulation, film: string,
) => ) =>
`Photos shot on Fujifilm ${labelForFilm(film).large}`; `Photos shot on ${labelForFilm(film).large}`;
export const descriptionForFilmPhotos = ( export const descriptionForFilmPhotos = (
photos: Photo[], photos: Photo[],
@ -69,7 +81,7 @@ export const descriptionForFilmPhotos = (
); );
export const generateMetaForFilm = ( export const generateMetaForFilm = (
film: FilmSimulation, film: string,
photos: Photo[], photos: Photo[],
explicitCount?: number, explicitCount?: number,
explicitDateRange?: PhotoDateRange, explicitDateRange?: PhotoDateRange,
@ -93,13 +105,13 @@ export const convertFilmsForForm = (
includeAllFujifilmSimulations?: boolean, includeAllFujifilmSimulations?: boolean,
): AnnotatedTag[] => { ): AnnotatedTag[] => {
const films = includeAllFujifilmSimulations const films = includeAllFujifilmSimulations
? FILM_SIMULATION_FORM_INPUT_OPTIONS ? FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS
.map(({ value }) => ({ value } as AnnotatedTag)) .map(({ value }) => ({ value } as AnnotatedTag))
: []; : [];
_films.forEach(({ film, count }) => { _films.forEach(({ film, count }) => {
const index = films.findIndex(f => f.value === film); const index = films.findIndex(f => f.value === film);
const fujifilmSimulation = FILM_SIMULATION_FORM_INPUT_OPTIONS const fujifilmSimulation = FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS
.find(f => f.value === film); .find(f => f.value === film);
const meta = { const meta = {
annotation: formatCount(count), annotation: formatCount(count),

View File

@ -2,13 +2,10 @@ import { Photo } from '../photo';
import ImageCaption from './components/ImageCaption'; import ImageCaption from './components/ImageCaption';
import ImagePhotoGrid from './components/ImagePhotoGrid'; import ImagePhotoGrid from './components/ImagePhotoGrid';
import ImageContainer from './components/ImageContainer'; import ImageContainer from './components/ImageContainer';
import {
labelForFilm,
} from '@/platforms/fujifilm/simulation';
import PhotoFilmIcon from import PhotoFilmIcon from
'@/film/PhotoFilmIcon'; '@/film/PhotoFilmIcon';
import { FilmSimulation } from '@/film';
import { NextImageSize } from '@/platforms/next-image'; import { NextImageSize } from '@/platforms/next-image';
import { labelForFilm } from '@/film';
export default function FilmImageResponse({ export default function FilmImageResponse({
film, film,
@ -17,7 +14,7 @@ export default function FilmImageResponse({
height, height,
fontFamily, fontFamily,
}: { }: {
film: FilmSimulation, film: string,
photos: Photo[] photos: Photo[]
width: NextImageSize width: NextImageSize
height: number height: number

View File

@ -6,7 +6,9 @@ import type { NextImageSize } from '@/platforms/next-image';
import { formatTag } from '@/tag'; import { formatTag } from '@/tag';
import { generateRecipeText, getPhotoWithRecipeFromPhotos } from '@/recipe'; import { generateRecipeText, getPhotoWithRecipeFromPhotos } from '@/recipe';
import PhotoFilmIcon from '@/film/PhotoFilmIcon'; import PhotoFilmIcon from '@/film/PhotoFilmIcon';
import { isStringFilmSimulationLabel } from '@/platforms/fujifilm/simulation'; import {
isStringFujifilmSimulationLabel,
} from '@/platforms/fujifilm/simulation';
import IconRecipe from '@/components/icons/IconRecipe'; import IconRecipe from '@/components/icons/IconRecipe';
const MAX_RECIPE_LINES = 8; const MAX_RECIPE_LINES = 8;
@ -109,7 +111,7 @@ export default function RecipeImageResponse({
flexGrow: 1, flexGrow: 1,
}}> }}>
{text} {text}
{isStringFilmSimulationLabel(text) && film && {isStringFujifilmSimulationLabel(text) && film &&
<div tw="flex"> <div tw="flex">
<PhotoFilmIcon <PhotoFilmIcon
film={film} film={film}

View File

@ -60,7 +60,6 @@ import { convertUploadToPhoto } from './storage';
import { UrlAddStatus } from '@/admin/AdminUploadsClient'; import { UrlAddStatus } from '@/admin/AdminUploadsClient';
import { convertStringToArray } from '@/utility/string'; import { convertStringToArray } from '@/utility/string';
import { after } from 'next/server'; import { after } from 'next/server';
import { FilmSimulation } from '@/film';
// Private actions // Private actions
@ -315,7 +314,7 @@ export const renamePhotoTagGloballyAction = async (formData: FormData) =>
export const getPhotosNeedingRecipeTitleCountAction = async ( export const getPhotosNeedingRecipeTitleCountAction = async (
recipeData: string, recipeData: string,
film: FilmSimulation, film: string,
photoIdToExclude?: string, photoIdToExclude?: string,
) => ) =>
runAuthenticatedAdminServerAction(async () => runAuthenticatedAdminServerAction(async () =>

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, Films } from '@/film'; import { Films } from '@/film';
import { ADMIN_SQL_DEBUG_ENABLED } from '@/app/config'; import { ADMIN_SQL_DEBUG_ENABLED } from '@/app/config';
import { import {
GetPhotosOptions, GetPhotosOptions,
@ -380,7 +380,7 @@ export const getUniqueRecipes = async () =>
export const getRecipeTitleForData = async ( export const getRecipeTitleForData = async (
data: string | object, data: string | object,
film: FilmSimulation, film: string,
) => ) =>
// Includes legacy check on pre-stringified JSON // Includes legacy check on pre-stringified JSON
safelyQueryPhotos(() => sql` safelyQueryPhotos(() => sql`
@ -395,7 +395,7 @@ export const getRecipeTitleForData = async (
export const getPhotosNeedingRecipeTitleCount = async ( export const getPhotosNeedingRecipeTitleCount = async (
data: string, data: string,
film: FilmSimulation, film: string,
photoIdToExclude?: string, photoIdToExclude?: string,
) => ) =>
safelyQueryPhotos(() => sql` safelyQueryPhotos(() => sql`
@ -411,7 +411,7 @@ export const getPhotosNeedingRecipeTitleCount = async (
export const updateAllMatchingRecipeTitles = ( export const updateAllMatchingRecipeTitles = (
title: string, title: string,
data: string, data: string,
film: FilmSimulation, film: string,
) => ) =>
safelyQueryPhotos(() => sql` safelyQueryPhotos(() => sql`
UPDATE photos UPDATE photos
@ -430,7 +430,7 @@ export const getUniqueFilms = async () =>
ORDER BY film ASC ORDER BY film ASC
`.then(({ rows }): Films => rows `.then(({ rows }): Films => rows
.map(({ film, count }) => ({ .map(({ film, count }) => ({
film: film as FilmSimulation, film,
count: parseInt(count, 10), count: parseInt(count, 10),
}))) })))
, 'getUniqueFilms'); , 'getUniqueFilms');

View File

@ -1,7 +1,6 @@
import FieldSetWithStatus from '@/components/FieldSetWithStatus'; import FieldSetWithStatus from '@/components/FieldSetWithStatus';
import { ComponentProps, useEffect, useState } from 'react'; import { ComponentProps, useEffect, useState } from 'react';
import { getPhotosNeedingRecipeTitleCountAction } from '../actions'; import { getPhotosNeedingRecipeTitleCountAction } from '../actions';
import { FilmSimulation } from '@/film';
export default function ApplyRecipeTitleGloballyCheckbox({ export default function ApplyRecipeTitleGloballyCheckbox({
photoId, photoId,
@ -16,7 +15,7 @@ export default function ApplyRecipeTitleGloballyCheckbox({
recipeTitle?: string recipeTitle?: string
hasRecipeTitleChanged?: boolean hasRecipeTitleChanged?: boolean
recipeData?: string recipeData?: string
film?: FilmSimulation film?: string
onMatchResults: (didFindMatchingPhotos: boolean) => void onMatchResults: (didFindMatchingPhotos: boolean) => void
}) { }) {
const [matchingPhotosCount, setMatchingPhotosCount] = useState<number>(); const [matchingPhotosCount, setMatchingPhotosCount] = useState<number>();

View File

@ -41,7 +41,7 @@ import ErrorNote from '@/components/ErrorNote';
import { convertRecipesForForm, Recipes } from '@/recipe'; import { convertRecipesForForm, Recipes } from '@/recipe';
import deepEqual from 'fast-deep-equal/es6/react'; import deepEqual from 'fast-deep-equal/es6/react';
import ApplyRecipeTitleGloballyCheckbox from './ApplyRecipesGloballyCheckbox'; import ApplyRecipeTitleGloballyCheckbox from './ApplyRecipesGloballyCheckbox';
import { convertFilmsForForm, Films, FilmSimulation } from '@/film'; import { convertFilmsForForm, Films } from '@/film';
import IconFavs from '@/components/icons/IconFavs'; import IconFavs from '@/components/icons/IconFavs';
import IconHidden from '@/components/icons/IconHidden'; import IconHidden from '@/components/icons/IconHidden';
import { isMakeFujifilm } from '@/platforms/fujifilm'; import { isMakeFujifilm } from '@/platforms/fujifilm';
@ -431,7 +431,7 @@ export default function PhotoForm({
hasRecipeTitleChanged={ hasRecipeTitleChanged={
changedFormKeys.includes('recipeTitle')} changedFormKeys.includes('recipeTitle')}
recipeData={formData.recipeData} recipeData={formData.recipeData}
film={formData.film as FilmSimulation} film={formData.film}
onMatchResults={onMatchResults} onMatchResults={onMatchResults}
{...fieldProps} {...fieldProps}
/>; />;

View File

@ -16,12 +16,12 @@ import {
import { roundToNumber } from '@/utility/number'; import { roundToNumber } from '@/utility/number';
import { convertStringToArray, parameterize } from '@/utility/string'; import { convertStringToArray, parameterize } from '@/utility/string';
import { generateNanoid } from '@/utility/nanoid'; import { generateNanoid } from '@/utility/nanoid';
import { FilmSimulation } from '@/film';
import { GEO_PRIVACY_ENABLED } from '@/app/config'; import { GEO_PRIVACY_ENABLED } from '@/app/config';
import { TAG_FAVS, getValidationMessageForTags } from '@/tag'; import { TAG_FAVS, getValidationMessageForTags } from '@/tag';
import { MAKE_FUJIFILM } from '@/platforms/fujifilm'; import { MAKE_FUJIFILM } from '@/platforms/fujifilm';
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { FujifilmSimulation } from '@/platforms/fujifilm/simulation';
type VirtualFields = type VirtualFields =
'favorite' | 'favorite' |
@ -276,7 +276,7 @@ export const convertPhotoToFormData = (photo: Photo): PhotoFormData => {
export const convertExifToFormData = ( export const convertExifToFormData = (
data: ExifData, data: ExifData,
film?: FilmSimulation, film?: FujifilmSimulation,
recipeData?: FujifilmRecipe, recipeData?: FujifilmRecipe,
): Omit< ): Omit<
Record<keyof PhotoExif, string | undefined>, Record<keyof PhotoExif, string | undefined>,
@ -347,7 +347,7 @@ export const convertFormDataToPhotoDbInsert = (
return { return {
...(photoForm as PhotoFormData & { ...(photoForm as PhotoFormData & {
film?: FilmSimulation film?: FujifilmSimulation
recipeData?: FujifilmRecipe recipeData?: FujifilmRecipe
}), }),
...!photoForm.id && { id: generateNanoid() }, ...!photoForm.id && { id: generateNanoid() },

View File

@ -1,6 +1,6 @@
import { formatFocalLength } from '@/focal'; import { formatFocalLength } from '@/focal';
import { getNextImageUrlForRequest } from '@/platforms/next-image'; import { getNextImageUrlForRequest } from '@/platforms/next-image';
import { FilmSimulation, photoHasFilmData } from '@/film'; import { photoHasFilmData } from '@/film';
import { import {
HIGH_DENSITY_GRID, HIGH_DENSITY_GRID,
IS_PREVIEW, IS_PREVIEW,
@ -22,6 +22,7 @@ import camelcaseKeys from 'camelcase-keys';
import { isBefore } from 'date-fns'; import { isBefore } from 'date-fns';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { FujifilmSimulation } from '@/platforms/fujifilm/simulation';
// INFINITE SCROLL: FEED // INFINITE SCROLL: FEED
export const INFINITE_SCROLL_FEED_INITIAL = export const INFINITE_SCROLL_FEED_INITIAL =
@ -65,7 +66,7 @@ export interface PhotoExif {
exposureCompensation?: number exposureCompensation?: number
latitude?: number latitude?: number
longitude?: number longitude?: number
film?: FilmSimulation film?: FujifilmSimulation
recipeData?: string recipeData?: string
takenAt?: string takenAt?: string
takenAtNaive?: string takenAtNaive?: string

View File

@ -7,11 +7,11 @@ import {
convertFormDataToPhotoDbInsert, convertFormDataToPhotoDbInsert,
} from '@/photo/form'; } from '@/photo/form';
import { import {
FujifilmSimulation,
getFujifilmSimulationFromMakerNote, getFujifilmSimulationFromMakerNote,
} from '@/platforms/fujifilm/simulation'; } from '@/platforms/fujifilm/simulation';
import { ExifData, ExifParserFactory } from 'ts-exif-parser'; import { ExifData, ExifParserFactory } from 'ts-exif-parser';
import { PhotoFormData } from './form'; import { PhotoFormData } from './form';
import { FilmSimulation } from '@/film';
import sharp, { Sharp } from 'sharp'; import sharp, { Sharp } from 'sharp';
import { import {
GEO_PRIVACY_ENABLED, GEO_PRIVACY_ENABLED,
@ -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 film: FilmSimulation | undefined; let film: FujifilmSimulation | 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;

View File

@ -80,7 +80,7 @@ interface FujifilmSimulationLabel {
large: string large: string
} }
const FILM_SIMULATION_LABELS: Record< const FUJIFILM_SIMULATION_LABELS: Record<
FujifilmSimulation, FujifilmSimulation,
FujifilmSimulationLabel FujifilmSimulationLabel
> = { > = {
@ -206,30 +206,30 @@ const FILM_SIMULATION_LABELS: Record<
}, },
}; };
export const FILM_SIMULATION_FORM_INPUT_OPTIONS = Object export const FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS = Object
.entries(FILM_SIMULATION_LABELS) .entries(FUJIFILM_SIMULATION_LABELS)
.map(([value, label]) => ( .map(([value, label]) => (
{ value, label: label.large } as { value, label: label.large } as
{ value: FujifilmSimulation, label: string } { value: FujifilmSimulation, label: string }
)) ))
.sort((a, b) => a.label.localeCompare(b.label)); .sort((a, b) => a.label.localeCompare(b.label));
const ALL_POSSIBLE_FILM_SIMULATION_LABELS = Object const ALL_POSSIBLE_FUJIFILM_SIMULATION_LABELS = Object
.values(FILM_SIMULATION_LABELS) .values(FUJIFILM_SIMULATION_LABELS)
.flatMap(({ small, medium, large }) => [ .flatMap(({ small, medium, large }) => [
small.toLocaleLowerCase(), small.toLocaleLowerCase(),
medium.toLocaleLowerCase(), medium.toLocaleLowerCase(),
large.toLocaleLowerCase(), large.toLocaleLowerCase(),
]); ]);
export const isStringFilmSimulation = (film?: string) => export const isStringFujifilmSimulation = (film?: string) =>
film && Object.keys(FILM_SIMULATION_LABELS).includes(film); film && Object.keys(FUJIFILM_SIMULATION_LABELS).includes(film);
export const isStringFilmSimulationLabel = (film: string) => export const isStringFujifilmSimulationLabel = (film: string) =>
ALL_POSSIBLE_FILM_SIMULATION_LABELS.includes(film.toLocaleLowerCase()); ALL_POSSIBLE_FUJIFILM_SIMULATION_LABELS.includes(film.toLocaleLowerCase());
export const labelForFilm = (film: FujifilmSimulation) => export const labelForFujifilmSimulation = (film: FujifilmSimulation) =>
FILM_SIMULATION_LABELS[film]; FUJIFILM_SIMULATION_LABELS[film];
export const getFujifilmSimulationFromMakerNote = ( export const getFujifilmSimulationFromMakerNote = (
bytes: Buffer, bytes: Buffer,

View File

@ -15,11 +15,11 @@ import {
generateRecipeText, generateRecipeText,
RecipeProps, RecipeProps,
} from '.'; } from '.';
import { labelForFilm } from '@/platforms/fujifilm/simulation';
import { TbChecklist } from 'react-icons/tb'; import { TbChecklist } from 'react-icons/tb';
import CopyButton from '@/components/CopyButton'; import CopyButton from '@/components/CopyButton';
import { pathForRecipe } from '@/app/paths'; import { pathForRecipe } from '@/app/paths';
import LinkWithStatus from '@/components/LinkWithStatus'; import LinkWithStatus from '@/components/LinkWithStatus';
import { labelForFilm } from '@/film';
export default function PhotoRecipeOverlay({ export default function PhotoRecipeOverlay({
ref, ref,

View File

@ -7,8 +7,7 @@ import {
formatCountDescriptive, formatCountDescriptive,
} from '@/utility/string'; } from '@/utility/string';
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { FilmSimulation } from '@/film'; import { labelForFilm } from '@/film';
import { labelForFilm } from '@/platforms/fujifilm/simulation';
export type RecipeWithCount = { export type RecipeWithCount = {
recipe: string recipe: string
@ -20,7 +19,7 @@ export type Recipes = RecipeWithCount[]
export interface RecipeProps { export interface RecipeProps {
title?: string title?: string
recipe: FujifilmRecipe recipe: FujifilmRecipe
film: FilmSimulation film: string
iso?: string iso?: string
exposure?: string exposure?: string
} }