Merge pull request #228 from sambecker/universal-films
Prepare for universal films
This commit is contained in:
commit
1a6d65add3
@ -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);
|
||||
|
||||
@ -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() {
|
||||
</div>
|
||||
<div>
|
||||
<EntityLink
|
||||
icon={<PhotoFilmSimulationIcon simulation="astia" />}
|
||||
icon={<PhotoFilmIcon film="astia" />}
|
||||
label="Astia/Soft"
|
||||
type="icon-last"
|
||||
iconWide
|
||||
@ -149,7 +149,7 @@ export default function ComponentsPage() {
|
||||
</div>
|
||||
<div>
|
||||
<EntityLink
|
||||
icon={<PhotoFilmSimulationIcon simulation="astia" />}
|
||||
icon={<PhotoFilmIcon film="astia" />}
|
||||
label="Astia/Soft"
|
||||
type="icon-last"
|
||||
iconWide
|
||||
@ -184,7 +184,7 @@ export default function ComponentsPage() {
|
||||
</div>
|
||||
<div>
|
||||
<EntityLink
|
||||
icon={<PhotoFilmSimulationIcon simulation="astia" />}
|
||||
icon={<PhotoFilmIcon film="astia" />}
|
||||
label="Astia/Soft"
|
||||
type="icon-last"
|
||||
iconWide
|
||||
|
||||
@ -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={<AdminRecipeBadge {...{ recipe, count, hideBadge: true }} />}
|
||||
accessory={recipeData && filmSimulation &&
|
||||
accessory={recipeData && film &&
|
||||
<AdminShowRecipeButton
|
||||
title={recipe}
|
||||
recipe={recipeData}
|
||||
simulation={filmSimulation}
|
||||
film={film}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
||||
@ -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,
|
||||
]);
|
||||
|
||||
@ -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() {
|
||||
<div className="dark:text-gray-500/50 uppercase">
|
||||
Film Simulation:
|
||||
</div>
|
||||
<PhotoFilmSimulation
|
||||
simulation={FILM_SIMULATION_FORM_INPUT_OPTIONS[index].value}
|
||||
<PhotoFilm
|
||||
film={FILM_SIMULATION_FORM_INPUT_OPTIONS[index].value}
|
||||
type="icon-first"
|
||||
/>
|
||||
<div className="mt-4 text-dim relative">
|
||||
|
||||
@ -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 (
|
||||
<div className="space-y-1 my-12">
|
||||
{FILM_SIMULATION_FORM_INPUT_OPTIONS.map(({ value }) =>
|
||||
<div key={value}>
|
||||
<PhotoFilmSimulation
|
||||
simulation={value}
|
||||
<PhotoFilm
|
||||
film={value}
|
||||
type="icon-first"
|
||||
/>
|
||||
</div>)}
|
||||
|
||||
@ -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<Metadata> {
|
||||
const { photoId, simulation } = await params;
|
||||
}: PhotoFilmProps): Promise<Metadata> {
|
||||
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 (
|
||||
<PhotoDetailPage {...{
|
||||
photo,
|
||||
photos,
|
||||
photosGrid,
|
||||
simulation,
|
||||
film: film,
|
||||
indexNumber,
|
||||
count,
|
||||
dateRange,
|
||||
@ -3,34 +3,37 @@ import {
|
||||
IMAGE_OG_DIMENSION_SMALL,
|
||||
MAX_PHOTOS_TO_SHOW_PER_CATEGORY,
|
||||
} from '@/image-response';
|
||||
import FilmSimulationImageResponse from
|
||||
'@/image-response/FilmSimulationImageResponse';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
import FilmImageResponse from
|
||||
'@/image-response/FilmImageResponse';
|
||||
import { FilmSimulation } from '@/film';
|
||||
import { getIBMPlexMono } from '@/app/font';
|
||||
import { ImageResponse } from 'next/og';
|
||||
import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
|
||||
import { getUniqueFilmSimulations } from '@/photo/db/query';
|
||||
import { getUniqueFilms } from '@/photo/db/query';
|
||||
import { staticallyGenerateCategoryIfConfigured } from '@/app/static';
|
||||
|
||||
export const generateStaticParams = staticallyGenerateCategoryIfConfigured(
|
||||
'films',
|
||||
'image',
|
||||
getUniqueFilmSimulations,
|
||||
simulations => 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(
|
||||
<FilmSimulationImageResponse {...{
|
||||
simulation,
|
||||
<FilmImageResponse {...{
|
||||
film,
|
||||
photos,
|
||||
width,
|
||||
height,
|
||||
@ -1,38 +1,38 @@
|
||||
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||
import { getUniqueFilmSimulations } from '@/photo/db/query';
|
||||
import { FilmSimulation, generateMetaForFilmSimulation } from '@/simulation';
|
||||
import FilmSimulationOverview from '@/simulation/FilmSimulationOverview';
|
||||
import { getPhotosFilmSimulationDataCached } from '@/simulation/data';
|
||||
import { getUniqueFilms } from '@/photo/db/query';
|
||||
import { FilmSimulation, generateMetaForFilm } from '@/film';
|
||||
import FilmOverview from '@/film/FilmOverview';
|
||||
import { getPhotosFilmDataCached } from '@/film/data';
|
||||
import { Metadata } from 'next/types';
|
||||
import { cache } from 'react';
|
||||
import { PATH_ROOT } from '@/app/paths';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { staticallyGenerateCategoryIfConfigured } from '@/app/static';
|
||||
|
||||
const getPhotosFilmSimulationDataCachedCached =
|
||||
cache(getPhotosFilmSimulationDataCached);
|
||||
const getPhotosFilmDataCachedCached =
|
||||
cache(getPhotosFilmDataCached);
|
||||
|
||||
export const generateStaticParams = staticallyGenerateCategoryIfConfigured(
|
||||
'films',
|
||||
'page',
|
||||
getUniqueFilmSimulations,
|
||||
simulations => 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<Metadata> {
|
||||
const { simulation } = await params;
|
||||
}: FilmProps): Promise<Metadata> {
|
||||
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 (
|
||||
<FilmSimulationOverview {...{
|
||||
simulation,
|
||||
<FilmOverview {...{
|
||||
film,
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
@ -29,7 +29,7 @@ export default async function GridPage() {
|
||||
lenses,
|
||||
tags,
|
||||
recipes,
|
||||
simulations,
|
||||
films,
|
||||
focalLengths,
|
||||
] = await Promise.all([
|
||||
getPhotosCached()
|
||||
@ -49,7 +49,7 @@ export default async function GridPage() {
|
||||
cameras,
|
||||
lenses,
|
||||
tags,
|
||||
simulations,
|
||||
films,
|
||||
recipes,
|
||||
focalLengths,
|
||||
}}
|
||||
|
||||
@ -16,7 +16,7 @@ import { Metadata } from 'next/types';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import Nav from '@/app/Nav';
|
||||
import Footer from '@/app/Footer';
|
||||
import CommandK from '@/app/CommandK';
|
||||
import CommandK from '@/cmdk/CommandK';
|
||||
import SwrConfigClient from '@/state/SwrConfigClient';
|
||||
import AdminBatchEditPanel from '@/admin/AdminBatchEditPanel';
|
||||
import ShareModals from '@/share/ShareModals';
|
||||
|
||||
@ -2,14 +2,14 @@ import CameraOGTile from '@/camera/CameraOGTile';
|
||||
import FocalLengthOGTile from '@/focal/FocalLengthOGTile';
|
||||
import PhotoOGTile from '@/photo/PhotoOGTile';
|
||||
import { getPhotosCached } from '@/photo/cache';
|
||||
import FilmSimulationOGTile from '@/simulation/FilmSimulationOGTile';
|
||||
import FilmOGTile from '@/film/FilmOGTile';
|
||||
import { TAG_FAVS } from '@/tag';
|
||||
import TagOGTile from '@/tag/TagOGTile';
|
||||
|
||||
const tag = 'cicadas';
|
||||
const camera = { make: 'Fujifilm', model: 'X-T5' };
|
||||
const cameraIcon = { make: 'Apple', model: 'iPhone 13 Pro' };
|
||||
const simulation = 'acros';
|
||||
const film = 'acros';
|
||||
const focal = 90;
|
||||
|
||||
export default async function OGOverviewPage() {
|
||||
@ -19,7 +19,7 @@ export default async function OGOverviewPage() {
|
||||
photosTag,
|
||||
photosFavs,
|
||||
photosCamera,
|
||||
photosSimulation,
|
||||
photosFilm,
|
||||
photosFocal,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ limit: 1 }).then(photos => 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() {
|
||||
<TagOGTile tag={tag} photos={photosTag} />
|
||||
<TagOGTile tag={TAG_FAVS} photos={photosFavs} />
|
||||
<CameraOGTile camera={camera} photos={photosCamera} />
|
||||
<FilmSimulationOGTile simulation={simulation} photos={photosSimulation} />
|
||||
<FilmOGTile film={film} photos={photosFilm} />
|
||||
<FocalLengthOGTile focal={focal} photos={photosFocal} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -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,
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
}}
|
||||
|
||||
@ -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
|
||||
? <ScoreCardRow
|
||||
key={category}
|
||||
icon={<IconFilmSimulation size={15} />}
|
||||
content={pluralize(filmSimulationsCount, 'film simulation')}
|
||||
icon={<IconFilm size={15} />}
|
||||
content={pluralize(filmsCount, 'film')}
|
||||
/>
|
||||
: null;
|
||||
case 'focal-lengths':
|
||||
|
||||
@ -53,7 +53,7 @@ export interface PhotoStats {
|
||||
lensesCount: number
|
||||
tagsCount: number
|
||||
recipesCount: number
|
||||
filmSimulationsCount: number
|
||||
filmsCount: number
|
||||
focalLengthsCount: number
|
||||
dateRange?: PhotoDateRange
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<string, number>),
|
||||
filmSimulations: filmSimulations.reduce((acc, filmSimulation) => {
|
||||
acc[filmSimulation.simulation] = filmSimulation.count;
|
||||
films: films.reduce((acc, film) => {
|
||||
acc[film.film] = film.count;
|
||||
return acc;
|
||||
}, {} as Record<string, number>),
|
||||
focalLengths: focalLengths.reduce((acc, focalLength) => {
|
||||
|
||||
@ -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 = <T extends { count: number }>(
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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<string, number>),
|
||||
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,
|
||||
]);
|
||||
|
||||
|
||||
@ -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}
|
||||
@ -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: <IconFilmSimulation size={14} />,
|
||||
items: simulations.map(({ simulation, count }) => ({
|
||||
label: labelForFilmSimulation(simulation).medium,
|
||||
heading: 'Films',
|
||||
accessory: <IconFilm size={14} />,
|
||||
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',
|
||||
@ -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,
|
||||
@ -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 <IoFilmOutline {...props} />;
|
||||
}
|
||||
@ -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 (
|
||||
<PhotoHeader
|
||||
simulation={simulation}
|
||||
entity={<PhotoFilmSimulation {...{ simulation }} />}
|
||||
entityDescription={descriptionForFilmSimulationPhotos(
|
||||
film={film}
|
||||
entity={<PhotoFilm {...{ film }} />}
|
||||
entityDescription={descriptionForFilmPhotos(
|
||||
photos, undefined, count, dateRange)}
|
||||
photos={photos}
|
||||
selectedPhoto={selectedPhoto}
|
||||
@ -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 (
|
||||
<OGTile {...{
|
||||
title: titleForFilmSimulation(simulation, photos, count),
|
||||
title: titleForFilm(film, photos, count),
|
||||
description:
|
||||
descriptionForFilmSimulationPhotos(photos, true, count, dateRange),
|
||||
path: pathForFilmSimulation(simulation),
|
||||
pathImageAbsolute: absolutePathForFilmSimulationImage(simulation),
|
||||
descriptionForFilmPhotos(photos, true, count, dateRange),
|
||||
path: pathForFilm(film),
|
||||
pathImageAbsolute: absolutePathForFilmImage(film),
|
||||
loadingState: loadingStateExternal,
|
||||
onLoad,
|
||||
onFail,
|
||||
@ -1,16 +1,16 @@
|
||||
import { Photo, PhotoDateRange } from '@/photo';
|
||||
import FilmSimulationHeader from './FilmSimulationHeader';
|
||||
import FilmHeader from './FilmHeader';
|
||||
import { FilmSimulation } from '.';
|
||||
import PhotoGridContainer from '@/photo/PhotoGridContainer';
|
||||
|
||||
export default function FilmSimulationOverview({
|
||||
simulation,
|
||||
export default function FilmOverview({
|
||||
film,
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
animateOnFirstLoadOnly,
|
||||
}: {
|
||||
simulation: FilmSimulation,
|
||||
film: FilmSimulation,
|
||||
photos: Photo[],
|
||||
count: number,
|
||||
dateRange?: PhotoDateRange,
|
||||
@ -18,12 +18,12 @@ export default function FilmSimulationOverview({
|
||||
}) {
|
||||
return (
|
||||
<PhotoGridContainer {...{
|
||||
cacheKey: `simulation-${simulation}`,
|
||||
cacheKey: `film-${film}`,
|
||||
photos,
|
||||
count,
|
||||
simulation,
|
||||
header: <FilmSimulationHeader {...{
|
||||
simulation,
|
||||
film,
|
||||
header: <FilmHeader {...{
|
||||
film,
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
23
src/film/FilmShareModal.tsx
Normal file
23
src/film/FilmShareModal.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { absolutePathForFilm } from '@/app/paths';
|
||||
import { PhotoSetAttributes } from '../category';
|
||||
import ShareModal from '@/share/ShareModal';
|
||||
import FilmOGTile from './FilmOGTile';
|
||||
import { FilmSimulation, shareTextForFilm } from '.';
|
||||
|
||||
export default function FilmShareModal({
|
||||
film,
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
}: {
|
||||
film: FilmSimulation
|
||||
} & PhotoSetAttributes) {
|
||||
return (
|
||||
<ShareModal
|
||||
pathShare={absolutePathForFilm(film)}
|
||||
socialText={shareTextForFilm(film)}
|
||||
>
|
||||
<FilmOGTile {...{ film, photos, count, dateRange }} />
|
||||
</ShareModal>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<EntityLink
|
||||
{...props}
|
||||
label={medium}
|
||||
labelSmall={small}
|
||||
href={pathForFilmSimulation(simulation)}
|
||||
icon={<PhotoFilmSimulationIcon
|
||||
simulation={simulation}
|
||||
href={pathForFilm(film)}
|
||||
icon={<PhotoFilmIcon
|
||||
film={film}
|
||||
className={clsx(
|
||||
contrast === 'frosted' && 'text-black',
|
||||
type === 'icon-only'
|
||||
@ -37,7 +37,7 @@ export default function PhotoFilmSimulation({
|
||||
: 'translate-y-[-1px]',
|
||||
)}
|
||||
/>}
|
||||
title={`Film Simulation: ${large}`}
|
||||
title={`Film: ${large}`}
|
||||
type={type}
|
||||
badged={badged}
|
||||
contrast={contrast}
|
||||
@ -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({
|
||||
<svg
|
||||
className={className}
|
||||
style={style}
|
||||
aria-description={simulation
|
||||
? labelForFilmSimulation(simulation).large
|
||||
: 'Film Simulation'}
|
||||
aria-description={film
|
||||
? labelForFilm(film).large
|
||||
: 'Film'}
|
||||
width={INTRINSIC_WIDTH * height / INTRINSIC_HEIGHT}
|
||||
height={height}
|
||||
viewBox="0 0 28 16"
|
||||
@ -33,7 +33,7 @@ export default function PhotoFilmSimulationIcon({
|
||||
{(() => {
|
||||
// Self-calling switch function and non-fragment groups
|
||||
// necessary for ImageResponse compatibility
|
||||
switch (simulation) {
|
||||
switch (film) {
|
||||
case 'monochrome': return <g>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M16.25 14H22.5C22.6381 14 22.75 13.8881 22.75 13.75V10.5202C22.75 10.4539 22.7763 10.3903 22.8232 10.3434L25.677 7.48989C25.7238 7.44301 25.7502 7.37942 25.7502 7.31311V4.25C25.7502 4.11193 25.6383 4 25.5002 4H16.25C16.1119 4 16 4.11194 16 4.25002L16.0002 6.49998C16.0002 6.63806 15.8882 6.75 15.7502 6.75H14.7502C14.6121 6.75 14.5002 6.86192 14.5002 6.99999L14.5 11C14.5 11.1381 14.6119 11.25 14.75 11.25H15.75C15.8881 11.25 16 11.3619 16 11.5V13.75C16 13.8881 16.1119 14 16.25 14ZM18.75 5H17V6.5H18.75V5ZM17 11.5H18.75V13H17V11.5ZM21.75 5H20V6.5H21.75V5ZM20 11.5H21.75V13H20V11.5ZM24.75 5H23V6.5H24.75V5Z" fill="currentColor"/>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M5.25 3.49999L2 3.49999C1.86193 3.49999 1.75 3.61192 1.75 3.74999V5.24998C1.75 5.38806 1.86193 5.49998 2 5.49998H3.00001C3.13808 5.49998 3.25001 5.61191 3.25001 5.74999L3.25 12.25C3.25 12.3881 3.13807 12.5 3 12.5H2C1.86193 12.5 1.75 12.6119 1.75 12.75V14.25C1.75 14.3881 1.86193 14.5 2 14.5H14.25C14.3881 14.5 14.5 14.3881 14.5 14.25V12.75C14.5 12.6119 14.3881 12.5 14.25 12.5H13.25C13.1119 12.5 13 12.3881 13 12.25L13 5.75C13 5.61193 13.112 5.5 13.25 5.5H14.25C14.3881 5.5 14.5 5.38807 14.5 5.25V3.74999C14.5 3.61192 14.3881 3.49999 14.25 3.49999L11 3.49999C10.8619 3.49998 10.75 3.38806 10.75 3.24999V1.74998C10.75 1.61191 10.6381 1.49998 10.5 1.49998H5.75C5.61193 1.49998 5.5 1.61191 5.5 1.74998V3.24999C5.5 3.38806 5.38807 3.49998 5.25 3.49999ZM9.03232 4.7985H5.38982V13H9.36132C9.65899 13 9.92532 12.9412 10.1603 12.8237C10.3953 12.6984 10.5951 12.53 10.7596 12.3185C10.9241 12.107 11.0494 11.8602 11.1356 11.5782C11.2217 11.2884 11.2648 10.9829 11.2648 10.6617C11.2648 10.3328 11.2139 10.0547 11.1121 9.8275C11.0102 9.5925 10.8771 9.4045 10.7126 9.2635C10.5481 9.11467 10.364 9.00892 10.1603 8.94625C9.96449 8.88358 9.77257 8.85225 9.58457 8.85225V8.66425C9.7569 8.65642 9.92532 8.62508 10.0898 8.57025C10.2543 8.50758 10.3992 8.41358 10.5246 8.28825C10.6577 8.15508 10.7635 7.97883 10.8418 7.7595C10.928 7.53233 10.9711 7.24642 10.9711 6.90175C10.9711 6.26725 10.8105 5.75808 10.4893 5.37425C10.176 4.99042 9.69032 4.7985 9.03232 4.7985ZM8.89132 11.5782H7.07007V9.55725H8.89132C9.07932 9.55725 9.22815 9.61208 9.33782 9.72175C9.45532 9.83142 9.51407 10.0155 9.51407 10.274V10.8615C9.51407 11.12 9.45532 11.3041 9.33782 11.4137C9.22815 11.5234 9.07932 11.5782 8.89132 11.5782ZM8.62107 8.17075H7.07007V6.22025H8.62107C8.8169 6.22025 8.96965 6.27508 9.07932 6.38475C9.18899 6.49442 9.24382 6.67458 9.24382 6.92525V7.46575C9.24382 7.71642 9.18899 7.89658 9.07932 8.00625C8.96965 8.11592 8.8169 8.17075 8.62107 8.17075Z" fill="currentColor"/>
|
||||
17
src/film/data.ts
Normal file
17
src/film/data.ts
Normal file
@ -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 }),
|
||||
]);
|
||||
84
src/film/index.ts
Normal file
84
src/film/index.ts
Normal file
@ -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);
|
||||
@ -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: <PhotoFilmSimulationIcon
|
||||
simulation={simulation}
|
||||
icon: <PhotoFilmIcon
|
||||
film={film}
|
||||
height={height * .081}
|
||||
style={{ transform: `translateY(${height * .001}px)`}}
|
||||
/>,
|
||||
title: labelForFilmSimulation(simulation).medium.toLocaleUpperCase(),
|
||||
title: labelForFilm(film).medium.toLocaleUpperCase(),
|
||||
}} />
|
||||
</ImageContainer>
|
||||
);
|
||||
@ -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 &&
|
||||
<div tw="flex">
|
||||
<PhotoFilmSimulationIcon
|
||||
simulation={filmSimulation}
|
||||
<PhotoFilmIcon
|
||||
film={film}
|
||||
height={height * .06}
|
||||
style={{ transform: `translateY(${-height * .001}px)`}}
|
||||
/>
|
||||
|
||||
@ -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 } =
|
||||
|
||||
@ -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 = <FilmSimulationHeader
|
||||
simulation={simulation}
|
||||
} else if (film) {
|
||||
customHeader = <FilmHeader
|
||||
film={film}
|
||||
photos={photos}
|
||||
selectedPhoto={photo}
|
||||
indexNumber={indexNumber}
|
||||
@ -129,13 +129,13 @@ export default function PhotoDetailPage({
|
||||
showTitleAsH1
|
||||
showCamera={!camera}
|
||||
showLens={!lens}
|
||||
showSimulation={!simulation}
|
||||
showFilm={!film}
|
||||
showRecipe={!recipe}
|
||||
shouldShare={shouldShare}
|
||||
shouldShareCamera={camera !== undefined}
|
||||
shouldShareLens={lens !== undefined}
|
||||
shouldShareTag={tag !== undefined}
|
||||
shouldShareSimulation={simulation !== undefined}
|
||||
shouldShareFilm={film !== undefined}
|
||||
shouldShareRecipe={recipe !== undefined}
|
||||
shouldShareFocalLength={focal !== undefined}
|
||||
includeFavoriteInAdminMenu={includeFavoriteInAdminMenu}
|
||||
@ -149,7 +149,7 @@ export default function PhotoDetailPage({
|
||||
selectedPhoto={photo}
|
||||
tag={tag}
|
||||
camera={camera}
|
||||
simulation={simulation}
|
||||
film={film}
|
||||
focal={focal}
|
||||
animateOnFirstLoadOnly
|
||||
/>}
|
||||
|
||||
@ -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
|
||||
? <HeaderList
|
||||
key="films"
|
||||
title="Films"
|
||||
icon={<IconFilmSimulation size={15} />}
|
||||
icon={<IconFilm size={15} />}
|
||||
maxItems={maxItemsPerCategory}
|
||||
items={simulations
|
||||
.map(({ simulation, count }) =>
|
||||
<PhotoFilmSimulation
|
||||
key={simulation}
|
||||
simulation={simulation}
|
||||
items={films
|
||||
.map(({ film, count }) =>
|
||||
<PhotoFilm
|
||||
key={film}
|
||||
film={film}
|
||||
countOnHover={count}
|
||||
type="text-only"
|
||||
prefetch={false}
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
doesPhotoNeedBlurCompatibility,
|
||||
shouldShowCameraDataForPhoto,
|
||||
shouldShowExifDataForPhoto,
|
||||
shouldShowFilmSimulationDataForPhoto,
|
||||
shouldShowFilmDataForPhoto,
|
||||
shouldShowLensDataForPhoto,
|
||||
shouldShowRecipeDataForPhoto,
|
||||
titleForPhoto,
|
||||
@ -21,7 +21,7 @@ import ShareButton from '@/share/ShareButton';
|
||||
import DownloadButton from '@/components/DownloadButton';
|
||||
import PhotoCamera from '../camera/PhotoCamera';
|
||||
import { cameraFromPhoto } from '@/camera';
|
||||
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
|
||||
import PhotoFilm from '@/film/PhotoFilm';
|
||||
import { sortTagsArray } from '@/tag';
|
||||
import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid';
|
||||
import PhotoLink from './PhotoLink';
|
||||
@ -65,7 +65,7 @@ export default function PhotoLarge({
|
||||
showTitleAsH1,
|
||||
showCamera = true,
|
||||
showLens = true,
|
||||
showSimulation = true,
|
||||
showFilm = true,
|
||||
showRecipe = true,
|
||||
showZoomControls: showZoomControlsProp = true,
|
||||
shouldZoomOnFKeydown = true,
|
||||
@ -73,7 +73,7 @@ export default function PhotoLarge({
|
||||
shouldShareCamera,
|
||||
shouldShareLens,
|
||||
shouldShareTag,
|
||||
shouldShareSimulation,
|
||||
shouldShareFilm,
|
||||
shouldShareRecipe,
|
||||
shouldShareFocalLength,
|
||||
includeFavoriteInAdminMenu,
|
||||
@ -90,7 +90,7 @@ export default function PhotoLarge({
|
||||
showTitleAsH1?: boolean
|
||||
showCamera?: boolean
|
||||
showLens?: boolean
|
||||
showSimulation?: boolean
|
||||
showFilm?: boolean
|
||||
showRecipe?: boolean
|
||||
showZoomControls?: boolean
|
||||
shouldZoomOnFKeydown?: boolean
|
||||
@ -98,7 +98,7 @@ export default function PhotoLarge({
|
||||
shouldShareCamera?: boolean
|
||||
shouldShareLens?: boolean
|
||||
shouldShareTag?: boolean
|
||||
shouldShareSimulation?: boolean
|
||||
shouldShareFilm?: boolean
|
||||
shouldShareRecipe?: boolean
|
||||
shouldShareFocalLength?: boolean
|
||||
includeFavoriteInAdminMenu?: boolean
|
||||
@ -120,7 +120,7 @@ export default function PhotoLarge({
|
||||
lensCount,
|
||||
tagCounts,
|
||||
recipeCount,
|
||||
simulationCount,
|
||||
filmCount,
|
||||
} = useCategoryCountsForPhoto(photo);
|
||||
|
||||
const showZoomControls = showZoomControlsProp && areZoomControlsShown;
|
||||
@ -150,8 +150,7 @@ export default function PhotoLarge({
|
||||
const showTagsContent = tags.length > 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({
|
||||
<AnimatePresence>
|
||||
{(shouldShowRecipeOverlay || shouldDebugRecipeOverlays) &&
|
||||
photo.recipeData &&
|
||||
photo.filmSimulation &&
|
||||
photo.film &&
|
||||
<PhotoRecipeOverlay
|
||||
ref={refRecipe}
|
||||
title={photo.recipeTitle}
|
||||
recipe={photo.recipeData}
|
||||
simulation={photo.filmSimulation}
|
||||
film={photo.film}
|
||||
iso={photo.isoFormatted}
|
||||
exposure={photo.exposureCompensationFormatted}
|
||||
onClose={hideRecipeOverlay}
|
||||
@ -391,13 +390,13 @@ export default function PhotoLarge({
|
||||
<li>{photo.isoFormatted}</li>
|
||||
<li>{photo.exposureCompensationFormatted ?? '0ev'}</li>
|
||||
</ul>
|
||||
{(showRecipeButton || showSimulationContent) &&
|
||||
{(showRecipeButton || showFilmContent) &&
|
||||
<div className="flex items-center gap-2 *:w-auto">
|
||||
{showSimulationContent && photo.filmSimulation &&
|
||||
<PhotoFilmSimulation
|
||||
simulation={photo.filmSimulation}
|
||||
{showFilmContent && photo.film &&
|
||||
<PhotoFilm
|
||||
film={photo.film}
|
||||
prefetch={prefetchRelatedLinks}
|
||||
countOnHover={simulationCount}
|
||||
countOnHover={filmCount}
|
||||
/>}
|
||||
{showRecipeButton &&
|
||||
<Tooltip content="Fujifilm Recipe">
|
||||
@ -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
|
||||
? <IoCloseSharp size={15} />
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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++}`);
|
||||
|
||||
@ -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) =>
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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<typeof FieldSetWithStatus> & {
|
||||
@ -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<number>();
|
||||
@ -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);
|
||||
|
||||
@ -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}
|
||||
/>;
|
||||
|
||||
@ -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<keyof PhotoExif, string | undefined>,
|
||||
@ -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() },
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<ReturnType<typeof convertFormDataToPhotoDbInsert>> => {
|
||||
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,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<HTMLDivElement | null>
|
||||
recipe: FujifilmRecipe
|
||||
simulation: FilmSimulation
|
||||
iso?: string
|
||||
exposure?: string
|
||||
}) {
|
||||
const {
|
||||
dynamicRange,
|
||||
whiteBalance,
|
||||
highISONoiseReduction,
|
||||
noiseReductionBasic,
|
||||
highlight,
|
||||
shadow,
|
||||
color,
|
||||
sharpness,
|
||||
clarity,
|
||||
colorChromeEffect,
|
||||
colorChromeFXBlue,
|
||||
grainEffect,
|
||||
bwAdjustment,
|
||||
bwMagentaGreen,
|
||||
} = recipe;
|
||||
|
||||
const whiteBalanceTypeFormatted = formatWhiteBalance(recipe);
|
||||
|
||||
const renderRow = (children: ReactNode, className?: string) =>
|
||||
<div className={clsx(
|
||||
'flex gap-2 *:w-full *:grow',
|
||||
className,
|
||||
)}>
|
||||
{children}
|
||||
</div>;
|
||||
|
||||
const renderDataSquare = (
|
||||
value: ReactNode,
|
||||
label?: string,
|
||||
className?: string,
|
||||
) => (
|
||||
<div className={clsx(
|
||||
'flex flex-col items-center justify-center gap-0.5 rounded-md min-w-0',
|
||||
'rounded-md border',
|
||||
'border-transparent',
|
||||
'bg-neutral-100/60',
|
||||
label && 'p-1',
|
||||
className,
|
||||
)}>
|
||||
<div className="flex truncate max-w-full tracking-wide text-lg">
|
||||
{typeof value === 'number' ? addSign(value) : value}
|
||||
</div>
|
||||
{label && <div className={clsx(
|
||||
'text-[11px] leading-none tracking-wide font-medium text-black/50',
|
||||
'uppercase',
|
||||
)}>
|
||||
{label}
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'flex z-10',
|
||||
'w-[37rem] p-10 aspect-video',
|
||||
'text-[13.5px] text-black',
|
||||
'bg-white/50',
|
||||
'backdrop-blur-xl saturate-[300%]',
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
{renderRow(<>
|
||||
<div className={clsx(
|
||||
'flex',
|
||||
'text-lg leading-none text-black truncate',
|
||||
)}>
|
||||
KODAK PORTRA 500
|
||||
</div>
|
||||
<PhotoFilmSimulation
|
||||
contrast="frosted"
|
||||
simulation={simulation}
|
||||
className="w-auto! grow-0!"
|
||||
/>
|
||||
</>, 'flex items-center gap-4')}
|
||||
{renderRow(<>
|
||||
{renderDataSquare(`DR${dynamicRange.development}`)}
|
||||
{renderDataSquare(iso)}
|
||||
{renderDataSquare(exposure ?? '0ev')}
|
||||
</>)}
|
||||
{renderRow(<>
|
||||
{renderDataSquare(
|
||||
whiteBalanceTypeFormatted.toUpperCase(),
|
||||
`R${addSign(whiteBalance?.red)} / B${addSign(whiteBalance?.blue)}`,
|
||||
)}
|
||||
{renderDataSquare(
|
||||
highISONoiseReduction ?? noiseReductionBasic ?? 'OFF',
|
||||
'ISO NR',
|
||||
)}
|
||||
{renderDataSquare(highlight, 'Highlight')}
|
||||
{renderDataSquare(shadow, 'Shadow')}
|
||||
</>)}
|
||||
{renderRow(<>
|
||||
{renderDataSquare(color, 'Color')}
|
||||
{renderDataSquare(sharpness, 'Sharpness')}
|
||||
{renderDataSquare(clarity, 'Clarity')}
|
||||
</>)}
|
||||
{renderRow(<>
|
||||
{renderDataSquare(
|
||||
colorChromeEffect?.toLocaleUpperCase() ?? 'N/A',
|
||||
'Color Chrome',
|
||||
)}
|
||||
{renderDataSquare(
|
||||
colorChromeFXBlue?.toLocaleUpperCase() ?? 'N/A',
|
||||
'FX Blue',
|
||||
)}
|
||||
</>)}
|
||||
{renderRow(<>
|
||||
{renderDataSquare(
|
||||
grainEffect.roughness.toLocaleUpperCase(),
|
||||
grainEffect.size === 'large'
|
||||
? 'Large Grain'
|
||||
: grainEffect.size === 'small'
|
||||
? 'Small Grain'
|
||||
: 'Grain',
|
||||
)}
|
||||
{renderDataSquare(bwAdjustment ?? 0, 'BW ADJ')}
|
||||
{renderDataSquare(bwMagentaGreen ?? 0, 'BW M/G')}
|
||||
</>)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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<HTMLDivElement | null>
|
||||
@ -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({
|
||||
<div className="col-span-8">
|
||||
{renderDataSquare(
|
||||
<div className="flex items-center gap-1.5">
|
||||
{labelForFilmSimulation(simulation).medium.toLocaleUpperCase()}
|
||||
<PhotoFilmSimulation
|
||||
{labelForFilm(film).medium.toLocaleUpperCase()}
|
||||
<PhotoFilm
|
||||
contrast="frosted"
|
||||
simulation={simulation}
|
||||
film={film}
|
||||
type="icon-only"
|
||||
className="opacity-80 translate-y-[-0.5px]"
|
||||
/>
|
||||
|
||||
@ -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,
|
||||
})
|
||||
|
||||
@ -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[],
|
||||
|
||||
@ -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 <CameraShareModal {...{ camera, ...attributes }} />;
|
||||
} else if (lens) {
|
||||
return <LensShareModal {...{ lens, ...attributes }} />;
|
||||
} else if (simulation) {
|
||||
return <FilmSimulationShareModal {...{ simulation, ...attributes }} />;
|
||||
} else if (film) {
|
||||
return <FilmShareModal {...{ film, ...attributes }} />;
|
||||
} else if (recipe) {
|
||||
return <RecipeShareModal {...{ recipe, ...attributes }} />;
|
||||
} else if (focal !== undefined) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<ShareModal
|
||||
pathShare={absolutePathForFilmSimulation(simulation)}
|
||||
socialText={shareTextForFilmSimulation(simulation)}
|
||||
>
|
||||
<FilmSimulationOGTile {...{ simulation, photos, count, dateRange }} />
|
||||
</ShareModal>
|
||||
);
|
||||
};
|
||||
@ -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 }),
|
||||
]);
|
||||
@ -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);
|
||||
Loading…
Reference in New Issue
Block a user